首页 > 系统相关 >linux内核等待队列详解

linux内核等待队列详解

时间:2023-09-01 12:46:14浏览次数:58  
标签:__ task 队列 up 详解 wake 内核 linux wait

https://www.cnblogs.com/xinghuo123/p/13347964.html

等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。

1 数据结构

1.1 等待队列头

等待队列结构如下,因为每个等待队列都可以再中断时被修改,因此,在操作等待队列之前必须获得一个自旋锁。

定义位于:linux-3.10.73\include\linux\wait.h

1 struct __wait_queue_head {
2     spinlock_t lock;
3     struct list_head task_list;
4 };
5 typedef struct __wait_queue_head wait_queue_head_t;

1.2 等待队列

等待队列是通过task_list双链表来实现,其数据成员是以下数据结构:

复制代码
1 typedef struct __wait_queue wait_queue_t;
2 
3 struct __wait_queue {
4     unsigned int flags;
5 #define WQ_FLAG_EXCLUSIVE    0x01
6     void *private;
7     wait_queue_func_t func;
8     struct list_head task_list;
9 };
复制代码

关系如下:

等待队列使用分两步:

(1)为了使得等待进程在一个等待队列中睡眠,需要调用函数wait_event()函数。进程进入睡眠,将控制权释放给调度器。

(2)在内核中另一处,调用wake_up()函数唤醒等待队列中的睡眠进程。

注:使用wait_event()函数使得进程睡眠;而在内核另一处有一个对应的wake_up()函数被调用。

2 等待队列的初始化

有两种方法初始化队列,分为动态初始化和静态初始化。

2.1 静态初始化

复制代码
1 #define DEFINE_WAIT_FUNC(name, function)                \
2     wait_queue_t name = {                        \
3         .private    = current,                \
4         .func        = function,                \
5         .task_list    = LIST_HEAD_INIT((name).task_list),    \
6     }
7 
8 #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
复制代码

2.2 动态初始化

复制代码
1 static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
2 {
3     q->flags = 0;
4     q->private = p;
5     q->func = default_wake_function;
6 }
复制代码

其中函数autoremove_wake_function()是用来唤醒进程的,该函数不经调用default_wake_function(),还将所属等待队列成员从等待队列删除。

3 进程睡眠

通过add_wait_queue()函数将一个进程添加到等待队列,首先获得队列的自旋锁,然后调用__add_wait_queue()实现将新的等待进程添加等待队列(添加到等待队列的头部),然后解锁;代码如下:

1 static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
2 {
3     list_add(&new->task_list, &head->task_list);
4 }

另一个函数add_wait_queue_exclusive()的含义与add_wait_queue()函数类似,但是将等待进程添加到等待队列的尾部,并设置WQ_EXCLUSIXE标志。

使得进程在等待队列上睡眠的另一种方法是:prepare_to_wait(),除了有add_wait_queue()函数的参数外,还要设置进程的状态。另一个函数prepare_to_wait_exclusive()语义类似。       

通常情况下,add_wait_queue()函数不会直接使用,因为add_wait_queue()函数不与具体的逻辑相管理,单纯的一个等待队列的模型是没有意义的,因此通常使用的是wait_event()函数:

复制代码
 1 #define wait_event(wq, condition)                     \
 2 do {                                    \
 3     if (condition)                             \
 4         break;                            \
 5     __wait_event(wq, condition);                    \
 6 } while (0)
 7 
 8 #define __wait_event(wq, condition)                     \
 9 do {                                    \
10     DEFINE_WAIT(__wait);                        \
11                                     \
12     for (;;) {                            \
13         prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \
14         if (condition)                        \
15             break;                        \
16         schedule();                        \
17     }                                \
18     finish_wait(&wq, &__wait);                    \
19 } while (0)
复制代码

其中wq是等待进程需要加入的等待队列,而condition是通过与所等待时间有关的一个C表达式形式给出。表示,条件满足时,可以立即停止处理。

主要工作由__wait_event()来完成:

(1) 调用DEFINE_WAIT宏创建等待队列成员;

(2) 使用一个无线循环,在循环体内,

1) 调用prepare_to_wait()使得进程在等待队列上等待,并将进程状态置为不可中断TASK_UNINTERRUPTIBLE;

2) 当进程被唤醒时,检查指定的条件condition是否满足,如果满足则跳出循环,否则将控制权交给调度器,然后进程继续睡眠。

(3) 调用函数finish_wait()将进程状态设置为TASK_RUNNING,并从等待队列的链表中移除对应的成员。

其他与wait_event类似的函数:

