linux 信号量(2.6)

信号量(semaphore) (2.6 内核)

  Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量是一样的,但是它绝不可能在内核之外使用,因此它与System V的IPC机制信号量毫不相干。

  信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。

  一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必 须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。

  当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

信号量数据结构,它定义在include/asm-i386/semaphore.h中:

struct semaphore {

       atomic_t count;

       int sleepers;

       wait_queue_head_t wait;

};

count相当于资源计数,为正数或0时表示可用资源数,-1则表示没有空闲资源且有等待进程。而等待进程的数量并不关心。

  信号量的API有:

1.DECLARE_MUTEX(name)

  该宏声明一个信号量name并初始化它的值为0,即声明一个互斥锁。

2.DECLARE_MUTEX_LOCKED(name)

  该宏声明一个互斥锁name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。因此对于这种锁,一般是先释放后获得。

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

  该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。

4.void init_MUTEX (struct semaphore *sem);

  该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1。

5.void init_MUTEX_LOCKED (struct semaphore *sem);

  该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。

6.void down(struct semaphore * sem);

  该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和softirq上下文)使用该函数。该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。

//调用关系:down() (by asm, /include/asm-i386/semaphore.h )calls __down__failed() (by asm,/arch/i386/kernel/semaphore.c),which calls __down() (by C, /arch/i386/kernel/semaphore.c)

// It should note that, down() isn’t atomic operation. This two cmds could be interrupted, but, before executing interrupt routine, the context including SF will be stored and it will be restored before it back to down() from interrupt routine, so“jns” still can get right SF.

// function down()

static inline void down(struct semaphore * sem)

{     might_sleep();

       __asm__ __volatile__(

              "# atomic down operation/n/t"

              LOCK_PREFIX "decl %0/n/t"     /* --sem->count */

              "jns 1f/n/t"                 

              "call __down_failed/n"

              "1:"

              :"=m" (sem->count)

              :"D" (sem)

              :"memory");

}

 

//function _down_failed()

asm(

".section .sched.text/n"

".align 4/n"

".globl __down_failed/n"

"__down_failed:/n/t"

#if defined(CONFIG_FRAME_POINTER)

       "pushl %ebp/n/t"

       "movl  %esp,%ebp/n/t"

#endif

       "pushl %edx/n/t"

       "pushl %ecx/n/t"

       "call __down/n/t"

       "popl %ecx/n/t"

       "popl %edx/n/t"

#if defined(CONFIG_FRAME_POINTER)

       "movl %ebp,%esp/n/t"

       "popl %ebp/n/t"

#endif

       "ret"

);

 

//function __down()

fastcall void __sched __down(struct semaphore * sem)

{

       struct task_struct *tsk = current;

       DECLARE_WAITQUEUE(wait, tsk);

       unsigned long flags;

 

       tsk->state = TASK_UNINTERRUPTIBLE;

       spin_lock_irqsave(&sem->wait.lock, flags);

       add_wait_queue_exclusive_locked(&sem->wait, &wait);

 

       sem->sleepers++;

       for (;;) {

              int sleepers = sem->sleepers;

 

              /*

               * Add "everybody else" into it. They aren't

               * playing, because we own the spinlock in

               * the wait_queue_head.

               */

              if (!atomic_add_negative(sleepers - 1, &sem->count)) {

                     sem->sleepers = 0;

                     break;

              }

              sem->sleepers = 1;      /* us - see -1 above */

              spin_unlock_irqrestore(&sem->wait.lock, flags);

 

              schedule();

 

              spin_lock_irqsave(&sem->wait.lock, flags);

              tsk->state = TASK_UNINTERRUPTIBLE;

       }

       remove_wait_queue_locked(&sem->wait, &wait);

       wake_up_locked(&sem->wait);

       spin_unlock_irqrestore(&sem->wait.lock, flags);

       tsk->state = TASK_RUNNING;

}

__down()-->当前进程进入wait等待队列,状态为不可中断的挂起,sleepers++,如果这是第一次申请失败,则sleepers值为1,否则为2--这个设置纯粹是为了下面这句原子加而安排的。

