LDD3源码分析之poll分析

转载自http://blog.csdn.net/liuhaoyutz/article/details/7400037

作者:刘昊昱

博客:http://blog.csdn.net/liuhaoyutz

编译环境:Ubuntu 10.10

内核版本:2.6.32-38-generic-pae

LDD3源码路径:examples/scull/pipe.c examples/scull/main.c

本文分析LDD36章的poll(轮询)操作。要理解驱动程序中poll函数的作用和实现,必须先理解用户空间中pollselect函数的用法。本文与前面的文章介绍的顺序有所不同,首先分析测试程序,以此理解用户空间中的pollselect函数的用法。然后再分析驱动程序怎样对用户空间的pollselect函数提供支持。

一、poll函数的使用

用户态的poll函数用以监测一组文件描述符是否可以执行指定的I/O操作,如果被监测的文件描述符都不能执行指定的I/O操作,则poll函数会阻塞,直到有文件描述符的状态发生变化,可以执行指定的I/O操作才解除阻塞。poll函数还可以指定一个最长阻塞时间,如果时间超时,将直接返回。poll函数的函数原型如下:

[cpp] view plain copy print ?
  1. int poll(struct pollfd *fds, nfds_t nfds,int timeout);

要监测的文件描述符由第一个参数fds指定,它是一个struct pollfd数组,pollfd结构体定义如下:

[cpp] view plain copy print ?
  1. struct pollfd {
  2. int fd; /* file descriptor */
  3. short events; /* requested events */
  4. short revents; /* returned events */
  5. };

pollfd结构体的第一个成员fd是文件描述符,代表一个打开的文件。第二个成员events是一个输入参数,用于指定poll监测哪些事件(如可读、可写等等)。第三个成员revents是一个输出参数,由内核填充,指示对于文件描述符fd,发生了哪些事件(如可读、可写等等)

poll函数的第二个参数nfds代表监测的文件描述符的个数,即fds数组的成员个数。

poll函数的第三个参数timeout代表阻塞时间(以毫秒为单位),如果poll要求监测的事件没有发生,则poll会阻塞最多timeout毫秒。如果timeout设置为负数,则poll会一直阻塞,直到监测的事件发生。

poll函数如果返回一个正数,代表内核返回了状态(保存在pollfd.revents)的文件描述符的个数。如果poll返回0,表明是因为超时而返回的。如果poll返回-1,表明poll调用出错。

poll函数可以监测哪些状态(pollfd.events指定),以及内核可以返回哪些状态(保存在pollfd.revents),由下面的宏设定:

POLLINThere is data to read.

POLLOUTWriting now will not block.

POLLPRIThere is urgent data to read (e.g., out-of-band data on TCP socket; pseudo-terminal master in packet mode has seen state change in slave).

POLLRDHUP (since Linux 2.6.17) Stream socket peer closed connection, or shut down writing half of connection. The _GNU_SOURCE feature test macro must be defined in order to obtain this definition.

POLLERRError condition (output only).

POLLHUPHang up (output only).

POLLNVALInvalid request: fd not open (output only).

When compiling with _XOPEN_SOURCE defined, one also has the following, which convey no further information beyond the bits listed above:

POLLRDNORMEquivalent to POLLIN.

POLLRDBANDPriority band data can be read (generally unused on Linux).

POLLWRNORMEquivalent to POLLOUT.

POLLWRBANDPriority data may be written.

下面我们看一个测试scullpipe设备的poll操作(内核态poll)的测试程序,该程序使用了我们前面介绍的poll函数(用户态poll)。其代码如下:

[cpp] view plain copy print ?
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <linux/poll.h>
  7. #include <sys/time.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. int main(int argc,char *argv[])
  11. {
  12. int fd0, fd1, fd2, fd3;
  13. struct pollfd poll_fd[4];
  14. char buf[100];
  15. int retval;
  16. if(argc != 2 || ((strcmp(argv[1], "read") != 0) && (strcmp(argv[1], "write") != 0)))
  17. {
  18. printf("usage: ./poll_test read|write\n");
  19. return -1;
  20. }
  21. fd0 = open("/dev/scullpipe0", O_RDWR);
  22. if ( fd0 < 0)
  23. {
  24. printf("open scullpipe0 error\n");
  25. return -1;
  26. }
  27. fd1 = open("/dev/scullpipe1", O_RDWR);
  28. if ( fd1 < 0)
  29. {
  30. printf("open scullpipe1 error\n");
  31. return -1;
  32. }
  33. fd2 = open("/dev/scullpipe2", O_RDWR);
  34. if ( fd2 < 0)
  35. {
  36. printf("open scullpipe2 error\n");
  37. return -1;
  38. }
  39. fd3 = open("/dev/scullpipe3", O_RDWR);
  40. if ( fd3 < 0)
  41. {
  42. printf("open scullpipe3 error\n");
  43. return -1;
  44. }
  45. if(strcmp(argv[1], "read") == 0)
  46. {
  47. poll_fd[0].fd = fd0;
  48. poll_fd[1].fd = fd1;
  49. poll_fd[2].fd = fd2;
  50. poll_fd[3].fd = fd3;
  51. poll_fd[0].events = POLLIN | POLLRDNORM;
  52. poll_fd[1].events = POLLIN | POLLRDNORM;
  53. poll_fd[2].events = POLLIN | POLLRDNORM;
  54. poll_fd[3].events = POLLIN | POLLRDNORM;
  55. retval = poll(poll_fd, 4, 10000);
  56. }
  57. else
  58. {
  59. poll_fd[0].fd = fd0;
  60. poll_fd[1].fd = fd1;
  61. poll_fd[2].fd = fd2;
  62. poll_fd[3].fd = fd3;
  63. poll_fd[0].events = POLLOUT | POLLWRNORM;
  64. poll_fd[1].events = POLLOUT | POLLWRNORM;
  65. poll_fd[2].events = POLLOUT | POLLWRNORM;
  66. poll_fd[3].events = POLLOUT | POLLWRNORM;
  67. retval = poll(poll_fd, 4, 10000);
  68. }
  69. if (retval == -1)
  70. {
  71. printf("poll error!\n");
  72. return -1;
  73. }
  74. else if (retval)
  75. {
  76. if(strcmp(argv[1], "read") == 0)
  77. {
  78. if(poll_fd[0].revents & (POLLIN | POLLRDNORM))
  79. {
  80. printf("/dev/scullpipe0 is readable!\n");
  81. memset(buf, 0, 100);
  82. read(fd0, buf, 100);
  83. printf("%s\n", buf);
  84. }
  85. if(poll_fd[1].revents & (POLLIN | POLLRDNORM))
  86. {
  87. printf("/dev/scullpipe1 is readable!\n");
  88. memset(buf, 0, 100);
  89. read(fd1, buf, 100);
  90. printf("%s\n", buf);
  91. }
  92. if(poll_fd[2].revents & (POLLIN | POLLRDNORM))
  93. {
  94. printf("/dev/scullpipe2 is readable!\n");
  95. memset(buf, 0, 100);
  96. read(fd2, buf, 100);
  97. printf("%s\n", buf);
  98. }
  99. if(poll_fd[3].revents & (POLLIN | POLLRDNORM))
  100. {
  101. printf("/dev/scullpipe3 is readable!\n");
  102. memset(buf, 0, 100);
  103. read(fd3, buf, 100);
  104. printf("%s\n", buf);
  105. }
  106. }
  107. else
  108. {
  109. if(poll_fd[0].revents & (POLLOUT | POLLWRNORM))
  110. {
  111. printf("/dev/scullpipe0 is writable!\n");
  112. }
  113. if(poll_fd[1].revents & (POLLOUT | POLLWRNORM))
  114. {
  115. printf("/dev/scullpipe1 is writable!\n");
  116. }
  117. if(poll_fd[2].revents & (POLLOUT | POLLWRNORM))
  118. {
  119. printf("/dev/scullpipe2 is writable!\n");
  120. }
  121. if(poll_fd[3].revents & (POLLOUT | POLLWRNORM))
  122. {
  123. printf("/dev/scullpipe3 is writable!\n");
  124. }
  125. }
  126. }
  127. else
  128. {
  129. if(strcmp(argv[1], "read") == 0)
  130. {
  131. printf("No data within ten seconds.\n");
  132. }
  133. else
  134. {
  135. printf("Can not write within ten seconds.\n");
  136. }
  137. }
  138. return 0;
  139. }

