Linux 阻塞IO(等待队列)原理及架构

2023-10-27

一. 阻塞操作

阻塞操作是指在执行折本操作时,若不能获得自愿,则挂起进程直到满足可操作性的条件后在进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

假设recvfrom函数是一个系统调用:
在这里插入图片描述
阻塞不是低效率,如果设备驱动不阻塞,用户想获取设备资源只能不断查询,消耗CPU资源,阻塞访问时,不能获取资源的进程将进入休眠,将CPU资源让给其他资源。
因为阻塞的进程会进入休眠状态,因此,必须确保有一个地方能够唤醒休眠的进程。最大可能发生在中断函数里。

二. 等待队列

在Linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。
Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制,也可以用来同步对系统资源的访问。
在这里插入图片描述
等待队列有两种数据结构:等待队列头 (wait_queue_head_t)和等待队列项(wait_queue_t)。
等待队列头和等待队列项中都包含一个list_head类型的域作为”连接件”。它通过一个双链表和把等待task的头,和等待的进程列表链接起来。

struct __wait_queue_head {
      spinlock_t lock;                    /* 保护等待队列的原子锁 (自旋锁),在对task_list与操作的过程中,使用该锁实现对等待队列的互斥访问*/
      struct list_head task_list;          /* 等待队列,双向循环链表,存放等待的进程 */
};

struct __wait_queue {
	 unsigned int flags;	/* 其中flags域指明该等待的进程是互斥进程还是非互斥进程。其中0是非互斥进程,WQ_FLAG_EXCLUSIVE(0×01)是互斥进程 */
	 void *private;        /* 通常指向当前任务控制块 */
	 wait_queue_func_t func;             
	 struct list_head task_list;   /* 挂入wait_queue_head的挂载点 */
};
typedef struct __wait_queue wait_queue_t;

等待队列的使用分为以下两部分:
(1)为使当前进程在一个等待队列中睡眠,需要调用wait_event(或某个等价函数),此后,进程进入睡眠,将控制权交给调度器。
(2)相对应的,是当数据到达后,必须调用wake_up函数(或某个等价函数)来唤醒等待队列中睡眠的进程

  1. 定义等待队列头
    wait_queue_head_t my_queue;  
  1. 初始化等待队列头
	init_waitqueue_head(&my_queue);
	-->__init_waitqueue_head((q), #q, &__key)
		--> INIT_LIST_HEAD(&q->task_list);
			--> static inline void INIT_LIST_HEAD(struct list_head *list)	//将双循环链表的指针指向队列头部
				{
					list->next = list;
					list->prev = list;
				}

定义并初始化等待队列头

DECLARE_WAIT_QUEUE_HEAD(name)
-->wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALZER(name)
	-->#define __WAIT_QUEUE_HEAD_INITIALZER(name){
		...
		.task_list = {&(name).task_list, &(name).task_list} ==> INIT_LIST_HEAD(struct list_head *list)
		}
  1. 定义等待队列
    静态定义:
DECLARE_WAITQUEUE(name, tsk)
-->#define DECLARE_WAITQUEUE(name, tsk)
	wait_queue_t name = __WAITQUEUE_INITIALZER(name, tsk)
	-->#define __WAITQUEUE_INITIALZER(name, tsk){
		.private 	= current,
		.func 		= default_wake_function==> try_to_wake_up
		.task_list	= {NULL, NULL}
	}

静态定义并初始化等待队列(即2+3):

DEFINE_WAIT(name)

动态声明和初始化一个等待队列

struct wait_queue_t myqueue;
init_waitqueue_entry(&myqueue, tsk);
-->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;
	}
  1. 添加/移除等待队列
//设置等待的进程为非互斥进程,并将其添加进等待队列头(q)的队头中。
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);
    -->list_add(&new->task_list, &head->task_list);
    		-->void __list_add(struct list_head *new, struct list_head *prev, struct list_head next)
    			{
    				next->prev = new;
    				new->next = next;
    				new->prev = prev;
    				prev->next = new;
    			}
    					
    spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);

//下面函数也和add_wait_queue()函数功能基本一样,只不过它是将等待的进程(wait)设置为互斥进程。
void add_wait_queue_exclusive(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_tail(q, wait);
	-->list_add_tail(&new->task_list, &head->task_list);
		-->__list_add(new, head->prv, head);

    spin_unlock_irqrestore(&q->lock, flags);
    }
    EXPORT_SYMBOL(add_wait_queue_exclusive);
