linux下内核态锁与用户态锁详细介绍

2023-11-02

1 内核态下锁

1.1 spinlock_t

spinlock_t成为自旋锁,它用在临界区代码非常少的情况下。自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,如果释放了该锁,请求锁的调用者可以立即得到它,继续执行。自旋锁可用于中断服务程序之中。

初始化

spinlock_t使用前需进行初始化,自旋锁的初始化有两种方式:

  1. 静态初始化 SPIN_LOCK_UNLOCKED

如:spinlock_tx lock = SPIN_LOCK_UNLOCKED

  1. 动态初始化spin_lock_init()

如:spin_lock_init( spinlock_tx* lock)

加锁

spin_is_locked(lock)

该函数用于判断自旋锁lock是否已经被某执行单元保持(即被锁),如果是,返回真,否则返回假。

spin_lock(lock)

该函数用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放,这时,它获得锁并返回。总之,只有它获得锁才返回。

spin_lock_irqsave(lock, flags)

该函数获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中断。

spin_lock_irq(lock)

该函数类似于spin_lock_irqsave,只是该宏不保存标志寄存器的值。

spin_lock_bh(lock)

该函数在得到自旋锁的同时失效本地软中断。

spin_trylock(lock)

该函数尽力获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则不能立即获得锁,立即返回假。它不会自旋等待lock被释放。这是一个非阻塞自旋锁操作。

spin_trylock_bh(lock)

该宏如果获得了自旋锁,它也将失效本地软中断。如果得不到锁,它什么也不做。因此,如果得到了锁,它等同于spin_lock_bh,如果得不到锁,它等同于spin_trylock。如果该宏得到了自旋锁,需要使用spin_unlock_bh来释放。

解锁

spin_unlock(lock)

该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。如果spin_trylock返回假,表明没有获得自旋锁,因此不必使用spin_unlock释放。

spin_unlock_irqrestore(lock, flags)

该宏释放自旋锁lock的同时,也恢复标志寄存器的值为变量flags保存的值。它与spin_lock_irqsave配对使用。

spin_unlock_irq(lock)

该宏释放自旋锁lock的同时,也使能本地中断。它与spin_lock_irq配对应用。

spin_unlock_bh(lock)

该宏释放自旋锁lock的同时,也使能本地的软中断。它与spin_lock_bh配对使用。

1.2 mutex_t

互斥锁(Mutex)是在原子操作API的基础上实现的信号量行为。互斥锁不能进行递归锁定或解锁,能用于交互上下文但是不能用于中断上下文,同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行解锁。当无法获取锁时,线程进入睡眠等待状态。

初始化

mutex_t使用前需进行初始化,自旋锁的初始化有两种方式:

  1. 静态初始化 SPIN_LOCK_UNLOCKED

如:DEFINE_MUTEX(lock)

  1. 动态初始化 mutex_init()

如:mutex_init(struct mutex *lock)

加锁

void mutex_lock(struct mutex *lock);

无法获得锁时,睡眠等待,不会被信号中断。

int mutex_trylock(struct mutex *lock);

此函数是mutex_lock()的非阻塞版本,成功返回1,失败返回0。

int mutex_lock_interruptible(struct mutex *lock);

和mutex_lock()一样,也是获取互斥锁。在获得了互斥锁或进入睡眠直到获得互斥锁之后会返回0。如果在等待获取锁的时候进入睡眠状态收到一个信号(被信号打断睡眠),则返回_EINIR。

解锁

void mutex_unlock(struct mutex *lock);

1.3 semaphore

信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

数据结构

struct semaphore

{

     atomic_t count;                                                                        //共享计数值

     int sleepers;                                                     //等待当前信号量进入睡眠进程的个数

     wait_queue_head_t wait;                                                     //当前信号量的等待队列

};

初始化

void sema_init(struct semaphore *sem, int val);

该函数初始化信号量,并设置信号量的值为val。

获得信号量

void down(struct semaphore *sem);

该函数用户获得信号量sem,它会导致睡眠,因此不能用在中断上下文。

int down_interruptible(struct semaphore *sem);

该函数与down类似,不同之处,down不能被信号打断,down_interruptible可以被信号打断。

void down_trylock(struct semaphore *sem);

该函数尝试获得信号量,如果能立即获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文使用。

释放信号量

void up(struct semaphore *sem);

该函数释放信号量,唤醒等待者。

1.4 rwlock_t

一个或多个任务可以并发地持有读者锁;相反,用于写的锁只能被一个写任务持有,而且此时不能有并发地读操作。读写锁也叫做共享/排斥锁,或者并发/排斥锁,因为对于读者而言是共享的,对于写着以排斥的形式获取。

