Linux等待队列原理与实现

摘要:
等待队列是内核用来管理等待资源的进程。当进程获得的资源尚未就绪时,可以调用add_wait_queue()函数将一个进程添加到等待队列中,然后切换到另一个进程继续执行。您可以调用init_waitqueue_entry()函数来初始化wait_queue_t结构变量如下:staticlinevoidinit_wait_queue_entry{q-˃flags=0;q-˃private=p;q-˃func=default_wake_function;}您还可以调用init_waitqueue_func_entry()函数,该函数被初始化为用户定义的唤醒函数:staticlinevoidinit_wait_queue_func_entry{q-˃flags=0;q-˃private=NULL;q-˃func=func;}初始化后等待_queue_t结构变量后,可以调用add_Wait_queue()函数将等待进程添加到等待队列中,其实现方式如下:voidad_Wait_queue{unsignedlongflags;Wait-˃flags&=~WQ_FLAG_EXCLUSIVE;spin_lock_irqsave;__add_Wait_queue;spin_unlock_irqrestore;}staticinlinevoid__添加等待队列{list_add;}add_wait_queue()函数的实现非常简单。首先,调用spin_lock_lock-irqsave(),然后调用list_add()函数将节点添加到等待队列中。

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

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

等待队列初始化

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

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};

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

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

void init_waitqueue_head(wait_queue_head_t *q)
{
    spin_lock_init(&q->lock);
    INIT_LIST_HEAD(&q->task_list);
}

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

向等待队列添加等待进程

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

typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key);

struct __wait_queue {
    unsigned int flags;
    void *private;
    wait_queue_func_t func;
    struct list_head task_list;
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Linux等待队列原理与实现第1张

电脑刺绣绣花厂 http://www.szhdn.com广州品牌设计公司https://www.houdianzi.com

休眠等待进程

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

set_current_state(TASK_INTERRUPTIBLE);
schedule();

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

唤醒等待队列

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

static void __wake_up_common(wait_queue_head_t *q,
    unsigned int mode, int nr_exclusive, int sync, void *key)
{
    wait_queue_t *curr, *next;

    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
        unsigned flags = curr->flags;

        if (curr->func(curr, mode, sync, key) &&
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
            break;
    }
}

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

免责声明:文章转载自《Linux等待队列原理与实现》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇PHP如何实现网址伪静态(转)高德地图-搜索服务-POI搜索下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

debian基本操作

debian基本操作 1 给账户添加sudo权限 1、Debian默认没有sudo功能,因此需要自己安装:# apt-get install sudo2、# chmod +w /etc/sudoers3、# vim /etc/sudoers,添加如下行 root ALL=(ALL:ALL) ALL toney ALL=(ALL:ALL) AL...

(转)CentOS系统启动流程图文详解

CentOS系统启动流程图文详解. 原文:http://www.linuxidc.com/Linux/2017-03/141966.htm 熟悉系统启动流程对于我们学习Linux系统是非常有帮助的,虽然基础,但能帮助我们更加理解Linux系统的工作机制。以下将以CentOS发行版为例来介绍Linux系统的启动流程,因为在CentOS 5、CentOS 6以...

Linux磁盘分区扩容

随着业务的增长,aliyun数据盘容量可能无法满足数据存储的需要,这时可以使用“”磁盘扩容“”功能扩容数据盘。 本文以一个SSD云盘的数据盘和一个运行Ubuntu 16..4 64位的 ECS 实例为例,说明如何扩容磁盘分区并使扩容后的容量可用。未扩容前的数据盘只有一个主分区(/dev/vdb1,ext4 文件系统),文件系统的挂载点为 /data,文件系...

linux命令<服务进程、查看日志、文件编辑、赋权等>

sudo命令以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。 sudo apt-get update  更新 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的地址,这样才能获取到最新的软件包; sudo apt-get upgrade  升级已安装的...

【 Linux 】Systemd 配置文件说明及编写(2)

1. 开机启动 对于支持 systemd 的软件,如果想要设置开机启动,就执行如下命令(以 http 为例): systemctl enable httpd 上面的命令相当于在 /etc/systemd/system/ 目录里添加了一个符号链接,指向 /usr/lib/systemd/system/ 里面的 httpd.service 文件。 这是因为...

Linux环境下常用软件(个人笔记编辑更改中)

近期使用CentOS,就在这里记录一下。首先,个人版本是CentOS6.5,属于centos系列,Fedora系列的理论上也可以用。 工欲善其事,必先利其器,这里介绍我的软件包配置: 1、vim(增强版vi)和Emacs(PS:有人说他们是神的编辑器和编辑器的神) 对vim: # yum -y install vim*或者 #apt-get install...