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

linux 内核等待队列

时间:2023-04-22 20:46:02浏览次数:27  
标签:head 队列 内核 wq linux 进程 entry wait

一 背景和意义

在实际编程中,我们会经常碰到这种场景:进程P需要等待条件C的成立,才能继续执行某个动作。例如,当串口没有数据可以读取时,我们可以通过轮询的方式,等到有数据来的时候,串口程序再去读取。但是这种方式显得比较笨拙,影响CPU的性能。因此,内核中提供了等待队列的方式,即可以将进程P先挂到等待队列Q(wait_queue)上去,并将进程的状态由RUNNING切换为睡眠状态,主动让出CPU,直到条件满足时,由内核调用wake_up()接口,自动唤醒Q上的所有进程,这样进程P就继续执行。

因此,wait_queue的实现,能够提高整个系统以及CPU运行的效率。

二 数据结构分析

wait_queue使用到的数据结构很简单,就是使用内核链表,将等待同一事件的所有进程串联起来。

第一个数据结构是等待队列头:

struct wait_queue_head {
    spinlock_t      lock;        \\用于同步的自旋锁
    struct list_head    head;    \\等待队列头
};
typedef struct wait_queue_head wait_queue_head_t;

 

等待队列头可以用以下API进行初始化:

wait_queue_head_t wait_queue_head;
init_waitqueue_head(&wait_queue_head);

 

第二个数据结构是等待队列的队列元素:

struct wait_queue_entry {                                                                                 
    unsigned int        flags; // 最低位为1代表互斥进程,其他代表非互斥进程
    void            *private;  //一般用于指向当前进程,即设置为current指针
    wait_queue_func_t   func;  //唤醒方式
    struct list_head    entry; //用于链表管理
};

/* wait_queue_entry::flags */ #define WQ_FLAG_EXCLUSIVE   0x01 #define WQ_FLAG_WOKEN       0x02 #define WQ_FLAG_BOOKMARK    0x04 #define WQ_FLAG_CUSTOM      0x08 #define WQ_FLAG_DONE        0x10 #define WQ_FLAG_PRIORITY    0x20

 

这里,睡眠进程被分成两种,最低位为1代表的是互斥进程,这些进程在唤醒时会被有选择地唤醒;其他代表的是非互斥进程,唤醒时,内核将唤醒所有的非互斥进程。

当多个进程等待互斥资源时,同时唤醒所有进程将会导致又一次的竞争,而只有一个进程会获得互斥资源,因此其他进程又必须重新睡眠。为了避免以上的情况,才定义了互斥进程的概念,并使用flags来区分互斥进程和非互斥进程。

func字段是进程被唤醒的方式,默认会被初始化为default_wake_function()。

可以使用以下API对等待队列的队列元素进行动态的初始化:

struct wait_queue_entry wait_entry;
init_wait(&wait_entry);

或者使用静态的初始化方法:

DEFINE_WAIT(wait_entry);

以上两种方式的结果相同,以init_wait 为例:

#define DEFINE_WAIT_FUNC(name, function)                    \
    struct wait_queue_entry name ={     \ 
        .private    = current,                  \
        .func       = function,                 \
        .entry      = LIST_HEAD_INIT((name).entry),         \
    }
 
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

 

等待队列头和等待队列的关系图

 

 

三 等待队列的操作

3.1 典型应用

/* 1. 定义一个等待队列项 */
DEFINE_WAIT(wait);
/* 2. 将等待队列项wait插入等待队列wq中,并将进程的状态设置为INTERRUPTIBLE */
prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE);
...
/* 3. 如果期望的条件不满足,则主动让出CPU,切换其他进程运行 */
if (!condition)
    schedule();
/* 
 * 4. 进程被唤醒后,继续向下执行,将进程的状态设置为运行状态,并将等待队列项wait从等待队列wq中删除。
 */
finish_wait(&wq, &wait);
 

3.2 将等待队列项插入等待队列

一般,定义并初始化完等待队列后,需要通过prepare_to_wait() 或者 prepare_to_wait_exclusive ()函数,将进程的状态改变,并将其加入对应的等待队列中:

void
prepare_to_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state)
{
    unsigned long flags;
 
    wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&wq_head->lock, flags);
    if (list_empty(&wq_entry->entry))
        __add_wait_queue(wq_head, wq_entry);
    set_current_state(state);
    spin_unlock_irqrestore(&wq_head->lock, flags);
}
EXPORT_SYMBOL(prepare_to_wait);
 
void
prepare_to_wait_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state)
{
    unsigned long flags;
 
    wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&wq_head->lock, flags);
    if (list_empty(&wq_entry->entry))
        __add_wait_queue_entry_tail(wq_head, wq_entry);
    set_current_state(state);
    spin_unlock_irqrestore(&wq_head->lock, flags);
}
EXPORT_SYMBOL(prepare_to_wait_exclusive);

从以上源码可以看出,prepare_to_wait() 或者 prepare_to_wait_exclusive ()函数有以下区别:

1. prepare_to_wait_exclusive ()函数会将等待队列项对应的flags的WQ_FLAG_EXCLUSIVE使能,也就是说,通过prepare_to_wait_exclusive ()函数添加的等待队列项,对应的进程被设置为互斥进程;而通过prepare_to_wait()添加的等待队列项,对应的进程被设置为非互斥进程。

2. 通过prepare_to_wait()添加的非互斥等待队列项,会被添加至等待队列的队头,而通过 prepare_to_wait_exclusive ()函数添加的互斥项,会被添加至等待队列的队尾。

二者都会改变当前进程的状态,由传入的参数state 决定。

