10. poll和select

本节是本书中《高级字符驱动程序操作》章节的第三节内容。本节主要涉及到的是多路复用IO接口 pollselectepoll

本文主要的内容有以下:

  • pollselectepoll 的作用;
  • 驱动中的 poll 操作;
  • epoll 实例

1. poll、select和epoll的作用

在非阻塞IO的应用程序中,经常会使用到 pollselectepoll 系统调用,这三个系统调用的功能本质上是一样的:都允许进程决定是否可以对一个或多个打开的文件做非阻塞的读取或写入。

这些调用会阻塞进程,直到给定的文件描述符集合中的任何一个可读取或者写入,因此,它们常常用于那些要使用多个输入或者输出流,而又不会被其中某个流阻塞的进程中。

下面说一下这三个系统调用的关系:pollselect 由两个Unix团体几乎同时实现,所以同时存在这两个系统调用,而 epoll 是在2.5.45中引入的,扩展了 poll 函数使其能够处理数千个文件描述符。

要实现上面系统调用的功能,需要驱动代码通过 poll 函数提供相应的支持。驱动的 poll 函数在之前的文章《字符设备驱动(上)》 中有说过,是 pollepollselect 这三个系统调用的后端实现,用于实现IO的多路复用。

2. 驱动中的poll操作

驱动中的poll函数原型如下:

__poll_t (*poll) (struct file *, struct poll_table_struct *);

当用户空间程序在驱动关联的文件描述符上执行 pollselectepoll 系统调用时,该驱动程序的方法将被调用。

传递给 poll 的第二个参数是 poll_table_struct,它的声明在 <linux/poll.h> 中,驱动程序源代码必须包含这个头文件。使用过程中我们可以不用了解这个结构体的细节,把它当做一个不透明的对象使用即可。

poll 的返回值描述哪个操作可以立即执行的位掩码,这些掩码包括:

  • POLLIN:当前设备可以无阻塞读取
  • POLLRDNORM:与 POLLIN 相同
  • POLLOUT:当前设备可以无阻塞地写入
  • POLLWRNORM:与 POLLOUT 相同
  • POLLERR:设备发生了错误
  • POLLHUP:当前读取设备的进程达到文件尾

poll 方法中,通过 poll_wait 函数,驱动程序向 poll_table_struct 结构体中添加一个等待队列头,poll_wait函数的原型如下:

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

3. epoll驱动实例

我们基于之前scull_sleep设备进行修改,之前的设备是在读取过程中休眠,直到有数据写入且达到数据量要求后才唤醒读取进程继续读取数据。使用epoll后,设备将会在写入的数据达到要求后才会开始读取数据。

全部代码位于:
https://gitee.com/Quehehe/LinuxDeviceDriver

首先说明一下驱动代码,驱动中,最主要的就是实现了poll函数,具体实现如下:

static __poll_t scull_poll (struct file *filp, struct poll_table_struct *poll_table)
{
    struct scull_lock_dev *dev = filp->private_data;
    unsigned int mask = 0;

    poll_wait(filp, &dev->read_wait_queue, poll_table);

    if(dev->data_length >= DATA_SIZE_LIMIT) {
        mask |= POLLIN | POLLRDNORM;
    }
    return mask;
}

static struct file_operations fops = {
    ......
    .poll   =   scull_poll,
};

其实现也很简单,主要就是调用了 poll_wai() 函数,将读取数据的等待队列头加入到了poll_table中。

然后看一下用户空间的代码,代码位于 scull_poll_test.c 文件中,其部分代码如下:

int main(int argc, char **argv)
{
    int fd = open("/dev/scull_poll0",   O_RDWR);
    int epoll_fd = epoll_create(MAXEVENTS);

    struct epoll_event scull_epoll_event;
    struct epoll_event temp_event[MAXEVENTS];

    char buf[100];

    int i = 0;
    int n = 0;

    if(fd<0) {
        printf("can't open \n");
        return -1;
    }
    
    scull_epoll_event.events = EPOLLIN;
    scull_epoll_event.data.fd = fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &scull_epoll_event);

    printf("start wait data!\n");

    n = epoll_wait(epoll_fd, temp_event, MAXEVENTS, -1);

    if(-1 == n) {
        printf("Failed to wait!\n");
        return -1;
    }

    printf("data ready!\n");

    for(i = 0; i < n; i++) {
        if((temp_event[i].data.fd == fd) && (temp_event[i].events & EPOLLIN)) {
            n = read(fd, buf, 100);
            if(n < (sizeof(buf) - 1)) {
                buf[n] = 0;
            }

            if(n < 0) {
                printf("read data ERR!\n");
            }
            printf("read data length = %d, data is:\n%s\n", n, buf);
        }
    }
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &scull_epoll_event);

    return 0;
}

其中主要的几个函数是:

int epoll_create(int size)  //创建一个epoll句柄

创建一个 epoll_ctl 用于注册 epoll 事件,其注册的类型有三种,通过以下三个宏来表示:

  • EPOLL_CTL_ADD:注册新的fd到epfd中;
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL:从epfd中删除一个fd;

注册完事件之后,调用 epoll_wait() 函数,此时,当前线程会阻塞,直到注册的 epoll 事件中,某个事件有唤醒操作,则此线程会被唤醒,继续执行。

通过对比读取到的epoll事件相关参数,就能够知道是哪个设备唤醒了当前线程,同时可以知道可以对该设备进行什么操作。

scull_poll_test.c 文件需要使用以下命令编译生成可执行文件:

gcc -o scull_poll_test scull_poll_test.c

可能读者对于这个操作还是不太了解,不知道和之前的休眠唤醒有什么区别。让我们考虑这样一种情况,当前有1000个设备或文件需要我们监听,当其有变化值,我们要读取其中的数据。这时,如果采用休眠唤醒的方法来监听,则我们针对每个文件或设备都要创建一个线程,这样就需要创建1000个线程;如果采用epoll的方法,则将所有设备或文件注册为epoll事件,使用一个线程进行监听,当某些设备或文件有更新时,唤醒这个线程进行数据读取操作,这样就只需要一个线程即可。

测试方法和之前的类似,这边就不再进行演示了。

所有的测试代码位于:https://gitee.com/Quehehe/LinuxDeviceDriver

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容