服务器之家:专注于服务器技术及软件下载分享
分类导航

Linux|Centos|Ubuntu|系统进程|Fedora|注册表|Bios|Solaris|Windows7|Windows10|Windows11|windows server|

服务器之家 - 服务器系统 - Linux - 一文读懂Linux延时队列工作原理

一文读懂Linux延时队列工作原理

2021-10-20 21:13Linux内核那些事songsong001 Linux

当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。

一文读懂Linux延时队列工作原理

当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。

waitqueue (等待队列) 就是内核用于管理等待资源的进程,当某个进程获取的资源没有准备好的时候,可以通过调用 add_wait_queue() 函数把进程添加到 waitqueue 中,然后切换到其他进程继续执行。当资源准备好,由资源提供方通过调用 wake_up() 函数来唤醒等待的进程。

等待队列初始化

要使用 waitqueue 首先需要声明一个 wait_queue_head_t 结构的变量,wait_queue_head_t 结构定义如下:

  1. struct__wait_queue_head{
  2. spinlock_tlock;
  3. structlist_headtask_list;
  4. };

waitqueue 本质上是一个链表,而 wait_queue_head_t 结构是 waitqueue 的头部,lock 字段用于保护等待队列在多核环境下数据被破坏,而 task_list 字段用于保存等待资源的进程列表。

可以通过调用 init_waitqueue_head() 函数来初始化 wait_queue_head_t 结构,其实现如下:

  1. voidinit_waitqueue_head(wait_queue_head_t*q)
  2. {
  3. spin_lock_init(&q->lock);
  4. INIT_LIST_HEAD(&q->task_list);
  5. }

初始化过程很简单,首先调用 spin_lock_init() 来初始化自旋锁 lock,然后调用 INIT_LIST_HEAD() 来初始化进程链表。

向等待队列添加等待进程

要向 waitqueue 添加等待进程,首先要声明一个 wait_queue_t 结构的变量,wait_queue_t 结构定义如下:

  1. typedefint(*wait_queue_func_t)(wait_queue_t*wait,unsignedmode,intsync,void*key);
  2. struct__wait_queue{
  3. unsignedintflags;
  4. void*private;
  5. wait_queue_func_tfunc;
  6. structlist_headtask_list;
  7. };

下面说明一下各个成员的作用:

flags: 可以设置为 WQ_FLAG_EXCLUSIVE,表示等待的进程应该独占资源(解决惊群现象)。

private: 一般用于保存等待进程的进程描述符 task_struct。

func: 唤醒函数,一般设置为 default_wake_function() 函数,当然也可以设置为自定义的唤醒函数。

task_list: 用于连接其他等待资源的进程。

可以通过调用 init_waitqueue_entry() 函数来初始化 wait_queue_t 结构变量,其实现如下:

  1. staticinlinevoidinit_waitqueue_entry(wait_queue_t*q,structtask_struct*p)
  2. {
  3. q->flags=0;
  4. q->private=p;
  5. q->func=default_wake_function;
  6. }

也可以通过调用 init_waitqueue_func_entry() 函数来初始化为自定义的唤醒函数:

  1. staticinlinevoidinit_waitqueue_func_entry(wait_queue_t*q,wait_queue_func_tfunc)
  2. {
  3. q->flags=0;
  4. q->private=NULL;
  5. q->func=func;
  6. }

初始化完 wait_queue_t 结构变量后,可以通过调用 add_wait_queue() 函数把等待进程添加到等待队列,其实现如下:

  1. voidadd_wait_queue(wait_queue_head_t*q,wait_queue_t*wait)
  2. {
  3. unsignedlongflags;
  4. wait->flags&=~WQ_FLAG_EXCLUSIVE;
  5. spin_lock_irqsave(&q->lock,flags);
  6. __add_wait_queue(q,wait);
  7. spin_unlock_irqrestore(&q->lock,flags);
  8. }
  9. staticinlinevoid__add_wait_queue(wait_queue_head_t*head,wait_queue_t*new)
  10. {
  11. list_add(&new->task_list,&head->task_list);
  12. }

add_wait_queue() 函数的实现很简单,首先通过调用 spin_lock_irqsave() 上锁,然后调用 list_add() 函数把节点添加到等待队列即可。