3.3 将等待队列从等待队列中删除

当进程被唤醒后,一般会直接调用finish_wait函数,将进程的状态重新设置为running,并将该进程对应的等待队列项从等待队列中删除:

void finish_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
    unsigned long flags;
 
    __set_current_state(TASK_RUNNING);
    /*
    ¦* We can check for list emptiness outside the lock
    ¦* IFF:
    ¦*  - we use the "careful" check that verifies both
    ¦*    the next and prev pointers, so that there cannot
    ¦*    be any half-pending updates in progress on other
    ¦*    CPU's that we haven't seen yet (and that might
    ¦*    still change the stack area.
    ¦* and
    ¦*  - all other users take the lock (ie we can only
    ¦*    have _one_ other CPU that looks at or modifies
    ¦*    the list).
    ¦*/
    if (!list_empty_careful(&wq_entry->entry)) {
        spin_lock_irqsave(&wq_head->lock, flags);
        list_del_init(&wq_entry->entry);
        spin_unlock_irqrestore(&wq_head->lock, flags);
    }
}

3.4 将等待队列从等待队列中删除

唤醒等待队列上的进程:

一般,可以通过wake_up, wake_up_all等API对唤醒队列上的进程进行唤醒。wake_up将会唤醒所有的非互斥进程,而只唤醒一个互斥进程; wake_up_all将会唤醒所有的非互斥进程,和所有的互斥进程。其他API可以参看内核源码。

 

标签:head,队列,内核,wq,linux,进程,entry,wait
From: https://www.cnblogs.com/god-of-death/p/17343866.html

相关文章

  • 使用FlashFxp sftp无法连接Linux处理
    sftp无法连接[18:32:41]FlashFXP5.4.0(build3970)[18:32:41]SupportForumshttps://www.flashfxp.com/forum/[18:32:41]...[18:33:03][R]SSH错误:协商认证模式失败[18:33:03][R]SSH连接已关闭[18:33:03][R]连接失败处理:/etc/ssh/sshd_config文件夹的一......
  • 在 Alpine Linux 安装 Zerotier 并转发内网设备
    安装zerotier-onealpine:~#apkupdate&&apkaddzerotier-onefetchhttp://mirrors.ustc.edu.cn/alpine/v3.17/main/x86_64/APKINDEX.tar.gzfetchhttp://mirrors.ustc.edu.cn/alpine/v3.17/community/x86_64/APKINDEX.tar.gzfetchhttp://mirrors.ustc.edu.cn/......
  • linux中断总结
    中断分为上半部和下半部,上半部为硬中断处理,主要是为了处理一些对时间要求很高的数据,然后将剩下繁琐(需要很多时间)的工作交给下半部。下半部:软中断,tasklet和工作队列。 硬中断:中断电信号发送到中断控制器(将多路中断管线,采用复用技术只通过一个管线与处理器通信)的输入引脚中,中断......
  • [Linux]raspbian安装xrdp(远程桌面)
    1.首先换源:输入以下命令sudosed-i"s@http://deb.debian.org@https://mirrors.163.com@g"/etc/apt/sources.list2.update是更新软件列表,upgrade是更新软件。这两个命令一般是一起使用的。3.需要在Debian系统中安装xrdp,xrdpisadaemonthatsupportsMicrosoft'sRemote......
  • linux openClouldOS 8.6安装最新版MySQL详细教程
    参考linux安装最新版MySQL详细教程rpm包下载其中下载MySQL官网的仓库文件,根据服务器情况选择的RedHatEnterpriseLinux8/OracleLinux8(ArchitectureIndependent),RPMPackage下载地址改为wgethttp://repo.mysql.com/mysql80-community-release-el8-5.noarch.rpm再......
  • windows和linux进程的区别
     Windows线程进程Windows中,进程拥有一个进程描述符,包含一些地址空间,打开的文件等共享资源,进程包含指向线程的指针,线程没有进程描述符,只描述一些少量的独有资源(线程局部存储tls?),比较轻量,同时共享进程的资源。 linux线程进程linux内核中,进程是用一个task_struct的结构体描述......
  • docker的安装(linux、centos)
    环境:centos71.先确定linux是否是centos7cat/etc/redhat-release2.如果自己的linux上之前有安装docker,先卸载。如果没有,则直接跳过这一步。执行下面的命令:yumremovedocker\docker-client\docker-client-latest\do......
  • linux内网替换redhat-6.5为CentOS6.5操作
    下载CentOS6.5系统源http://mirror.nsc.liu.se/centos-store/6.5/isos/x86_64/CentOS-6.5-x86_64-bin-DVD1.iso下载CentOS6.5系统的yum组件http://mirror.nsc.liu.se/centos-store/6.5/os/x86_64/Packages/yum-3.2.29-40.el6.centos.noarch.rpmhttp://mirror.nsc.liu.se/ce......
  • deque:双端队列库
    #include<deque>usingnamespacestd;deque<int>deq1;//定义一个空的deque,元素类型为intdeque<int>deq2(10);//定义一个大小为10的deque,元素类型为int,初始值为0deque<int>deq3(10,1);//定义一个大小为10的deque,元素类型为int,初始值为1deque<int>deq4={1,2,3};//......
  • C++恶意软件开发(五)Linux shellcoding
    什么是shellcode?Shellcode通常指的是一段用于攻击的机器码(二进制代码),可以被注入到目标计算机中并在其中执行。Shellcode的目的是利用目标系统的漏洞或弱点,以获取系统控制权或执行恶意操作。它的名称来自于它经常被注入到攻击者编写的恶意软件的shell环境中,以便让攻击者可以更......