`

【Linux 驱动】第六章 高级字符驱动程序操作 ----阻塞型I/O

 
阅读更多

序言:试想如果在驱动方法中的read/write中,当数据不可用时,用户可能调用read,当输出缓冲区满时,设备并未准备好接受数据,这种情况下驱动程序可以阻塞该进程,并且置入休眠状态直到满足条件。


一,休眠


当一个进程休眠时,它会被标记为一种特殊状态并从调度器的运行队列中移走,直到某些情况修改了这个状态,进程才会在任意cpu上调度,即运行该进程。
在linux下,为了让进程安全的进入休眠状态,有两条规则需要牢记:

1)永远不要在原子上下文中休眠。

说明:原子上下文:在执行多个步骤时,不能有任何并发访问。不能再拥有自旋锁,seqlock,rcu锁时休眠,进程1在拥有信号量时休眠是合法的,但是要确保进程1拥有信号量不会阻塞唤醒我们的那个进程2。


2)对唤醒之后的状态不能做任何假定,必须检查以确保我们等待的条件为真。

初始化一个等待队列有两种方法:


1)静态
DECLARE_WAIT_QUEUE_HEAD(my_queue);
2)动态
wait_queue_head_t my_queue;

init_waitqueue_head(&my_queue);


一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在<linux/wait.h>




二, 简单休眠

等待队列:一个进程链表,包含了等待某个特定事件的所有进程
在linux中,一个等待队列通过一个"wait queue head"来管理,类型为 wait_queue_head_t,在<linux/wait.h>中定义。


休眠宏: wait_event(queue,condition); //queue是等待队列头 condition是boolea类型
wait_event_interruptible(queue,condition); //可以中断休眠
wait_event_timeout(queue,condition,timeout);//等待限定的时间
wait_event_interruptible_timeout(queue,condition,timeout);


上述宏的调用如果condition条件不满足将引起调用进程的阻塞,并且宏会在休眠前后对condition求值,从名字可以看出带interruptible的宏用户可以中断休眠,返回非0值表示被中断,此时驱动程序要返回 -ERESTARTSYS.而带timeout的宏表示在给定的时间到期时,宏都会返回0.


唤醒函数:


void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

wake_up会唤醒在queue上的所有进程,而wake_up_interruptible只唤醒那些可中断的进程。


三,高级休眠


进程如何休眠?

step1:分配初始化一个wait_queue_t 结构,然后加入到对应的等待队列。

step2:通过函数set_current_state(int new_state)设置进程状态。

驱动程序关心的进程状态主要是TASK_RUNNING(可运行),TASK_INTERRUPTIBLE(可中断休眠),TASK_UNINTERRPUTIBLE(不可中断休眠)。


step3:调用schedule(),记住在调用之前,必须先检查进入休眠的条件。如果不做检查会引入竞态: 如果在忙于上面的这个过程时有其他的线程刚刚试图唤醒你,你可能错过

唤醒且长时间休眠,经典的检查代码如下:


if(!condition)

schedule();

四,手工休眠 <linux/sched.h>中包含必须的定义

(1)创建和初始化一个等待队列。常由宏定义完成:
DEFINE_WAIT(my_wait); //name 是等待队列入口项的名字.

方法二:

wait_queue_t my_wait;
init_wait(&my_wait);

常用的做法是放一个 DEFINE_WAIT 在循环的顶部,来实现休眠。


(2)添加等待队列入口到队列,并设置进程状态:
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
queue 等待队列头

wait 进程入口

state 进程的新状态:TASK_INTERRUPTIBLE(可中断休眠,推荐)或TASK_UNINTERRUPTIBLE(不可中断休眠,不推荐)。


(3)在检查确认仍然需要休眠之后调用 schedule
schedule();

(4)schedule 返回,就到了清理时间:

void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);

认真地看简单休眠中的 wait_event(queue, condition) 和 wait_event_interruptible(queue, condition) 底层源码会发现,其实他们只是手工休眠中的函数的组合。所以怕麻烦的话还是用wait_event比较好。





五, 独占等待

当一个进程调用 wake_up 在等待队列上,所有的在这个队列上等待的进程被置为可运行的。 这在许多情况下是正确的做法。但有时,可能只有一个被唤醒的进程将成功获得需要的资源,而其余的将再次休眠。这时如果等待队列中的进程数目大,这可能严重降低系统性能。为此,内核开发者增加了一个“独占等待”选项

独占等待进程与普通休眠进程的不同:

1)等待队列入口设置了WQ_FLAG_EXCLUSIVE标志,并且添加到等待队列的尾部,没有这个标志的进程被添加到等待队列的头部。

2)在某个等待队列上调用wake_up时,它会唤醒第一个具有WQ_FLAG_EXCLUSIVE标志的进程之后再唤醒其他进程。

什么时候采用独占等待进程呢?满足以下两个条件可以考虑:

1)对某个资源存在严重竞争。

2)唤醒单个进程就能完整消耗该资源

六,唤醒的相关函数
很少会需要调用wake_up_interruptible 之外的唤醒函数,但为完整起见,这里是整个集合:
wake_up(wait_queue_head_t *queue); //唤醒队列中的每个非独占等待进程和一个独占等待进程
wake_up_interruptible(wait_queue_head_t *queue); //过处于不可中断休眠的进程。它们在返回之前, 使一个或多个进程被唤醒、被调度(如果它们被从一个原子上下文调用, 这就不会发生)

wake_up_nr(wait_queue_head_t *queue, int nr);
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);

这些函数类似 wake_up, 除了它们能够唤醒多达 nr 个独占等待者, 而不只是一个. 注意传递 0 被解释为请求所有的互斥等待者都被唤醒

wake_up_all(wait_queue_head_t *queue);
wake_up_interruptible_all(wait_queue_head_t *queue);

这种 wake_up 唤醒所有的进程, 不管它们是否进行独占等待(可中断的类型仍然跳过在做不可中断等待的进程)

wake_up_interruptible_sync(wait_queue_head_t *queue);
一个被唤醒的进程可能抢占当前进程, 并且在 wake_up 返回之前被调度到处理器。 但是, 如果你需要不要被调度出处理器时,可以使用 wake_up_interruptible 的"同步"变体. 这个函数最常用在调用者首先要完成剩下的少量工作,且不希望被调度出处理器时。



七,阻塞和非阻塞操作


设置非阻塞I/O


显式的非阻塞I/O由filp->f_flags中的O_NONBLOCK标志决定,为了保持和System V代码兼容,标志O_NDELAY和O_NONBLOCK是一个意思,非阻塞io的read和write会返回-EAGAIN.


应用程序有两种方式制定非阻塞IO:


1)在open的时候指定,用于在open调用可能会阻塞很长的时间。


2)调用fcntl函数。具体使用方法可在网上查哦这里就不列出了。


阻塞操作的标准语义

如果一个进程调用了read但是没有数据可读,进程阻塞,数据到达时进程被唤醒,并把数据返回给调用者,即使数据少于count;如果一个进程调用了write但是缓冲区满,该进程必须阻塞,休眠在与read进程不同的等待队列上,当缓冲区有空闲时,进程被唤醒,写入数据成功,即使写入了小于count的字节数。

一个阻塞IO的例子scullp




分享到:
评论

相关推荐

    Linux设备驱动程序.pdf

    第6章涉及高级字符驱动操作。本章详细讲解了ioctl接口,它用于执行设备特定的操作;阻塞I/O;poll和select机制,它们用于非阻塞I/O;异步通知机制;以及如何在设备文件上设置存取控制。 第7章讲述了时间、延时和延...

    Linux设备驱动详解第二版

    第6章 字符设备驱动 118 第7章 Linux设备驱动中的并发控制 139 第8章 Linux设备驱动中的阻塞与非阻塞I/O 161 第9章 Linux设备驱动中的异步通知与异步I/O 176 第10章 中断与时钟 193 第11章 内存与I/...

    LINUX设备驱动第三版_588及代码.rar

    第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet ...

    国嵌培训课件Linux驱动程序设计

    第一天 1.Linux驱动简介 2.字符设备驱动程序设计 3.驱动调试技术 4. 并发与竞态 第二天 1.Ioctl型驱动 2.内核等待队列 3. 阻塞型驱动程序设计 4.Poll设备操作 第三天 1.Mmap设备操作 2. 硬件访问 3. 混杂...

    Linux驱动程序开发第三版-英文6

    在第六章“高级字符设备操作”中,作者深入探讨了如何构建更复杂、功能更丰富的字符设备驱动程序。 ### ioctl系统调用 在讨论高级字符设备驱动程序时,一个关键的概念是`ioctl`系统调用。这个调用允许用户空间程序...

    华清远见驱动教程

    -第6章、字符设备驱动 -第7章、Linux设备驱动中的并发控制 -第8章、Linux设备驱动中的阻塞与非阻塞IO -第9章、Linux设备驱动中的异步通知与异步IO -第10章、中断与时钟 -第11章、内存与I-O访问 -第12章、Linux字符...

    Linux设备驱动程序(第三版)

    第六章“高级字符驱动操作”进一步探讨了字符驱动程序的高级特性,如ioctl接口、阻塞I/O操作、poll和select机制、异步通知以及设备文件的存取控制等。这些高级特性能够帮助驱动程序更好地与用户空间程序进行交互。 ...

    linux设备驱动程序

    第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 ch07.第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 ...

    Linux设备驱动程序.英文第三版[Linux Device Driver]

    #### 六、高级字符驱动操作 - **ioctl命令**:`ioctl`是一个非常强大的系统调用,可用于执行设备特定的操作。 - **阻塞I/O**:阻塞I/O是一种等待方式,当请求的数据不可用时,系统会将当前进程挂起直到数据可用为止...

    linux设备驱动程序开发 第三版 源码

    《Linux设备驱动程序开发》第三版的源码是学习Linux内核驱动开发的重要参考资料,它提供了丰富的实践案例,帮助读者深入理解Linux系统如何与硬件交互。"lld3"是该书的一个简称,其中"3"代表第三版。下面将详细探讨这...

    Linux设备驱动程序第三版_英文PDF版带源码.rar

    《Linux设备驱动程序》第三版是一本深入探讨Linux内核设备驱动编程的权威书籍,英文原版,不含扫描图像,适合程序员和系统开发者阅读。这本书详细介绍了如何为各种硬件设备编写驱动程序,以使它们在Linux操作系统下...

    LINUX设备驱动程序第三版配套源码

    《LINUX设备驱动程序第三版配套源码》是学习Linux内核驱动开发的重要参考资料,它包含了大量的实际示例代码,帮助读者深入理解Linux系统下的设备驱动工作原理。这份源码库是针对书中理论知识的实践展示,对于想要...

    linux 驱动开发详解

    《Linux驱动开发详解》是宋宝华先生撰写的一本深入探讨Linux驱动程序开发的专业书籍,主要针对第四版进行了详尽的阐述。这本书以其丰富的实践经验和深入的理论解析,为读者揭示了Linux内核驱动开发的奥秘。在提供的...

Global site tag (gtag.js) - Google Analytics