编译环境:Ubuntu 10.10

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

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

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

一、poll函数的使用

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

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

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

struct pollfd {
  int fd; /* file descriptor */
  short events; /* requested events */
  short revents; /* returned events */
};

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中),由下面的宏设定:

POLLIN:There is data to read.

POLLOUT:Writing now will not block.

POLLPRI:There 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.) 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.

POLLERR:Error condition (output only).

POLLHUP:Hang up (output only).

POLLNVAL:Invalid 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:

POLLRDNORM:Equivalent to POLLIN.

POLLRDBAND:Priority band data can be read (generally unused on Linux).

POLLWRNORM:Equivalent to POLLOUT.

POLLWRBAND:Priority data may be written.

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/poll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h> int main(int argc, char *argv[])
{
int fd0, fd1, fd2, fd3;
struct pollfd poll_fd[];
char buf[];
int retval; if(argc != || ((strcmp(argv[], "read") != ) && (strcmp(argv[], "write") != )))
{
printf("usage: ./poll_test read|write\n");
return -;
} fd0 = open("/dev/scullpipe0", O_RDWR);
if ( fd0 < )
{
printf("open scullpipe0 error\n");
return -;
} fd1 = open("/dev/scullpipe1", O_RDWR);
if ( fd1 < )
{
printf("open scullpipe1 error\n");
return -;
} fd2 = open("/dev/scullpipe2", O_RDWR);
if ( fd2 < )
{
printf("open scullpipe2 error\n");
return -;
} fd3 = open("/dev/scullpipe3", O_RDWR);
if ( fd3 < )
{
printf("open scullpipe3 error\n");
return -;
} if(strcmp(argv[], "read") == )
{
poll_fd[].fd = fd0;
poll_fd[].fd = fd1;
poll_fd[].fd = fd2;
poll_fd[].fd = fd3; poll_fd[].events = POLLIN | POLLRDNORM;
poll_fd[].events = POLLIN | POLLRDNORM;
poll_fd[].events = POLLIN | POLLRDNORM;
poll_fd[].events = POLLIN | POLLRDNORM; retval = poll(poll_fd, , );
}
else
{
poll_fd[].fd = fd0;
poll_fd[].fd = fd1;
poll_fd[].fd = fd2;
poll_fd[].fd = fd3; poll_fd[].events = POLLOUT | POLLWRNORM;
poll_fd[].events = POLLOUT | POLLWRNORM;
poll_fd[].events = POLLOUT | POLLWRNORM;
poll_fd[].events = POLLOUT | POLLWRNORM; retval = poll(poll_fd, , );
} if (retval == -)
{
printf("poll error!\n");
return -;
}
else if (retval)
{
if(strcmp(argv[], "read") == )
{
if(poll_fd[].revents & (POLLIN | POLLRDNORM))
{
printf("/dev/scullpipe0 is readable!\n");
memset(buf, , );
read(fd0, buf, );
printf("%s\n", buf);
} if(poll_fd[].revents & (POLLIN | POLLRDNORM))
{
printf("/dev/scullpipe1 is readable!\n");
memset(buf, , );
read(fd1, buf, );
printf("%s\n", buf);
} if(poll_fd[].revents & (POLLIN | POLLRDNORM))
{
printf("/dev/scullpipe2 is readable!\n");
memset(buf, , );
read(fd2, buf, );
printf("%s\n", buf);
} if(poll_fd[].revents & (POLLIN | POLLRDNORM))
{
printf("/dev/scullpipe3 is readable!\n");
memset(buf, , );
read(fd3, buf, );
printf("%s\n", buf);
}
}
else
{
if(poll_fd[].revents & (POLLOUT | POLLWRNORM))
{
printf("/dev/scullpipe0 is writable!\n");
} if(poll_fd[].revents & (POLLOUT | POLLWRNORM))
{
printf("/dev/scullpipe1 is writable!\n");
} if(poll_fd[].revents & (POLLOUT | POLLWRNORM))
{
printf("/dev/scullpipe2 is writable!\n");
} if(poll_fd[].revents & (POLLOUT | POLLWRNORM))
{
printf("/dev/scullpipe3 is writable!\n");
}
}
}
else
{
if(strcmp(argv[], "read") == )
{
printf("No data within ten seconds.\n");
}
else
{
printf("Can not write within ten seconds.\n");
}
} return ;
}

