Linux设备驱动程式学习5高级字符驱动程式操作2阻塞型IO和休眠Word格式.docx
- 文档编号:20217151
- 上传时间:2023-01-20
- 格式:DOCX
- 页数:14
- 大小:58.10KB
Linux设备驱动程式学习5高级字符驱动程式操作2阻塞型IO和休眠Word格式.docx
《Linux设备驱动程式学习5高级字符驱动程式操作2阻塞型IO和休眠Word格式.docx》由会员分享,可在线阅读,更多相关《Linux设备驱动程式学习5高级字符驱动程式操作2阻塞型IO和休眠Word格式.docx(14页珍藏版)》请在冰豆网上搜索。
typedefstruct__wait_queue_headwait_queue_head_t;
他包含一个自旋锁和一个链表。
这个链表是个等待队列入口,他被声明做wait_queue_t。
wait_queue_head_t包含关于睡眠进程的信息和他想怎样被唤醒。
简单休眠(其实是高级休眠的宏)
Linux内核中最简单的休眠方式是称为wait_event的宏(及其变种),他实现了休眠和进程等待的条件的检查。
形式如下:
wait_event(queue,condition)/*不可中断休眠,不推荐*/
wait_event_interruptible(queue,condition)/*推荐,返回非零值意味着休眠被中断,且驱动应返回-ERESTARTSYS*/
wait_event_timeout(queue,condition,timeout)
wait_event_interruptible_timeout(queue,condition,timeout)
/*有限的时间的休眠;
若超时,则不管条件为何值返回0,*/
唤醒休眠进程的函数称为wake_up,形式如下:
voidwake_up(wait_queue_head_t*queue);
voidwake_up_interruptible(wait_queue_head_t*queue);
惯例:
用wake_up唤醒wait_event;
用wake_up_interruptible唤醒wait_event_interruptible。
简单休眠实验
模块程式链接:
sleepy
模块测试程式链接:
sleepy-test
实验现象:
[Tekkaman2440@SBC2440V4]#cd/lib/modules/
[Tekkaman2440@SBC2440V4]#insmodsleepy.ko
[Tekkaman2440@SBC2440V4]#cd/dev/
[Tekkaman2440@SBC2440V4]#cat/proc/devices
Characterdevices:
1mem
2pty
3ttyp
4/dev/vc/0
4tty
4ttyS
5/dev/tty
5/dev/console
5/dev/ptmx
7vcs
10misc
13input
14sound
81video4linux
89i2c
90mtd
116alsa
128ptm
136pts
180usb
189usb_device
204s3c2410_serial
252sleepy
253usb_endpoint
254rtc
Blockdevices:
1ramdisk
256rfd
7loop
31mtdblock
93nftl
96inftl
179mmc
[Tekkaman2440@SBC2440V4]#mknod-m666sleepyc2520
[Tekkaman2440@SBC2440V4]#cd/tmp/
[Tekkaman2440@SBC2440V4]#./sleepy_testr&
[Tekkaman2440@SBC2440V4]#ps
PIDUidVSZStatCommand
1root1744Sinit
2rootSW[kthreadd]
3rootSWN[ksoftirqd/0]
4rootSW[watchdog/0]
5rootSW[events/0]
6rootSW[khelper]
59rootSW[kblockd/0]
60rootSW[ksuspend_usbd]
63rootSW[khubd]
65rootSW[kseriod]
77rootSW[pdflush]
78rootSW[pdflush]
79rootSW[kswapd0]
80rootSW[aio/0]
707rootSW[mtdblockd]
708rootSW[nftld]
709rootSW[inftld]
710rootSW[rfdd]
742rootSW[kpsmoused]
751rootSW[kmmcd]
769rootSW[rpciod/0]
778root1752S-sh
779root1744Sinit
781root1744Sinit
783root1744Sinit
787root1744Sinit
799root1336S./sleepy_testr
800root1336S./sleepy_testr
802root1744Rps
[Tekkaman2440@SBC2440V4]#./sleepy_testw
readcode=0
writecode=0
[2]+Done./sleepy_testr
799root1336S./sleepy_testr
804root1744Rps
[Tekkaman2440@SBC2440V4]#readcode=0
[1]+Done./sleepy_testr
806root1744Rps
阻塞和非阻塞操作
全功能的read和write方法涉及到进程能决定是进行非阻塞I/O还是阻塞I/O操作。
明确的非阻塞I/O由filp->
f_flags中的O_NONBLOCK标志来指示(定义再,被自动包含)。
浏览源码,会发现O_NONBLOCK的另一个名字:
O_NDELAY,这是为了兼容SystemV代码。
O_NONBLOCK标志缺省地被清除,因为等待数据的进程的正常行为只是睡眠.
其实不一定只有read和write方法有阻塞操作,open也能有阻塞操作。
后面会见到。
而我的项目有一个和CPLD的接口的驱动,我决定要在ioctl中使用阻塞。
以下是后面的scullpipe实验的有关阻塞的代码,我觉得写得非常好,先结合书上的介绍看看吧:
while(dev->
rp==dev->
wp)
{/*nothingtoread*/
up(&
dev->
sem);
/*releasethelock*/
if(filp->
f_flags&
O_NONBLOCK)
return-EAGAIN;
PDEBUG("
\"
%s\"
reading:
goingtosleep\n"
current->
comm);
if(wait_event_interruptible(dev->
inq,(dev->
rp!
=dev->
wp)))
return-ERESTARTSYS;
/*signal:
tellthefslayertohandleit*//*otherwiseloop,butfirstreacquirethelock*/
if(down_interruptible(&
sem))
}
/*ok,dataisthere,returnsomething*/
......
高级休眠
步骤:
(1)分配和初始化一个wait_queue_t结构,随后将其添加到正确的等待队列。
(2)设置进程状态,标记为休眠。
在
中定义有几个任务状态:
TASK_RUNNING意思是进程能够运行。
有2个状态指示一个进程是在睡眠:
TASK_INTERRUPTIBLE和TASK_UNTINTERRUPTIBLE。
2.6内核的驱动代码通常不必直接操作进程状态。
但如果需要这样做使用的代码是:
voidset_current_state(intnew_state);
在老的代码中,你常常见到如此的东西:
current->
state=TASK_INTERRUPTIBLE;
不过象这样直接改动current是不推荐的,当数据结构改动时这样的代码将会失效。
通过改动current状态,只改动了调度器对待进程的方式,但进程还未让出处理器。
(3)最后一步是放弃处理器。
但必须先检查进入休眠的条件。
如果不做检查会引入竞态:
如果在忙于上面的这个过程时有其他的线程刚刚试图唤醒你,你可能错过唤醒且长时间休眠。
因此典型的代码下:
if(!
condition)
schedule();
如果代码只是从schedule返回,则进程处于TASK_RUNNING状态。
如果不需睡眠而跳过对schedule的调用,必须将任务状态重置为TASK_RUNNING,还必要从等待队列中去除这个进程,否则他可能被多次唤醒。
手工休眠
/*
(1)创建和初始化一个等待队列。
常由宏定义完成:
*/
DEFINE_WAIT(my_wait);
/*name是等待队列入口项的名字.也能用2步来做:
wait_queue_tmy_wait;
init_wait(&
my_wait);
/*常用的做法是放一个DEFINE_WAIT在循环的顶部,来实现休眠。
/*
(2)添加等待队列入口到队列,并设置进程状态:
voidprepare_to_wait(wait_queue_head_t*queue,wait_queue_t*wait,intstate);
/*queue和wait分别地是等待队列头和进程入口。
state是进程的新状态:
TASK_INTERRUPTIBLE(可中断休眠,推荐)或TASK_UNINTERRUPTIBLE(不可中断休眠,不推荐)。
/*(3)在检查确认仍然需要休眠之后调用schedule*/
schedule();
/*(4)schedule返回,就到了清理时间:
voidfinish_wait(wait_queue_head_t*queue,wait_queue_t*wait);
认真地看简单休眠中的wait_event(queue,condition)和wait_event_interruptible(queue,condition)底层源码会发现,其实他们只是手工休眠中的函数的组合。
所以怕麻烦的话还是用wait_event比较好。
独占等待
当一个进程调用wake_up在等待队列上,所有的在这个队列上等待的进程被置为可运行的。
这在许多情况下是正确的做法。
但有时,可能只有一个被唤醒的进程将成功获得需要的资源,而其余的将再次休眠。
这时如果等待队列中的进程数目大,这可能严重降低系统性能。
为此,内核研发者增加了一个“独占等待”选项。
他和一个正常的睡眠有2个重要的不同:
(1)当等待队列入口设置了WQ_FLAG_EXCLUSEVE标志,他被添加到等待队列的尾部;
否则,添加到头部。
(2)当wake_up被在一个等待队列上调用,他在唤醒第一个有WQ_FLAG_EXCLUSIVE标志的进程后停止唤醒.但内核仍然每次唤醒所有的非独占等待。
采用独占等待要满足2个条件:
(1)希望对资源进行有效竞争;
(2)当资源可用时,唤醒一个进程就足够来完全消耗资源。
使一个进程进入独占等待,可调用:
voidprepare_to_wait_exclusive(wait_queue_head_t*queue,wait_queue_t*wait,intstate);
注意:
无法使用wait_event和他的变体来进行独占等待.
唤醒的相关函数
非常少会需要调用wake_up_interruptible之外的唤醒函数,但为完整起见,这里是整个集合:
wake_up(wait_queue_head_t*queue);
wake_up_interruptible(wait_queue_head_t*queue);
/*wake_up唤醒队列中的每个非独占等待进程和一个独占等待进程。
wake_up_interruptible同样,除了他跳过处于不可中断休眠的进程。
他们在返回之前,使一个或多个进程被唤醒、被调度(如果他们被从一个原子上下文调用,这就不会发生).*/
wake_up_nr(wait_queue_head_t*queue,intnr);
wake_up_interruptible_nr(wait_queue_head_t*queue,intnr);
/*这些函数类似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的"
同步"
变体.这个函数最常用在调用者首先要完成剩下的少量工作,且不希望被调度出处理器时。
*/
poll和select
当应用程式需要进行对多文件读写时,若某个文件没有准备好,则系统会处于读写阻塞的状态,并影响了其他文件的读写。
为了避免这种情况,在必须使用多输入输出流又不想阻塞在他们所有一个上的应用程式常将非阻塞I/O和poll(SystemV)、select(BSDUnix)、epoll(linux2.5.45开始)系统调用配合使用。
当poll函数返回时,会给出一个文件是否可读写的标志,应用程式根据不同的标志读写相应的文件,实现非阻塞的读写。
这些系统调用功能相同:
允许进程来决定他是否可读或写一个或多个文件而不阻塞。
这些调用也可阻塞进程直到所有一个给定集合的文件描述符可用来读或写。
这些调用都需要来自设备驱动中poll方法的支持,poll返回不同的标志,告诉主进程文件是否能读写,其原型(定义在):
unsignedint(*poll)(structfile*filp,poll_table*wait);
实现这个设备方法分两步:
1.在一个或多个可指示查询状态变化的等待队列上调用poll_wait.如果没有文件描述符可用来执行I/O,内核使这个进程在等待队列上等待所有的传递给系统调用的文件描述符.驱动通过调用函数poll_wait增加一个等待队列到poll_table结构,原型:
voidpoll_wait(structfile*,wait_queue_head_t*,poll_table*);
2.返回一个位掩码:
描述可能不必阻塞就即时进行的操作,几个标志(通过
定义)用来指示可能的操作:
标志
含义
POLLIN
如果设备无阻塞的读,就返回该值
POLLRDNORM
通常的数据已准备好,能读了,就返回该值。
通常的做法是会返回(POLLLIN|POLLRDNORA)
POLLRDBAND
如果能从设备读出带外数据,就返回该值,他只可在linux内核的某些网络代码中使用,通常不用在设备驱动程式中
POLLPRI
如果能无阻塞的读取高优先级(带外)数据,就返回该值,返回该值会导致select报告文件发生异常,以为select八带外数据当作异常处理
POLLHUP
当读设备的进程到达文件尾时,驱动程式必须返回该值,依照select的功能描述,调用select的进程被告知进程时可读的。
POLLERR
如果设备发生错误,就返回该值。
POLLOUT
如果设备能无阻塞地些,就返回该值
POLLWRNORM
设备已准备好,能写了,就返回该值。
通常地做法是(POLLOUT|POLLNORM)
POLLWRBAND
于POLLRDBAND类似
考虑poll方法的scullpipe实现:
staticunsignedintscull_p_poll(structfile*filp,poll_table*wait)
{
structscull_pipe*dev=filp->
private_data;
unsignedintmask=0;
/*
*Thebufferiscircular;
itisconsideredfull
*if"
wp"
isrightbehind"
rp"
andemptyifthe
*twoareequal.
down(&
poll_wait(filp,&
inq,wait);
outq,wait);
if(dev->
wp)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 设备 驱动 程式 学习 高级 字符 操作 阻塞 IO 休眠