linux中的等待队列

最近看epoll 和 select 都涉及到一个东西叫做设备等待队列,等待队列是如何工作的,内核是怎么管理的?看这篇文章

  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
    等等。
    那么,对于不同状态的进程,内核是如何来管理的呢?

    • 就绪队列:状态为TASK_RUNNING的进程组成的列表;

    • 处于TASK_STOPPED、EXIT_ZOMBIE或者EXIT_DEAD状态的进程是不需要连接进特定链表的。因为对于这些状态的进程而言,父进程只会通过
      PID或者子进程链表来进行访问。

    • 而处于TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE状态的进程分为很多种类型,其每个进程对应一
      种特定事件。在这种情况下,进程的状态信息是不能提供足够的信息去快速的检索所需进程,因此有必要介绍一些其他的链表组织结构。比如等待队列。

  2. 等待队列:
    在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。我们主要讨论其在进程同步中的应用。
    有时候,一个进程可能要等待一些事件的发生,如磁盘操作结束、一些系统资源的释放等等。个人理理解:等待队列就是暂时存放等待某些事件发生的进程的集合。如果一个进程要等待一个事件发生,那么该进程便将自身放入相应的等待队列中进入睡眠,而放弃控制权,直到等待事件发生后才会被内核唤醒。

  3. 等待队列的结构:



    • 等待队列是以双循环链表的形式实现的,而且队列中的成分(等待队列项)包含了指向进程描述符task的指针。

    • 等待队列头:include/linux/wait.h
      每一个等待队列是有一个等待队列头,等待队列头是一个wait_queue_head_t的数据结构:
      struct
      __wait_queue_head {
      spinlock_t lock;
         
      struct list_head task_list;
      };
      typedef struct
      __wait_queue_head wait_queue_head_t;
      成员说明:
      lock:因为等待队列是不允许多个进程同时进行访问的,以防产生不可预料的结果,因此在此结构中定义了"自旋锁"以实现访问间的同步。
      task_list:用于实现双向链表形式。
      struct
      list_head {
          struct list_head *next, *prev;
      };

    • 等待队列项:include/linux/wait.h
      struct __wait_queue
      {
          unsigned int flags;
      struct task_struct
      *task;(2.6.25.5中是void *private;)
         
      wait_queue_func_t func;
          struct list_head
      task_list;
      };
      typedef struct __wait_queue
      wait_queue_t;

      等待队列中的每一个成分:等待队列项,代表着一个正在等待特定事件发生的睡眠进程。
      成员解释:
      task:存放着睡眠进程状态描述符的地址;
      task_list:用于将进程链接进等待相同事件发生的进程链表中(等待队列)。
      flag:



      互斥进程(exclusive
      processes)和非互斥进程:

      我们来考虑一下,如果等待的事件发生了、变为真的了,那么是不是要唤醒等待该事件的所有进程(某个等待队列中)呢?
        
      总是唤醒所有等待该事件的进程并不一定是合适的。比如考虑这样一种情况:如果队列中的多个进程等待的资源是要互斥访问的,一定时间内只允许一个进程去访问的话,这时候,只需要唤醒一个进程就可以了,其他进程继续睡眠。如果唤醒所有的进程,最终也只有一个进程获得该资源,其他进程让需返回睡眠。

      因此,等待队列中的睡眠进程可被划分为互斥、非互斥进程。
        
      互斥进程:等待的资源是互斥访问的;互斥进程由内核有选择的唤醒,等待队列项的flag字段为1;
        
      非互斥进程:等待的资源是可多进程同时访问的。非互斥进程在事件发生时,总是被内核唤醒,等待队列元素的flag字段为0。



      func:
      指定等待队列中的睡眠进程如何被唤醒。

  4. 等待队列的创建:include/linux/wait.h
    DECLARE_WAITQUEUE()
    init_waitqueue_head()
    可以用DECLARE_WAIT_QUEUE_HEAD(name)宏定义一个新的等待队列,该宏静态地声明和初始化名为name的等待队列头变量。
    init_waitqueue_head()函数用于初始化已动态分配的wait queue head变量。
    等待队列可以通过
    DECLARE_WAITQUEUE()静态创建,也可以用 init_waitqueue_head()动态创建。进程把自己放入等待队列中并设置成不可执行状态。
    例:
    The init_waitqueue_entry(q, p) function initializes a wait_queue_t
    structure q as follows:

        q->flags =
    0;
        q->task = p;
        q->func =
    default_wake_function;
    非互斥进程p(flags =
    0)将被default_wake_function函数唤醒,而default_wake_function唤醒函数是try_to_wake_up(
    )的包装而已。

    DEFINE_WAIT:
    可以用宏DEFINE_WAIT声明一个新的wait_queue_t变量(等待队列项),并且对其进行初始化:
    #define
    DEFINE_WAIT(name)           
               
    \
        wait_queue_t name = {   
                   
        \
           
    .private    = current,       
            \
       
        .func        =
    autoremove_wake_function,       
    \
            .task_list    =
    LIST_HEAD_INIT((name).task_list),    \
       
    }

  5. 等待队列的添加和删除:

    • add_wait_queue(
      ):kernel/wait.c
      add_wait_queue_exclusive(
      )
      add_wait_queue()函数把一个非互斥进程插入等待队列链表的第一个位置;



      在wait.c中:
      void add_wait_queue(wait_queue_head_t *q,
      wait_queue_t *wait)
      {
          unsigned long
      flags;

          wait->flags &=
      ~WQ_FLAG_EXCLUSIVE;
      spin_lock_irqsave(&q->lock,
      flags);
          __add_wait_queue(q,
      wait);
      spin_unlock_irqrestore(&q->lock,
      flags);
      }
      EXPORT_SYMBOL(add_wait_queue);
      内嵌内核函数__add_wait_queue(),并且使用了所机制对该操作进行互斥保护。



      add_wait_queue_exclusive( )函数把一个互斥进程插入等待队列链表的最后一个位置;

    • remove_wait_queue( ):
      remove_wait_queue(
      )函数从等待队列链表中删除一个进程;

    • waitqueue_active( ):
      waitqueue_active(
      )函数检查一个给定的等待队列是否为空。

  6. 等待队列的使用:睡眠和唤醒:/kernel/sched.c
    该组函数使用任务管理中公用形式的等待队列。
    希望等待一个特定事件的进程能调用下列函数中的任一个:

    • 睡眠操作:思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡眠"状态,直至被"唤醒"(wake_up()),即其任务状态重新被修改回就绪态。
      常用的睡眠操作有interruptible_sleep_on和sleep_on,两个函数类似,是把调用进程加入到特定的等待队列中,只不过前者将进程的状态从就绪态
      (TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态
      设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。

      在当前进程上操作的sleep_on()函数:
      void
      sleep_on(wait_queue_head_t *wq)
         
      {
              wait_queue_t
      wait;
              /* 构造当前进程对应的等待队列项
      */
      init_waitqueue_entry(&wait, current); 
      //wait.h中
              /*
      将当前进程的状态从TASK_RUNNING改为TASK_UNINTERRUPTIBLE
      */
              current->state =
      TASK_UNINTERRUPTIBLE;  
              /*
      将等待队列项添加到指定链表中 */  
             
      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);
          /*
      至此,等待过程结束,本进程可以正常执行下面的逻辑 */
         
      }
      该函数把当前进程的状态设置为TASK_UNINTERRUPTIBLE,并把它插入到特定的等待队列。然后,它调用调度程序,而调度程序重新开始另一个进程的执行。当睡眠进程被唤醒时,调度程序重新开始执行sleep_on()函数,把该进程队列中删除。

    • interruptible_sleep_on():
      interruptible_sleep_on()与sleep_on()函数基本上是一样的,但是interruptible_sleep_on()把
      当前进程的状态设置为TASK_INTERRUPTIBLE而不是TASK_UNINTERRUPTIBLE,因此,接受一个信号就可以唤醒当前进程;

    • sleep_on_timeout()interruptible_sleep_on_timeout()于上述两个函数类似,只是他们还允许调用者定义一个时间间隔使得进程被内核唤醒;但是,在这两个函数中调用的是schedule_timeout()来代替schedule()。

    • prepare_to_wait()、prepare_to_wait_exclusive()、finish_wait():在wait.c中:
      是在linux2.6中介绍的,将当前进程放入等待队列的另一种方式。作用同sleep_on()。
    • 对应的唤醒操作包括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);
         
      }
      }
      参数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;
          
         }
         
      }
      }

      其他的还有wake_up_nr, wake_up_all,
      wake_up_interruptible_nr, wake_up_interruptible_all,
      wake_up_interruptible_sync,
      wake_up_locked.