wait_queue_head_t 结构与 wait_queue_t 结构之间的关系如下图:

一文读懂Linux延时队列工作原理

waitqueue

休眠等待进程

当把进程添加到等待队列后,就可以休眠当前进程,让出CPU给其他进程运行,要休眠进程可以通过一下方式:

  1. set_current_state(TASK_INTERRUPTIBLE);
  2. schedule();

代码 set_current_state(TASK_INTERRUPTIBLE) 可以把当前进程运行状态设置为 可中断休眠 状态,调用 schedule() 函数可以使当前进程让出CPU,切换到其他进程执行。

唤醒等待队列

当资源准备好后,就可以唤醒等待队列中的进程,可以通过 wake_up() 函数来唤醒等待队列中的进程。wake_up() 最终会调用 __wake_up_common(),其实现如下:

  1. staticvoid__wake_up_common(wait_queue_head_t*q,
  2. unsignedintmode,intnr_exclusive,intsync,void*key)
  3. {
  4. wait_queue_t*curr,*next;
  5. list_for_each_entry_safe(curr,next,&q->task_list,task_list){
  6. unsignedflags=curr->flags;
  7. if(curr->func(curr,mode,sync,key)&&
  8. (flags&WQ_FLAG_EXCLUSIVE)&&!--nr_exclusive)
  9. break;
  10. }
  11. }

可以看出,唤醒等待队列就是变量等待队列的等待进程,然后调用唤醒函数来唤醒它们。

原文链接:https://mp.weixin.qq.com/s/2W9TNepvJ041KU9LvfLvOg

延伸 · 阅读

精彩推荐
  • LinuxLinux中环境变量配置的步骤详解

    Linux中环境变量配置的步骤详解

    Linux中环境变量包括系统级和用户级,系统级的环境变量是每个登录到系统的用户都要读取的系统变量,而用户级的环境变量则是该用户使用系统时加载的...

    Myths7882022-02-10
  • LinuxLinux常用的日志文件和常用命令

    Linux常用的日志文件和常用命令

    成功地管理任何系统的关键之一,是要知道系统中正在发生什么事。 Linux 中提供了异常日志,并且日志的细节是可配置的。Linux 日志都以明文形式存储,所...

    Linux教程网2632020-04-18
  • LinuxLinux上设置用户通过SFTP访问目录的权限的方法

    Linux上设置用户通过SFTP访问目录的权限的方法

    这篇文章主要介绍了Linux上设置用户通过SFTP访问目录的权限的方法,SFTP可以理解为使用SSH协议进行FTP传输的协议,因而同时要对OpenSSH进行相关设置,需要的朋...

    OSChina10022019-06-19
  • LinuxLinux lnmp下无法使用mail发邮件的两种解决方法

    Linux lnmp下无法使用mail发邮件的两种解决方法

    在配置了lnmp环境后,出现了mail函数不能发送邮件的问题,其实有两种方法,一是使用sendmail组件,而是使用postfix。下面为大家一一介绍下 ...

    Linux之家4042019-09-17
  • Linux详解Linux系统下PXE服务器的部署过程

    详解Linux系统下PXE服务器的部署过程

    这篇文章主要介绍了Linux系统下PXE服务器的部署过程,包括对PXE的API架构作了一个基本的简介,需要的朋友可以参考下...

    运维之道9812019-07-04
  • Linux手把手教您在 Linux 上使用 GPG 加解密文件

    手把手教您在 Linux 上使用 GPG 加解密文件

    在本教程中,我将告诉你如何用 GPG 加密和解密文件。这是一个简单的教程,你可以在你的 Linux 系统上尝试所有的练习。这将帮助你练习 GPG 命令,并在你...

    Linux中国6962021-12-15
  • Linux确保Linux系统安全的前提条件 漏洞防护

    确保Linux系统安全的前提条件 漏洞防护

    Linux 作为开放式的操作系统受到很多程序员的喜爱,很多高级程序员都喜欢编写Linux操作系统的相关软件。这使得Linux操作系统有着丰富的软件支持,还有无...

    Linux之家2642020-04-11
  • Linuxlinux中rmdir命令使用详解(删除空目录)

    linux中rmdir命令使用详解(删除空目录)

    今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的 ...

    linux命令大全5372019-11-19