在网上看到的,讲的不错,遂转载过来。原文地址:http://blog.chinaunix.net/uid-25014876-id-61749.html
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
接下来会讲系统调用select在驱动中的实现,如果对系统调用select不太懂的话,建议先看这篇文章http://www.techbulo.com/1654.html。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、系统调用select的简介
简单来说,select这个系统调用的作用就是在应用层调用驱动函数中的poll来检测指定的文件的状态(读、写和异常)。如果某个状态满足,select函数调用成功后返回,应用程序就可以通过指定的函数来判断现在的文件状态。注意的是:select可以指定判断的时间,指定时间内,应用程序会阻塞在select函数,直到状态满足或者超时。
二、驱动函数poll的实现
先上代码:
#include <linux/poll.h> #include <asm/uaccess.h> #include <linux/errno.h> 。。。。。省略。。。。。。 struct _test_t{ char kbuf[DEV_SIZE]; unsigned int major; unsigned int minor; unsigned int cur_size; dev_t devno; struct cdev test_cdev; wait_queue_head_t test_queue; wait_queue_head_t read_queue; //定义等待队列 }; 。。。。。。省略。。。。。。。 ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset) { int ret; struct _test_t *dev = filp->private_data; if(copy_from_user(dev->kbuf, buf, count)){ ret = - EFAULT; }else{ ret = count; dev->cur_size += count; P_DEBUG("write %d bytes, cur_size:[%d]\n", count, dev->cur_size); P_DEBUG("kbuf is [%s]\n", dev->kbuf); wake_up_interruptible(&dev->test_queue); wake_up_interruptible(&dev->read_queue); //唤醒等待队列 } return ret; //返回实际写入的字节数或错误号 } /*poll的实现*/ unsigned int test_poll (struct file *filp, struct poll_table_struct *table) { struct _test_t *dev = filp->private_data; unsigned int mask = 0; poll_wait(filp, &dev->read_queue, table); if(dev->cur_size > 0) //设备可读 mask |= POLLIN; P_DEBUG("***maks[%d]***\n", mask); return mask; } struct file_operations test_fops = { .open = test_open, .release = test_close, .write = test_write, .read = test_read, .poll = test_poll, //切记要添加,不然多牛X的代码都不能执行 }; struct _test_t my_dev; static int __init test_init(void) //模块初始化函数 { int result = 0; my_dev.cur_size = 0; my_dev.major = 0; my_dev.minor = 0; if(my_dev.major){ my_dev.devno = MKDEV(my_dev.major, my_dev.minor); result = register_chrdev_region(my_dev.devno, 1, "test new driver") ; }else{ result = alloc_chrdev_region(&my_dev.devno, my_dev.minor, 1, "test alloc diver"); my_dev.major = MAJOR(my_dev.devno); my_dev.minor = MINOR(my_dev.devno); } if(result < 0){ P_DEBUG("register devno errno!\n"); goto err0; } printk("major[%d] minor[%d]\n", my_dev.major, my_dev.minor); cdev_init(&my_dev.test_cdev, &test_fops); my_dev.test_cdev.owner = THIS_MODULE; /*初始化等待队列头,注意函数调用的位置*/ init_waitqueue_head(&my_dev.test_queue); init_waitqueue_head(&my_dev.read_queue); result = cdev_add(&my_dev.test_cdev, my_dev.devno, 1); if(result < 0){ P_DEBUG("cdev_add errno!\n"); goto err1; } printk("hello kernel\n"); return 0; err1: unregister_chrdev_region(my_dev.devno, 1); err0: return result; } 。。。。。省略。。。。。
poll函数的实现同样需要使用等待队列,在这里没有把上节阻塞型IO代码注释掉,主要是想说明一个问题,它们两个的功能是不一样的,并不会冲突。后面会具体讲述。
1)定义等待队列头:
poll_wait函数里面的操作需要用到等待队列,所以需要定义并初始化等待队列头。
2)test_poll的实现:
test_poll的实现有两个步骤:
2.1)调同poll_wait,将进程添加到指定的等待队列(注意,仅仅是添加,没有休眠)。
poll_wait的原型是:
上面的函数其实也就三部:
1定义并初始化等待队列头;
2实现test_poll;
3唤醒等待队列。
接下来先对照程序说一下poll函数的实现:
unsigned int test_poll (struct file *filp, struct poll_table_struct *table)
注意:这里的两个参数都不是用户传给它的,全部都是有内核传的。可以这样说,poll没有做实际的什么操作,只是返回些信息给内核来操作。
来个代码来分析poll_wait究竟干了什么:
/*include/linux/poll.h */ typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *); typedef struct poll_table_struct { //poll_table_struct的原型 poll_queue_proc qproc; } poll_table; static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) { if (p && wait_address) p->qproc(filp, wait_address, p); //这里就断了线索 } static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc) { pt->qproc = qproc; } struct poll_table_entry { struct file *filp; wait_queue_t wait; wait_queue_head_t *wait_address; }; !!!J÷浴!!!!? struct poll_wqueues { poll_table pt; struct poll_table_page *table; struct task_struct *polling_task; int triggered; int error; int inline_index; struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES]; }; l_wait执行了一个函数,但没找出函数是做什么的。在另外的文件我找到一点线索: s/select.c*/ struct poll_table_page { struct poll_table_page * next; struct poll_table_entry * entry; struct poll_table_entry entries[0]; }; !!!!!!? /* Add a new entry */ static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p) { struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt); struct poll_table_entry *entry = poll_get_entry(pwq); if (!entry) return; get_file(filp); entry->filp = filp; entry->wait_address = wait_address; init_waitqueue_func_entry(&entry->wait, pollwake); entry->wait.private = pwq; add_wait_queue(wait_address, &entry->wait); }
因为函数的传参和名字都差不多,我猜想内核是调用该函数的。
从上面的代码和《设备驱动程序》我得出来一下的结论:
1.应用层调用函数select,内核为了管理等待队列(有时候不止一个等待队列,因为select函数可以检测多个文件的状态),建立了一个poll_table_struct结构体(一个select系统调用对应一个结构体)。
2.poll_wait函数的调用,将三个参数传给了内核。内核中,通过结构体poll_table_struct找到另一个结构体poll_table_page,上面的代码可以看出来,这个结构体是一个维护多个poll_table_entry结构体的内存页链表,poll_wait函数的参数就是传到poll_table_entry结构体中。
3.再看一下poll_table_entry里面的成员,第一个成员srutct file是poll_wait的第一个参数,第二个成员就是定义了一个wait_queue_t的结构体,而这个结构体是正要添加到等待队列头中,也就是从poll_wait传来的第二个参数。
4.现在重头戏了,poll_wait的调用实际上调用了__pollwiat。看一下大概的操作:
4.1使用container_of函数,通过poll_table(即poll_table_struct)找到poll_wqueues,一看名字就猜到,它是存放等待队列的!poll_wqueues包含成员poll_table_page。
4.2通过传入的filp和等待队列头两个参数,新建一个poll_table_enter并添加到poll_table_page中。
2.2)对应设备的状态,返回相应的掩码。那就是说,如果设备可读,那就返回可读的掩码。
什么是掩码?有什么掩码?
掩码 | 含义 |
POLLIN | 设备可读。 |
POLLRDNORM | 数据可读。一般的,驱动可读,返回(POLLIN|POLLRDNORM),当然,只返回POLLIN也行,因为意思其实都可不多 |
POLLOUT | 设备可写 |
POLLWRNORM | 数据可写。一般的,驱动可写,返回(POLLOUT|POLLWRNORM),当然,只返回POLLOUT也行,因为意思其实都可不多 |
当然,还有其他的掩码,我这里就不意义介绍。
3)唤醒等待队列
其实一开始我也很奇怪为什么需要唤醒,毕竟poll_wait函数并不会导致休眠。为什么要唤醒?在哪里唤醒?
我上面的驱动函数,test_poll返回掩码,如果掩码为0,则表示设备不可读,这时,内核接到返回的掩码,知道设备不可读,此时select函数就会阻塞,进程休眠,等待有数据时被唤醒。所以,在写入数据后,需要唤醒等待队列头read_queue。此时设备可读了,就会再次调用test_poll函数,返回掩码POLLIN,select调用成功。
所以,这里得出两个结论:
1.test_poll并不会导致休眠,进程阻塞是系统调用select搞的鬼。
2.系统调用select的阻塞会导致test_poll被调用多次。
既然大概知道了函数怎么写的。那就验证一下程序吧。应用程序我就不贴了。在app目录下,直接来结果:
现象一:先写后读
[root: 1st]# insmod test.ko major[253] minor[0] hello kernel [root: 1st]# mknod /dev/test c 253 0 [root: app]# ./monitor& //1.先后台运行检测程序monitor <kernel>[test_poll]***maks[0]*** //2.在我还没写之前,test_poll被调度了两遍后阻塞 <kernel>[test_poll]***maks[0]*** [root: app]# ./app_write //3过了一段时间,我写入数据 <kernel>[test_write]write 8 bytes, cur_size:[8] <kernel>[test_write]kbuf is [techbulo] <kernel>[test_poll]***maks[1]*** //4.test_poll再次被调用,掩码改变了! <app>monitor:[device readable] [root: app]# <kernel>[test_poll]***maks[1]*** <app>monitor:[device readable] //5select隔四秒就调用一遍,没有被阻塞 <kernel>[test_poll]***maks[1]*** <app>monitor:[device readable] <kernel>[test_poll]***maks[1]*** <app>monitor:[device readable] <kernel>[test_poll]***maks[1]*** <app>monitor:[device readable] [root: app]# ./app_read //6我读数据 <kernel>[test_read]read data..... <kernel>[test_read]read 8 bytes, cur_size:[0] <app_read>[techbulo] [root: app]# <kernel>[test_poll]***maks[0]*** //7读完他又阻塞了。
现象二:先写后读
[root: app]# ./monitor& //1.先后台运行检测程序monitor <kernel>[test_poll]***maks[0]*** //2.在我还没写之前,test_poll被调度了两遍后阻塞 <kernel>[test_poll]***maks[0]*** [root: app]# ./app_read& //3.再后台运行read [root: app]# <kernel>[test_read]read data..... //4.它阻塞了,这里不关poll的原因,这是因为上节说的阻塞型IO [root: app]# ./app_write //5.再写数据 <kernel>[test_write]write 10 bytes, cur_size:[8] <kernel>[test_write]kbuf is [techbulo] <kernel>[test_poll]***maks[1]*** //select被唤醒,返回可读掩码 <kernel>[test_read]read 8 bytes, cur_size:[0] //test_read被唤醒,读取数据 <app>monitor:[device readable] <app_read>[techbulo] [2] + Done ./app_read [root: app]# <kernel>[test_poll]***maks[0]*** //没数据,select又阻塞了
注:上面的驱动程序使用了两个等待队列头,细心可以发现,其实只要一个等待队列头,就可以实现阻塞型IO和poll了。具体就不讲解了,程序在3rd_char_6/and,很简单的改动。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、poll同时检测可读和可写两个状态
也很简单,直接上程序
/*3rd_char_6/2st/test.c*/ struct _test_t{ char kbuf[DEV_SIZE]; unsigned int major; unsigned int minor; unsigned int cur_size; dev_t devno; struct cdev test_cdev; wait_queue_head_t test_queue; wait_queue_head_t read_queue; //定义两个等待队列 wait_queue_head_t write_queue; }; !!!!J÷浴!!!!!!? ssize_t test_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) { int ret; struct _test_t *dev = filp->private_data; if(filp->f_flags & O_NONBLOCK) return - EAGAIN; P_DEBUG("read data.....\n"); if(wait_event_interruptible(dev->test_queue, dev->cur_size > 0)) return - ERESTARTSYS; if (copy_to_user(buf, dev->kbuf, count)){ ret = - EFAULT; }else{ ret = count; dev->cur_size -= count; P_DEBUG("read %d bytes, cur_size:[%d]\n", count, dev->cur_size); wake_up_interruptible(&dev->write_queue); } return ret; //返回实际写入的字节数或错误号 } ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset) { int ret; struct _test_t *dev = filp->private_data; if(copy_from_user(dev->kbuf, buf, count)){ ret = - EFAULT; }else{ ret = count; dev->cur_size += count; P_DEBUG("write %d bytes, cur_size:[%d]\n", count, dev->cur_size); P_DEBUG("kbuf is [%s]\n", dev->kbuf); wake_up_interruptible(&dev->test_queue); wake_up_interruptible(&dev->read_queue); } return ret; //返回实际写入的字节数或错误号 } unsigned int test_poll (struct file *filp, struct poll_table_struct *table) { struct _test_t *dev = filp->private_data; unsigned int mask = 0; poll_wait(filp, &dev->read_queue, table); poll_wait(filp, &dev->write_queue, table); if(dev->cur_size > 0) //设备可读 mask |= POLLIN; if(dev->cur_size < DEV_SIZE) //设备可写 mask |= POLLOUT; P_DEBUG("***************************\n"); return mask; } struct file_operations test_fops = { .open = test_open, .release = test_close, .write = test_write, .read = test_read, .poll = test_poll, }; struct _test_t my_dev; static int __init test_init(void) //模块初始化函数 { int result = 0; my_dev.cur_size = 0; my_dev.major = 0; my_dev.minor = 0; if(my_dev.major){ my_dev.devno = MKDEV(my_dev.major, my_dev.minor); result = register_chrdev_region(my_dev.devno, 1, "test new driver") ; }else{ result = alloc_chrdev_region(&my_dev.devno, my_dev.minor, 1, "test alloc diver"); my_dev.major = MAJOR(my_dev.devno); my_dev.minor = MINOR(my_dev.devno); } if(result < 0){ P_DEBUG("register devno errno!\n"); goto err0; } printk("major[%d] minor[%d]\n", my_dev.major, my_dev.minor); cdev_init(&my_dev.test_cdev, &test_fops); my_dev.test_cdev.owner = THIS_MODULE; /*初始化等待队列头,注意函数调用的位置*/ init_waitqueue_head(&my_dev.test_queue); init_waitqueue_head(&my_dev.read_queue); init_waitqueue_head(&my_dev.write_queue); result = cdev_add(&my_dev.test_cdev, my_dev.devno, 1); if(result < 0){ P_DEBUG("cdev_add errno!\n"); goto err1; } printk("hello kernel\n"); return 0; err1: unregister_chrdev_region(my_dev.devno, 1); err0: return result; } 。。。。。。。省略。。。。。。。
验证一下:注意的是,这次的select并没有阻塞,原因很简单,要不就可读要不就可写,肯定有掩码返回,根本不用阻塞。
[root: app]# ./monitor& //1.执行建材程序 [root: app]# <kernel>[test_poll]***mask[4]***//2每个四秒调用一次select,没阻塞 <app>monitor:[device writeable] <kernel>[test_poll]***mask[4]*** <app>monitor:[device writeable] <kernel>[test_poll]***mask[4]*** <app>monitor:[device writeable] [root: app]# ./app_write //3写入数据 <kernel>[test_write]write 8 bytes, cur_size:[8] <kernel>[test_write]kbuf is [techbulo] [root: app]# <kernel>[test_poll]***mask[5]*** <app>monitor:[device readable] //4掩码改变,但没有阻塞 <app>monitor:[device writeable] <kernel>[test_poll]***mask[5]*** <app>monitor:[device readable] <app>monitor:[device writeable] <kernel>[test_poll]***mask[5]*** <app>monitor:[device readable] <app>monitor:[device writeable] [root: app]# ./app_read //5读取数据 <kernel>[test_read]read data..... <kernel>[test_read]read 8 bytes, cur_size:[0] <app_read>[techbulo] [root: app]# <kernel>[test_poll]***mask[4]*** <app>monitor:[device writeable] //6掩码改变,但没有阻塞 <kernel>[test_poll]***mask[4]*** <app>monitor:[device writeable]
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、select的阻塞操作
上面说了select会造成休眠,接下来简单的谈谈。select里面会调用函数do_select。
贴上程序,并附上大致的运行顺序:
/*fs/select.c*/ int do_select(int n, fd_set_bits *fds, struct timespec *end_time) { ktime_t expire, *to = NULL; struct poll_wqueues table; //1.这个就是前面说用来方等待队列的poll_wqueues !!J÷浴!!!? poll_initwait(&table); wait = &table.pt; if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { wait = NULL; timed_out = 1; } if (end_time && !timed_out) slack = estimate_accuracy(end_time); retval = 0; for (;;) { //2.注意这个大循环,如果条件不成立休眠后,唤醒正在这个大循环里 unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; inp = fds->in; outp = fds->out; exp = fds->ex; rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex; for (i = 0; i < n; ++rinp, ++routp, ++rexp) { !!!J÷浴!!!!? for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) { int fput_needed; if (i >= n) break; if (!(bit & all_bits)) continue; file = fget_light(i, &fput_needed); if (file) { //3.循环里面里边所有所有被检测的filp f_op = file->f_op; mask = DEFAULT_POLLMASK; if (f_op && f_op->poll) //4.调用我们实现的poll函数,这也是poll被多次调用的原因,以为他在循环里面。 mask = (*f_op->poll)(file, retval ? NULL : wait); fput_light(file, fput_needed); if ((mask & POLLIN_SET) && (in & bit)) { //5.判断poll返回的掩码,只要掩码不是0,下面的起码有一个条件会实现。retval++。 res_in |= bit; retval++; } if ((mask & POLLOUT_SET) && (out & bit)) { res_out |= bit; retval++; } if ((mask & POLLEX_SET) && (ex & bit)) { res_ex |= bit; retval++; } } } if (res_in) *rinp = res_in; if (res_out) *routp = res_out; if (res_ex) *rexp = res_ex; cond_resched(); } wait = NULL; if (retval || timed_out || signal_pending(current))//6.如果条件成立或者 延时或者被中断 break; //7.调用break跳出大循环,do_select调用完毕 if (table.error) { retval = table.error; break; } /* * If this is the first loop and we have a timeout * given, then we convert to ktime_t and set the to * pointer to the expiry value. */ if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } /*8如果上面的条件没有成立,走到这里,进程状态改变有TASK_INTERRUPTIBLE,并加入指定的等待队列头,让出CPU,进程休眠,等待唤醒*/ if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; } poll_freewait(&table); return retval; }
上面只是想说明:poll只是做了一个判断工作,真正的阻塞在select中。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、总结:
今天内容有讲完了,讲了以下内容:
1、poll的实现:
1.1调用poll_wait
1.2返回掩码。
2.poll_wait的实现。
3.select中的阻塞。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx