分类: LINUX
2015-02-27 15:51:31
原文地址:linux2.6内核信号量学习 作者:lanlovehua
信号量(semaphore)是用于保护临界区的一种常用方法。只有得到信号量的进程才能执行临界区代码,而没有得到信号量的进程进入休眠等待状态。
Linux系统中与信号量相关的操作主要有如下4种。
1 定义信号量
下面代码定义名为sem的信号量。
struct semaphore sem;
struct semaohore结构体在内核中定义如下:
在/include/linux/semaphore.h目录下:
struct semaphore{
spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
2初始化信号量
在/include/linux/semaphore.h目录下,void sema_init(struct semaphore*
sem, int val) 函数用于初始化信号量,并设置信号量sem的值为val。尽管信号量可以被初始化为大于1的值从而成为一个计数信号量,但是它通常不被这样使用。
内核定义了两个宏来把sem的值设置为1或者0。
#define init_MUTEX(sem) sema_init(sem, 1)
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0)
使用init_MUTEX(sem) 初始化信号量时,表示信号量最初是可以被获取的。而使用init_MUTEX_LOCKED(sem) 初始化信号量时,此信号量只有先被释放才可以获取。
3获取信号量
void down(struct semaphore *sem);
该函数用于获取信号量sem,它会导致睡眠,因此不能在中断上下文使用。
在内核里该函数的源代码如下:
在kernel/semaphore.c文件里:
53 void down(struct semaphore *sem)
54 {
55 unsigned long flags;
56
57 spin_lock_irqsave(&sem->lock, flags);
58 if (likely(sem->count > 0))
59 sem->count--;
60 else
61 __down(sem);
62 spin_unlock_irqrestore(&sem->lock, flags);
63 }
这里重点看58行:if (likely(sem->count > 0)),这句话表示当获取信号量成功时,就执行sem->count—; 即对信号量的值减一。else表示获取信号量失败,此时调用__down函数进入睡眠状态,并将此进程插入到等待队列尾部。
内核定义了信号量的等待队列结构体:
193 struct semaphore_waiter {
194 struct list_head list;
195 struct task_struct *task;
196 int up;
197 };
此结构体是一个双向循环链表。
int down_interruptible(struct semaphore *sem);
该函数功能与down()类似,不同之处是,down()在获取信号量失败进入睡眠状态时的进程是不能被打断的,而down_interruptible()在进入睡眠状态时的进程能被信号打断,信号也会导致函数返回。下面我们也来看一看这个函数的源码:
在kernel/semaphore.c文件里:
75 int down_interruptible(struct semaphore *sem)
76 {
77 unsigned long flags;
78 int result = 0;
79
80 spin_lock_irqsave(&sem->lock, flags);
81 if (likely(sem->count > 0))
82 sem->count--;
83 else
84 result = __down_interruptible(sem);
85 spin_unlock_irqrestore(&sem->lock, flags);
86
87 return result;
88 }
这里我们可以看到,当获取信号量成功时,返回0,而获取信号量失败时,返回一个非0的值。在使用down_interruptible()函数获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回-ERESTARTSYS。如:
if ( down_interruptible(&sem) )
return -ERESTARTSYS;
这里还有一个问题:在获取信号量失败后,为什么down不能被中断,而down_interruptible却可以被中断呢?我们从down和down_interruptible的源代码可以得知,在获取信号量失败后,down函数运行了__down函数,而down_interruptible函数运行了__down_interruptible。那么让我们来看一下这两个函数的源码:
在kernel/semaphore.c文件里:
236 static noinline void __sched __down(struct semaphore *sem)
237 {
238 __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
239 }
240
241 static noinline int __sched __down_interruptible(struct semaphore *sem)
242 {
243 return __down_common(sem,TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
244 }
在__down函数里,是把进程的状态设置为TASK_UNINTERRUPTIBLE ,即不可中断状态。
而在__down_interruptible里,是把进程的状态设置为TASK_INTERRUPTIBLE ,即可中断状态。这就解释了以上提出的问题。
4释放信号量
void up(struct semaphore *sem);
该函数用于释放信号量sem,唤醒等待者。
它的源代码如下:
178 void up(struct semaphore *sem)
179 {
180 unsigned long flags;
181
182 spin_lock_irqsave(&sem->lock, flags);
183 if (likely(list_empty(&sem->wait_list)))
184 sem->count++;
185 else
186 __up(sem);
187 spin_unlock_irqrestore(&sem->lock, flags);
在183行的语句中,up函数首先判断等待队列是否为空,如果是空的话,就执行sem->count++;否则,执行__up() 函数,释放掉等待队列尾部的信号量。
信号量用于同步举例:
如果信号量被初始化为0,则它可以用于同步,同步意味着一个执行单元的继续执行需要等待另一个执行单元完成某事,保证执行的先后顺序。
如上图所示,执行单元A执行代码区域a之前,必须等待执行单元B执行完代码区域b后释放信号量给它。
以下模块很好地使用了信号量:
#include
#include
#include
#include
struct semaphore sem1;
struct semaphore sem2;
int num[2][5] = {
{0,2,4,6,8},
{1,3,5,7,9}
};
int thread_one(void *p);
int thread_two(void *p);
int thread_one(void *p)
{
int *num = (int *)p;
int i;
for(i = 0; i < 5; i++){
down(&sem1); //获取信号量1
printk("%d ", num[i]);
up(&sem2); //释放信号量2
}
return 0;
}
int thread_two(void *p)
{
int *num = (int *)p;
int i;
for(i = 0; i < 5; i++){
down(&sem2); //获取信号量2
printk("%d ", num[i]);
up(&sem1); //释放信号量1
}
return 0;
}
static int lan_init(void)
{
printk("lan is coming\n");
init_MUTEX(&sem1); //初始化信号量1, 使信号量1最初可被获取
init_MUTEX_LOCKED(&sem2); //初始化信号量2,使信号量2只有被释放后才可被获取
kernel_thread(thread_one, num[0], CLONE_KERNEL);
kernel_thread(thread_two, num[1], CLONE_KERNEL);
return 0;
}
static void lan_exit(void)
{
printk("\nlan exit\n");
}
module_init(lan_init);
module_exit(lan_exit);