Waitqueue、Event及Semaphore的实现机制分析

Waitqueue、Event及Semaphore的实现机制分析

Sailor_forever sailing_9806@163.com 转载请注明

 

【摘要】本文分析了内核同步及互斥的几种机制Waitqueue、Event及Semaphore的实现,详细分析了其实现流程。Event及Semaphore本质上都是基于Waitqueue和自旋锁实现的。总结了静态定义及动态初始化数据结构的相关规则,这对于自定义的数据类型具有重要的借鉴意义。

【关键词】Waitqueue;Event;Semaphore;Spinlock;XXX_INITIALIZER();DECLARE_XXX;静态定义;动态初始化

 

1     资源总览... 1

2     wait_queue_head_t 2

3     wait_queue_t 3

4     数据结构设计规则... 3

5     等待事件event 4

5.1      prepare_to_wait和finish_wait 4

5.2      wait_event 6

5.3      wait_event_timeout 7

5.4      wait_event_interruptible_timeout 8

6     基于等待队列的semaphore. 9

 

---------------------------------------------------------------------------------------------------------

1         资源总览
内核版本:  2. 6. 19

主要源文件

/include/linux/wait.h

/kernel /wait.c

/arch/arm/kernel/semaphore.c

/include/asm-arm/ semaphore.h

 

_wait_queue_head {}

wait_queue_head_t

__WAITQUEUE_INITIALIZER()

DECLARE_WAITQUEUE()

__WAIT_QUEUE_HEAD_INITIALIZER()

DECLARE_WAIT_QUEUE_HEAD()

 

init_waitqueue_head(wait_queue_head_t *q)

init_waitqueue_entry(wait_queue_t *q,struct task_struct *p)

 

__wait_event(wq,condition)

wait_event(wq,condition)

__wait_event_timeout(wq,condition,timeout)           

wait_event_timeout(wq,condition,timeout)

__wait_event_interruptible_timeout(wq,condition,ret)

wait_event_interruptible_timeout(wq,condition,timeout)

 

__down(struct semaphore * sem)

__down_interruptible(struct semaphore * sem)

down_interruptible(struct semaphore * sem)

__down_trylock(struct semaphore * sem)

---------------------------------------------------------------------------------------------------------

 

2         wait_queue_head_t
/include/linux/wait.h

struct __wait_queue_head {

       spinlock_t lock;

       struct list_head task_list;

};

typedef struct __wait_queue_head  wait_queue_head_t;

关于自定义结构体的风格,若需要提供别名,则原始类型前面加”__”或者“tag_”,表示其为内部数据类型,对外是不可见的。typedef之后的类型为了和原始类型分开一般会在后面添加“_t”,表示是typedef的,对外使用。

 

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                        /

       .lock              = __SPIN_LOCK_UNLOCKED(name.lock),              /

       .task_list  = { &(name).task_list, &(name).task_list } /

}

因为Linux内核对于链表的遍历方式的问题,通常一个双向循环链表中有一个头节点,其与其他节点的结构不一样,并且通常无有效信息。此处的等待队列头有两个域:

1)        操作循环链表的互斥锁;

2)        嵌入到等待队列头中的链表头。

为了用.域的形式初始化成员不能采用单独的初始化锁和链表头部的宏,但可以采用声明一个结构体类型的宏,如__SPIN_LOCK_UNLOCKED(name.lock);.task_list的初始化应该采用LIST_HEAD_INIT宏的,这样提高了可移植性。定义等待队列头部的同时,初始化了其成员,尤其是链表头的初始化是添加后续等待队列的前提。

 

#define DECLARE_WAIT_QUEUE_HEAD(name) /

       wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

定义一个等待队列头同时分配内存并进行初始化。对外的接口。

 

extern void init_waitqueue_head(wait_queue_head_t *q);

void init_waitqueue_head(wait_queue_head_t *q)

{

       spin_lock_init(&q->lock);

       INIT_LIST_HEAD(&q->task_list);

}

动态初始化一个已经分配了内存的wait_queue_head_t结构。当wait_queue_head_t类型成员内嵌到其他结构体中时需要采用此方法,而不能采用DECLARE_WAIT_QUEUE_HEAD全局或在栈中定义初始化一个wait_queue_head_t结构。

 

3  wait_queue_t
struct __wait_queue {

       unsigned int flags;

#define WQ_FLAG_EXCLUSIVE 0x01

       void *private;

       wait_queue_func_t func;

       struct list_head task_list; //与该等待队列对应的链表

};

 

