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

   在linux内核中进程的状态主要有几种状态:

   1.运行态:即进程正在CPU上进行运行,它此刻正在占有CPU;

   2.就绪态:即进程除了CPU之外,已经具备了运行的所有条件,在就绪队列中等待调度器(schedule)的调度;

   3.阻塞态:即进程除了缺少CPU外,还缺少其他条件,在等待队列中等待所需要的条件;

   介绍linux内核中的等待队列的组织结构以及各种对等待队列的操作:

   等待队列由两部分构成:等待队列头 + 等待队列中的等待项;

   等待队列头的结构:

      struct __wait_queue_head

            {

                  spinlock_t lock;  // 自选锁,用于保护等待队列,防止并发访问

                  struct list_head task_list;//用于构建等待队列

            }

     typedef struct __wait_queue_head wait_queue_head_t;

等待队列中的等待项:

   struct __wait_queue

      {

        unsigned int flags;

        void *private;

        wait_queue_func_t func;

        struct list_head task_list;

      }

每个字段的介绍:

    flags : 用于判断当等待条件满足时,是唤醒等待队列中的等待该条件的所有

            等待进程,还是只唤醒一个;

            flags 的最后一位为 1  时表示每次只唤醒一个等待队列中的进程;

            flags 的最后一位为 0 时表示每次唤醒等待该条件的所有进程;

            #define WQ_FLAG_EXCLUSIVE 0x01


    private: 用于存放 struct task_struct 的地址;

    wait_queue_func_t :等待队列中的进程被唤醒的方式;

         typedef int (*wait_queue_func_t)(wait_queue_t *wait,unsigned mode,

                                          int flags, void *key)

等待的队列的结构图:

对等待队列的各种操作:

  1.等待队列头的静态初始化:

     #define __WAIT_QUEUE_INITIALIZER(name) {         \

      .lock = __SPIN_LOCK_UNLOCK(name.lock),// lock = 1;  \

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

     #define DECLARE WAIT_QUEUE_HEAD(name) \

           wait_queue_head_t name = __WAIT_QUEUE_INITIALIZER(name)


      所以可以直接通过 : DECLARE_WAIT_QUEUE_HEAD(name) 来完成一个等待队列头的定义以及初始化操作;


2. 等待队列头的动态初始化操作:

    void __init_waitqueue_head(wait_queue_head_t *p, struct lock_class_key *key)

    {

         spin_lock_init(&p->lock); // 初始化spinlock_t lock lock = 1;

         INIT_LIST_HEAD(&p->task_list);// 初始化链表头;

    }

    #define init_waitqueue_head(name) \

        do {    \

             static struct lock_class_key  __key; \

             __init_wait_queue_head((name), &__key);\

           } while(0)

    所以如果使用 init_waitqueue_head()的用法为:

           wait_queue_head_t name;

           init_waitqueue_head(&name);

DECLARE_WAIT_QUEUE_HEAD() 与 init_waitqueue_head()的区别:

     1.DECLARE_WAIT_QUEUE_HEAD完全时用宏定义实现的,因此程序在编译的时候就完成了变量的定义与初始化。而 init_waitqueue_head()本质上来说是对 __init_waitqueue_head()的一次封装。虽然使用了宏定义,其仍然是一个函数,它对变量的初始化是在程序运行的时候进行的;

3.对等待队列中的等待项进行静态定义与初始化:

    #define __WAITQUEUE_INITIALIZER(name, task) {  \

         .private   = task, \

         .func      = default_wake_function,  \

         .task_list = {NULL,NULL} }


    #define DECLARE_WAITQUEUE(name,task)     \

         wait_queue_t name = __WAITQUQUE_INITIALIZER(name, task)

相当与:

         wait_queue_t entry;

         struct task_struct task;

         entry = {

             .private = &task,

             .func    = default_wake_function,

             .taks_list = {NULL, NULL}

                 }

4.动态初始化等待队列中的数据项:

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

   {

       p->flags   = 0;

       p->private = task;

       p->func    = default_wake_function;

      

   }

  用法:wait_queue_t wait;

       struct task_struct task;

        init_waitqueue_entry(&wait, &task);

5.使用自定义的函数作为等待队列中的进程唤醒做处理:

   static inline void init_waitqueue_func_entry(wait_queue_t *wait,

                      wait_queue_func_t func)

   {

       p->flags = 0;

       p->private = NULL;

       p->func    = func;

   }

