linux内核信号量学习(2.6.23(i386))
2011-03-21 09:53
260 查看
一、定义:
/linux/include/asm-i386/semaphore.h
二、作用:
Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量
。主要用在linux内核中的同步和互斥。
三、字段详解:
1、atomic_t count;
在此根据count.counter的值不同该字段代表不同的意义:
(1)如果count.counter大于0,则资源是空闲的,该资源现在可以被使用。
(2)如果count.counter等于0,则信号量是忙的,但没有进程等待这个被保护的资源,当前只有该进程在访问被保护的资源。
(3)如果count.counter小于0,则该资源不可用,并且至少有一个进程在等待该资源。
2、int sleepers;
存放一个标志,表示是否有一些进程在信号量上睡眠。在获取信号量操作的时候,使用该字段和count字段来判断信号量的状态和进行不同的操作。
3、wait_queue_head_t;
task_list字段存放当前等待该信号量的所有进程的链表。如果count.counter大于或等于0,该链表就为空。
四、特点:
信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。
一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。
五、操作:
1、定义及初始化:
(1)
struct semapom sem;
sema_init(&sem,1);
直接定义一个信号量sem,并调用sema_init()对其进行初始化:
该函数会将sem->count.counter初始化为val。虽然val可以为任何整数,但通常会取1、0。并置sleepers为0,sem->wait.task_list为空链表。
(2)
srtuct semaphore sem;
init_MUTEX(&sem);
直接定义信号量sem并初始化为互斥信号量。
该函数直接将信号量的count.counter置为1,便是初始化一个用于互斥访问的信号量。也就是说被保护的资源不能同时被多个进程访问,此刻资源是空闲的,那么当有一个进程访问该资源时,也即获得了信号量,当再有进程到来且当前访问资源的进程没有释放信号量时,后来的进程是不能访问该资源的。此刻它被置入信号量的等待进程队列,并进入休眠状态。
(3)
struct semaphore sem;
init_MUTEX_LOCKED(&sem);
直接定义信号量sem并初始化为资源忙状态,用于同步。
也就是说当前该信号量已经被锁定,一个执行单元的继续执行需等待另一执行单元完成,保证执行的先后顺序。例如:
如上,进程A先运行,运行到down(&sem);的时候发现信号量为资源忙状态,不能获得,于是被置入信号量的等待队列中。当进程B执行完CODE 2代码段到up(&sem)时,会释放信号量,发现进程A在等待信号量,就将A从等待队列中删除,并唤醒A。这样就保证了代码的执行顺序是CODE 1 → CODE 2 → CODE 3。实现了同步。
(4)
DECLARE_MUTEX(sem);
DECLARE_MUTEX_LOCKED(sem);
此两个宏都是定义初始化信号量。DECLARE_MUTEX()等同于以上所讲的第二个,DECLARE_MUTEX_LOCKED()等同于以上所讲的第三个。
2、获得信号量:
(1)
从代码中可知:
如果count.counter为1,则置count.counter为0,直接跳出函数。
如果count.counter为0,count.counter被减为-1,之后执行__down_failed()函数。
__down_failed()函数首先将当前进程设置为不可中断状态(TASK_UNINTERRUPTIBLE
)然后将其添加进等待进程队列,接下来在whlie循环处试图获得信号量。如果count.counter大于0就获得了信号量,则不进入循环,将当前进程从等待队列中删除,并设置其状态为可运行状态(TASK_RUNNING),最后唤醒等待该信号量的进程(此处初始count.counter为0,表示没有等待进程,所以此句相当于没有)。否则将count.counter设置为-1,并进入whlie循环,挂起当前进程,随后又恢复,继续测试count.counter字段直到其大于0(即获得信号量)。
如果count.counter为-1,则其被置为-2,之后执行__down_failed()函数。与count.counter等于0不同的是其在获得了信号量之后,由于有等待进程(count.counter=-1),所以退出时会唤醒等待进程。
由于信号量会导致睡眠,所以不能用在中断上下文。再者使用down()而进入睡眠的进程不能被信号打断。
(2)
由代码可以看出down_interruptible()和down()不同的是:down_interruptible()有返回值,
在调用__down_failed_interruptible()函数时,while循环中稍有不同。__down_failed_interruptible()在while中时,如果收到TIF_SIGPENDING信号时,会置count.counter为0,跳出循环,可见down_interruptible()是可以被信号打断的,且返回非零(EINIR)。
(3)
该函数尝试获得信号量sem,如果能够立即获得,则获得信号量sem并返回0,否则,返回非0。它不会导致调用者睡眠,可以在中断上下文使用。
3、释放信号量:
up()函数首先使count.counter自增,如果其大于0,则,说明其初始值是0,没有等待进程,所以直接跳出。否则调用__up_wake_up()函数将count.counter置为1,再唤醒等待进程。
六、使用实例:
信号量的一般使用形式是:
DECLARE_MUTEX(sem);
down(&sem); //获得信号量
...[CODE]... //临界区(被保护的资源)
up(&sem); //释放信号量
七、信号量与自旋锁的比较:
信号量是进程级别的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来竞争资源的。如果竞争失败,就会发生进程上下文切换,当前进程进入睡眠状态,CPU将运行其他进程。鉴于进程上下文切换的开销也很大,因此只有当进程占用资源时间较长时,用信号量才是较好的选择。当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是CPU得不到自旋锁会在那里空转知道其他执行单元解锁为止。所以要求锁不能在临界区里常时间停留,否则会降低系统的效率。
自旋锁对信号量
------------------------------------------------------
需求 建议的加锁方法
低开销加锁 优先使用自旋锁
短期锁定 优先使用自旋锁
长期加锁 优先使用信号量
中断上下文中加锁 使用自旋锁
持有锁是需要睡眠、调度 使用信号量
/linux/include/asm-i386/semaphore.h
44struct semaphore { 45 atomic_t count; 46 int sleepers; 47 wait_queue_head_t wait; 48}; |
Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量
。主要用在linux内核中的同步和互斥。
三、字段详解:
1、atomic_t count;
typedef struct { int counter; } atomic_t;
在此根据count.counter的值不同该字段代表不同的意义:
(1)如果count.counter大于0,则资源是空闲的,该资源现在可以被使用。
(2)如果count.counter等于0,则信号量是忙的,但没有进程等待这个被保护的资源,当前只有该进程在访问被保护的资源。
(3)如果count.counter小于0,则该资源不可用,并且至少有一个进程在等待该资源。
2、int sleepers;
存放一个标志,表示是否有一些进程在信号量上睡眠。在获取信号量操作的时候,使用该字段和count字段来判断信号量的状态和进行不同的操作。
3、wait_queue_head_t;
50struct __wait_queue_head { 51 spinlock_t lock; 52 struct list_head task_list; 53}; 54typedef struct __wait_queue_head wait_queue_head_t; |
四、特点:
信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。
一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。
五、操作:
1、定义及初始化:
(1)
struct semapom sem;
sema_init(&sem,1);
直接定义一个信号量sem,并调用sema_init()对其进行初始化:
64static inline void sema_init (struct semaphore *sem, int val) 65{ 66/* 67 * *sem = (struct semaphore)__SEMAPHORE_INITIALIZER((*sem),val); 68 * 69 * i'd rather use the more flexible initialization above, but sadly 70 * GCC 2.7.2.3 emits a bogus warning. EGCS doesn't. Oh well. 71 */ 72 atomic_set(&sem->count, val); 73 sem->sleepers = 0; 74 init_waitqueue_head(&sem->wait); 75} |
(2)
srtuct semaphore sem;
init_MUTEX(&sem);
直接定义信号量sem并初始化为互斥信号量。
77static inline void init_MUTEX (struct semaphore *sem) 78{ 79 sema_init(sem, 1); 80} |
(3)
struct semaphore sem;
init_MUTEX_LOCKED(&sem);
直接定义信号量sem并初始化为资源忙状态,用于同步。
82static inline void init_MUTEX_LOCKED (struct semaphore *sem) 83{ 84 sema_init(sem, 0); 85} |
进程A 进程B struct semaphore sem; ...[CODE 2]... init_MUTEX_LOCKED(&sem); up(&sem); ...[CODE 1]... down(&sem); ...[CODE 3]... |
(4)
DECLARE_MUTEX(sem);
DECLARE_MUTEX_LOCKED(sem);
51#define __SEMAPHORE_INITIALIZER(name, n) / 52{ / 53 .count = ATOMIC_INIT(n), / 54 .sleepers = 0, / 55 .wait = __WAIT_QUEUE_HEAD_INITIALIZER((name).wait) / 56} 57 58#define __DECLARE_SEMAPHORE_GENERIC(name,count) / 59 struct semaphore name = __SEMAPHORE_INITIALIZER(name,count) 60 61#define DECLARE_MUTEX(name) __DECLARE_SEMAPHORE_GENERIC(name,1) 62#define DECLARE_MUTEX_LOCKED(name) __DECLARE_SEMAPHORE_GENERIC(name,0) |
2、获得信号量:
(1)
97static inline void down(struct semaphore * sem) 98{ 99 might_sleep(); 100 __asm__ __volatile__( 101 "# atomic down operation/n/t" 102 LOCK_PREFIX "decl %0/n/t" /* --sem->count */ 103 "jns 2f/n" 104 "/tlea %0,%%eax/n/t" 105 "call __down_failed/n" 106 "2:" 107 :"+m" (sem->count) 108 : 109 :"memory","ax"); 110} 64void __sched 65__down_failed(struct semaphore *sem) 66{ 67 struct task_struct *tsk = current; 68 DECLARE_WAITQUEUE(wait, tsk); 69 70#ifdef CONFIG_DEBUG_SEMAPHORE 71 printk("%s(%d): down failed(%p)/n", 72 tsk->comm, tsk->pid, sem); 73#endif 74 75 tsk->state = TASK_UNINTERRUPTIBLE; 76 wmb(); 77 add_wait_queue_exclusive(&sem->wait, &wait); 78 79 /* 80 * Try to get the semaphore. If the count is > 0, then we've 81 * got the semaphore; we decrement count and exit the loop. 82 * If the count is 0 or negative, we set it to -1, indicating 83 * that we are asleep, and then sleep. 84 */ 85 while (__sem_update_count(sem, -1) <= 0) { 86 schedule(); 87 set_task_state(tsk, TASK_UNINTERRUPTIBLE); 88 } 89 remove_wait_queue(&sem->wait, &wait); 90 tsk->state = TASK_RUNNING; 91 92 /* 93 * If there are any more sleepers, wake one of them up so 94 * that it can either get the semaphore, or set count to -1 95 * indicating that there are still processes sleeping. 96 */ 97 wake_up(&sem->wait); 98 99#ifdef CONFIG_DEBUG_SEMAPHORE 100 printk("%s(%d): down acquired(%p)/n", 101 tsk->comm, tsk->pid, sem); 102#endif 103} |
如果count.counter为1,则置count.counter为0,直接跳出函数。
如果count.counter为0,count.counter被减为-1,之后执行__down_failed()函数。
__down_failed()函数首先将当前进程设置为不可中断状态(TASK_UNINTERRUPTIBLE
)然后将其添加进等待进程队列,接下来在whlie循环处试图获得信号量。如果count.counter大于0就获得了信号量,则不进入循环,将当前进程从等待队列中删除,并设置其状态为可运行状态(TASK_RUNNING),最后唤醒等待该信号量的进程(此处初始count.counter为0,表示没有等待进程,所以此句相当于没有)。否则将count.counter设置为-1,并进入whlie循环,挂起当前进程,随后又恢复,继续测试count.counter字段直到其大于0(即获得信号量)。
如果count.counter为-1,则其被置为-2,之后执行__down_failed()函数。与count.counter等于0不同的是其在获得了信号量之后,由于有等待进程(count.counter=-1),所以退出时会唤醒等待进程。
由于信号量会导致睡眠,所以不能用在中断上下文。再者使用down()而进入睡眠的进程不能被信号打断。
(2)
112/* 113 * Interruptible try to acquire a semaphore. If we obtained 114 * it, return zero. If we were interrupted, returns -EINTR 115 */ 116static inline int down_interruptible(struct semaphore * sem) 117{ 118 int result; 119 120 might_sleep(); 121 __asm__ __volatile__( 122 "# atomic interruptible down operation/n/t" 123 "xorl %0,%0/n/t" 124 LOCK_PREFIX "decl %1/n/t" /* --sem->count */ 125 "jns 2f/n/t" 126 "lea %1,%%eax/n/t" 127 "call __down_failed_interruptible/n" 128 "2:" 129 :"=&a" (result), "+m" (sem->count) 130 : 131 :"memory"); 132 return result; 133} 105int __sched 106__down_failed_interruptible(struct semaphore *sem) 107{ 108 struct task_struct *tsk = current; 109 DECLARE_WAITQUEUE(wait, tsk); 110 long ret = 0; 111 112#ifdef CONFIG_DEBUG_SEMAPHORE 113 printk("%s(%d): down failed(%p)/n", 114 tsk->comm, tsk->pid, sem); 115#endif 116 117 tsk->state = TASK_INTERRUPTIBLE; 118 wmb(); 119 add_wait_queue_exclusive(&sem->wait, &wait); 120 121 while (__sem_update_count(sem, -1) <= 0) { 122 if (signal_pending(current)) { 123 /* 124 * A signal is pending - give up trying. 125 * Set sem->count to 0 if it is negative, 126 * since we are no longer sleeping. 127 */ 128 __sem_update_count(sem, 0); 129 ret = -EINTR; 130 break; 131 } 132 schedule(); 133 set_task_state(tsk, TASK_INTERRUPTIBLE); 134 } 135 136 remove_wait_queue(&sem->wait, &wait); 137 tsk->state = TASK_RUNNING; 138 wake_up(&sem->wait); 139 140#ifdef CONFIG_DEBUG_SEMAPHORE 141 printk("%s(%d): down %s(%p)/n", 142 current->comm, current->pid, 143 (ret < 0 ? "interrupted" : "acquired"), sem); 144#endif 145 return ret; 146} |
在调用__down_failed_interruptible()函数时,while循环中稍有不同。__down_failed_interruptible()在while中时,如果收到TIF_SIGPENDING信号时,会置count.counter为0,跳出循环,可见down_interruptible()是可以被信号打断的,且返回非零(EINIR)。
(3)
139static inline int down_trylock(struct semaphore * sem) 140{ 141 int result; 142 143 __asm__ __volatile__( 144 "# atomic interruptible down operation/n/t" 145 "xorl %0,%0/n/t" 146 LOCK_PREFIX "decl %1/n/t" /* --sem->count */ 147 "jns 2f/n/t" 148 "lea %1,%%eax/n/t" 149 "call __down_failed_trylock/n/t" 150 "2:/n" 151 :"=&a" (result), "+m" (sem->count) 152 : 153 :"memory"); 154 return result; 155} |
3、释放信号量:
168static inline void up(struct semaphore * sem) 169{ 170 __asm__ __volatile__( 171 "# atomic up operation/n/t" 172 LOCK_PREFIX "incl %0/n/t" /* ++sem->count */ 173 "jg 1f/n/t" 174 "call __up_wakeup/n" 175 "1:" 176 :"=m" (sem->count) 177 :"D" (sem) 178 :"memory"); 179} 148void 149__up_wakeup(struct semaphore *sem) 150{ 151 /* 152 * Note that we incremented count in up() before we came here, 153 * but that was ineffective since the result was <= 0, and 154 * any negative value of count is equivalent to 0. 155 * This ends up setting count to 1, unless count is now > 0 156 * (i.e. because some other cpu has called up() in the meantime), 157 * in which case we just increment count. 158 */ 159 __sem_update_count(sem, 1); 160 wake_up(&sem->wait); 161} |
六、使用实例:
信号量的一般使用形式是:
DECLARE_MUTEX(sem);
down(&sem); //获得信号量
...[CODE]... //临界区(被保护的资源)
up(&sem); //释放信号量
七、信号量与自旋锁的比较:
信号量是进程级别的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来竞争资源的。如果竞争失败,就会发生进程上下文切换,当前进程进入睡眠状态,CPU将运行其他进程。鉴于进程上下文切换的开销也很大,因此只有当进程占用资源时间较长时,用信号量才是较好的选择。当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是CPU得不到自旋锁会在那里空转知道其他执行单元解锁为止。所以要求锁不能在临界区里常时间停留,否则会降低系统的效率。
自旋锁对信号量
------------------------------------------------------
需求 建议的加锁方法
低开销加锁 优先使用自旋锁
短期锁定 优先使用自旋锁
长期加锁 优先使用信号量
中断上下文中加锁 使用自旋锁
持有锁是需要睡眠、调度 使用信号量
相关文章推荐
- 读写信号量(2.6.23内核,i386)
- 读写信号量(2.6.23内核,i386)
- 读写信号量(2.6.23内核,i386)
- linux内核学习-6信号量(关注新浪微博:寂寞侵蚀的岁月(4000多篇技术分享))
- Linux内核与驱动开发学习总结:自旋锁和信号量(五)
- 用qemu模拟i386的linux内核,用于内核学习
- linux学习之二十一---信号量
- linux汇编学习(4)-----引导linux内核
- μC/OS-II学习之:任务,信号量、邮箱、队列及其区别
- 学习笔记 --- LINUX内核里面的IS_ERR宏解析
- Linux内核的ioctl函数学习
- linux内核学习笔记----进程状态
- 嵌入式Linux驱动学习之路(七)Linux内核启动流程
- Linux驱动学习笔记(6)信号量(semaphore)与互斥量(mutex)【转】
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】深入剖析Linux中断机制之四--中断API
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Oops在Linux 2.6内核+PowerPC架构下的前世今生
- Linux内核学习笔记六——并发和同步概念
- Linux内核学习笔记:前言
- Linux内核学习开始
- Linux内核学习和研究及嵌入式(ARM)学习和研究的开放文档