/*Macros for declaration and initialisaton of the datatypes*/

#define __WAITQUEUE_INITIALIZER(name, tsk) {                       /

       .private    = tsk,                                         /

       .func              = default_wake_function,                    /

       .task_list  = { NULL, NULL } /

}

GNU语法中对于结构体成员赋初值采用了域的形式,如“.private      =”,其好处在于:

a)         可以选择性的对部分成员赋值。当结构体成员变量较多而大部分无须初始值时,此方法显得尤为重要,因此减少了不必要的赋值。

b)        赋值顺序与数据结构定义中成员的顺序无关,因此若结构体成员顺序变化,初始化部分不会受到任何影响。

 

#define DECLARE_WAITQUEUE(name, tsk)                                  /

       wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

全局或者在栈中定义一个wait_queue_t类型变量的同时对其初始化,这保证了系统的可靠性,避免因用户忘记初始化时导致的问题。分两步:

1)        内部宏__WAITQUEUE_INITIALIZER初始化相应成员;当wq内嵌在别的结构体中时,此宏很重要,提高了可移植性;

2)        提供给外部的接口,定义一个变量,并将第一步的结果赋值给该变量。

 

static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)

{

       q->flags = 0;

       q->private = p;

       q->func = default_wake_function;

}

动态初始化一个等待队列入口项,将其和当前进程关联起来,以便唤醒当前进程。

 

4         数据结构设计规则
今后凡遇到新设计一类结构体,若此类结构体变量必须初始化且有相对集中的操作,则应提供以下两个操作接口:

a)         定义新建一个结构体变量,并初始化之;


b)        动态初始化一个已经分配内存的该类变量


为了适应在堆栈及全局等任意地方分配的该变量,其应该接收指向该类变量的指针。

 

5         等待事件event
5.1    prepare_to_wait和finish_wait
/*

 * Used to distinguish between sync and async io wait context:

 * sync i/o typically specifies a NULL wait queue entry or a wait

 * queue entry bound to a task (current task) to wake up.

 * aio specifies a wait queue entry with an async notification

 * callback routine, not associated with any task.

 */

#define is_sync_wait(wait)    (!(wait) || ((wait)->private))

同步io等待将唤醒当前进程,异步io等待和当前进程无关,时间到后执行安装的回调函数

 

void fastcall

prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)

{

       unsigned long flags;

 

       wait->flags &= ~WQ_FLAG_EXCLUSIVE;

       spin_lock_irqsave(&q->lock, flags);

       if (list_empty(&wait->task_list)) //等待节点尚未添加到任何等待队列中

              __add_wait_queue(q, wait);

       if (is_sync_wait(wait))

              set_current_state(state);

       spin_unlock_irqrestore(&q->lock, flags);

}

 

void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait)

{

       unsigned long flags;

 

       __set_current_state(TASK_RUNNING);

       if (!list_empty_careful(&wait->task_list)) {

              spin_lock_irqsave(&q->lock, flags);

              list_del_init(&wait->task_list);

              spin_unlock_irqrestore(&q->lock, flags);

       }

}

 


 

5.2    wait_event
/include/linux/wait.h

#define  __wait_event(wq,condition)                /

     do {                           /

       DEFINE_WAIT(__wait);                       /

                                    /

for(;;) {                      /

          prepare_to_wait(&wq,&__wait,TASK_UNINTERRUPTIBLE);   /

/// 添加到等待队列中,同时更改进程状态 

        if (condition)                        /

           break;                          /

       schedule();   //何时返回呢???                       /

     }                               /

     finish_wait(&wq,&__wait);                    /

    } while (0)

// “__”表示内部函数,默认为condition不满足,添加至等待队列,调度

注意prepare_to_wait和finish_wait的匹配关系

 

#define  wait_event(wq,condition)                    /

do {                                   /

    if(condition)                              /

       break;                               /

   __wait_event(wq,condition);                    /

 }while (0)   

//对外的接口函数,需要判断condition,若假则等待

  --------------------------------------------------------------------------------------------------------------

等待系列函数架构设计:


 

5.3    wait_event_timeout
#define __wait_event_timeout(wq, condition, ret)                     /

do {                                                         /

       DEFINE_WAIT(__wait);                                          /

                                                               /

       for (;;) {                                            /

              prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);       /

              if (condition)                                      /

                     break;                                        /

              ret = schedule_timeout(ret);                        /

              if (!ret)                                       /

                     break;            //延时到,退出                          /

       }                                                      /

       finish_wait(&wq, &__wait);                              /

} while (0)

 