6.判断一个等待队列是否可用:

   static inline int waitqueue_active(wait_queue_head_t *p)

   {

      return !list_empty(&p->task_list);

   }

   waitqueue_active()本质上实在判断一个等待队列是否为空。

         如果为空的话,等待队列不可用返回 0;

         如果不为空,等待队列可用返回 1;


7.向等待队列中添加一个数据项:

    static inline void __add_wait_queue(wait_queue_head_t *head,

                                        wait_queue_t *new)

    {

        list_add(&new->task_list, &head->task_list);

    }


    void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

    {

        unsigned int flags ;

        wait->flags &= ~WQ_FLAG_EXCLUSIVE; // define WQ_FLAG_EXCLUSIVE 0x01

        spin_lock_irqsave(&q->lock, flags);

        __add_wait_queue(q, wait);

        spin_unlock_irqstore(&q->lock, flags);

    }

   

   所以 add_wait_queue()是__add_wait_queue()的封装,并且加入了一些保护等待队列防止并发访问的措施, 所以在向等待队列中添加元素时使用 add_wait_queue()会更安全;

8.向等待队列中添加一个每次只能唤醒一个数据项:

    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);

  }

   void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)

   {

       unsigned int flags ;

       wait->flags |=  WQ_FLAG_EXCLUSIVE;

       spin_lock_irqsave(&q->lock, flags);

       __add_wait_queue_tail(q,wait);

       spin_unlock_irqstore(&q->lock, flags);

   }

 可以看出 add_wait_queue(wait_queue_head_t *head, wait_queue_t *wait)

          每次向等待队列的头后面添加一个非互斥唤醒的等待项;

         add_wait_queue_exclusive(wait_queue_head_t *head, wait_queue_t *wait)

          每次向等待队列的尾部添加一个互斥唤醒的等待项;

  

9.将一个等待项从等待队列中删除:

   static inline void __remove_wait_queue(wait_queue_head_t *head,

                                          wait_queue_t *wait)

   {

       list_del(&wait->task_list);

   }

   void remove_wait_queue(wait_queue_head_t *head, wait_queue_t *wait)

   {

       unsigned int flags;

       spin_lock_irqsave(&head->lock, flags);

       __remove_wait_queue(head,wait);

       spin_unlock_irqstore(&head->lock, flags);

   }

 小总结: add_wait_queue();   EXPORT_SYMBOL(add_wait_queue);

          add_wait_queue_exclusive();  EXPORT_SYMBOL(add_wait_queue_exclusive);

          remove_wait_queue(); EXPORT_SYMBOL(remove_wait_queue);

     这三个对等待队列的操作是内核提过的函数,


    内核中的等待队列中的进程是需要在一定条件下给唤醒的,然后加入到就绪队列中的。以及正在运行的进程如何将其加入到等待队列之中的。

    内核也提供了一些函数,帮助我们将进程正在运行的进程阻塞,让后将其加入到等待队列中。以及在等待队列中唤醒唤醒阻塞进程。

阻塞正在运行的进程,然后将其加入到等待队列中的函数:

  1. void prepare_to_wait(wait_queue_head_t *head, wait_queue_t *wait, int state)

     state :表示进程的状态

            取值 TASK_RUNNING, TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE

    void prepare_to_wait(wait_queue_head_t *head, wait_queue_t *wait, int state)

    {

       unsigned int flags;

       wait->flags &= ~WQ_FLAG_EXCLUSIVE; // 非互斥唤醒进程;

       spin_lock_irqsave(&head->lock, flags);

       if( list_empty(&wait->task_list) ) // 如果为空,说明其之前并不在等待对列中

          __add_wait_queue(head, wait);  // 将wait entry 加入到等待队列之中

       set_current_state(state);   // 使用 set_current_state()改变进程的运行状态

       spin_unlock_irqstore(&head->lock, flags);

    }

    EXPORT_SYMBOL(prepare_to_wait);

void prepare_to_wait_exclusive(wait_queue_head_t *head,

                                   wait_queue_head *wait, int state)

    {

       unsigned int flags;

       wait->flags |= WQ_FLAG_EXCLUSIVE;  // 互斥唤醒进程;

       spin_lock_irqsave(&head->lock, flags);

       if(  list_empty(&wait->task_list) )

          __add_wait_queue_tail(head, wait); //添加对尾

       set_current_state(state);

       spin_unlock_irqstore(&head->lock, flags);

    }

   EXPORT_SYMBOL(prepare_to_wait_exclusive);

  