在真正进入休眠以前,__down()还是需要判断一下是不是确实没有资源可用,因为在spin_lock之前什么都可能发生。atomic_add_negative()sleepers-1(只可能是0或者1,分别表示仅有一个等待进程或是多个)加到count(如果有多个进程申请资源失败进入__down()count可能为-2-3等)之上,这个加法完成后,结果为0只可能是在sleepers等于1的时候发生(因为如果sleepers等于2,表示有多个进程执行了down(),则count必然小于-1,因此sleepers-1+count必然小于0),表示count在此之前已经变为0了,也就是说已经有进程释放了资源,因此本进程不用休眠而是获得资源退出__down(),从而也从down()中返回;如果没有进程释放资源,那么在所有等待进程的这一加法完成后,count将等于-1。因此,从down()调用外面看(无论是在down()中休眠还是获得资源离开down()),count为负时只可能为-1(此时sleepers等于1),这么设计使得up()操作只需要对count1,判断是否为0就可以知道是否有必要执行唤醒操作__up_wakeup()了。

获得了资源的进程将把sleepers设为0,并唤醒所有其他等待进程,这个操作实际上只是起到恢复count-1,并使它们再次进入休眠的作用,因为第一个被唤醒的等待进程执行atomic_add_negative()操作后会将count恢复为-1,然后将sleepers置为1;以后的等待进程则会像往常一样重新休眠。

down()操作设计得如此复杂的原因和结果就是up操作相当简单。up()利用汇编原子地将count1,如果小于等于0表示有等待进程,则调用__up_wakeup()-->__up()唤醒wait;否则直接返回。

down()中竞争获得资源的进程并不是按照优先级排序的,只是在up()操作完成后第一个被唤醒或者正在__down()中运行而暂未进入休眠的进程成功的可能性稍高一些。

尽管可以将信号量的count初始化为1从而实现一种互斥锁(mutex),但Linux并不保证这个count不会超过1,因为up操作并不考虑count的初值,所以只能依靠程序员自己来控制不要无谓的执行up()从而破坏mutex的语义。相关的初始化接口定义在include/asm/semaphore.h中,但一般程序员可以通过sema_init()接口来初始化信号量:

#define DECLARE_MUTEX(name) __DECLARE_SEMAPHORE_GENERIC(name,1) #define DECLARE_MUTEX_LOCKED(name) __DECLARE_SEMAPHORE_GENERIC(name,0) static inline void sema_init (struct semaphore *sem, int val) static inline void init_MUTEX (struct semaphore *sem) static inline void init_MUTEX_LOCKED (struct semaphore *sem)

除了down()以外,Linux还提供了一个down_interruptible(),操作序列与down()基本相同,仅在休眠状态为可中断和信号处理上有所不同。

7.int down_interruptible(struct semaphore * sem);

  该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。

8.int down_trylock(struct semaphore * sem);

  该函数试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。因此,它不会导致调用者睡眠,可以在中断上下文使用。

9.void up(struct semaphore * sem);

  该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。

这些操作利用了LOCK_PREFIX宏,对于SMP内核,该宏是总线锁指令的前缀,对于单CPU这个宏不起任何作用。这就保证了在SMP环境下访问的原子性。

 

 信号量在绝大部分情况下作为互斥锁使用,下面以console驱动系统为例说明信号量的使用。

  在内核源码树的kernel/printk.c中,使用宏DECLARE_MUTEX声明了一个互斥锁console_sem,它用于保护console驱动列表console_drivers以及同步对整个console驱动系统的访问。

  其中定义了函数acquire_console_sem来获得互斥锁console_sem,定义了release_console_sem来 释放互斥锁console_sem,定义了函数try_acquire_console_sem来尽力得到互斥锁console_sem。这三个函数实际 上是分别对函数down,up和down_trylock的简单包装。

  需要访问console_drivers驱动列表时就需要使用acquire_console_sem来保护console_drivers列表,当访问完该列表后,就调用release_console_sem释放信号量console_sem。

  函数console_unblank,console_device,console_stop,console_start, register_console和unregister_console都需要访问console_drivers,因此它们都使用函数对 acquire_console_sem和release_console_sem来对console_drivers进行保护。

参考文档:《Linux进程管理》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值