同步原语之信号量

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同步原语之信号量相关的知识,希望对你有一定的参考价值。

一、semaphore信号量分析

  1. 首先,说明信号量的作用,信号量的作用类似于自旋锁(其实,同步原语均有相似之处),相似之处在于,都有临界区,各进程需互斥访问,linux中提供了两种信号量的实现,有内核态的和用户态的,这里只介绍内核态的
  2. 相关数据结构
1 struct semaphore { 
2   spinlock_t lock; 
3   unsigned int count; 
4   struct list_head wait_list; 
5 };
 1 struct mutex {
 2         /* 1: unlocked, 0: locked, negative: locked, possible waiters */
 3         atomic_t                count;
 4         spinlock_t              wait_lock;
 5         struct list_head        wait_list;
 6 #if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
 7         struct task_struct      *owner;
 8 #endif
 9 #ifdef CONFIG_MUTEX_SPIN_ON_OWNER
10         void                    *spin_mlock;    /* Spinner MCS lock */
11 #endif
12 #ifdef CONFIG_DEBUG_MUTEXES
13         const char              *name;
14         void                    *magic;
15 #endif
16 #ifdef CONFIG_DEBUG_LOCK_ALLOC
17         struct lockdep_map      dep_map;
18 #endif
19 };

 不同于有些书上所说的结构,该结构是3.10.104内核中的结构,两个结构实际上是一样的,只是mutex结构增加了一些debug的时候使用到的变量

  3. 信号量初始化

 1 #define __SEMAPHORE_INITIALIZER(name, n)                                 2 {                                                                        3         .lock           = __RAW_SPIN_LOCK_UNLOCKED((name).lock),         4         .count          = n,                                             5         .wait_list      = LIST_HEAD_INIT((name).wait_list),              6 }   
 7                               
 8 #define DEFINE_SEMAPHORE(name)   9         struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
10 
11 static inline void sema_init(struct semaphore *sem, int val)
12 {
13         static struct lock_class_key __key; 
14         *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
15         lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
16 }

  初始化操作很好理解,就是__SEMAPHORE_INITIALIZER宏实现的,重点是后面两个初始化操作,需要明确指定将信号量初始化成locked状态还是unlocked状态,用

到了lockdep_init_map函数来初始化spinlock的值,这个操作会在spinlock里面讲解

  4. 信号量操作

1 extern void down(struct semaphore *sem);
2 extern int __must_check down_interruptible(struct semaphore *sem);
3 extern int __must_check down_killable(struct semaphore *sem);
4 extern int __must_check down_trylock(struct semaphore *sem);
5 extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
6 extern void up(struct semaphore *sem);

  5. 获取信号量锁

 1 void down(struct semaphore *sem)
 2 {   
 3         unsigned long flags;  
 4     
 5         spin_lock_irqsave(&sem->lock, flags);
 6         if (likely(sem->count > 0))        
 7                 sem->count--;
 8         else                  
 9                 __down(sem);  
10         spin_unlock_irqrestore(&sem->lock, flags);
11 }

   if条件成立部分很好理解,下面就仔细分析一下__down()函数的实现:

1 static noinline void __sched __down(struct semaphore *sem)                                                                      
2 {   
3         __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);                                                         
4 }

  #define __sched __attribute__((__section__(".sched.text")))  使用section 修饰的函数或者是变量表示这个函数或者是变量可以当做全局变量或者函数来使用,

而不是局部变量,贴出gcc文档里面的一段话看看:Use the section attribute with global variables and not local variables, as shown in the example.

  这个函数的核心部分如下:

 1 static inline int __sched __down_common(struct semaphore *sem, long state,
 2                                                                 long timeout)                      
 3 {
 4         ...
 8         list_add_tail(&waiter.list, &sem->wait_list);
 9         waiter.task = task;   
10         waiter.up = 0;        
11     
12         for (;;) {            
13                 if (signal_pending_state(state, task))
14                         goto interrupted;                  
15                 if (timeout <= 0)
16                         goto timed_out;                    
17                 __set_task_state(task, state);     
18                 spin_unlock_irq(&sem->lock);       
19                 timeout = schedule_timeout(timeout);
20                 spin_lock_irq(&sem->lock);         
21                 if (waiter.up)
22                         return 0;
23         }
24         ...
25 }
  • 前面部分是将当前进程加入等待队列不仔细说啦

    技术分享

  • 两个if条件均不满足,具体分析这里不详细讲了,进程调度部分会详细讲讲,下面就是设置自己的状态,然后去调度啦,需要注意的是,schedule_timeout后面的代码会在下次进程被唤醒的时候才执行,因为在schedule_timeout里面已经schedule一次啦,调度之后当前进程已经睡啦,只能等下次醒来再干活

  6. 释放信号量

 1 void up(struct semaphore *sem)
 2 {   
 3         unsigned long flags;  
 4     
 5         spin_lock_irqsave(&sem->lock, flags);
 6         if (likely(list_empty(&sem->wait_list)))
 7                 sem->count++; 
 8         else                  
 9                 __up(sem);    
10         spin_unlock_irqrestore(&sem->lock, flags);
11 }

  if部分不需要多说啦,下面来分析一下__up函数

