Linux内核的等待队列[转]

Linux内核的等待队列[转]
 
 
    1. 问题:进程是如何组织起来的?
      我们知道,进程是有很多种状态的:include/linux/sched.h
      #define TASK_RUNNING        0
      #define TASK_INTERRUPTIBLE    1
      #define TASK_UNINTERRUPTIBLE    2
      #define __TASK_STOPPED        4
      #define __TASK_TRACED        8
      /* in tsk->exit_state */
      #define EXIT_ZOMBIE        16
      #define EXIT_DEAD        32
      等等。
      那么,对于不同状态的进程,内核是如何来管理的呢?
    2. 就绪队列:状态为TASK_RUNNING的进程组成的列表;
    • 处于TASK_STOPPED、EXIT_ZOMBIE或者EXIT_DEAD状态的进程是不需要连接进特定链表的。因为对于这些状态的进程而言,父进程只会通过 PID或者子进程链表来进行访问。
    • 而处于TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE状态的进程分为很多种类型,其每个进程对应一种特定事件。在这种情况下,进程的状态信息是不能提供足够的信息去快速的检索所需进程,因此有必要介绍一些其他的链表组织结构。比如等待队列。
  1. 等待队列:
    在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。我们主要讨论其在进程同步中的应用。
    有时候,一个进程可能要等待一些事件的发生,如磁盘操作结束、一些系统资源的释放等等。个人理理解:等待队列就是暂时存放等待某些事件发生的进程的集合。如果一个进程要等待一个事件发生,那么该进程便将自身放入相应的等待队列中进入睡眠,而放弃控制权,直到等待事件发生后才会被内核唤醒。
Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一个通过list_head连接的典型双循环链表,如下图所示。

