Linux内核驱动开发与等待队列机制实例.docx

上传人:b****6 文档编号:3326925 上传时间:2022-11-21 格式:DOCX 页数:18 大小:28.14KB
下载 相关 举报
Linux内核驱动开发与等待队列机制实例.docx_第1页
第1页 / 共18页
Linux内核驱动开发与等待队列机制实例.docx_第2页
第2页 / 共18页
Linux内核驱动开发与等待队列机制实例.docx_第3页
第3页 / 共18页
Linux内核驱动开发与等待队列机制实例.docx_第4页
第4页 / 共18页
Linux内核驱动开发与等待队列机制实例.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

Linux内核驱动开发与等待队列机制实例.docx

《Linux内核驱动开发与等待队列机制实例.docx》由会员分享,可在线阅读,更多相关《Linux内核驱动开发与等待队列机制实例.docx(18页珍藏版)》请在冰豆网上搜索。

Linux内核驱动开发与等待队列机制实例.docx

Linux内核驱动开发与等待队列机制实例

相信很多写程序的人都写过socket的程序。

当我们open一个socket之后,接着去读取这个socket,如果此时没有任何资料可供读取,那read就会block住。

(这是没有加上O_NONBLOCK的情形),直到有资料可读取才会传回来。

在Linuxkernel里有一个数据结构可以帮助我们做到这样的功能。

这个数据结构就是这里要跟各位介绍的waitqueue。

在kernel里,wait_queue的应用很广,举凡devicedriversemaphore等方面都会使用到wait_queue来implement。

所以,它算是kernel里蛮基本的一个数据结构。

接下来,我要跟各位介绍一下wait_queue的用法,以及用一个例子来说明如何使用wait_queue。

最后,我会带各位去trace一下wait_queue的原始程序代码,看看wait_queue是如何做到的。

我想有件事要先提及的是Linux在userspace跟在kernelspace上的差异。

我们知道Linux是multi-tasking的环境,同时可以有很多人执行很多的程序。

这是从user的观点来看的。

如果就kernel的观点来看,是没有所谓的multi-tasking的。

在kernel里,只有single-thread。

也就是说,如果你的kernelcode正在执行,那系统里只有那部分在执行。

不会有另一部分的kernelcode也在运作。

当然,这是指singleprocessor的情况下,如果是SMP的话,那我就不清楚了。

我想很多人都在Windows3.1下写过程序,在那种环境下写程序,每一个程序都必须适当的将CPU让给别的程序使用。

如果有个程序里面有一个

while

(1);

的话,那保证系统就停在那里了。

这种的多任务叫做non-preemptive。

它多任务的特性是由各个程序相互合作而造成的。

在Linux的userspace下,则是所谓的preemptive,各个process喜欢执行什么就执行什么,就算你在你的程序里加上while

(1);这一行也不会影响系统的运作。

反正时间到了,系统自动就会将你的程序停住,让别的程序去执行。

这是在userspace的情况下,在kernel这方面,就跟Windows3.1程序是一样的。

在kernel里,你必须适当的将CPU的执行权释放出来。

如果你在kernel里加入while

(1);这一行。

那系统就会跟Windows3.1一样。

卡在那里。

当然啦,我是没试过这样去改kernel,有兴趣的人可以去试试看,如果有不同的结果,请记得告诉我。

假设我们在kernel里产生一个buffer,user可以经由read,write等systemcall来读取或写资料到这个buffer里。

如果有一个user写资料到buffer时,此时buffer已经满了。

那请问你要如何去处理这种情形呢?

第一种,传给user一个错误讯息,说buffer已经满了,不能再写入。

第二种,将user的要求block住,等有人将buffer内容读走,留出空位时,再让user写入资料。

但问题来了,你要怎么将user的要求block住。

难道你要用

while(is_full);

write_to_buffer;

这样的程序代码吗?

想想看,如果你这样做会发生什么事?

第一,kernel会一直在这个while里执行。

第二个,如果kernel一直在这个while里执行,表示它没有办法去maintain系统的运作。