1 wait_event_interrupable()函数 ,使得进程处于可中断(TASK_INTERRUPTIBLE)状态,从而睡眠进程可以通过接收信号被唤醒;
2 wait_event_timeout()函数,等待满足指定的条件,但是如果等待时间超过指定的超时限制则停止睡眠,可以防止进程永远睡眠;
3 wait_event_interruptible_timeout() 使得进程睡眠,不但可以通过接收信号被唤醒,也具有超时限制。

4 进程唤醒

内核中虽然定义了很多唤醒等待队列中进程的函数,但是最终调用的都是__wake_up()

复制代码
1 #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
2 #define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
3 #define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
4 #define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL)
5 #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
6 #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
7 #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
8 #define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
复制代码

而__wake_up()函数在加锁之后调用的是__wake_up_common()

复制代码
 1 static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
 2             int nr_exclusive, int wake_flags, void *key)
 3 {
 4     wait_queue_t *curr, *next;
 5   
 6     list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
 7         unsigned flags = curr->flags;
 8   
 9         if (curr->func(curr, mode, wake_flags, key) &&
10                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
11             break;
12     }
13 }
复制代码

其中:q是等待队列,mode指定进程的状态,用于控制唤醒进程的条件,nr_exclusive表示将要唤醒的设置了WQ_FLAG_EXCLUSIVE标志的进程的数目。然后扫描链表,调用func(注册的进程唤醒函数,默认为default_wake_function)唤醒每一个进程,直至队列为空,或者没有更多的进程被唤醒,或者被唤醒的的独占进程数目已经达到规定数目。

5 进程运用等待队列的实例