数据结构

typedef struct

{

     Volatile unsigned int lock;

     #ifdef  CONFIG_DEBUG_SPINLOCK

     unsigned magic;

     #endif

     #ifdef CONFIG_PREEMPT  

/*表示进程正在忙等待自旋锁,只有内核支持SMP和内核抢占时才使用本标志*/

     Unsigned int break_lock;

     #endif

}rwlock_t;

读写自旋锁的lock分为两个部分:

0-23位:表示并发读的数量。数据以补码的形式存放。

24位:未锁标识。如果没有读或者写设置时,清0.

如果写者获得了锁,则锁为0x0000 0000(未锁标志清0,表示已锁但无读者。如果一个或多个进程获得了读锁,则锁的值为0x00ff ffff, 0x00ff fffe锁标志清0)。

初始化

rwlock_init(lock);

动态初始化指定锁。

rwlock_tx lock = RW_LOCK_UNLOCKED

静态初始化指定锁。

加锁

加读锁:

read_trylock(lock)

读者用它来尽力获得读写锁lock,如果能够立即获得读写锁,它就获得锁并返回真,否则不能获得锁,返回假。无论是否能够获得锁,它都将立即返回,绝不自旋在那里。

read_lock(lock)

读者要访问被读锁lock保护的共享资源,需要使用该函数来得到读锁lock。如果能够立即获得,它将立即获得读锁并返回,否则,将自旋在那里,直到获得该读锁。

read_lock_irqsave(lock, flags)

读者也可以使用该函数来获得读锁,与read_lock不同的是,该函数还同时把标志寄存器的值保存到了变量flags中,并失效了本地中断。

read_lock_irq(lock)

读者也可以用它来获得读锁,与read_lock不同的是,该函数还同时失效了本地中断。该函数与read_lock_irqsave的不同之处是,它没有保存标志寄存器。

read_lock_bh(lock)

读者也可以用它来获得读锁,与read_lock不同的是,该函数还同时失效了本地的软中断。

加写锁:

write_trylock(lock)

写者用它来尽力获得写锁lock,如果能够立即获得写锁,它就获得锁并返回真,否则不能获得锁,返回假。无论是否能够获得锁,它都将立即返回,绝不自旋在那里。

write_lock(lock)

写者要想访问被写锁lock保护的共享资源,需要使用该宏来得到写锁lock。如果能够立即获得,它将立即获得写锁并返回,否则,将自旋在那里,直到获得该写锁。

write_lock_irqsave(lock, flags)

写者可以用它来获得写锁,与write_lock不同的是,该宏还同时把标志寄存器的值保存到了变量flags中,并失效了本地中断。

write_lock_irq(lock)

写者也可以用它来获得锁,与write_lock不同的是,该宏还同时失效了本地中断。该宏与write_lock_irqsave的不同之处是,它没有保存标志寄存器。

write_lock_bh(lock)

写者也可以用它来获得写锁,与write_lock不同的是,该宏还同时失效了本地的软中断。

释放锁

    释放读锁

read_unlock(lock)

读者使用该函数来释放读锁lock。它必须与read_lock配对使用。

read_unlock_irqrestore(lock, flags)

读者也可以使用该函数来释放读锁,与read_unlock不同的是,该函数还同时把标志寄存器的值恢复为变量flags的值。它必须与read_lock_irqsave配对使用。

read_unlock_irq(lock)

读者也可以使用该函数来释放读锁,与read_unlock不同的是,该函数还同时使能本地中断。它必须与read_lock_irq配对使用。

read_unlock_bh(lock)

读者也可以使用该函数来释放读锁,与read_unlock不同的是,该函数还同时使能本地软中断。它必须与read_lock_bh配对使用。

  释放写锁

write_unlock(lock)

写者使用该函数来释放写锁lock。它必须与write_lock配对使用。

write_unlock_irqrestore(lock, flags)

写者也可以使用该函数来释放写锁,与write_unlock不同的是,该函数还同时把标志寄存器的值恢复为变量flags的值,并使能本地中断。它必须与write_lock_irqsave配对使用。

write_unlock_irq(lock)

写者也可以使用该函数来释放写锁,与write_unlock不同的是,该函数还同时使能本地中断。它必须与write_lock_irq配对使用。

write_unlock_bh(lock)

写者也可以使用该函数来释放写锁,与write_unlock不同的是,该函数还同时使能本地软中断。它必须与write_lock_bh配对使用。

用户态下锁

2.1 pthread_spinlock_t

自旋锁最多可能被一个可执行线程所持有。一个被征用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间)。所以自旋锁不应该被长时间持有。自旋锁是不可递归的!