那此时系统就相当于当掉了。

在这里is_full是一个变量,当然,你可以让is_full是一个function,在这个function里会去做别的事让kernel可以运作,那系统就不会当。

这是一个方式。

但是,如果我们使用wait_queue的话,那程序看起来会比较漂亮,而且也比较让人了解,如下所示:

 

structwait_queue*wq=NULL;/*globalvariable*/

while(is_full){

interruptible_sleep_on(&wq);

}

write_to_buffer();

interruptible_sleep_on(&wq)是用来将目前的process,也就是要求写资料到buffer的process放到wq这个wait_queue里。

在interruptible_sleep_on里,则是最后会呼叫schedule()来做schedule的动作,也就是去找另一个process来执行以维持系统的运作。

当执行完interruptible_sleep_on之后,要求write的process就会被block住。

那什么时候会恢复执行呢?

这个process之所以会被block住是因为buffer的空间满了,无法写入。

但是如果有人将buffer的资料读取掉,则buffer就有空间可以让人写入。

所以,有关于叫醒process的动作应该是在readbuffer这方面的程序代码做的。

externstructwait_queue*wq;

if(!

is_empty){

read_from_buffer();

wake_up_interruptible(&wq);

}

....

以上的程序代码应该要放在readbuffer这部分的程序代码里,当buffer有多余的空间时,我们就呼叫wake_up_interruptible(&wq)来将挂在wq上的所有process叫醒。

请记得,我是说将wq上的所有process叫醒,所以,如果如果有10个process挂在wq上的话,那这10个都会被叫醒。

之后,至于谁先执行。

则是要看schedule是怎么做的。

就是因为这10个都会被叫醒。

如果A先执行,而且万一很不凑巧的,A又把buffer写满了,那其它9个process要怎么办呢?

所以在writebuffer的部分,需要用一个while来检查buffer目前是否满了.如果是的话,那就继续挂在wq上面.

上面所谈的就是wait_queue的用法。

很简单不是吗?

接下来,我会再介绍一下wait_queue提供那些function让我们使用。

让我再重申一次。

wait_queue应设为globalvariable,比方叫wq,只要任何的process想将自己挂在上面,就可以直接叫呼叫sleep_on等function。

要将wq上的process叫醒。

只要呼叫wake_up等function就可以了.

就我所知,wait_queue提供4个function可以使用,两个是用来将process加到wait_queue的:

sleep_on(structwait_queue**wq);

interruptible_sleep_on(structwait_queue**wq);

另外两个则是将process从wait_queue上叫醒的。

wake_up(structwait_queue**wq);

wake_up_interruptible(structwait_queue**wq);

我现在来解释一下为什么会有两组。

有interruptible的那一组是这样子的。

当我们去read一个没有资料可供读取的socket时,process会block在那里。

如果我们此时按下Ctrl+C,那read()就会传回EINTR。

像这种的blockIO就是使用interruptible_sleep_on()做到的。

也就是说,如果你是用interruptible_sleep_on()来将process放到wait_queue时,如果有人送一个signal给这个process,那它就会自动从wait_queue中醒来。

但是如果你是用sleep_on()把process放到wq中的话,那不管你送任何的signal给它,它还是不会理你的。

除非你是使用wake_up()将它叫醒。

sleep有两组。

wake_up也有两组。

wake_up_interruptible()会将wq中使用interruptible_sleep_on()的process叫醒。

至于wake_up()则是会将wq中所有的process叫醒。

包括使用interruptible_sleep_on()的process。

在使用wait_queue之前有一点需要特别的小心,呼叫interruptible_sleep_on()以及sleep_on()的function必须要是reentrant。

简单的说,reentrant的意思是说此function不会改变任何的globalvariable,或者是不会dependon任何的globalvariable,或者是在呼叫interruptible_sleep_on()或sleep_on()之后不会dependon任何的globalvariable。

因为当此function呼叫sleep_on()时,目前的process会被暂停执行。

可能另一个process又会呼叫此function。