1 static noinline void __sched __up(struct semaphore *sem)
2 {
3         struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
4                                                 struct semaphore_waiter, list);
5         list_del(&waiter->list);
6         waiter->up = 1;
7         wake_up_process(waiter->task);
8 }

  这里就很简单啦,拿出来一个进程waiter结构体的up成员置1,然后再看一下__down_common函数,就可以直接返回啦,从而获得了信号量。

  至此semaphore信号量的初始化、获取、释放就都分析完啦,下面分析一下mutex结构的信号量:

二、mutex结构的信号量

  1. 数据结构和锁操作
 1 /*  
 2  * Simple, straightforward mutexes with strict semantics:
 3  *  
 4  * - only one task can hold the mutex at a time
 5  * - only the owner can unlock the mutex
 6  * - multiple unlocks are not permitted
 7  * - recursive locking is not permitted
 8  * - a mutex object must be initialized via the API
 9  * - a mutex object must not be initialized via memset or copying
10  * - task may not exit with mutex held
11  * - memory areas where held locks reside must not be freed
12  * - held mutexes must not be reinitialized
13  * - mutexes may not be used in hardware or software interrupt
14  *   contexts such as tasklets and timers
15  */
16 struct mutex {
17         /* 1: unlocked, 0: locked, negative: locked, possible waiters */
18         atomic_t                count;
19         spinlock_t              wait_lock;
20         struct list_head        wait_list;
21 #if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
22         struct thread_info      *owner;
23 #endif
24 #ifdef CONFIG_DEBUG_MUTEXES
25         const char              *name;
26         void                    *magic;
27 #endif
28 #ifdef CONFIG_DEBUG_LOCK_ALLOC
29         struct lockdep_map      dep_map;
30 #endif
31 };
 1 extern void mutex_lock(struct mutex *lock);
 2 extern int __must_check mutex_lock_interruptible(struct mutex *lock);
 3 extern int __must_check mutex_lock_killable(struct mutex *lock);
 4 /*
 5  * NOTE: mutex_trylock() follows the spin_trylock() convention,
 6  *       not the down_trylock() convention!
 7  *  
 8  * Returns 1 if the mutex has been acquired successfully, and 0 on contention.
 9  */ 
10 extern int mutex_trylock(struct mutex *lock);
11 extern void mutex_unlock(struct mutex *lock);
12 extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);

 2. 初始化操作

 1 /** 
 2  * mutex_init - initialize the mutex
 3  * @mutex: the mutex to be initialized
 4  *  
 5  * Initialize the mutex to unlocked state.
 6  *  
 7  * It is not allowed to initialize an already locked mutex.
 8  */ 
 9 # define mutex_init(mutex) \\  