初始化

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

分配使用自旋锁lock所需要的资源,并且初始化锁lock为未锁状态。

加锁

int pthread_spin_lock(pthread_spinlock_t *lock);

锁住自旋锁lock。当锁没有被某个线程持有时,调用的线程将获得锁,否则线程将不断自旋,知道锁可用。

int pthread_spin_trylock(pthread_spinlock_t *lock);

 如果锁没有被某个线程持有,自旋锁lock将被锁住,否则将会失败。

解锁

int pthread_spin_unlock(pthread_spinlock_t *lock);

释放被锁住的自旋锁lock。

销毁

int pthread_spin_destroy(pthread_spinlock_t *lock);

销毁自旋锁lock,并且回收被锁lock使用的任何资源。

2.2.2 pthread_mutex_t

pthread_mutex_t对应内核的spinlock_t。内核持有自旋锁时不应休眠,而用户态的pthread_mutex_t没有此限制。在持有锁时可以进行休眠的操作。

初始化

pthread_mutex_t使用前需进行初始化,自旋锁的初始化有两种方式:

1、静态初始化PTHREAD_MUTEX_INITIALIZER

如:pthread_mutex_t g_stACLPubLock = PTHREAD_MUTEX_INITIALIZER;

2、动态初始化pthread_mutex_init()

如:pthread_mutex_init(pthread_mutex_t *stLock,  NULL)

加锁

pthread_mutex_lock(pthread_mutex_t *stLock)

加锁,如果锁被占用则一直阻塞,直到持有该锁/异常原因导致失败。

pthread_mutex_trylock(pthread_mutex_t *stLock)

加锁,尝试加锁,如果锁被占用,则返回一个错误码。

pthread_mutex_timedlock(pthread_mutex_t *stLock,const struct timespec tsptr)

加锁,设置一个时间上限,如果超过时间仍然得不到该锁,则返回一个错误码。

解锁

pthread_mutex_unlock(pthread_mutex_t *stLock)

解锁。

销毁

pthread_mutex_destroy(pthread_mutex_t *stLock)

销毁锁。

高级特性

pthread_mutex_init()可以传入pthread_mutexattr_t,用来指明锁的额外特性,例如:

PTHREAD_MUTEX_ERRORCHECK:开启错误检测,当重复释放/重复获取时,lock/unlock函数会返回错误码。

使用实例如下:

pthread_mutex_init(&lock,0);

….

pthread_mutex_lock(&lock)

/*临界区资源…*/

pthread_mutex_unlock(&lock)

pthread_mutex_destroy(&lock)

/*需要真正保护的是数据而不是代码,采用特定的锁保护自己的共享数据*/

2.2 sem_t

信号量也就是操作系统中所用到的PV原语,它广泛用于进程或线程间的同步与互斥。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。信号量分为有名信号量和无名信号量,无名信号量主要用于线程间的互斥,有名信号量用于进程间同步。下面主要介绍无名信号量的相关函数,有名信号量则放置下节的进程间同步。

初始化

sem_init()

sem_init()用来初始化一个semaphore。它的原型为:

extern int sem_init_P(sem_t* _sem, int _pshared, unsigned int _value);

sem为指向信号量结构的一个指针,_pshared不为0时此时信号量在进程间共享,否则只能为当前进程的所有线程共享,value给出了信号量的初始值。

获取

sem_wait(sem_t *sem)

获取semaphore,如果获取不到则阻塞,或者其他异常错误发生。被用来阻塞当前线程直到信号量sem值大于0,解除阻塞后将sem值减一,表明公共资源经使用后减少。它会等到信号量为一个大于0的值才开始减一。

sem_trywait(sem_t *sem)

获取semaphore,如果获取不到,则返回错误码。为sem_wait()的非阻塞版本。它直接将信号量的值减一。

sem_timedwait(sem_t *sem, const struct timespec tsptr)

获取semaphore,如果获取不到,则最多等待指定的时间tsptr,如果超时后仍然获取不到,则返回错误码。

释放

sem_post(sem_t *sem, const struct timespec tsptr)

释放指定的semaphore。等待在sem_wait()上的进程可以被唤醒。用来增加信号量的值,当有线程阻塞在这个信号量上时,调用这个函数会使其中一个线程不在阻塞,选择机制同样由线程的调度策略决定的。

销毁

sem_destroy(sem_t *sem)

销毁semaphore。

2.3 pthread_rwlock_t

pthread_rwlock_t对应内核的rwlock_t。内核持有rwlock_t时不能休眠,但是pthread_rwlock_t则无此限制。

初始化

pthread_rwlock_init(pthread_rwlock_t *lock)