测试过程如下图所示:

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

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

  

二、select函数的使用

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

select函数的函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

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

select的第一个参数nfds是三个文件描述符集readfds、writefds、exceptfds中最大文件描述符值加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操作的测试程序,其代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h> int main(int argc, char *argv[])
{
fd_set rfds, wfds;
int fd0, fd1, fd2, fd3;
char buf[];
int retval; /* Wait up to ten seconds. */
struct timeval tv;
tv.tv_sec = ;
tv.tv_usec = ; if(argc != || ((strcmp(argv[], "read") != ) && (strcmp(argv[], "write") != )))
{
printf("usage: ./select_test read|write\n");
return -;
} fd0 = open("/dev/scullpipe0", O_RDWR);
if ( fd0 < )
{
printf("open scullpipe0 error\n");
return -;
} fd1 = open("/dev/scullpipe1", O_RDWR);
if ( fd1 < )
{
printf("open scullpipe1 error\n");
return -;
} fd2 = open("/dev/scullpipe2", O_RDWR);
if ( fd2 < )
{
printf("open scullpipe2 error\n");
return -;
} fd3 = open("/dev/scullpipe3", O_RDWR);
if ( fd3 < )
{
printf("open scullpipe3 error\n");
return -;
} if(strcmp(argv[], "read") == )
{
FD_ZERO(&rfds);
FD_SET(fd0, &rfds);
FD_SET(fd1, &rfds);
FD_SET(fd2, &rfds);
FD_SET(fd3, &rfds);
retval = select(fd3 + , &rfds, NULL, NULL, &tv);
}
else
{
FD_ZERO(&wfds);
FD_SET(fd0, &wfds);
FD_SET(fd1, &wfds);
FD_SET(fd2, &wfds);
FD_SET(fd3, &wfds);
retval = select(fd3 + , NULL, &wfds, NULL, &tv);
} if (retval == -)
{
printf("select error!\n");
return -;
}
else if (retval)
{
if(strcmp(argv[], "read") == )
{
if(FD_ISSET(fd0, &rfds))
{
printf("/dev/scullpipe0 is readable!\n");
memset(buf, , );
read(fd0, buf, );
printf("%s\n", buf);
} if(FD_ISSET(fd1, &rfds))
{
printf("/dev/scullpipe1 is readable!\n");
memset(buf, , );
read(fd1, buf, );
printf("%s\n", buf);
} if(FD_ISSET(fd2, &rfds))
{
printf("/dev/scullpipe2 is readable!\n");
memset(buf, , );
read(fd2, buf, );
printf("%s\n", buf);
} if(FD_ISSET(fd3, &rfds))
{
printf("/dev/scullpipe3 is readable!\n");
memset(buf, , );
read(fd3, buf, );
printf("%s\n", buf);
}
}
else
{
if(FD_ISSET(fd0, &wfds))
{
printf("/dev/scullpipe0 is writable!\n");
} if(FD_ISSET(fd1, &wfds))
{
printf("/dev/scullpipe1 is writable!\n");
} if(FD_ISSET(fd2, &wfds))
{
printf("/dev/scullpipe2 is writable!\n");
} if(FD_ISSET(fd3, &wfds))
{
printf("/dev/scullpipe3 is writable!\n");
}
}
}
else
{
if(strcmp(argv[], "read") == )
{
printf("No data within ten seconds.\n");
}
else
{
printf("Can not write within ten seconds.\n");
}
} return ;
}

测试过程如下图所示:

  

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

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

  

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

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

unsigned int (*poll) (struct file *filp, poll_table *wait);

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

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

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

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

void poll_wait (struct file *, wait_queue_head_t *, poll_table *);

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

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

228static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = ; /*
234 * The buffer is circular; it is considered full
235 * if "wp" is right behind "rp" and empty if the
236 * two are equal.
237 */
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}

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

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

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

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