#define  wait_event_timeout(wq,condition,timeout)      /

({        /

      long   __ret=timeout;     /

    if( !(condition) )             /

      __wait_event_timeout( wq,condition,__ret);     /

    __ret;     /

})

 


 

5.4    wait_event_interruptible_timeout
#define   __wait_event_interruptible_timeout(wq,condition,ret)     /

do {                               /

       DEFINE_WAIT(__wait);                     /

                                   /

       for (;;) {                          /

           prepare_to_wait(&wq,&__wait,TASK_INTERRUPTIBLE);   /  

               if (condition)                   /

                   break;                     /

               if(!signal_pending(current)) {                 /

                   // 当前进程无信号需要处理

ret = schedule_timeout(ret);            /

                   if(!ret)                   /              

                      break; //时间片用完唤醒                     /

                   continue;                 /              .

               }                            /

               ret = _ERESTARTSYS;   //被信号唤醒                  /

               break;                            /

             }                            /

             finish_wait(&wq,&__wait);                   /

  } while (0)   

 

#define   wait_event_interruptible_timeout(wq,condition,timeout)    /

( {                                 /

    long__ret = timeout;                      /

    if(!(condition))                       /

        __wait_event_interruptible_timeout(wq,condition,__ret);  /

    __ret;                               /

 })

 

    wait_event_interruptible_timeout()类架构: 


 

6         基于等待队列的semaphore
struct semaphore {

       atomic_t count;

       int sleepers;

       wait_queue_head_t wait;

};

Count该信号量表示的可以资源数目,其必须支持原子操作;

Sleepers,睡眠在该信号量上的进程数目,等于等待队列中的表项数;

Wait内嵌的等待队列头结构,信号量本质上是一种资源,资源不可用时,睡眠的进程通过等待队列和该信号量相关联,以便资源可用时从等待队列中唤醒相应进程。

信号量本身的互斥操作是由wait中的互斥锁提供的。

 

功能:  获取信号灯进行临界资源操作,如果获得失败则睡眠,睡眠为深度睡眠,不可中断

fastcall void__sched  __down(struct semaphore * sem)

  {

     struct task_struct *tsk = current;

     DECLARE_WAITQUEUE(wait,tsk);

//这个wait为等待结点,其和当前进程关联,若资源不可用则将该进程添加到等待队列中

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;

         

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

              sem->sleepers = 0;

              break;

           }

           sem->sleepers = 1;

// 休眠前先解开锁,否则无人可以唤醒之

           spin_unlock_irqrestore(&sem->waitlock,flags);

//由于无可用资源,当前进程对应的wait_queue_t{}结点插入该sem{}信号等待队列中,然后通过schedule()让出cpu,让cpu去调度执行其他进程,而当前进程睡眠去了

         schedule();

// 当schedule返回时,说明当前进程被唤醒了

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

           tsk-<state = TASK_UNINTERRUPTIBLE;

//回到for,再次检查资源是否可用

           }

 

// 当前资源可用,将进程从等待队列中移除

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

           wake_up_locked(&sem->wait;

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

           tsk->state = TASK_RUNNING;

        }

当该临界资源一直未被释放时, 所有等待该临界资源的进程均在等待队列中sleep,不断的用schedule()让出cpu调度其他进程运行直至sem->count增加后至少变为0或正数后,当前进程直接从队列末尾出来进入执行,同时还唤醒了等待队列中第一个等待进程,不过由于sem->sleepers被赋为0,sem->count= =0,此进程刚唤醒,又要去sleep,除非sem->count又增加了,此唤醒进程才能执行


  针对上面示意图分析:  atomic_add_negative(sleepers-1,&sem->count) 


 

__down( )函数流程分析:

定义一个等待队列结点wait,将其private指

针指向当前进程,并设定为深度睡眠标志.
 
对等待队列进行加锁保存EFLAGS将该结点wait插入等待队列未尾
调整count,sleepers值.
 

        当当前进程

count + = sleepers – 1后小于0否?
 
        重新获得cpu时

 

 

                         yes                    NO

                  此时 (count = = -1)                  情况1:count = = 0   情况2: count> 0

sleepers = 1
 
让出cpu,调度运行其他进程,当

前进程为等待队列中sleep去了.
 
sleepers = 0 跳出循环
 
当前进程出队列准备运行
 
唤醒等待队列中第一个等待进程.
 
等待队列解锁,恢复标志.
 