测试过程如下图所示:

LDD3源码分析之poll分析_第1张图片

从上图可以看出,scullpipe0 - scullpipe3都是可写的。但是因为没有向其中写入任何内容,所以读的时候会阻塞住,因为测试程序设置最长阻塞时间为10秒,所以10秒后解除阻塞退出,并打印”No data within ten seconds.”

下面再次测试读操作,因为设备中没有内容,还是阻塞住,但是在10秒钟内,从另外一个终端向/dev/scullpipe2写入数据,这里是写入字符串”hello”,可以看到,写入数据后,测试程序解除阻塞,并打印”/dev/scullpipe2 is readable!””hello”字符串,这个过程如下图所示:

LDD3源码分析之poll分析_第2张图片

二、select函数的使用

select函数的作用与poll函数类似,也是监测一组文件描述符是否准备好执行指定的I/O操作。如果没有任何一个文件描述符可以完成指定的操作,select函数会阻塞住。select函数可以指定一个最长阻塞时间,如果超时,则直接返回。

select函数的函数原型如下:

[cpp] view plain copy print ?
  1. int select(int nfds, fd_set *readfds, fd_set *writefds,
  2. fd_set *exceptfds, struct timeval *timeout);

select通过三个独立的文件描述符集(fd_set)readfdswritefdsexceptfds指示监测哪些文件描述符,其中readfds中的文件描述符监测是否可进行非阻塞的读操作。writefds数组中的文件描述符监测是否可进行非阻塞的写操作。exceptfds数组中的文件描述符监测是否有异常(exceptions)

select的第一个参数nfds是三个文件描述符集readfdswritefdsexceptfds中最大文件描述符值加1

select的最后一个参数timeout代表最长阻塞时间。

select函数返回满足指定I/O要求的文件描述符的个数,如果是超时退出的,select函数返回0。如果出错,select函数返回-1

有四个宏用来操作文件描述符集:

void FD_ZERO(fd_set *set); 清空一个文件描述符集。

void FD_SET(int fd, fd_set *set);将一个文件描述符fd加入到指定的文件描述符集set中。

void FD_CLR(int fd, fd_set *set);将一个文件描述符fd从指定的文件描述符集set中删除。

int FD_ISSET(int fd, fd_set *set);测试文件描述符fd是否在指定的文件描述符集set中。

下面我们看使用select函数实现的测试驱动中poll操作的测试程序,其代码如下:

[cpp] view plain copy print ?
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <sys/time.h>
  7. #include <sys/types.h>
  8. #include <sys/stat.h>
  9. int main(int argc,char *argv[])
  10. {
  11. fd_set rfds, wfds;
  12. int fd0, fd1, fd2, fd3;
  13. char buf[100];
  14. int retval;
  15. /* Wait up to ten seconds. */
  16. struct timeval tv;
  17. tv.tv_sec = 10;
  18. tv.tv_usec = 0;
  19. if(argc != 2 || ((strcmp(argv[1], "read") != 0) && (strcmp(argv[1], "write") != 0)))
  20. {
  21. printf("usage: ./select_test read|write\n");
  22. return -1;
  23. }
  24. fd0 = open("/dev/scullpipe0", O_RDWR);
  25. if ( fd0 < 0)
  26. {
  27. printf("open scullpipe0 error\n");
  28. return -1;
  29. }
  30. fd1 = open("/dev/scullpipe1", O_RDWR);
  31. if ( fd1 < 0)
  32. {
  33. printf("open scullpipe1 error\n");
  34. return -1;
  35. }
  36. fd2 = open("/dev/scullpipe2", O_RDWR);
  37. if ( fd2 < 0)
  38. {
  39. printf("open scullpipe2 error\n");
  40. return -1;
  41. }
  42. fd3 = open("/dev/scullpipe3", O_RDWR);
  43. if ( fd3 < 0)
  44. {
  45. printf("open scullpipe3 error\n");
  46. return -1;
  47. }
  48. if(strcmp(argv[1], "read") == 0)
  49. {
  50. FD_ZERO(&rfds);
  51. FD_SET(fd0, &rfds);
  52. FD_SET(fd1, &rfds);
  53. FD_SET(fd2, &rfds);
  54. FD_SET(fd3, &rfds);
  55. retval = select(fd3 + 1, &rfds, NULL, NULL, &tv);
  56. }
  57. else
  58. {
  59. FD_ZERO(&wfds);
  60. FD_SET(fd0, &wfds);
  61. FD_SET(fd1, &wfds);
  62. FD_SET(fd2, &wfds);
  63. FD_SET(fd3, &wfds);
  64. retval = select(fd3 + 1, NULL, &wfds, NULL, &tv);
  65. }
  66. if (retval == -1)
  67. {
  68. printf("select error!\n");
  69. return -1;
  70. }
  71. else if (retval)
  72. {
  73. if(strcmp(argv[1], "read") == 0)
  74. {
  75. if(FD_ISSET(fd0, &rfds))
  76. {
  77. printf("/dev/scullpipe0 is readable!\n");
  78. memset(buf, 0, 100);
  79. read(fd0, buf, 100);
  80. printf("%s\n", buf);
  81. }
  82. if(FD_ISSET(fd1, &rfds))
  83. {
  84. printf("/dev/scullpipe1 is readable!\n");
  85. memset(buf, 0, 100);
  86. read(fd1, buf, 100);
  87. printf("%s\n", buf);
  88. }
  89. if(FD_ISSET(fd2, &rfds))
  90. {
  91. printf("/dev/scullpipe2 is readable!\n");
  92. memset(buf, 0, 100);
  93. read(fd2, buf, 100);
  94. printf("%s\n", buf);
  95. }
  96. if(FD_ISSET(fd3, &rfds))
  97. {
  98. printf("/dev/scullpipe3 is readable!\n");
  99. memset(buf, 0, 100);
  100. read(fd3, buf, 100);
  101. printf("%s\n", buf);
  102. }
  103. }
  104. else
  105. {
  106. if(FD_ISSET(fd0, &wfds))
  107. {
  108. printf("/dev/scullpipe0 is writable!\n");
  109. }
  110. if(FD_ISSET(fd1, &wfds))
  111. {
  112. printf("/dev/scullpipe1 is writable!\n");
  113. }
  114. if(FD_ISSET(fd2, &wfds))
  115. {
  116. printf("/dev/scullpipe2 is writable!\n");
  117. }
  118. if(FD_ISSET(fd3, &wfds))
  119. {
  120. printf("/dev/scullpipe3 is writable!\n");
  121. }
  122. }
  123. }
  124. else
  125. {
  126. if(strcmp(argv[1], "read") == 0)
  127. {
  128. printf("No data within ten seconds.\n");
  129. }
  130. else
  131. {
  132. printf("Can not write within ten seconds.\n");
  133. }
  134. }
  135. return 0;
  136. }

测试过程如下图所示:

LDD3源码分析之poll分析_第3张图片

从上图可以看出,scullpipe0 - scullpipe3都是可写的。但是因为没有向其中写入任何内容,所以读的时候会阻塞住,因为测试程序设置最长阻塞时间为10秒,所以10秒后解除阻塞退出,并打印”No data within ten seconds.”

下面再次测试读操作,因为设备中没有内容,还是阻塞住,但是在10秒钟内,从另外一个终端向/dev/scullpipe2写入数据,这里是写入字符串”hello”,可以看到,写入数据后,测试程序解除阻塞,并打印”/dev/scullpipe2 is readable!””hello”字符串,这个过程如下图所示:

LDD3源码分析之poll分析_第4张图片

三、驱动程序中poll操作的实现

用户空间的pollselect函数,最终都会调用驱动程序中的poll函数,其函数原型如下:

[cpp] view plain copy print ?
  1. unsigned int (*poll) (struct file *filp, poll_table *wait);