246行,将标志位返回。

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

 /*
31 * structures and helpers for f_op->poll implementations
32 */
33typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *); 35typedef struct poll_table_struct {
poll_queue_proc qproc;
unsigned long key;
} 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;
pt->key = ~0UL; /* all events enabled */
}
struct poll_table_entry {
struct file *filp;
unsigned long key;
wait_queue_t wait;
wait_queue_head_t *wait_address;
};

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

LDD3源码分析之poll分析的更多相关文章

  1. ArrayList源码和多线程安全问题分析

    1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 Arr ...

  2. Okhttp3源码解析(3)-Call分析(整体流程)

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  3. Okhttp3源码解析(2)-Request分析

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  4. Spring mvc之源码 handlerMapping和handlerAdapter分析

    Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mv ...

  5. HashMap的源码学习以及性能分析

    HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...

  6. ThreadLocal源码及相关问题分析

    前言 在高并发的环境下,当我们使用一个公共的变量时如果不加锁会出现并发问题,例如SimpleDateFormat,但是加锁的话会影响性能,对于这种情况我们可以使用ThreadLocal.ThreadL ...

  7. 物联网防火墙himqtt源码之MQTT协议分析

    物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...

  8. Netty 源码学习——客户端流程分析

    Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...

  9. linux源码Makefile的详细分析

    目录 一.概述 1.本文的意义 2.Linux内核Makefile文件组成 二.Linux内核Makefile的“make解析”过程 1 顶层Makefile阶段 1.从总目标uImage说起 2.v ...

随机推荐

  1. java实现在线预览--poi实现word、excel、ppt转html

    java实现在线预览 - -之poi实现word.excel.ppt转html 简介 java实现在线预览功能是一个大家在工作中也许会遇到的需求,如果公司有钱,直接使用付费的第三方软件或者云在线预览服 ...

  2. android之自定义viewGroup仿scrollView的两种实现(滚动跟粘性)

    最近一直在研究自定义控件,一般大致分为三种情况:自绘控件,组合控件,继承控件.接下来我们来看下继承控件.在此借鉴一位博主的文章,补充粘性的实现效果,并且加注自己的一些理解.大家也可以查看原文博客.an ...

  3. Telnet入侵Windows2000

    开启Telnet 打开控制面板,管理工具 计算机管理 连接刚刚探测到的主机 输入探测到的主机IP 如下图所示,连接成功 找到Telnet服务 启动Telnet服务 远程登录 注意 Telnet登录需要 ...

  4. 在DoNetCore MVC 中如何使用AutoMapper

    刚开始,按照在donet mvc 的方法写了一遍,发现行不通啊,于是百度了一下,找到这么一篇 https://stackoverflow.com/questions/41284349/automapp ...

  5. php框架路由美化后提示No input file specified

    此问题出现在.htaccess上 Apache按如下代码修改即可: RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteCond % ...

  6. Xshell连接虚拟机中的Ubuntu

    虚拟机中安装好Ubuntu系统后使用cmd测试ping 设置xshell的连接ip 连接 连接失败 安装openssh-server sudo apt install openssh-server 再 ...

  7. PHP开启慢日志查询

    1.找到php-fpm.conf文件 2.去掉request_slowlog_timeout前面的分号,并设置时间.如:request_slowlog_timeout=5表示超过5秒的 慢日志文件位置 ...

  8. OpenGL学习(1)—— 测试OpenGL环境是否搭建成功

    一个用来验证OpenGL(glfw + glad)环境是否搭建成功的测试代码 内容为生成一个小窗口 #include <glad/glad.h> #include <GLFW/glf ...

  9. Python使用pip安装TensorFlow模块

    1.首先确保已经安装python,然后用pip来安装matplotlib模块. 2.进入到cmd窗口下,建议执行python -m pip install -U pip setuptools进行升级. ...

  10. crc计算工具推荐

    比较好的crc计算工具,32位64位系统都可以用的.crc的校验方法也很多.推荐使用 下载地址