pthread_rwlock_t使用前需要使用pthread_rwlock_init()进行初始化操作。

加锁

       加读锁:

pthread_rwlock_rdlock(pthread_rwlock_t *lock)

       加读锁,如果锁被占用,则阻塞,直到持有该锁或者异常原因导致失败。

pthread_rwlock_tryrdlock(pthread_rwlock_t *lock)

       加读锁,如果锁被占用,则返回错误码。

pthread_rwlock_timedrdlock(pthread_rwlock_t *lock,const struct timespec tsptr)

       加读锁,如果锁被占用,则最长等待指定的时间,如果指定时间后仍然获取不到锁,则返回错误码。

       加写锁:

pthread_rwlock_wrlock(pthread_rwlock_t *lock)

       加写锁,如果锁被占用,则阻塞,直到持有该锁或者异常原因导致失败。

pthread_rwlock_trywrlock(pthread_rwlock_t *lock)

       加写锁,如果锁被占用,则返回错误码。

pthread_rwlock_timedwrlock(pthread_rwlock_t *lock,const struct timespec tsptr)

       加写锁,如果锁被占用,则最长等待指定的时间,如果指定时间后仍然获取不到锁,则返回错误码。

解锁

pthread_rwlock_unlock(pthread_rwlock_t *lock)

       无论读锁还是写锁,解锁时都使用pthread_rwlock_unlock()。

销毁

pthread_rwlock_destroy(pthread_rwlock_t *lock);

高级特性

       pthread_rwlock_init时可以传入pthread_mutexattr_t,用来指明锁的额外特性。

使用实例如下:

pthread_rwlock_t lock;

pthread_rwlock_init(&lock)

….

pthread_rwlock_rdlock(&lock)

/*临界区资源…*/

pthread_rwlock_unlock(&lock)

pthread_rwlock_destroy(&lock)

/*需要真正保护的是数据而不是代码,采用特定的锁保护自己的共享数据*/

2.4 pthread_cond_t

pthread_cond_t提供和semaphore类似的功能,不过无需指定类似semaphore的init value。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要是包括两个动作:一个线程等待“条件变量的条件成立”而挂起;另一个线程使“条件成立”(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起,条件变量的类型为pthread_cond_t。

初始化

主要有动态和静态初始化,动态初始化:

pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

pthread_cond_init()用来初始化一个条件变量。成功返回0,失败返回错误码。一般cond_attr定义为NULL且被忽略。

静态初始化:

Pthread_cond_t cond=PTHREAD_COND_INTIALIZER;

等待

pthread_cond_wait()

用来等待一个条件变量满足,调用时,参数的pthread_mutex_t必须被锁住。函数返回时则cond满足,且持有该pthread_mutex_t。若条件变量没有满足则会阻塞当前线程并释放互斥锁,直到条件变量满足,此线程会先获得互斥锁并继续往下执行。

pthread_cond_timedwait()

通pthread_cond_wait()类似,用来等待一个条件变量满足,调用时,参数的pthread_mutex_t必须被锁住。函数返回时则cond满足或者超时(通过错误码表示),返回时持有该pthread_mutex_t。

这两种等待方式都必须和一个互斥锁配合,以防止多个线程同时请求wait()的竞争条件,互斥锁必须是普通锁或者适应锁,且在调用pthread_cond_wait()前必须由本线程加锁,而在更新条件等待队列前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex被重新加锁,以与进入pthread_cond_wait()前加的锁动作对应。

使用实例:

pthread_mutex_lock();

while(condition_is_false)

    pthread_cond_wait();

pthread_mutex_unlock();

它首先将当前线程加入到唤醒队列,然后立即解锁mutex,最后等待被唤醒,又对metux加锁。

 

唤醒

pthread_cond_signal()

唤醒至少一个等待在cond上的线程,具体激活哪些由调度策略决定。如果没有等待在该cond上的线程,则什么也不发生。

thread_cond_broadcast()

唤醒全部等待在cond上的线程。

销毁

pthread_cond_destroy(pthread_cond_t *cond)

销毁一个cond。成功返回0,失败返回错误码。只有在没有线程在该条件变量上等待的时候才能有注销这个条件变量,否则返回busy。因为linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。

3 各锁用于进程间同步

3.1 互斥量属性

我们已经知道了互斥量可以用于在线程间同步,但实际上,互斥量也可以用于进程间的同步。为了达到这一目的,可以在pthread_mutex_init初始化之前,修改其属性为进程间共享。mutex的属性修改函数主要有以下几个:

pthread_mutexattr_t mattr 类型:                                                //用于定义互斥量的属性

pthread_mutexattr_init函数:                                                 //初始化一个mutex属性对象

pthread_mutexattr_destroy函数:                                      //销毁mutex属性对象 (而非销毁锁)

pthread_mutexattr_setpshared函数:                                                    //修改mutex属性。

int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

我们重点看第二个参数:pshared,它有以下两个取值:

线程锁:PTHREAD_PROCESS_PRIVATE (mutex的默认属性即为线程锁,进程间私有)

进程锁:PTHREAD_PROCESS_SHARED

要想实现进程间同步,需要将mutex的属性改为PTHREAD_PROCESS_SHARED。

使用如下:

pthread_mutexattr_t ma;

pthread_mutex_t stLock;

pthread_mutexattr_init(&ma);

pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);