//在等待的资源或事件满足时,进程被唤醒,使用该函数被从等待头中删除。
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
     unsigned long flags; 
     spin_lock_irqsave(&q->lock, flags);
     
     __remove_wait_queue(q, wait);
	-->list_del(&old->task_list);
		-->static inline void list_del(struct liset_head *entry)
			{
				__list_del(entry->prev, entry->next);
				-->static inline void __list_del(struct list_head *prev, struct list_head *next)
					{
						next->prev = prev;
						prev->next = next;
					}
					
				entry->next = LIST_POISON1; ==>0x00100100
				entry->prev = LIST_POISON2; ==>0x00200200
			}

     spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(remove_wait_queue);
  1. 等待事件
#define wait_event(wq, condition)                  
do {                                   
        if (condition)                         
        	break;                         
        
        __wait_event(wq, condition);               
        -->(void) __wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule()) 
        	-->for(;;)
        		{
        			if(condition)
        				break;
        			prepare_to_wait_event(wait_queue_head_t *q, wait_queue_head_t *wait, int state); /* 主要是将进程状态改变为state */
        			cmd; /* 执行schedule()==>进程调度 */
        		}   
} while (0)

wait_event(queue,condition);等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞  
wait_event_interruptible(queue,condition);可被信号打断  
wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回  
wait_event_interruptible_timeout(queue,condition,timeout)
  1. 唤醒队列
/*
void __wake_up(wait_queue_head_t *q, unsigned int mode,
                int nr_exclusive, void *key)
{
    unsigned long flags;
    spin_lock_irqsave(&q->lock, flags);
    
    __wake_up_common(q, mode, nr_exclusive, 0, key);
    -->try_to_wake_up()	/*  最后调用到这个函数唤醒进程 */
    
    spin_unlock_irqrestore(&q->lock, flags);
}
    EXPORT_SYMBOL(__wake_up);
//唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,和wait_event/wait_event_timeout成对使用

#define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
//和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程.,与wait_event_interruptible
wait_event_interruptible_timeout
wait_event_interruptible_exclusive成对使用

#define wake_up_all(x)          __wake_up(x, TASK_NORMAL, 0, NULL) 
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
//这些也基本都和wake_up/wake_up_interruptible一样

wait_event()宏:

在等待会列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值.

wait_event_interruptible()函数:

和wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查 condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则 返回0.

wait_event_timeout()宏:

也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0.

wait_event_interruptible_timeout()宏

与wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码.

wait_event_interruptible_exclusive()宏

同样和wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程.
7. 在等待队列上睡眠