参考文章:
http://hi.baidu.com/abigbigman/blog/item/a0a1fb54f2faa85cd009065f.html
http://linux.chinaunix.net/techdoc/system/2008/03/08/982296.shtml

时间: 2024-10-11 13:04:18

linux中的等待队列的相关文章

Linux中等待队列的实现

1.       等待队列数据结构 等待队列由双向链表实现,其元素包括指向进程描述符的指针.每个等待队列都有一个等待队列头(wait queue head),等待队列头是一个类型为wait_queque_head_t的数据结构: struct __wait_queue_head { spinlock_t lock; struct list_head task_list; }; typedef struct __wait_queue_head wait_queue_head_t; 其中,lock是

linux内核中的等待队列的基本操作

   在linux内核中进程的状态主要有几种状态:    1.运行态:即进程正在CPU上进行运行,它此刻正在占有CPU:    2.就绪态:即进程除了CPU之外,已经具备了运行的所有条件,在就绪队列中等待调度器(schedule)的调度:    3.阻塞态:即进程除了缺少CPU外,还缺少其他条件,在等待队列中等待所需要的条件:    介绍linux内核中的等待队列的组织结构以及各种对等待队列的操作:    等待队列由两部分构成:等待队列头 + 等待队列中的等待项:    等待队列头的结构:   