在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。由于我们只需要对队列进行添加和删除操作,并不会修改其中的对象(等待队列项),因此,我们只需要提供一把保护整个基础设施和所有对象的锁,这把锁保存在等待队列头中,为wq_lock_t类型。在实现中,可以支持读写锁(rwlock)或自旋锁(spinlock)两种类型,通过一个宏定义来切换。如果使用读写锁,将wq_lock_t定义为rwlock_t类型;如果是自旋锁,将wq_lock_t定义为spinlock_t类型。无论哪种情况,分别相应设置wq_read_lock、wq_read_unlock、wq_read_lock_irqsave、wq_read_unlock_irqrestore、wq_write_lock_irq、wq_write_unlock、wq_write_lock_irqsave和wq_write_unlock_irqrestore等宏。
等待队列头
struct __wait_queue_head {
 wq_lock_t lock;
 struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
前面已经说过,等待队列的主体是进程,这反映在每个等待队列项中,是一个任务结构指针(struct task_struct * task)。flags为该进程的等待标志,当前只支持互斥。
等待队列项
struct __wait_queue {
 unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
 struct task_struct * task;
 struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
声明和初始化
#define DECLARE_WAITQUEUE(name, tsk)     /
 wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) {    /
 task:  tsk,      /
 task_list: { NULL, NULL },     /
    __WAITQUEUE_DEBUG_INIT(name)}
通过DECLARE_WAITQUEUE宏将等待队列项初始化成对应的任务结构,并且用于连接的相关指针均设置为空。其中加入了调试相关代码。
#define DECLARE_WAIT_QUEUE_HEAD(name) /
 wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {    /
 lock:  WAITQUEUE_RW_LOCK_UNLOCKED,   /
 task_list: { &(name).task_list, &(name).task_list }, /
   __WAITQUEUE_HEAD_DEBUG_INIT(name)}
通过DECLARE_WAIT_QUEUE_HEAD宏初始化一个等待队列头,使得其所在链表为空,并设置链表为"未上锁"状态。其中加入了调试相关代码。
static inline void init_waitqueue_head(wait_queue_head_t *q)
该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。
{
    q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;
    INIT_LIST_HEAD(&q->task_list);
}
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
该函数初始化一个已经存在的等待队列项,它设置对应的任务结构,同时将标志位清0。
{
    q->flags = 0;
    q->task = p;
}
static inline int waitqueue_active(wait_queue_head_t *q)
该函数检查等待队列是否为空。
{
    return !list_empty(&q->task_list);
}
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
将指定的等待队列项new添加到等待队列头head所在的链表头部,该函数假设已经获得锁。
{
    list_add(&new->task_list, &head->task_list);
}
static inline void __add_wait_queue_tail(wait_queue_head_t *head, wait_queue_t *new)
将指定的等待队列项new添加到等待队列头head所在的链表尾部,该函数假设已经获得锁。
{
    list_add_tail(&new->task_list, &head->task_list);
}
static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)
将函数从等待队列头head所在的链表中删除指定等待队列项old,该函数假设已经获得锁,并且old在head所在链表中。
{
    list_del(&old->task_list);
}
睡眠和唤醒操作
对等待队列的操作包括睡眠和唤醒(相关函数保存在源代码树的/kernel/sched.c和include/linux/sched.h中)。思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡眠"状态,直至被"唤醒",即其任务状态重新被修改回就绪态。
常用的睡眠操作有interruptible_sleep_on和sleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。以interruptible_sleep_on为例,其展开后的代码是:
void interruptible_sleep_on(wait_queue_head_t *q)
{
    unsigned long flags;
    wait_queue_t wait;
    /* 构造当前进程对应的等待队列项 */
    init_waitqueue_entry(&wait, current);
    /* 将当前进程的状态从TASK_RUNNING改为TASK_INTERRUPTIBLE */
    current->state = TASK_INTERRUPTIBLE;
    /* 将等待队列项添加到指定链表中 */
    wq_write_lock_irqsave(&q->lock,flags);
    __add_wait_queue(q, &wait); 
    wq_write_unlock(&q->lock);
    /* 进程重新调度,放弃执行权 */
    schedule();
    /* 本进程被唤醒,重新获得执行权,首要之事是将等待队列项从链表中删除 */
    wq_write_lock_irq(&q->lock);
    __remove_wait_queue(q, &wait);
    wq_write_unlock_irqrestore(&q->lock,flags);
    /* 至此,等待过程结束,本进程可以正常执行下面的逻辑 */
}
对应的唤醒操作包括wake_up_interruptible和wake_up。wake_up函数不仅可以唤醒状态为TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。这两个宏的定义如下:
#define wake_up(x)   __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)
__wake_up函数主要是获取队列操作的锁,具体工作是调用__wake_up_common完成的。
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
{
    if (q) {
        unsigned long flags;
        wq_read_lock_irqsave(&q->lock, flags);
        __wake_up_common(q, mode, nr, 0);
        wq_read_unlock_irqrestore(&q->lock, flags);
    }
}
/* The core wakeup function.  Non-exclusive wakeups (nr_exclusive == 0) just wake everything up.  If it's an exclusive wakeup (nr_exclusive == small +ve number) then we wake all the non-exclusive tasks and one exclusive task.
There are circumstances in which we can try to wake a task which has already started to run but is not in state TASK_RUNNING.  try_to_wake_up() returns zero in this (rare) case, and we handle it by contonuing to scan the queue. */
static inline void __wake_up_common (wait_queue_head_t *q, unsigned int mode, int nr_exclusive, const int sync)
参数q表示要操作的等待队列,mode表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE等。nr_exclusive是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示???
{
    struct list_head *tmp;
    struct task_struct *p;
    CHECK_MAGIC_WQHEAD(q);
    WQ_CHECK_LIST_HEAD(&q->task_list);
    /* 遍历等待队列 */
    list_for_each(tmp,&q->task_list) {
        unsigned int state;
        /* 获得当前等待队列项 */
        wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
        CHECK_MAGIC(curr->__magic);
        /* 获得对应的进程 */
        p = curr->task;
        state = p->state;
        /* 如果我们需要处理这种状态的进程 */
        if (state & mode) {
            WQ_NOTE_WAKER(curr);
            if (try_to_wake_up(p, sync) && (curr->flags&WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                break;
        }
    }
}
/* 唤醒一个进程,将它放到运行队列中,如果它还不在运行队列的话。"当前"进程总是在运行队列中的(except when the actual re-schedule is in progress),and as such you're allowed to do the simpler "current->state = TASK_RUNNING" to mark yourself runnable without the overhead of this. */
static inline int try_to_wake_up(struct task_struct * p, int synchronous)
{
    unsigned long flags;
    int success = 0;
    /* 由于我们需要操作运行队列,必须获得对应的锁 */
    spin_lock_irqsave(&runqueue_lock, flags);
    /* 将进程状态设置为TASK_RUNNING */
    p->state = TASK_RUNNING;
    /* 如果进程已经在运行队列中,释放锁退出 */
    if (task_on_runqueue(p))
        goto out;
    /* 否则将进程添加到运行队列中 */
    add_to_runqueue(p);
    /* 如果设置了同步标志 */
    if (!synchronous || !(p->cpus_allowed & (1UL << smp_processor_id())))
        reschedule_idle(p);
    /* 唤醒成功,释放锁退出 */
    success = 1;
out:
    spin_unlock_irqrestore(&runqueue_lock, flags);
    return success;
}
等待队列应用模式
等待队列的的应用涉及两个进程,假设为A和B。A是资源的消费者,B是资源的生产者。A在消费的时候必须确保资源已经生产出来,为此定义一个资源等待队列。这个队列同时要被进程A和进程B使用,我们可以将它定义为一个全局变量。
DECLARE_WAIT_QUEUE_HEAD(rsc_queue); /* 全局变量 */
在进程A中,执行逻辑如下:
while (resource is unavaiable) {
    interruptible_sleep_on( &wq );
}
consume_resource();
在进程B中,执行逻辑如下:
produce_resource();
wake_up_interruptible( &wq );

linux内核的 等待队列 使用方法,wait_queue_head_t,进程休眠 收藏
 当你在用户空间需要读写一大片数据的时候,这个就用上了。

以下来自:http://www.yuanma.org/data/2006/1207/article_1916.htm

假设我们在 kernel 里产生一个 buffer,user 可以经由 read,write 等 system call 来读取或写资料到这个 buffer 里。如果有一个 user 写资料到 buffer 时,此时 buffer 已经满了。那请问你要如何去处理这种情形呢 ? 第一种,传给 user 一个错误讯息,说 buffer 已经满了,不能再写入。第二种,将 user 的要求 block 住, 等有人将 buffer 内容读走,留出空位时,再让 user 写入资料。但问题来了,你要怎么将 user 的要求 block 住。难道你要用
 while ( is_full );
write_to_buffer;
这样的程序代码吗? 想想看,如果你这样做会发生什么事? 第一,kernel会一直在这个 while 里执行。第二个,如果 kernel 一直在这个 while 里执行,表示它没有办法去 maintain系统的运作。那此时系统就相当于当掉了。在这里 is_full 是一个变量,当然,你可以让 is_full 是一个 function,在这个 function里会去做别的事让 kernel 可以运作,那系统就不会当。这是一个方式。还有,你说可以在while里面把buffer里的内容读走,再把is_full的值改了,但是我们会可能把重要的数据在我们不想被读的时候被读走了,那是比较麻烦的,而且很不灵活.如果我们使用 wait_queue 的话,那程序看起来会比较漂亮,而且也比较让人了解,如下所示:
 
struct wait_queue_head_t wq; /* global variable */
DECLARE_WAIT_QUEUE_HEAD (wq);
 
while ( is_full ){
interruptible_sleep_on( &wq );
} write_to_buffer();
 
interruptible_sleep_on( &wq ) 是用来将目前的 process,也就是要求写资料到buffer 的 process放到 wq 这个 wait_queue 里。在 interruptible_sleep_on 里,则是最后会呼叫 schedule() 来做 schedule 的动作,谁调用了schedule谁就趴下,让别人去运行,醒来就原地起来,执行schedule()后的代码。那那个调用了schedule的家伙什么醒过来呢?这时候就需要用到另一个函数了wake_up_interruptible()了。

以下来自:http://tauruspdj.blog.163.com/blog/static/4312500620090794030998/

linux中最简单的休眠方式是下面的宏,
wait_event(queue, condition)  /* 进程将被置于非中断休眠(uninterruptible sleep)*/
wait_event_interruptible(queue, condition) /*进程可被信号中断休眠,返回非0值表示休眠被信号中断*/
wait_event_timeout(queue, condition, timeout)    /*等待限定时间jiffy,condition满足其一返回0*/
wait_event_interruptible_timeout(queue, condition, timeout)
queue是等待队列头,传值方式
condition是任意一个布尔表达式,在休眠前后多次对condition求值,为真则唤醒

唤醒进程的基本函数是wake_up
void wake_up(wait_queue_head_t *queue);     /*唤醒等待在给定queue上的所有进程*/
void wake_up_interruptible(wait_queue_head_t *queue);

实践中,一般是wait_event和 wake_up, wait_event_interruptible和 wake_up_interruptible 成对使用。

【补充】其实看了那么多,他们也没有给个立即可用的步骤,写blog嘛,就是分享心得。我基于2.6.24总结一下,希望对大家有帮助:

1、定义:wait_queue_head_t my_queue;

2、初始化 init_waitqueue_head(&my_queue);

3、在一个函数里面等待:wait_event(queue, condition) ;(别在中断里面搞)


4、在另一个函数里面唤醒:wake_up(wait_queue_head_t *queue); (这个可以在中断调用,去唤醒别的进程,特别是dma操作类的)


有好几个等待和唤醒函数,大家可以慢慢试。

 

等待队列分析
等待队列操作分析:
     linux驱动程序中可以用等待队列(wiat queue)来实现阻塞的唤醒
1)定义等待队列头
等待队列头结构体的定义:
struct __wait_queue_head {
	spinlock_t  lock;          //自旋锁变量,用于在对等待队列头
//指向的等待队列链表进行操作时锁上,查看哪有对
lock
的操作?
struct list_head task_list; // 指向等待队列的list_head }; typedef struct __wait_queue_head wait_queue_head_t; #define DECLARE_WAIT_QUEUE_HEAD(name) / //声明一个待队列头对象 name: wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name) #define __WAIT_QUEUE_HEAD_INITIALIZER(name) { ///待队列头的初始化: .lock = __SPIN_LOCK_UNLOCKED(name.lock), / .task_list = { &(name).task_list, &(name).task_list } } lock赋为unlocked,将等待队列头指向的等待队列链表指向name,从而将等待队列头和
等待队列连起来
; 2)等待队列中存放的是在执行设备操作时不能获得资源而挂起的进程 定义等待对列: struct __wait_queue { unsigned int flags; //prepare_to_wait()里有对flags的操作,查看以得出其含义 #define WQ_FLAG_EXCLUSIVE 0x01 //一个常数,在prepare_to_wait()用于修改flags的值
wait_queue_func_t func; //唤醒阻塞任务的函数 struct list_head task_list; // 阻塞任务链表 }; typedef struct __wait_queue wait_queue_t; #define DECLARE_WAITQUEUE(name, tsk) ///声明一个等待队列并初始化为name wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk) #define __WAITQUEUE_INITIALIZER(name, tsk) { / //等待对列初始化: .private = tsk, / .func = default_wake_function, / .task_list = { NULL, NULL } } 下列两个函数用于对特定的成员进行赋值(当传入不同类型的参数时); 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; //使用默认的唤醒函数 } static inline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func) { q->flags = 0; q->private = NULL; q->func = func; // 自定义的唤醒函数 } 3)对等待队列进行操作 static inline int waitqueue_active(wait_queue_head_t *q) { return !list_empty(&q->task_list); } 判断等待对列头是否为空,当一个进程访问设备而得不到资源时就会被放入等待队列头指
向的等待队列中,当该它是第一个被阻塞的进程,(等待队列头是一开始就有的还
是有了第一个被阻塞的进程后才创建的?)
若此时等待队列头还是空的,要先创建(见上面)
然后再插入新的等待队列


对等待队列的链表操作
static inline void __add_wait_queue(wait_queue_head_t *head,/
 wait_queue_t *new) /
{ list_add(&new->task_list, &head->task_list); } /增加一个等待队列new到等待
队列头head指向的等待队列链表中;
static inline void __add_wait_queue_tail(wait_queue_head_t *head, // wait_queue_t *new) { list_add_tail(&new->task_list, &head->task_list); } 增加一个等待队列到表尾 static inline void __remove_wait_queue /
(wait_queue_head_t *head,wait_queue_t *old)
{ list_del(&old->task_list); } 删除一个等待队列4)等待事件当等待队列加入到链表中以后,就要等待特定的condition来 唤醒它; #define __wait_event(wq, condition) ///wq:在等待事件的等待队列;condition:等待的条件 do { / DEFINE_WAIT(__wait); /定义并初始化一个wait_queue_t结构 / for (;;) { / prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); / if (condition) / //wait_queue:wq要等的condition是否满足 break; / schedule(); ///condition不成立,放弃cpu重新调度一个task } / finish_wait(&wq, &__wait); / } while (0) 等待condition在成立,否则进程睡眠(TASK_UNINTERRUPTIBLE);condition满足
后等待结束,跳出循环(后面调用
wake_up(x)
进行唤醒)当任何能改变等待条件
的值的变量发生改变时,要调用
wake_up()


wait_event(wq, condition) 			
__wait_event()的基础上多了一次查询(每次被唤醒的时候)		

#define __wait_event_timeout(wq, condition, ret) /

condition满足或ret使用完了时进程被唤醒;返回值为:return timeout < 0 ? 0 : timeout

timeout是一个jiffies类型的变量,当时间用完了,函数返回0,当等待的条件成立了,
timeout
还未用完,则将最后的jiffies保留下来。类似的操作还有:
 #define __wait_event_interruptible_timeout(wq, condition, ret)
/
可中断,有超时时间的__wait_event(),当timeout长的时间完了后,函数返回0;当时间未完,
函数被信号中断则返回
-ERESTARTSYS
;如果timeout isn't out,保留jiffies最后的值;
#define wait_event_interruptible_timeout(wq, condition, timeout) /

多了一次查询


#define __wait_event_interruptible_exclusive(wq, condition, ret)	/

#define wait_event_interruptible_exclusive(wq, condition)		/

这几个函数都有用到prepare_to_wait()下面分析一下这个函数;
prepare_to_wait() 函数

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); //获得自旋锁并保存EFLAGS的值

if (list_empty(&wait->task_list)) //判断是等待队列是否为空:空时返回1;函数为:

//static inline int list_empty(const struct list_head *head)

__add_wait_queue(q, wait); //{ return head->next == head;}

/* 插入等待队列中(为何空时插入,非空时不行?

* don't alter the task state if this is just going to

* queue an async wait queue callback

*/

if (is_sync_wait(wait))

set_current_state(state); //因为非阻塞进程访问不到设备时并不挂起,所以不改状态

//set_current_state()->set_mb()->mb() :强制顺序执行(更改状态)

//函数mb()内存栅头文件中定义的

spin_unlock_irqrestore(&q->lock, flags);//解锁将EFLAGS的值读回

}

prepare_to_wait()的作用是将等待队列插入等待队列链表中,并更改等待队列的状态为state





inux将进程状态描述为如下五种:

TASK_RUNNING:可运行状态。处于该状态的进程可以被调度执行而成为当前进程。

TASK_INTERRUPTIBLE:可中断的睡眠状态。处于该状态的进程在所需资源有效时被唤醒,也可以通过信号或定时中断唤醒。

TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。处于该状态的进程仅当所需资源有效时被唤醒。

TASK_ZOMBIE:僵尸状态。表示进程结束且已释放资源,但其task_struct仍未释放。

TASK_STOPPED:暂停状态。处于该状态的进程通过其他进程的信号才能被唤醒。


唤醒:

#define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)


void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, void *key)

{

unsigned long flags;


spin_lock_irqsave(&q->lock, flags);

__wake_up_common(q, mode, nr_exclusive, 0, key);

spin_unlock_irqrestore(&q->lock, flags);

}

Wake_up()唤醒等待队列中的进程,其参数含义:

q:等待队列;

mode:要唤醒的进程

nr_exclusive:要唤醒的进程数;

keyis directly passed to the wakeup function


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值