复制代码
  1 /*a simple wait_queue demo
  2  *task_1,task_2 added into the wait_queue, if condition is 0.
  3  *task_3 change condition to 1, and task_1 task_2 will be wake up
  4  */
  5 
  6 #include <linux/kernel.h>
  7 #include <linux/init.h>
  8 #include <linux/module.h>
  9 #include <linux/sched.h>
 10 #include <linux/kthread.h>
 11 #include <linux/delay.h>
 12 
 13 MODULE_LICENSE("GPL");
 14 MODULE_AUTHOR("[email protected]");
 15 
 16 static int condition;
 17 static struct task_struct *task_1;
 18 static struct task_struct *task_2;
 19 static struct task_struct *task_3;
 20 
 21 DECLARE_WAIT_QUEUE_HEAD(wq);
 22 
 23 
 24 static int thread_func_1(void *data)
 25 {
 26     int i = 0;
 27     while (i++ < 100) {
 28         wait_event(wq, condition == 1);
 29         msleep(1000);
 30         printk(">>>>>this task 1\n");
 31     }
 32     return 0;
 33 }
 34 
 35 static int thread_func_2(void *data)
 36 {
 37     int i = 0;
 38     while (i++ < 100) {
 39         wait_event(wq, condition == 1);
 40         msleep(1000);
 41         printk(">>>>>this task 2\n");
 42     }
 43     return 0;
 44 }
 45 static int thread_func_3(void *data)
 46 {
 47     int i = 0;
 48     while (i++ < 10) {
 49         condition = 0;
 50         msleep(2000);
 51         printk(">>>>>this task 3\n");
 52         condition = 1;
 53         wake_up(&wq);
 54         msleep(2000);
 55     }
 56     return 0;
 57 }
 58 
 59 
 60 
 61 static int __init mod_init(void)
 62 {
 63     printk("=====mod set up===\n");
 64     condition = 0;
 65 
 66     task_1 = kthread_run(thread_func_1, NULL, "thread%d", 1);
 67     if (IS_ERR(task_1))
 68         printk("**********create thread 1 failed\n");
 69     else
 70         printk("======success create thread 1\n");
 71 
 72     task_2 = kthread_run(thread_func_2, NULL, "thread%d", 2);
 73     if (IS_ERR(task_2))
 74         printk("**********create thread 2 failed\n");
 75     else
 76         printk("======success create thread 2\n");
 77 
 78     task_3 = kthread_run(thread_func_3, NULL, "thread%d", 3);
 79     if (IS_ERR(task_3))
 80         printk("**********create thread 3 failed\n");
 81     else
 82         printk("======success create thread 3\n");
 83     return 0;
 84 }
 85 
 86 static void __exit mod_exit(void)
 87 {
 88     int ret;
 89     if (!IS_ERR(task_1)) {
 90         ret = kthread_stop(task_1);
 91         if (ret > 0)
 92             printk("<<<<<<<<, ret);
 93     }
 94         
 95     if (!IS_ERR(task_2)) {
 96         ret = kthread_stop(task_2);
 97         if (ret > 0)
 98             printk("<<<<<<<<, ret);
 99     }
100 
101     if (!IS_ERR(task_3)) {
102         ret = kthread_stop(task_3);
103         if (ret > 0)
104             printk("<<<<<<<<, ret);
105     }
106 }
107 module_init(mod_init);
108 module_exit(mod_exit);
复制代码

参考博文:http://blog.chinaunix.net/uid-27714502-id-3450323.html

标签:__,task,队列,up,详解,wake,内核,linux,wait
From: https://www.cnblogs.com/tomato-haha/p/17671533.html

相关文章

  • Linux 主机磁盘繁忙度监控实战shell脚本
    Linux磁盘繁忙度是指磁盘的使用率和活动水平。可以通过一些工具来监测磁盘繁忙度,如iostat、iotop、sar等。其中,iostat是一个常用的工具,可以提供关于磁盘活动的详细统计信息。通过运行命令iostat-x1可以实时监测磁盘的使用情况,其中%util列就表示磁盘的繁忙度,数值越高表......
  • 免苹果开发者账号申请iOS上架及证书打包ipa测试(2022最新详解)
    虽然xcode现在可以免证书进行测试了,但众多跨平台开发者,如果还没注册苹果开发者账号。想安装到自己非越狱手机测试是无能为力了。不过新技术来了,只需要普通免费的苹果账号无需付费成为开发者就可以申请ios证书打包ipa安装到自己手机测试,强大吧!这个神器就是Appuploader,iosapp测试及......
  • 巧妙利用Appuploader上架IPA方法详解
    Appuploader可以辅助在Windows、linux或mac系统直接申请iOS证书p12,及上传ipa到AppStore。方便在没有苹果电脑情况下上架IPA操作。一、下载安装iOS上架辅助软件Appuploader下载地址:下载软件包后解压直接使用,无需安装。二、申请iOS发布证书(p12文件)发布证书用于上架,证书有p12及mo......
  • linux定时任务的设置
    为当前用户创建cron服务1. 键入crontab -e编辑crontab服务文件     例如文件内容如下:    */2****/bin/sh/home/admin/jiaoben/buy/deleteFile.sh     保存文件并并退出     */2****/bin/sh/home/admin/jiaoben/buy/deleteFile.sh......
  • linux 分配git用户名和密码
    touch.git-credentials然后用代码编辑工具打开刚才的文件,编辑如下:https://你的用户名:你的密码@github.com这一步你要注意一下,如果你是dsdn的账号,注意一下后缀,应该是https://你的用户名:你的密码@code.csdn.net,这个根据情况而定1.2添加gitconfig内容进入gitbash终端输入......
  • Linux配置ADSL链接
    Linux配置ADSL链接在Linux中配置ADSL链接,可以按照以下步骤进行:安装rp-pppoeconf工具,这个工具可以通过终端窗口使用。运行命令“rp-pppoeconf”来配置ADSL链接。终端窗口会显示一个向导模式,用于配置ADSL链接。输入用户名和密码。这些信息是用来连接到ADSL服务器的。选择连接的速度。......
  • Linux文件扩展名:
    Linux文件扩展名:Linux文件扩展名:基本上,Linux的文件是没有所谓的“扩展名”的, 一个Linux文件能不能被执行,与他的第一栏的十个属性有关,与文件名根本一点关系也没有。这个观念跟Windows的情况不相同!在Windows下面,能被执行的文件扩展名通常是.com.exe.bat等等,而在Linux下......
  • linux 磁盘管理常用操作
    理论看前一篇动态扩展:vgs  查看vglvextend -L +10G  /dev/mapper/lv-name    其中lv-name可以通过df -Th查看lvs  查看lvresize2fs    /dev/mapper/lv-name          设置文件系统xfs_growfs  /dev/mapper/lv-name......
  • 链表详解
    C++中的链表是一种数据结构,用于存储一系列元素,每个元素都包含一个值以及一个指向下一个元素的指针。链表可以分为单向链表和双向链表,其中单向链表每个节点只有一个指向下一个节点的指针,而双向链表每个节点有一个指向下一个节点和一个指向前一个节点的指针。以下是关于C++链表的详......
  • XGboost详解
    一概述XGBoost提供梯度提升树(也称为GBDT,GBM),可以快速准确地解决许多数据科学问题,相同的代码可以在主要分布式环境运行(ApacheHadoop,ApacheSpark,ApacheFlink)。系统优化:并行计算:支持并行计算。树剪枝:用贪心算法来选择最佳分裂点,然后开始剪枝。硬件优化:有效利用硬件资源。......