zoukankan      html  css  js  c++  java
  • 【Linux开发】linux设备驱动归纳总结(三):6.poll和sellct

    linux设备驱动归纳总结(三):6.pollsellct


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    接下来会讲系统调用select在驱动中的实现,如果对系统调用select不太懂的话,建议先看书补习一下。

    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    一、系统调用select的简介


    简单来说,select这个系统调用的作用就是在应用层调用驱动函数中的poll来检测指定的文件的状态(读、写和异常)。如果某个状态满足,select函数调用成功后返回,应用程序就可以通过指定的函数来判断现在的文件状态。注意的是:select可以指定判断的时间,指定时间内,应用程序会阻塞在select函数,直到状态满足或者超时。


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    二、驱动函数poll的实现


    先上代码:

    9 #include

    10

    11 #include

    12 #include

    。。。。。。省略。。。。。。

    23 struct _test_t{

    24 char kbuf[DEV_SIZE];

    25 unsigned int major;

    26 unsigned int minor;

    27 unsigned int cur_size;

    28 dev_t devno;

    29 struct cdev test_cdev;

    30 wait_queue_head_t test_queue;

    31 wait_queue_head_t read_queue; //定义等待队列

    32 };

    。。。。。。省略。。。。。。。

    70 ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)

    71 {

    72 int ret;

    73 struct _test_t *dev = filp->private_data;

    74

    75 if(copy_from_user(dev->kbuf, buf, count)){

    76 ret = - EFAULT;

    77 }else{

    78 ret = count;

    79 dev->cur_size += count;

    80 P_DEBUG("write %d bytes, cur_size:[%d] ", count, dev->cur_size);

    81 P_DEBUG("kbuf is [%s] ", dev->kbuf);

    82 wake_up_interruptible(&dev->test_queue);

    83 wake_up_interruptible(&dev->read_queue); //唤醒等待队列

    84 }

    85

    86 return ret; //返回实际写入的字节数或错误号

    87 }

    88 /*poll的实现*/

    89 unsigned int test_poll (struct file *filp, struct poll_table_struct *table)

    90 {

    91 struct _test_t *dev = filp->private_data;

    92 unsigned int mask = 0;

    93

    94 poll_wait(filp, &dev->read_queue, table);

    95

    96 if(dev->cur_size > 0) //设备可读

    97 mask |= POLLIN;

    98

    99 P_DEBUG("***maks[%d]*** ", mask);

    100 return mask;

    101 }

    102

    103 struct file_operations test_fops = {

    104 .open = test_open,

    105 .release = test_close,

    106 .write = test_write,

    107 .read = test_read,

    108 .poll = test_poll, //切记要添加,不然多牛X的代码都不能执行

    109 };

    110

    111 struct _test_t my_dev;

    112

    113 static int __init test_init(void) //模块初始化函数

    114 {

    115 int result = 0;

    116 my_dev.cur_size = 0;

    117 my_dev.major = 0;

    118 my_dev.minor = 0;

    119

    120 if(my_dev.major){

    121 my_dev.devno = MKDEV(my_dev.major, my_dev.minor);

    122 result = register_chrdev_region(my_dev.devno, 1, "test new driver") ;

    123 }else{

    124 result = alloc_chrdev_region(&my_dev.devno, my_dev.minor, 1, "test alloc diver");

    125 my_dev.major = MAJOR(my_dev.devno);

    126 my_dev.minor = MINOR(my_dev.devno);

    127 }

    128

    129 if(result < 0){

    130 P_DEBUG("register devno errno! ");

    131 goto err0;

    132 }

    133

    134 printk("major[%d] minor[%d] ", my_dev.major, my_dev.minor);

    135

    136 cdev_init(&my_dev.test_cdev, &test_fops);

    137 my_dev.test_cdev.owner = THIS_MODULE;

    138 /*初始化等待队列头,注意函数调用的位置*/

    139 init_waitqueue_head(&my_dev.test_queue);

    140 init_waitqueue_head(&my_dev.read_queue);

    141

    142 result = cdev_add(&my_dev.test_cdev, my_dev.devno, 1);

    143 if(result < 0){

    144 P_DEBUG("cdev_add errno! ");

    145 goto err1;

    146 }

    147

    148 printk("hello kernel ");

    149 return 0;

    150

    151 err1:

    152 unregister_chrdev_region(my_dev.devno, 1);

    153 err0:

    154 return result;

    155 }

    。。。。。省略。。。。。

    poll函数的实现同样需要使用等待队列,在这里没有把上节阻塞型IO代码注释掉,主要是想说明一个问题,它们两个的功能是不一样的,并不会冲突。后面会具体讲述。


    上面的函数其实也就三部:

    1定义并初始化等待队列头;

    2实现test_poll

    3唤醒等待队列。

    接下来先对照程序说一下poll函数的实现:


    1)定义等待队列头:

    poll_wait函数里面的操作需要用到等待队列,所以需要定义并初始化等待队列头。


    2test_poll的实现:

    test_poll的实现有两个步骤:

    2.1)调同poll_wait,将进程添加到指定的等待队列(注意,仅仅是添加,没有休眠)。

    poll_wait的原型是:

    unsigned int test_poll (struct file *filp, struct poll_table_struct *table)

    注意:这里的两个参数都不是用户传给它的,全部都是有内核传的。可以这样说,poll没有做实际的什么操作,只是返回些信息给内核来操作。

    来个代码来分析poll_wait究竟干了什么:

    /*include/linux/poll.h */

    31 typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

    32

    33 typedef struct poll_table_struct { //poll_table_struct的原型

    34 poll_queue_proc qproc;

    35 } poll_table;

    36

    37 static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

    38 {

    39 if (p && wait_address)

    40 p->qproc(filp, wait_address, p); //这里就断了线索

    41 }

    42

    43 static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)

    44 {

    45 pt->qproc = qproc;

    46 }

    47

    48 struct poll_table_entry {

    49 struct file *filp;

    50 wait_queue_t wait;

    51 wait_queue_head_t *wait_address;

    52 };

    。。。。。省略。。。。。

    57 struct poll_wqueues {

    58 poll_table pt;

    59 struct poll_table_page *table;

    60 struct task_struct *polling_task;

    61 int triggered;

    62 int error;

    63 int inline_index;

    64 struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];

    65 };

    poll_wait执行了一个函数,但没找出函数是做什么的。在另外的文件我找到一点线索:

    /*fs/select.c*/

    85 struct poll_table_page {

    86 struct poll_table_page * next;

    87 struct poll_table_entry * entry;

    88 struct poll_table_entry entries[0];

    89 };

    。。。。。。。。

    198 /* Add a new entry */

    199 static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,

    200 poll_table *p)

    201 {

    202 struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);

    203 struct poll_table_entry *entry = poll_get_entry(pwq);

    204 if (!entry)

    205 return;

    206 get_file(filp);

    207 entry->filp = filp;

    208 entry->wait_address = wait_address;

    209 init_waitqueue_func_entry(&entry->wait, pollwake);

    210 entry->wait.private = pwq;

    211 add_wait_queue(wait_address, &entry->wait);

    212 }

    因为函数的传参和名字都差不多,我猜想内核是调用该函数的。

    从上面的代码和《设备驱动程序》我得出来一下的结论:

    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 filepoll_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函数,返回掩码POLLINselect调用成功

    所以,这里得出两个结论

    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

    [test_poll]***maks[0]*** //2.在我还没写之前,test_poll被调度了两遍后阻塞

    [test_poll]***maks[0]***

    [root: app]# ./app_write //3过了一段时间,我写入数据

    [test_write]write 10 bytes, cur_size:[10]

    [test_write]kbuf is [xiao bai]

    [test_poll]***maks[1]*** //4.test_poll再次被调用,掩码改变了!

    monitor:[device readable]

    [root: app]# [test_poll]***maks[1]***

    monitor:[device readable] //5select隔四秒就调用一遍,没有被阻塞

    [test_poll]***maks[1]***

    monitor:[device readable]

    [test_poll]***maks[1]***

    monitor:[device readable]

    [test_poll]***maks[1]***

    monitor:[device readable]

    [root: app]# ./app_read //6我读数据

    [test_read]read data.....

    [test_read]read 10 bytes, cur_size:[0]

    [xiao bai]

    [root: app]# [test_poll]***maks[0]*** //7读完他又阻塞了。


    现象二:先写后读

    [root: app]# ./monitor& //1.先后台运行检测程序monitor

    [test_poll]***maks[0]*** //2.在我还没写之前,test_poll被调度了两遍后阻塞

    [test_poll]***maks[0]***

    [root: app]# ./app_read& //3.再后台运行read

    [root: app]# [test_read]read data..... //4.它阻塞了,这里不关poll的原因,这是因为上节说的阻塞型IO

    [root: app]# ./app_write //5.再写数据

    [test_write]write 10 bytes, cur_size:[10]

    [test_write]kbuf is [xiao bai]

    [test_poll]***maks[1]*** //select被唤醒,返回可读掩码

    [test_read]read 10 bytes, cur_size:[0] //test_read被唤醒,读取数据

    monitor:[device readable]

    [xiao bai]

    [2] + Done ./app_read

    [root: app]# [test_poll]***maks[0]*** //没数据,select又阻塞了


    注:上面的驱动程序使用了两个等待队列头,细心可以发现,其实只要一个等待队列头,就可以实现阻塞型IOpoll了。具体就不讲解了,程序在3rd_char_6/and,很简单的改动。


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    三、poll同时检测可读和可写两个状态:


    也很简单,直接上程序

    /*3rd_char_6/2st/test.c*/

    23 struct _test_t{

    24 char kbuf[DEV_SIZE];

    25 unsigned int major;

    26 unsigned int minor;

    27 unsigned int cur_size;

    28 dev_t devno;

    29 struct cdev test_cdev;

    30 wait_queue_head_t test_queue;

    31 wait_queue_head_t read_queue; //定义两个等待队列

    32 wait_queue_head_t write_queue;

    33 };

    。。。。。。省略。。。。。。。

    48 ssize_t test_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)

    49 {

    50 int ret;

    51 struct _test_t *dev = filp->private_data;

    52

    53 if(filp->f_flags & O_NONBLOCK)

    54 return - EAGAIN;

    55

    56 P_DEBUG("read data..... ");

    57 if(wait_event_interruptible(dev->test_queue, dev->cur_size > 0))

    58 return - ERESTARTSYS;

    59

    60 if (copy_to_user(buf, dev->kbuf, count)){

    61 ret = - EFAULT;

    62 }else{

    63 ret = count;

    64 dev->cur_size -= count;

    65 P_DEBUG("read %d bytes, cur_size:[%d] ", count, dev->cur_size);

    66 wake_up_interruptible(&dev->write_queue);

    67 }

    68

    69 return ret; //返回实际写入的字节数或错误号

    70 }

    71

    72 ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)

    73 {

    74 int ret;

    75 struct _test_t *dev = filp->private_data;

    76

    77 if(copy_from_user(dev->kbuf, buf, count)){

    78 ret = - EFAULT;

    79 }else{

    80 ret = count;

    81 dev->cur_size += count;

    82 P_DEBUG("write %d bytes, cur_size:[%d] ", count, dev->cur_size);

    83 P_DEBUG("kbuf is [%s] ", dev->kbuf);

    84 wake_up_interruptible(&dev->test_queue);

    85 wake_up_interruptible(&dev->read_queue);

    86 }

    87

    88 return ret; //返回实际写入的字节数或错误号

    89 }

    90

    91 unsigned int test_poll (struct file *filp, struct poll_table_struct *table)

    92 {

    93 struct _test_t *dev = filp->private_data;

    94 unsigned int mask = 0;

    95

    96 poll_wait(filp, &dev->read_queue, table);

    97 poll_wait(filp, &dev->write_queue, table);

    98

    99 if(dev->cur_size > 0) //设备可读

    100 mask |= POLLIN;

    101 if(dev->cur_size < DEV_SIZE) //设备可写

    102 mask |= POLLOUT;

    103

    104 P_DEBUG("*************************** ");

    105 return mask;

    106 }

    107

    108 struct file_operations test_fops = {

    109 .open = test_open,

    110 .release = test_close,

    111 .write = test_write,

    112 .read = test_read,

    113 .poll = test_poll,

    114 };

    115

    116 struct _test_t my_dev;

    117

    118 static int __init test_init(void) //模块初始化函数

    119 {

    120 int result = 0;

    121 my_dev.cur_size = 0;

    122 my_dev.major = 0;

    123 my_dev.minor = 0;

    124

    125 if(my_dev.major){

    126 my_dev.devno = MKDEV(my_dev.major, my_dev.minor);

    127 result = register_chrdev_region(my_dev.devno, 1, "test new driver") ;

    128 }else{

    129 result = alloc_chrdev_region(&my_dev.devno, my_dev.minor, 1, "test alloc diver");

    130 my_dev.major = MAJOR(my_dev.devno);

    131 my_dev.minor = MINOR(my_dev.devno);

    132 }

    133

    134 if(result < 0){

    135 P_DEBUG("register devno errno! ");

    136 goto err0;

    137 }

    138

    139 printk("major[%d] minor[%d] ", my_dev.major, my_dev.minor);

    140

    141 cdev_init(&my_dev.test_cdev, &test_fops);

    142 my_dev.test_cdev.owner = THIS_MODULE;

    143 /*初始化等待队列头,注意函数调用的位置*/

    144 init_waitqueue_head(&my_dev.test_queue);

    145 init_waitqueue_head(&my_dev.read_queue);

    146 init_waitqueue_head(&my_dev.write_queue);

    147

    148 result = cdev_add(&my_dev.test_cdev, my_dev.devno, 1);

    149 if(result < 0){

    150 P_DEBUG("cdev_add errno! ");

    151 goto err1;

    152 }

    153

    154 printk("hello kernel ");

    155 return 0;

    156

    157 err1:

    158 unregister_chrdev_region(my_dev.devno, 1);

    159 err0:

    160 return result;

    161 }

    。。。。。。。省略。。。。。。。


    验证一下:注意的是,这次的select并没有阻塞,原因很简单,要不就可读要不就可写,肯定有掩码返回,根本不用阻塞。

    [root: app]# ./monitor& //1.执行建材程序

    [root: app]# [test_poll]***mask[4]***//2每个四秒调用一次select,没阻塞

    monitor:[device writeable]

    [test_poll]***mask[4]***

    monitor:[device writeable]

    [test_poll]***mask[4]***

    monitor:[device writeable]

    [root: app]# ./app_write //3写入数据

    [test_write]write 10 bytes, cur_size:[10]

    [test_write]kbuf is [xiao bai]

    [root: app]# [test_poll]***mask[5]***

    monitor:[device readable] //4掩码改变,但没有阻塞

    monitor:[device writeable]

    [test_poll]***mask[5]***

    monitor:[device readable]

    monitor:[device writeable]

    [test_poll]***mask[5]***

    monitor:[device readable]

    monitor:[device writeable]

    [root: app]# ./app_read //5读取数据

    [test_read]read data.....

    [test_read]read 10 bytes, cur_size:[0]

    [xiao bai]

    [root: app]# [test_poll]***mask[4]***

    monitor:[device writeable] //6掩码改变,但没有阻塞

    [test_poll]***mask[4]***

    monitor:[device writeable]


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    四、select的阻塞操作


    上面说了select会造成休眠,接下来简单的谈谈。select里面会调用函数do_select


    贴上程序,并附上大致的运行顺序:

    /*fs/select.c*/

    365 int do_select(int n, fd_set_bits *fds, struct timespec *end_time)

    366 {

    367 ktime_t expire, *to = NULL;

    368 struct poll_wqueues table; //1.这个就是前面说用来方等待队列的poll_wqueues

    。。。。省略。。。。

    380

    381 poll_initwait(&table);

    382 wait = &table.pt;

    383 if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {

    384 wait = NULL;

    385 timed_out = 1;

    386 }

    387

    388 if (end_time && !timed_out)

    389 slack = estimate_accuracy(end_time);

    390

    391 retval = 0;

    392 for (;;) { //2.注意这个大循环,如果条件不成立休眠后,唤醒正在这个大循环里

    393 unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;

    394

    395 inp = fds->in; outp = fds->out; exp = fds->ex;

    396 rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;

    397

    398 for (i = 0; i < n; ++rinp, ++routp, ++rexp) {

    。。。。。省略。。。。。

    411 for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {

    412 int fput_needed;

    413 if (i >= n)

    414 break;

    415 if (!(bit & all_bits))

    416 continue;

    417 file = fget_light(i, &fput_needed);

    418 if (file) { //3.循环里面里边所有所有被检测的filp

    419 f_op = file->f_op;

    420 mask = DEFAULT_POLLMASK;

    421 if (f_op && f_op->poll) //4.调用我们实现的poll函数,这也是poll被多次调用的原因,以为他在循环里面。

    422 mask = (*f_op->poll)(file, retval ? NULL : wait);

    423 fput_light(file, fput_needed);

    424 if ((mask & POLLIN_SET) && (in & bit)) { //5.判断poll返回的掩码,只要掩码不是0,下面的起码有一个条件会实现。retval++

    425 res_in |= bit;

    426 retval++;

    427 }

    428 if ((mask & POLLOUT_SET) && (out & bit)) {

    429 res_out |= bit;

    430 retval++;

    431 }

    432 if ((mask & POLLEX_SET) && (ex & bit)) {

    433 res_ex |= bit;

    434 retval++;

    435 }

    436 }

    437 }

    438 if (res_in)

    439 *rinp = res_in;

    440 if (res_out)

    441 *routp = res_out;

    442 if (res_ex)

    443 *rexp = res_ex;

    444 cond_resched();

    445 }

    446 wait = NULL;

    447 if (retval || timed_out || signal_pending(current))//6.如果条件成立或者 延时或者被中断

    448 break; //7.调用break跳出大循环,do_select调用完毕

    449 if (table.error) {

    450 retval = table.error;

    451 break;

    452 }

    453

    454 /*

    455 * If this is the first loop and we have a timeout

    456 * given, then we convert to ktime_t and set the to

    457 * pointer to the expiry value.

    458 */

    459 if (end_time && !to) {

    460 expire = timespec_to_ktime(*end_time);

    461 to = &expire;

    462 }

    463 /*8如果上面的条件没有成立,走到这里,进程状态改变有TASK_INTERRUPTIBLE,并加入指定的等待队列头,让出CPU,进程休眠,等待唤醒*/

    464 if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,

    465 to, slack))

    466 timed_out = 1;

    467 }

    468

    469 poll_freewait(&table);

    470

    471 return retval;

    472 }

    上面只是想说明:poll只是做了一个判断工作,真正的阻塞在select中。


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    五、总结:


    今天内容有讲完了,讲了以下内容:

    1poll的实现:

    1.1调用poll_wait

    1.2返回掩码。


    2.poll_wait的实现。


    3.select中的阻塞。


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    源代码: 3rd_char_6.rar   

  • 相关阅读:
    一个别人的心得(转发的)
    常见的游戏设计技术
    查看更新
    xml,json和各种序列化工具的对比
    python游戏环境搭建
    快速制作游戏
    子网和掩码
    nat
    pycharm使用技巧
    IP的面向无连接状态
  • 原文地址:https://www.cnblogs.com/huty/p/8518597.html
Copyright © 2011-2022 走看看