pthread_mutex_init(&stLock, &ma);

其中:mastLock需要初始化在共享内存或各进程都能访问到的文件中。具体见下面的小例子

 

注:若各个进程间没有父子关系,例如有三个进程同时执行在共享内存中初始化锁的步骤,很可能某两个进程发现共享内存不存在,然后同时新建并初始化锁。某一个 lock mutex,然后另外一个又 init mutex,就乱了。

可以使用如下方式创建共享内存:

shm_open(const char *shm_name, into flag,mode_t mode);

// 打开或创建一个共享内存

if (link(const char * shm_name, const char * shm_name_new) == 0) {

   // 我成功创建了这片共享内存,则在此共享内存内初始化锁

} else {

   // 别人已经创建了共享内存,不需要初始化锁了

}

shm_unlink(shm_name);//使用link时,最后一定要unlink掉shm_name的副本

其实 /dev/shm 是一个 mount 了的文件系统。这里面放的就是一堆通过 shm_open 新建的共享内存。都是以文件的形式展现出来。可以 rm,rename,link 各种文件操作。

其次也可以使用其他方式,如使用shm_open(dir,O_RDWR,0777)检测是否能够打开共享内存对象,若能打开则,说明共享内存已初始化即锁也得到了初始化,直接映射使用。若打开失败,则说明共享内存未创建,此时创建共享内存并初始化锁。如下:

If(-1 == shm_open(dir,O_RDWR,0777))

{

      共享内存未创建,创建共享内存并初始化锁。

}

else

{

      将共享内存直接映射到本进程,并直接使用锁即可

}

这里给出进程同步互斥锁初始化流程:

1、创建共享内存结构体

struct shm_mutex

{

         pthread_mutex_t mutex;

         pthread_mutexattr_t mutexattr;

          ……

          ……

    };

    struct shm_mutex   *pic_mutex;

 

 2、申请共享内存空间

 

      //进程间存在父子关系,若进程间不存在父子关系则使用上面提到的方式申请共享内存

    int fd = shm_open(const char filename, O_CREAT|O_RDWR,0777);

    ftruncate(fd,sizeof(*pic_mutex));                                               //改变文件大小

   

pic_mutex = mmap(NULL,sizeof(struct shm_mutex),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);  

//用户地址映射到内核空间

    close(fd);

    memset(pic_mutex,0,sizeof(*pic_mutex));

 

3、设置共享内存进程间属性

 

    pthread_mutexattr_init(&pic_mutex->mutexattr);

   pthread_mutexattr_setpshared(&pic_mutex->mutexattr,PTHREAD_PROCESS_SHARED);//此句将线程锁改变为进程间共用属性

  pthread_mutex_init(&pic_mutex->mutex,&pic_mutex->mutexattr);

在此线程锁的进程间共用属性设置完毕,即可对其进行上锁解锁操作

 

 4、使用锁

 

   pthread_mutex_lock(&pic_mutex->mutex);                                                   //上锁

   …………

  pthread_mutex_unlock(&pic_mutex->mutex);                                                //解锁

  

   5、销毁锁

    pthread_mutexattr_destroy(&pic_mutex->mutexattr);

pthread_mutex_destroy(&pic_mutex->mutex);

注:这里是将锁初始化在共享内存中,也可以不初始化在共享内存中,只需将锁初始化在各个进程都能访问到了地址空间即可。

这里给出互斥属性用于使用进程间同步的小例子。这里的例子进程间不存在父子关系。

    

3.2 读写锁属性

读写锁也有属性,使用pthread_rwlocksttr_init初始化pthread_rwlocksttr_t结构,再用pthread_rwlocksttr_destroy回收结构。读写锁唯一支持的属性是进程共享属性,该属性与互斥量的进程共享属性相同,就像互斥量的进程共享属性一样,用一对函数来读取和设置读写锁的进程属性。

主要应用函数:

Pthread_rwlocksttr_t rwlock 类型:                                            //用于定义读写锁的属性