cfi_cmdset_0002.c中关于等待队列的使用

1.linux下等待队列的基本概念 在内核里面,等待队列是有很多用处的,尤其是在中断处理.进程同步.定时等场合.可以使用等待队列在实现阻塞进程的唤醒.它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问等.它实现了在事件上的条件等待: 希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制全.因此,等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒它们. 等待队列由循环链表实现,其元素包括指向进程描述符的指针.正如list_head

Linux中四种进程或线程同步互斥控制方法

原文地址:http://blog.itpub.net/10697500/viewspace-612045/ 一.Linux中 四种进程或线程同步互斥的控制方法: 1.临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问. 2.互斥量:为协调共同对一个共享资源的单独访问而设计的. 3.信号量:为控制一个具有有限数量用户资源而设计. 4.事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始. 二.临界区(Critical Section) 保证在某一时刻只有一个线程

在linux中使用sar调优系统性能

在linux中使用sar调优系统性能 关键字: sar sar默认在linux下没有安装,需要我们手工安装,一般建议源码方式安装,下载类似sysstat-6.1.3.tar.gz 然后configure make make install即可使用. sar 命令行的常用格式: sar [options] [-A] [-o file] t [n] 在命令行中,n 和t 两个参数组合起来定义采样间隔和次数,t为采样间隔,是必须有的参数,n为采样次数,是可选的,默认值是1,-o file表示将命令结果

Linux中信号量处理

参考文章: http://blog.csdn.net/qinxiongxu/article/details/7830537/ 信号量一. 什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程( 线程)所拥有.信号量的值为正的时候,说明它空闲.所测试的线程可以锁定而使用它.若为0, 说明它被占用,测试的线程要进入睡眠队列中, 等待被唤醒.二. 信号量的分类在学习信号量之前,我们必须先知道—— Linux提供两种信号量:(1) 内核信号量,由内核控制路径使用(2) 用户态进

task_struct结构体字段介绍--Linux中的PCB

task_struct结构体 字段介绍 Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程, task_struct是Linux中的[进程控制块PCB结构]的具体数据结构 这个结构体包含了一个进程所需的所有信息.它定义在linux-2.6.38.8/include/linux/sched.h文件中. 下面对task_struct这个结构体 进行各个字段的详细介绍 1. 调度数据成员(1) volatile long states;表示进程的当前状态:? TASK_RU

查看linux中的TCP连接数【转】

转自:http://blog.csdn.net/he_jian1/article/details/40787269 查看linux中的TCP连接数 本文章已收录于:  计算机网络知识库  分类: 安全测试总结(2)  性能经验总结(107)  版权声明:本文为博主原创文章,未经博主允许不得转载. 一.查看哪些IP连接本机 netstat -an 二.查看TCP连接数 1)统计80端口连接数netstat -nat|grep -i "80"|wc -l 2)统计httpd协议连接数ps

linux 中常用的一些头文件

#include <linux/***.h> 是在linux-2.6.29/include/linux下面寻找源文件. #include <asm/***.h> 是在linux-2.6.29/arch/arm/include/asm下面寻找源文件. #include <mach/***.h> 是在linux-2.6.29/arch/arm/mach-s3c2410/include/mach下面寻找源文件. #include <plat/regs-adc.h>