__down()函数架构分析:


 

当前信号量上睡眠着三个进程。

  count    sleepers  sleepers-1   atomic_add_negative()

     -3        3       2           -1

 

 

fastcall int__sched  __down_interruptible(struct semaphore * sem)

功能: :获得信号灯,成功获得进入临界资源运行,获得失败,则睡眠,但为浅度睡眠,可被唤醒中断,但需一个返回值,指明是否获得信号灯,新建 wait这个wait_queue_t{}等待队列结点,并初始化之,private指针指向当前进程

  int retval = 0;                       

struct task_struct *tsk=current;     

DECLARE_WAITQUEUE(wait,tsk);    

  unsigned long flags;

 tsk->state = TASK_INTERRUPTIBLE;

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

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

  sem->sleepers++;                       

  for(;;) {                                              

int sleepers = sem->sleepers;           

if(signal_pending(current)){ //当前进程从浅度睡眠中被中断唤醒出循环.

  retval = _EINTR;

  sem->sleepers = 0;

  atomic_add(sleepers,&sem->count);  

  break; 

}             

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

        sem->sleepers = 0;       

       break;  //当sem->count> = 1时资源空闲,跳出循环.    

      }  

 

// sem->count<=0无空闲资源                 

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

       spin_unlock_irqrestore(&sem->wait.lock,flags); //便于其他进程释放资源,增加count值.

        schedule(); 

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

        tsk->state = YASK_INTERRUPTIBLE;

     }

 

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

   wake_up_locked(&sem->wait);

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

//在信号量sern的等待队列中,删除指向当前进程wait结点.唤醒该信号灯sern的等待队列中的第一个结点.释放该信号灯的等待队列首部的自旋锁,并恢复EFLAGS

   tsk->state = TASK_RUNNING;

   return retval; //0   获得信号灯

//-EINTR: 没有获得信号灯,中断返回,不再睡眠等待信号灯

  }                         

--------------------------------------------------------------------------------------------------------

 

 

―――――――――――――――――――――――――――――――――――

  static inline int down_interruptible(struct semaphore * sem)

 {

int result;

might_sleep();

_asm_ volatile_(

 “#atomic interruptible down operation/n/t”

LOCK “decl %1/n/t”   /*- -sem->count */

“js 2f/n/t”

“xorl %0,%0/n”

    “1:/n”

    LOCK_SECTION_START(“”)

“2:/tlea %1,%%eax/n/t”

“call__down _interruptible/n/t”

“gmp lb/n”

    LOCK_SECTION_END

:“=a”(result),“=m”(sem->count)

:

:“memory”);

return result;

-------------------------------------------------------------------------------------------------------

 down_interruptible()架构分析:

down_interruptible()
 
 

 

 

                                        先对其他需要调度的

might_sleep()
 
进程进行调度直至全部调度完毕.

锁定内存总线后

信号灯计数器减1.
 
信号灯指示的可

用资源还有否?

                  YES    NO

返回0
 
按fastcall,将参数放入eax中,并保存edx ,ecx值调用函数
 
 

 

 

 

 

__down_interruptible()
 
住下运行
 
                                                        

 

获取信号量成功否?即可用资源有么?
 

                                        没有            有

当前进程浅度睡眠
 
返回0
 
往下运行
 
有中断来唤醒否?
 

                           有    否

                                 

返回-EINTR
 
轮到该进程,又获得cpu.
 
往下走
 

fastcall int  __down_trylock(struct semaphore * sem)

//若临界资源现在变为空闲,则唤醒等待队列中第一个结点,自己结束返回.若临界资源仍忙,则结束返回,不阻塞也不唤醒等待队列中的结点进程.

 {

  int sleepers;

  unsigned long flags;

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

 sleepers = sem->sleepers + 1;

 sem->sleepers = 0;

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

     wake_up_locked(&sem->wait);

  }

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

     return 1;

  }

 

    _ _ down_try_lock()      sem->sleepers

sem->count原始值    sem->count- -    sleepers原始值    sleepeers+ +   atomic_add_negative

                                                                 (sleepers,$sem->count.)

          3              2             0            1            3

          2              1             0            1            2 

          1              0             0            1            1          

          0             --1             0            1            0

         --1             --2             1            2            0

         --2             --3             2            3            0

         --3             --4             3            4            0

                                       

 

+

 

参考资料:

信号灯的相关源代码分析  余旭  yuxu  9710108@163.com.

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sailor_8318/archive/2008/06/04/2509320.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值