pthread_rwlocksttr_init函数:                                             //初始化一个rwlock属性对象

pthread_rwlocksttr_destroy函数:                                   // 销毁rwlock属性对象 (而非销毁锁)

pthread_rwlocksttr_setpshared函数:                                                 //修改rwlock属性。

int pthread_rwlocksttr_setpshared(pthread_rwlocksttr_t *attr, int pshared);

我们重点看第二个参数:pshared,它只有一个取值:

进程锁:PTHREAD_PROCESS_SHARED

要想实现进程间同步,需要将rwlock的属性设为PTHREAD_PROCESS_SHARED。

它的使用方式与上面说的互斥锁是一致的,也是创建共享内存结构体、申请共享内存空间、设置共享内存进程间属性、使用锁和销毁锁。

注:若各个进程间没有父子关系,则也是用在互斥锁中提到的创建共享内存方式创建共享内存来初始化锁。

3.3 条件变量属性

Single UNIX Specification目前定义了条件变量的两个属性:进程共享属性和时钟属性。与其它属性对象一样,有一对函数用于初始化和反初始化的条件变量属性。

主要应用函数:

Pthread_condattr_t attr 类型:                                            //用于定义条件变量的属性

pthread_ condattr_t _init函数:                                           //初始化一个attr属性对象

pthread_ condattr_destroy函数:                                    // 销毁attr属性对象 (而非销毁锁)

pthread_condattr_setpshared函数:                                                   //修改attr属性。

与其它的同步属性一样,条件变量支持进程共享属性。它控制着条件变量可以是被单个进程的多个线程使用,也可以被多个进程的线程使用。要设置进程共享属性的当前值,可以使用pthread_condattr_setpshared函数将属性值设为PTHREAD_PROCESS_SHARED。

注:当属性值用于进程共享时,与其使用的互斥锁也要设置成进程间共享属性。

3.4 有名信号量

有名信号量的特点是把信号量的值保存在文件中。这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关进程。由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当然文件里面保存的有名信号量值就共享了。

       有名信号量是位于共享内存区的(/dev/shm),那么它要保护的资源也必须是位于共享内存区,只有这样才能被无相关的进程所共享。

初始化

sem_t *sem_open(const char *name, int flag)                   

 //打开一个有名信号量,此有名信号量已经存在

 sem_t *sem_open(const char *name, int flag,mode_t mode, unsigned int value)  

//创建一个有名信号量

返回值:若成功,则返回信号量的地址;若出错,返回SEM_FAILED

参数:

name:信号量的文件名。

flags:sem_open()函数的行为标志。

mode:文件的权限。

value:信号量初始值。

获取信号量

sem_wait(sem_t *sem)

获取信号量,如果获取不到则阻塞,或者其他异常错误发生。被用来阻塞当前线程直到信号量sem值大于0,解除阻塞后将sem值减一,表明公共资源经使用后减少。它会等到信号量为一个大于0的值才开始减一。

sem_trywait(sem_t *sem)

获取信号量,如果获取不到,则返回错误码。为sem_wait()的非阻塞版本。它直接将信号量的值减一。

sem_timedwait(sem_t *sem, const struct timespec tsptr)

获取信号量,如果获取不到,则最多等待指定的时间 tsptr,如果超时后仍然获取不到,则返回错误码。

释放

sem_post(sem_t *sem, const struct timespec tsptr)

释放指定的信号量。等待在sem_wait()上的进程可以被唤醒。用来增加信号量的值,当有线程阻塞在这个信号量上时,调用这个函数会使其中一个线程不在阻塞,选择机制同样由线程的调度策略决定的。

关闭有名信号量

sem_close(sem_t *sem)                    

sem 为指向信号量的指针。

删除有名信号量

sem_unlink(const char *name)                    

返回值:成功,返回0;出错返回-1。

注:由于有名信号量位于共享内存区,故它保护的资源也必须位于共享内存区。

4 辨析

下表是锁的内核态和用户态对应关系。其中pthread_cond_t是用户态特有的,内核并没有相对应的结构。

内核态

用户态

spinlock_t

pthread_ spinlock_t

mutex_t

pthread_mutex_t、pthread_rwlock_t

struct semaphore

sem_t

rwlock_t

pthread_ spinlock_t

 

pthread_cond_t

 

内核态下各锁的解析:

适合于中断上下文情况下用的锁

 

spinlock_t:自旋锁、rwlock_t:读写自旋锁

自旋锁和读写自旋锁都不会引起调用者进入休眠状态,故可用于中断上下文本中。

适合于进程下文的情况下用的锁

mutex_t、semaphore

互斥锁和信号量,它们会导致调用者进入休眠状态,因此只能在进程上下文使用。

 