void __sched sleep_on(wait_queue_head_t *q)
{  
	sleep_on_common(q, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}

static long __sched
sleep_on_common(wait_queue_head_t *q, int state, long timeout)
{
        unsigned long flags;
        wait_queue_t wait;
        init_waitqueue_entry(&wait, current);
        __set_current_state(state);
        spin_lock_irqsave(&q->lock, flags);
        __add_wait_queue(q, &wait);
        spin_unlock(&q->lock);
        timeout = schedule_timeout(timeout);
        spin_lock_irq(&q->lock);
        __remove_wait_queue(q, &wait);
        spin_unlock_irqrestore(&q->lock, flags); 
        return timeout;
}

该函数的作用是定义一个等待队列(wait),并将当前进程添加到等待队列中(wait),然后将当前进程的状态置为 TASK_UNINTERRUPTIBLE,并将等待队列(wait)添加到等待队列头(q)中。之后就被挂起直到资源可以获取,才被从等待队列头(q) 中唤醒,从等待队列头中移出。在被挂起等待资源期间,该进程不能被信号唤醒。
大多数设备驱动不调用,而是直接进行进程状态的改变和切换,如

static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
	...
	DECLARE_WAITQUEUE(wait, current);//定义等待队列
	add_wait_queue(&xxx_wait, &wait);//添加等待队列

	ret = count;
	
	//等待设备缓冲区可写
	do{
		avail = device_writeable(...);
		if(avail < 0)
			__set_current_state(TASK_INTERRUPTIBLE);

		if(avail < 0)
		{
			if(file->f_flags & O_NONBLOCK) //非阻塞
			{
				if(!ret)
					ret = - EAGAIN;
				goto out;
			}
			schedule();//调度其他进程执行
			if(signal_pending(current))//如果是因为信号唤醒
			{
				if(!ret)
					ret = - ERESTARTSYS;
				goto out;
			}
		}
	}while(avail < 0);

	//写设备缓冲区
	device_write(...);
out:
	remove_wait_queue(&xxx_wait, &wait);//将等待队列移出等待队列头
	set_current_state(TASK_RUNNING);//设置进程状态为TASK_RUNNING
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux 阻塞IO(等待队列)原理及架构 的相关文章

  • ssh 连接超时

    我无法在 git 中 ssh 到 github bitbucket 或 gitlab 我通常会收到以下错误消息 如何避免它 输出 ssh T email protected cdn cgi l email protection i ssh
  • Tomcat Intellij Idea:远程部署

    RackSpace 云服务器 Ubuntu 12 04 Intellij Idea 11 1 2 Windows 8 Tomcat 7 0 26 JDK 6 在 Intellij Idea 上 当我尝试在远程 Tomcat 7 服务器上运行
  • Linux 中 m 标志和 o 标志将存储在哪里

    我想知道最近收到的路由器通告的 m 标志和 o 标志的值 从内核源代码中我知道存储了 m 标志和 o 标志 Remember the managed otherconf flags from most recently received R
  • 为什么 Linux 没有 DirectX API?

    在考虑现代显卡的 Windows 系统上 DirectX API 的驱动程序端实现时 我想知道为什么此实现在非 Windows 系统 尤其是 Linux 上不可用 由于明显缺乏此功能 我只能假设有一个我无视的充分理由 但在我的原始理解中 我
  • python获取上传/下载速度

    我想在我的计算机上监控上传和下载速度 一个名为 conky 的程序已经在 conky conf 中执行了以下操作 Connection quality alignr wireless link qual perc wlan0 downspe
  • 就分页分段内存而言的程序寿命

    我对 x86 Linux 机器中的分段和分页过程有一个令人困惑的概念 如果有人能澄清从开始到结束所涉及的所有步骤 我们将很高兴 x86 使用分页分段内存技术进行内存管理 任何人都可以解释一下从可执行的 elf 格式文件从硬盘加载到主内存到它
  • linux-x64 二进制文件无法在 linuxmusl-x64 平台上使用错误

    我正在安装Sharp用于使用 package json 的 Nodejs 项目的 docker 映像上的映像压缩包 当我创建容器时 我收到有关 Sharp 包的以下错误 app node modules sharp lib libvips
  • 使用循环在 C 中管道传输两个或多个 shell 命令

    我正在尝试执行ls wc l通过 C 语言程序 而不是使用命令行 这是我当前的工作代码 int main int pfds 2 pipe pfds pid t pid fork if pid 0 The child process clos
  • 添加文件时运行 shell 命令

    我的 Linux 机器上有一个名为 images 的文件夹 该文件夹连接到一个网站 该网站的管理员可以向该网站添加图片 但是 当添加图片时 我想要一个命令来运行调整目录中所有图片的大小 简而言之 我想知道当新文件添加到特定位置时如何使服务器
  • 如何获取 (Linux) 机器的 IP 地址?

    这个问题和之前问的几乎一样如何获取本地计算机的IP地址 https stackoverflow com questions 122208 get the ip address of local computer 问题 但是我需要找到一个的I
  • 如何让R使用所有处理器?

    我有一台运行 Windows XP 的四核笔记本电脑 但查看任务管理器 R 似乎一次只使用一个处理器 如何让 R 使用全部四个处理器并加速我的 R 程序 我有一个基本系统 我使用它在 for 循环上并行化我的程序 一旦您了解需要做什么 此方
  • 使用包管理器时如何管理 Perl 模块?

    A 最近的问题 https stackoverflow com questions 397817 unable to find perl modules in intrepid ibex ubuntu这让我开始思考 在我尝试过的大多数 Li
  • .net-core:ILDASM / ILASM 的等效项

    net core 是否有相当于 ILDASM ILASM 的功能 具体来说 我正在寻找在 Linux 上运行的东西 因此为什么是 net core ildasm 和 ilasm 工具都是使用此存储库中的 CoreCLR 构建的 https
  • 内核的panic()函数是否完全冻结所有其他进程?

    我想确认内核的panic 功能和其他类似kernel halt and machine halt 一旦触发 保证机器完全冻结 那么 所有的内核和用户进程都被冻结了吗 是panic 可以被调度程序中断吗 中断处理程序仍然可以执行吗 用例 如果
  • 静态方法的 Java 内存模型

    我来自操作系统和 C 语言背景 在代码编译时 世界很简单 需要处理和理解堆栈 堆文本部分等 当我开始学习 Java 时 我确实了解 JVM 和垃圾收集器 我对静态方法感到很有趣 根据我的理解 类的所有实例都会在堆中创建 然后被清理 但是 对
  • 如何查找哪个 Yocto 项目配方填充图像根文件系统上的特定文件

    我经常与 Yocto 项目合作 一个常见的挑战是确定文件为何 或来自什么配方 包含在 rootfs 中 这有望从构建系统的环境 日志和元数据中得出 理想情况下 一组命令将允许将文件链接回源 即配方 我通常的策略是对元数据执行搜索 例如gre
  • Linux 为一组进程保留一个处理器(动态)

    有没有办法将处理器排除在正常调度之外 也就是说 使用sched setaffinity我可以指示线程应该在哪个处理器上运行 但我正在寻找相反的情况 也就是说 我想从正常调度中排除给定的处理器 以便只有已明确调度的进程才能在那里运行 我还知道
  • linux下如何从文本文件中获取值

    我有一些文本格式的文件 xxx conf 我在这个文件中有一些文本 disablelog 1 当我使用 grep r disablelog oscam conf 输出是 disablelog 1 但我只需要值1 请问你有什么想法吗 一种方法
  • 复制目录内容

    我想将目录 tmp1 的内容复制到另一个目录 tmp2 tmp1 可能包含文件和其他目录 我想使用C C 复制tmp1的内容 包括模式 如果 tmp1 包含目录树 我想递归复制它们 最简单的解决方案是什么 我找到了一个解决方案来打开目录并读
  • 从 Linux 内核模块中调用用户空间函数

    我正在编写一个简单的 Linux 字符设备驱动程序 以通过 I O 端口将数据输出到硬件 我有一个执行浮点运算的函数来计算硬件的正确输出 不幸的是 这意味着我需要将此函数保留在用户空间中 因为 Linux 内核不能很好地处理浮点运算 这是设

随机推荐

  • ACE_Message_Block功能简介

    ACE Message Block在Ace中用来表示消息的存放空间 可用做网络通信中的消息缓冲区 使用非常频繁 下面将在如下方简单的介绍一下ACE Message Block相关功能 创建消息块 释放消息块 从消息块中读写数据 数据的拷贝
  • Linux下设置归档路径不生效,缺少log_archive_config导致归档路径被禁用

    10g的DATA GUARD的一个主要特点就是引入了log archive config参数 如果缺少这个参数 可能会导致归档路径被禁用 看别人建立DATA GUARD时碰到了这个问题 当时觉得比较有意思 于是特意重现一下 当前是一个已经配
  • spring boot中动态增加数据源并且通过sharding-jdbc做分库分表查询

    最近项目中需要通过数仓对接数据出去 需要手动写一些查询 需要支持分库分表的场景 所以就选择了 google guava 做内存缓存 通过redis做持久化缓存 通过sharding jdbc实现跨表查询 一下贴的是几个主要的类 1 数据库信
  • 使用代码来使用 JS 的 download 库来下载资源

    如题 用以下代码来使用 JS 的 download 库来下载资源 引入 download 库 import download from downloadjs 要下载的文件的 URL 和文件名 const fileUrl https exam
  • unable to boot the simulator,无法启动模拟器已解决

    我在百度上所搜的都是以前的老方法 新版的Mac已经没有Recovery了 因此我只好去bing上查找资料 方法很简单 用Mac安装镜像进入Recovery模式 然后输入 csrutil disable 就行了 剩下的和原来的方法一致
  • 浮点数和整数之间的转换

    当一个整数int i 12345被强制转换为一个FLOAT型变量 float f float i 假设sizeof int 32 float为单精度 sizeof float 32 那么i 12345 十进制 000000000000000
  • UE4_聚合图

    UE4 聚合图
  • 【Python 1-0】10个学习Python的理由以及Python的优势有哪些?

    Python的由来 首发地址 Python的创始人是吉多 范罗苏姆 1989年他在阿姆斯特丹的CWI工作 圣诞节期间 吉多 范罗苏姆为了打发圣诞节的无聊 决定开发一个新的脚本解释程序 作为ABC 语言的一种继承 之所以选择Python作为编
  • Maven覆盖私服上的jar包,本地仓库无法更新的问题

    在上传第三方jar包到私服环境时 第一次上传成功后 突然发现上传的jar包有问题 但是因为已经指定了版本号 并且是release版本的jar包 因为不想更换版本号 所以再重复上传正确的jar包 就会出现如下错误 一种解决办法是指定另外一个版
  • Gitflow工作流程

    在工作场合实施Git的时候 有很多种工作流程可供选择 此时反而会让你手足无措 本文罗列了企业团队最常用的一些Git工作流程 包括Centralized Workflow Feature Branch Workflow Gitflow Wor
  • Container命令ctr,crictl的用法

    Container命令ctr crictl的用法 版本 ctr containerd io 1 4 3 containerd 相比于docker 多了namespace概念 每个image和container 都会在各自的namespace
  • 通过Vue.js的axios请求WFS数据并处理请求回来的XML文件

    前端小白的第一个博客 前言 这个是在GIS开发过程中遇到的一个小问题 因为里面包含了蛮多的知识点 故将其记录 废话不多说进入正文 正文 此次需要解决的问题是通过wfs接口来获取到一些需要的内容 然后以这些内容为基础进行一系列的操作 以下展示
  • 1.3远程控制及文件传输

    我们经常用的是Windows操作系统 又经常需要与Ubuntu进行文件传输 同时为了能在Windows上操作我们的Ubuntu 这里推荐一个文件传输和一个远程控制的程序 文件传输WinSCP 官方下载地址 https sourceforge
  • VsCode官网快速下载

    VsCode官网 以Win10下载为例 问题描述 下载时 发现速度很慢 甚至会没有下载速度 如下图 解决方法 右键复制这个下载链接 将其前半部分修改为vscode cdn azure cn 例如 原下载链接 https az764295 v
  • Codeforces 600C Make Palindrome 【贪心 找字典序最小回文串】

    一 题目概述 C Make Palindrome time limit per test 2 seconds memory limit per test 256 megabytes input standard input
  • 如何在Anaconda安装opencv,下面分享一下教程

    1 首先下载opencv安装包 下载地址 https download csdn net download qq 42375391 12333992 2 安装完成后 在Anaconda Prompt内使用pip install完整路径文件名
  • 算法:模拟思想算法

    文章目录 实现原理 算法思路 典型例题 替换所有问号 提莫攻击 N字型变换 外观序列 总结 本篇总结的是模拟算法 实现原理 模拟算法的实现原理很简单 就是依据题意实现题意的目的即可 考察的是你能不能实现题目题意的代码能力 算法思路 没有很明
  • openwrt 自动签到插件-食用指南

    目录 openwrt 自动签到插件下载 openwrt 插件安装需要的依赖 openwrt 插件安装 文件上传 openwrt 自动签到配置 设置详情 Cookie获取失败 解决方法 Charles 抓包获取Cookie openwrt 配
  • 计算机设备问题代码43,双击unknown device由于该设备有问题Windows已将其停止(代码 43)怎么办解决教程...

    金士顿U盘做的启动盘 8G 在别人的电脑上储存文件正常 但在自己的电脑上无法识别 在设备管理器中显示黄色叹号 属性显示 该设备存在问 题 windows已将其停止 代码43 本机win7系统 别人电脑为XP系统 已将 禁用 注册表 dos设
  • Linux 阻塞IO(等待队列)原理及架构

    一 阻塞操作 阻塞操作是指在执行折本操作时 若不能获得自愿 则挂起进程直到满足可操作性的条件后在进行操作 被挂起的进程进入休眠状态 被从调度器的运行队列移走 直到等待的条件被满足 假设recvfrom函数是一个系统调用 阻塞不是低效率 如果