从阻塞队列中删除一个等待进程的函数:

  void  finish_wait(wait_queue_head_t *head, wait_queue_t *wait)

  {

      unsigned int flags;

      __set_current_state(TASK_RUNNING); //将当前的进程状态改为运行态;

      if( !list_empty_careful(&wait->task_list) ) // 确定进程在等待队列之中

      {

         spin_lock_irqsave(&head->lock, flags);

         list_del_init(&wait->task_list);

         spin_unlock_irqstore(&head->lock, flags);

      }

  }

    EXPORT_SYMBOL(finish_wait);


时间: 2024-11-02 09:54:53

linux内核中的等待队列的基本操作的相关文章

Linux内核中等待队列的几种用法

Linux内核里的等待队列机制在做驱动开发时用的非常多,多用来实现阻塞式访问,下面简单总结了等待队列的四种用法,希望对读者有所帮助. 1. 睡眠等待某个条件发生(条件为假时睡眠): 睡眠方式:wait_event, wait_event_interruptible 唤醒方式:wake_up (唤醒时要检测条件是否为真,如果还为假则继续睡眠,唤醒前一定要把条件变为真) 2. 手工休眠方式一: 1)建立并初始化一个等待队列项 DEFINE_WAIT(my_wait) <==> wait_queue

向linux内核中添加外部中断驱动模块

本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内核中添加外部中断驱动模块.7.完整驱动程序代码.linux的内核版本为linux2.6.32.2. 一.linux模块的框架以及混杂设备相关知识 1.内核模块的框架如下图所示,其中module_init()(图中有误,不是modules_init)只有在使用insmod命令手动加载模块时才会被调用,

大话Linux内核中锁机制之完成量、互斥量

大话Linux内核中锁机制之完成量.互斥量 在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等待另一个执行单元完成某事后方可执行,它是一种轻量级机制.事实上,它即是为了完成进程间的同步而设计的,故而仅仅提供了代替同步信号量的一种解决方法,初值被初始化为0.它在include\linux\completion.h定义. 如图8.1所示,对于执行单元A

大话Linux内核中锁机制之信号量、读写信号量

大话Linux内核中锁机制之信号量.读写信号量 在上一篇博文中笔者分析了关于内存屏障.读写自旋锁以及顺序锁的相关内容,本篇博文将着重讨论有关信号量.读写信号量的内容. 六.信号量 关于信号量的内容,实际上它是与自旋锁类似的概念,只有得到信号量的进程才能执行临界区的代码:不同的是获取不到信号量时,进程不会原地打转而是进入休眠等待状态.它的定义是include\linux\semaphore.h文件中,结构体如图6.1所示.其中的count变量是计数作用,通过使用lock变量实现对count变量的保

linux内核中socket的创建过程源码分析(总结性质)

http://www.jianshu.com/p/5d82a685b5b6 在漫长地分析完socket的创建源码后,发现一片浆糊,所以特此总结,我的博客中同时有另外一篇详细的源码分析,内核版本为3.9,建议在阅读本文后若还有兴趣再去看另外一篇博文.绝对不要单独看另外一篇. 一:调用链: 二:数据结构 一一看一下每个数据结构的意义: 1) socket, sock, inet_sock, tcp_sock的关系创建完sk变量后,回到inet_create函数中: 这里是根据sk变量得到inet_s

linux内核中的线程创建接口

介绍一些内核给我们提供的用于在内核中创建线程的内核接口函数. 其实在linux内核中,无论是进程还是线程,都是用struct task_struct结构体来表示的. 用于创建一个内核线程的函数: struct task_struct * kthread_create( int (*threadfn)(void *data),                                           void *data,                                 

Linux内核中常用String库函数实现

//只列举了部分常用的strcpy,strcmp,strcat,strchr,strstr,strpbrk...  char *strcpy(char *dest, const char *src) { char *tmp = dest; while ((*dest++ = *src++) != '\0') /* nothing */; return tmp; } char *strncpy(char *dest, const char *src, size_t count) { char *t

Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构struct timeval{time_t tv_sec; /***second***/susecond_t tv_usec;/***microsecond***/}到底microsecond是毫秒还是微秒?? 1秒=1000毫秒(3个零),1秒=1000 000微秒(6个零),1秒=1000 000 000纳秒(9个零),1秒=1000 000 000

route-显示并设置Linux内核中的网络路由表

route命令 网络配置 route命令用来显示并设置Linux内核中的网络路由表,route命令设置的路由主要是静态路由.要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现. 语法 route(选项)(参数) 选项 -A:设置地址类型: -C:打印将Linux核心的路由缓存: -v:详细信息模式: -n:不执行DNS反向查找,直接显示数字形式的IP地址: -e:netstat格式显示路由表: -net:到一个网络的路由表: -host:到一个主机的路