自旋锁、信号量、互斥锁以及读写锁的区别于联系

 

联系:

 

1、读写自旋锁和自旋锁相相似,它们都不会引起调用者进入休眠状态,可用于中断上下文或给很小的代码段加锁。

2、当信号量的初值为1时,可以完成对资源的互斥访问,其功能与互斥锁相似。

3、互斥锁、信号量,它们都会会导致调用者进入休眠状态。

 

区别:

读写自旋锁与自旋锁的区别:

1、读写自旋锁在对数据结果读的次数要比写的次数多的情况下要比自旋锁性能要高,读写锁为读锁时,同一时间可以有多个线程获得读锁,而自旋锁同一时间只能有一个线程获得锁,其他线程进入休眠状态。

互斥锁与信号量的区别:

1、互斥锁用于线程的互斥,信号量用于线程的同步。

2、信号量的初始值可以为任意非负数,而互斥锁的初始值只能为0/1

3、互斥量的加锁和解锁必须由同一线程分别对应使用,而信号量可以由一个线程得到另一个线程释放

 

用户态下各锁的解析:

适合于中断上下文情况下用的锁

 

pthread_ spinlock_t:自旋锁

同一瞬间只能有一个线程能够获取锁,其他线程在等待获取锁的过程中不会进入休眠状态,而是在CPU上进入“自旋”等待。自旋锁的性能很高,但是只适合对很小的代码段加锁(或短期持有的锁),自旋锁对CPU的占用相对较高。

适合于进程下文的情况下用的锁

pthread_mutex_t、sem_t、pthread_rwlock_t,

互斥锁、信号量以及读写锁,它们会导致调用者进入休眠状态,因此只能在进程上下文使用。

 

 

 

信号量、互斥锁以及读写锁的区别于联系

 

联系:

 

1、读写锁pthread_rwlock_t为互斥锁pthread_mutex_t的衍生,在读写锁为写锁时,其作用和互斥锁一致。

2、互斥锁是信号量的特例,信号量的初始值表示有多少个任务可以同时访问共享资源,如果初始值为1,表示只有1个任务可以访问,信号量即与互斥锁相似。

3、互斥锁、信号量以及读写锁,它们都会会导致调用者进入休眠状态。

 

区别:

互斥锁与读写锁的区别:

1、在对文件进行读操作时,互斥锁同一时间只能有一个线程获得锁,其他线程在等待锁的时候会进入休眠状态,而读写锁为读锁时,同一时间可以有多个线程获得读锁,此时读写锁的性能要比互斥锁要高。

互斥锁与信号量的区别:

1、互斥锁的加锁和解锁必须在同一线程里对应使用,所以互斥锁只能用于线程的互斥;信号量可以由一个线程释放,另一个线程得到,所以信号量可以用于线程的同步。