若之前的process将某些information存在globalvariable,等它恢复执行时要使用,结果第二行程进来了,又把这个globalvariable改掉了。

等第一个process恢复执行时,放在globalvariable中的information都变了。

产生的结果恐怕就不是我们所能想象了。

其实,从process执行指令到此function中所呼叫的function都应该是要reentrant的。

不然,很有可能还是会有上述的情形发生.

由于wait_queue是kernel所提供的,所以,这个例子必须要放到kernel里去执行。

我使用的这个例子是一个简单的driver。

它会maintain一个buffer,大小是8192bytes。

提供read跟write的功能。

当buffer中没有资料时,read()会马上传回,也就是不做blockIO。

而当writebuffer时,如果呼叫write()时,空间已满或写入的资料比buffer大时,就会被block住,直到有人将buffer里的资料读出来为止。

在writebuffer的程序代码中,我们使用wait_queue来做到blockIO的功能。

在这里,我会将此driver写成module,方便加载kernel。

第一步,这个driver是一个简单的characterdevicedriver。

所以,我们先在/dev下产生一个characterdevice。

majornumber我们找一个比较没人使用的,像是54,minornumber就用0。

接着下一个命令.

mknod/dev/bufc540

mknod是用来产生specialfile的command。

/dev/buf表示要产生叫buf的档案,位于/dev下。

c表示它是一个characterdevice。

54为其majornumber,0则是它的minornumber。

有关characterdevicedriver的写法。

有机会我再跟各位介绍,由于这次是讲wait_queue,所以,就不再多提driver方面的东西.

第二步,我们要写一个module,底下是这个module的程序代码:

buf.c

#defineMODULE

#include

#include

#include

#include

#include

#defineBUF_LEN8192

intflag;/*whenrp=wp,flag=0forempty,flag=1for

non-empty*/

char*wp,*rp;

charbuffer[BUF_LEN];

EXPORT_NO_SYMBOLS;/*don'texportanything*/

staticssize_tbuf_read(structfile*filp,char*buf,size_tcount,

loff_t*ppos)

{

returncount;

}

staticssize_tbuf_write(structfile*filp,constchar*buf,size_tcount,

loff_t*ppos)

{

returncount;

}

staticintbuf_open(structinode*inode,structfile*filp)

{

MOD_INC_USE_COUNT;

return0;

}

staticintbuf_release(structinode*inode,structfile*filp)

{

MOD_DEC_USE_COUNT;

return0;

}

staticstructfile_operationsbuf_fops={

NULL,/*lseek*/

buf_read,

buf_write,

NULL,/*readdir*/

NULL,/*poll*/

NULL,/*ioctl*/

NULL,/*mmap*/

buf_open,/*open*/

NULL,/*flush*/

buf_release,/*release*/

NULL,/*fsync*/

NULL,/*fasync*/

NULL,/*check_media_change*/

NULL,/*revalidate*/

NULL/*lock*/

};

staticintbuf_init()