10 do {                                                    11         static struct lock_class_key __key;             12                                                         13         __mutex_init((mutex), #mutex, &__key);          14 } while (0)
1 #define __MUTEX_INITIALIZER(lockname) 2                 { .count = ATOMIC_INIT(1) 3                 , .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) 4                 , .wait_list = LIST_HEAD_INIT(lockname.wait_list) 5                 __DEBUG_MUTEX_INITIALIZER(lockname) 6                 __DEP_MAP_MUTEX_INITIALIZER(lockname) }
7 
8 #define DEFINE_MUTEX(mutexname) 9         struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)

  先从mutex_init说起,先看__mutex_init的实现

 1 void
 2 __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)
 3 {   
 4         atomic_set(&lock->count, 1);   
 5         spin_lock_init(&lock->wait_lock);
 6         INIT_LIST_HEAD(&lock->wait_list);
 7         mutex_clear_owner(lock);       
 8 #ifdef CONFIG_MUTEX_SPIN_ON_OWNER
 9         lock->spin_mlock = NULL;       
10 #endif
11     
12         debug_mutex_init(lock, name, key);
13 }

  注释中所说的key的作用是用来信号量锁的debug,这里我不仔细分析,统一放在spinlock部分分析,后面的操作也就不用多说了,与semaphore信号量的不同是把count初始化为1,因为该信号量只允许一个进程来拥有这把锁。

  3. 获取锁

 1 /***
 2  * mutex_lock - acquire the mutex
 3  * @lock: the mutex to be acquired
 4  *
 5  * Lock the mutex exclusively for this task. If the mutex is not
 6  * available right now, it will sleep until it can get it.
 7  *
 8  * The mutex must later on be released by the same task that
 9  * acquired it. Recursive locking is not allowed. The task
10  * may not exit without first unlocking the mutex. Also, kernel
11  * memory where the mutex resides mutex must not be freed with
12  * the mutex still locked. The mutex must first be initialized
13  * (or statically defined) before it can be locked. memset()-ing
14  * the mutex to 0 is not allowed.
15  *
16  * ( The CONFIG_DEBUG_MUTEXES .config option turns on debugging
17  *   checks that will enforce the restrictions and will also do
18  *   deadlock debugging. )
19  *
20  * This function is similar to (but not equivalent to) down().
21  */
22 void __sched mutex_lock(struct mutex *lock)
23 {
24         might_sleep();
25         /*
26          * The locking fastpath is the 1->0 transition from
27          * ‘unlocked‘ into ‘locked‘ state.
28          */
29         __mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
30         mutex_set_owner(lock);
31 }

  注释部分已经解释的很清楚啦,我就不再重复,重点说一下__mutex_fastpath_lock宏

 1 /**
 2  * __mutex_fastpath_lock - decrement and call function if negative
 3  * @v: pointer of type atomic_t
 4  * @fail_fn: function to call if the result is negative
 5  *
 6  * Atomically decrements @v and calls <fail_fn> if the result is negative.
 7  */
 8 #define __mutex_fastpath_lock(v, fail_fn)                        9 do {                                                            10         unsigned long dummy;                                    11                                                                 12         typecheck(atomic_t *, v);                               13         typecheck_fn(void (*)(atomic_t *), fail_fn);            14                                                                 15         asm volatile(LOCK_PREFIX "   decl (%%rdi)\\n"            16                      "   jns 1f         \\n"                     17                      "   call " #fail_fn "\\n"                   18                      "1:"                                       19                      : "=D" (dummy)                             20                      : "D" (v)                                  21                      : "rax", "rsi", "rdx", "rcx",              22                        "r8", "r9", "r10", "r11", "memory");     23 } while (0)

  结合上面的代码,这段代码的意思就是:如果结果是负数的话就去调用__mutex_lock_slowpath函数。是负数说明现在有人在持有锁,不然就直接获取锁啦。__mutex_lock_slowpath函数的功能类似于down函数,做一些进程调度相关的工作,这里不仔细讲了。

  3、释放锁

/***
 * mutex_unlock - release the mutex
 * @lock: the mutex to be released
 *
 * Unlock a mutex that has been locked by this task previously.
 *
 * This function must not be used in interrupt context. Unlocking
 * of a not locked mutex is not allowed.
 *
 * This function is similar to (but not equivalent to) up().
 */
void __sched mutex_unlock(struct mutex *lock)
{
        /*
         * The unlocking fastpath is the 0->1 transition from ‘locked‘
         * into ‘unlocked‘ state:
         */
      ...  
        __mutex_fastpath_unlock(&lock->count, _mutex_unlock_slowpath);
}    

  需要注意的是在没有人拥有锁的时候是不能释放的,所以在执行unlock之前需要先判断一下,使用前面提到的

1 static inline int mutex_is_locked(struct mutex *lock)
2 {
3         return atomic_read(&lock->count) != 1;
4 }

  保证输入合法,__mutex_fastpath_unlock宏的执行流程和__mutex_fastpath_lock宏的功能类似,这里的语意就是如果在释放锁之前有很多进程向获得锁就去调用_mutex_unlock_slowpath函数来唤醒一个进程获取这把锁,都是些进程调度的操作,其他的就是修改锁信息,没什么内容。

  至此,两种信号量锁都讲完啦,读写信号量锁单独来讲吧,篇幅有点长好像,博文里面粘的代码比较多,但是个人认为代码才是最重要的参考资料,代码里的注释就更宝贵了,比某些人出的书还好,毕竟是写内核的大神注释的,而且这些大神对注释是很吝啬的,不轻易去加,但凡加了的我都会好好去看的。

  




以上是关于同步原语之信号量的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核源码分析 -- 同步原语 -- 信号量 semaphore

Linux内核源码分析 -- 同步原语 -- 信号量 semaphore

允许确定哪个 PID 正在使用它们的跨平台同步原语

IPC之Posix信号量详解

Qt5 中存在哪些同步原语可以让我在单个线程中等待信号到达?

操作系统 王道考研2019 第二章:进程管理 -- 信号量机制(整型 / 记录型信号量)原语P / V操作用信号量机制实现进程互斥同步前驱关系