2、信号量通过初始值的设置可以决定有多少个任务可以同时访问共享资源,所以信号量的扩展性要好。而互斥锁同一时间只能有一个线程获得锁,可扩展性差。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux下内核态锁与用户态锁详细介绍 的相关文章

  • 查找并删除超过 x 天的文件或文件夹

    我想删除超过 7 天的文件和文件夹 所以我尝试了 17 07 14 email protected cdn cgi l email protection find tmp mindepth 1 maxdepth 1 ctime 7 exec
  • 如何从程序内部获取指向程序的特定可执行文件部分的指针? (也许是诽谤)

    我在 Linux 环境中 需要编写一个程序来检索放置在其可执行文件的某个部分中的一些数据 那么 如何从程序内部获取指向程序某个部分 通过其名称 的指针呢 我知道可以使用elf getdata 将节的索引作为参数传递给 get 和Elf Da
  • 如果输入被重定向则执行操作

    我想知道如果我的输入被重定向 我应该如何在 C 程序中执行操作 例如 假设我有已编译的程序 prog 并且我将输入 input txt 重定向到它 我这样做 prog lt input txt 我如何在代码中检测到这一点 一般来说 您无法判
  • 如何在 Linux 中向热敏打印机发送 ESC/POS 命令

    我正在尝试在热敏打印机上发送 ESC POS 命令 但每当我发送它们时 热敏打印机都会将它们打印为文本 而不是作为命令执行它们 我在 prn 文件中编写这些命令 每当我执行 lp 命令来打印文件时 这些 prn 文件也会被打印 但作为文本
  • Linux 中有没有一种轻量级的方法来获取当前进程数?

    我希望我的 基于 C C 的 程序显示一个数字指示器 指示本地系统上当前有多少个进程 将经常查询正在运行的进程数值 例如每秒一次 以更新我的显示 有没有一种轻量级的方法来获取该数字 显然我可以调用 ps ax wc l 但我不想强迫计算机生
  • 通过名称获取进程ID

    我想在 Linux 下获得一个给定其名称的进程 ID 有没有一种简单的方法可以做到这一点 我还没有在 C 上找到任何可以轻松使用的东西 如果追求 易于使用 char buf 512 FILE cmd pipe popen pidof s p
  • GCC 详细模式输出解释

    我是 Linux 新手 谁能向我解释一下我的 hello world 程序的以下详细模式输出 另外 这些文件是做什么用的crt1 o crti o crtend o crtbegin o and crtn o and lc and lgcc
  • Linux 中的 Windows NAmed Pipes 替代品

    我们正在将现有的 Windows 代码移植到 Linux 我们使用 ACE 作为抽象层 我们使用 Windows 命名管道与多个客户端进行通信并执行重叠操作 linux 下这个相当于什么 我检查了linux命名管道 FIFO 但它们似乎只支
  • 用于 e NetworkManager VPN 连接的 dbus 信号处理程序

    我需要开发一些在建立 VPN 连接时执行的 python 代码 VPN 由 NetworkManager 控制 我试图弄清楚如何为此使用 NM DBUS 事件 使用 dbus monitor system 我能够识别连接信号 signal
  • 在 MacOS 上构建需要 net461 的 dotnet SDK 项目的最简单方法

    我有一个 dotnet SDK sln and a build proj with
  • EULA 接受 Bash 脚本

    我有一个尝试安装垃圾箱的脚本 除了 bin 在 more 中打开 EULA 之外 一切正常 在脚本再次开始并自行完成安装之前 您必须手动 ctrl c 退出此 more 实例 因为这更多的是逃离 shell 所以脚本在打开后不知道要运行什么
  • 如何使用 PyAudio 选择特定的输入设备

    通过 PyAudio 录制音频时 如何指定要使用的确切输入设备 我的电脑有两个麦克风 一个内置 一个通过 USB 我想使用 USB 麦克风进行录音 这流类 https people csail mit edu hubert pyaudio
  • 使用 .htaccess 启用 PHP 短标签

    我在自己的 Centos 服务器上设置了 Apache 并具有多个虚拟 Web 服务器 并且我希望仅为位于以下位置的其中一个 Web 服务器启用 PHP 短标记 var www ostickets html 我可以通过添加成功启用短标签sh
  • 使用 Vala 和 GLib 的正则表达式

    有没有一个函数 比如http php net manual en function preg match all php http php net manual en function preg match all php 使用 GLibh
  • 为什么我的代码在编译用于分析 (-pg) 时在多线程下运行比在单线程下运行慢?

    我正在写一个光线追踪器 最近 我在程序中添加了线程 以利用 i5 四核上的附加内核 奇怪的是 应用程序的调试版本现在运行速度变慢 但优化后的构建运行速度比添加线程之前更快 我将 g pg 标志传递给 gcc 以进行调试构建 并将 O3 标志
  • 在Linux中创建可执行文件

    我计划做的一件事是编写 非常简单的 Perl 脚本 并且我希望能够在不从终端显式调用 Perl 的情况下运行它们 我明白 要做到这一点 我需要授予他们执行权限 使用 chmod 执行此操作非常简单 但它似乎也是一个稍微费力的额外步骤 我想要
  • 如何从 Linux 命令行确定 LCD 显示器是否打开

    如何通过 Linux 命令行判断计算机的显示器是否打开 关闭 我传统上认为显示器是仅输出的设备 但我注意到 Gnome 显示器首选项对话框具有 检测显示器 功能 这可以推广到确定显示器是否物理关闭吗 VESA DDC 连接是I2C http
  • 有没有办法只安装mysql客户端(Linux)? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 有没有不需要安装整个mysql db安装包的Linux mysql命令行工具 我想做的是从服务器 1 应用程序服务器 执行将在服务器 2
  • Linux mremap 不释放旧映射?

    我需要一种方法将页面从一个虚拟地址范围复制到另一个虚拟地址范围 而无需实际复制数据 范围很大 延迟很重要 mremap 可以做到这一点 但问题是它也会删除旧的映射 由于我需要在多线程环境中执行此操作 因此我需要旧映射能够同时使用 因此稍后当
  • SVN 不断提示我输入密码并拒绝缓存我的凭据

    环境 Eclipse Indigo Ubuntu 11 04 Subclipse 1 6 SVN 客户端 Subclipse RabbitVCS 我通过 svn ssh 连接 我的网址如下所示 svn ssh 我的名字 我的域名 路径 我可

随机推荐