{

intresult;

flag=0;

wp=rp=buf;

result=register_chrdev(54,"buf",&buf_fops);

if(result<0){

printk("<5>buf:

cannotgetmajor54\n");

returnresult;

}

return0;

}

staticvoidbuf_clean()

{

if(unregister_chrdev(54,"buf")){

printk("<5>buf:

unregister_chrdeverror\n");

}

}

intinit_module(void)

{

returnbuf_init();

}

voidcleanup_module(void)

{

buf_clean();

}

有关module的写法,请各位自行参考其它的文件,最重要的是要有init_module()和cleanup_module()这两个function。

我在这两个function里分别做initialize和finalize的动作。

现在分别解释一下。

在init_module()里,只有呼叫buf_init()而己。

其实,也可以将buf_init()的code写到init_module()里。

只是我觉得这样比较好而已。

flag=0;

wp=rp=buf;

result=register_chrdev(54,"buf",&buf_fops);

if(result<0){

printk("<5>buf:

cannotgetmajor54\n");

returnresult;

}

return0;

init_buf()做的事就是去注册一个characterdevicedriver。

在注册一个characterdevicedriver之前,必须要先准备一个型别为file_operations结构的变量,file_operations里包含了一些functionpointer。

driver的作者必须自己写这些function。

并将functionaddress放到这个结构里。

如此一来,当user去读取这个device时,kernel才有办法去呼叫对应这个driver的function。

其实,简要来讲。

characterdevicedriver就是这么一个file_operations结构的变量。

file_operations定义在这个档案里。

它的prototype在kernel2.2.1与以前的版本有些微的差异,这点是需要注意的地方。

register_chrdev()看名字就大概知道是要注册characterdevicedriver。

第一个参数是此device的majornumber。

第二个是它的名字。

名字你可以随便取。

第三个的参数就是一个file_operations变量的地址。

init_module()必须要传回0,module才会被加载。

在cleanup_module()的部分,我们也是只呼叫buf_clean()而已。

它做的事是unregister的动作。

if(unregister_chrdev(54,"buf")){

printk("<5>buf:

unregister_chrdeverror\n");

}

也就是将原本记录在devicedrivertable上的资料洗掉。

第一个参数是majornumber。

第二个则是此driver的名称,这个名字必须要跟register_chrdev()中所给的名字一样才行。

现在我们来看看此driver所提供的file_operations是那些。

staticstructfile_operationsbuf_fops={

NULL,/*lseek*/

buf_read,

buf_write,

NULL,/*readdir*/

NULL,/*poll*/

NULL,/*ioctl*/

NULL,/*mmap*/

buf_open,/*open*/

NULL,/*flush*/

buf_release,/*release*/

NULL,/*fsync*/

NULL,/*fasync*/

NULL,/*check_media_change*/

NULL,/*revalidate*/

NULL/*lock*/

};

在此,我们只打算implementbuf_read(),buf_write(),buf_open,和buf_release()等function而已。

当user对这个device呼叫open()的时候,buf_open()会在最后被kernel呼叫。

相同的,当呼叫close(),read(),和write()时,buf_release(),buf_read(),和buf_write()也都会分别被呼叫。

首先,我们先来看看buf_open()。

staticintbuf_open(structinode*inode,structfile*filp)

MOD_INC_USE_COUNT;

return0;

}

buf_open()做的事很简单。

就是将此module的usecount加一。

这是为了避免当此module正被使用时不会被从kernel移除掉。

相对应的,在buf_release()中,我们应该要将usecount减一。

就像开启档案一样。

有open(),就应该要有对应的close()才行。

如果module的usecount在不为0的话,那此module就无法从kernel中移除了。

staticintbuf_release(structinode*inode,structfile*filp)

{

MOD_DEC_USE_COUNT;

return0;

}

接下来,我们要看一下buf_read()和buf_write()。

staticssize_tbuf_read(structfile*filp,char*buf,size_tcount,

loff_t*ppos)

{

returncount;

}

staticssize_tbuf_write(structfile*filp,constchar*buf,

size_tcount,loff_t*ppos)

{

returncount;

}

 

在此,我们都只是回传user要求读取或写入的字符数目而已。

在此,我要说明一下这些参数的意义。

filp是一个file结构的pointer。

也就是指我们在/dev下所产生的buf档案的file结构。

当我们呼叫read()或write()时,必须要给一个buffer以及要读写的长度。

Buf指的就是这个buffer,而count指的就是长度。

至于ppos是表示目前这个档案的offset在那里。

这个值对普通档案是有用的。

也就是跟lseek()有关系。

由于在这里是一个drvice。

所以ppos在此并不会用到。

有一点要小心的是,上面参数buf是一个地址,而且还是一个userspace的地址,当kernel呼叫buf_read()时,程序在位于kernelspace。

所以你不能直接读写资料到buf里。

必须先切换FS这个register才行。

Makefile

P=buf

OBJ=buf.o

INCLUDE=-I/usr/src/linux/include/linux

CFLAGS=-D__KERNEL__-DMODVERSIONS-DEXPORT_SYMTAB-O$(INCLUDE)\

-i

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 哲学历史

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1