poll函数应该实现两个功能:

一是把能标志轮询状态变化的等待队列加入到poll_table中,这通过调用poll_wait函数实现。

二是返回指示能进行的I/O操作的标志位。

poll函数的第二个参数poll_table,是内核中用来实现pollselect系统调用的结构体,对于驱动开发者来说,不必关心其具体内容,可以把poll_table看成是不透明的结构体,只要拿过来使用就可以了。驱动程序通过poll_wait函数,把能够唤醒进程,改变轮询状态的等待队列加入到poll_table中。该函数定义如下:

[cpp] view plain copy print ?
  1. void poll_wait (struct file *, wait_queue_head_t *, poll_table *);

对于poll函数的第二个功能,返回的标志位与用户空间相对应,最常用的标志位是POLLIN | POLLRDNORMPOLLOUT | POLLWRNORM,分别标志可进行非阻塞的读和写操作。

下面看scullpipe设备对poll操作的实现,其内容其实非常简单:

[cpp] view plain copy print ?
  1. 228static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
  2. 229{
  3. 230 struct scull_pipe *dev = filp->private_data;
  4. 231 unsigned int mask = 0;
  5. 232
  6. 233 /*
  7. 234 * The buffer is circular; it is considered full
  8. 235 * if "wp" is right behind "rp" and empty if the
  9. 236 * two are equal.
  10. 237 */
  11. 238 down(&dev->sem);
  12. 239 poll_wait(filp, &dev->inq, wait);
  13. 240 poll_wait(filp, &dev->outq, wait);
  14. 241 if (dev->rp != dev->wp)
  15. 242 mask |= POLLIN | POLLRDNORM; /* readable */
  16. 243 if (spacefree(dev))
  17. 244 mask |= POLLOUT | POLLWRNORM; /* writable */
  18. 245 up(&dev->sem);
  19. 246 return mask;
  20. 247}

239行,调用poll_wait函数将读等待队列加入到poll_table中。

240行,调用poll_wait函数将写等待队列加入到poll_table中。

241 - 242行,如果有内容可读,设置可读标志位。

243 - 244行,如果有空间可写,设置可写标志位。

246行,将标志位返回。

驱动程序中的poll函数很简单,那么内核是怎么实现pollselect系统调用的呢?当用户空间程序调用pollselect函数时,内核会调用由用户程序指定的全部文件的poll方法,并向它们传递同一个poll_table结构。poll_table结构其实是一个生成实际数据结构的函数(名为poll_queue_proc)的封装,这个函数poll_queue_proc,不同的应用场景,内核有不同的实现,这里我们不仔细研究对应的函数。对于pollselect系统调用来说,这个实际数据结构是一个包含poll_table_entry结构的内存页链表。这里提到的相关数据结构在linux-2.6.32-38源码中,定义在include/linux/poll.h文件中,代码如下所示:

[cpp] view plain copy print ?
  1. 30/*
  2. 31 * structures and helpers for f_op->poll implementations
  3. 32 */
  4. 33typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *,struct poll_table_struct *);
  5. 34
  6. 35typedef struct poll_table_struct {
  7. 36 poll_queue_proc qproc;
  8. 37 unsigned long key;
  9. 38} poll_table;
  10. 39
  11. 40static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
  12. 41{
  13. 42 if (p && wait_address)
  14. 43 p->qproc(filp, wait_address, p);
  15. 44}
  16. 45
  17. 46static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
  18. 47{
  19. 48 pt->qproc = qproc;
  20. 49 pt->key = ~0UL; /* all events enabled */
  21. 50}
  22. 51
  23. 52struct poll_table_entry {
  24. 53 struct file *filp;
  25. 54 unsigned long key;
  26. 55 wait_queue_t wait;
  27. 56 wait_queue_head_t *wait_address;
  28. 57};

poll操作我们就分析完了,内核要求驱动程序做的事并不多,但是我们在学习时,要把poll操作和前面介绍的阻塞型read/write函数以及scullpipe设备的等待队列等结合起来考虑,因为scullpipe设备是一个完整的程序模块。

你可能感兴趣的:(LDD3源码分析之poll分析)