Linux select/poll机制原理分析

转载一篇文章,讲解select和poll机制的,分享给大家。


前言

  • Read the fucking source code!  --By 鲁迅

  • A picture is worth a thousand words. --By 高尔基

1. 概述

Linux系统在访问设备的时候,存在以下几种IO模型:

  1. Blocking IO Model,阻塞IO模型

  2. Nonblocking I/O Model,非阻塞IO模型

  3. I/O Multiplexing Model,IO多路复用模型;

  4. Signal Driven I/O Model,信号驱动IO模型

  5. Asynchronous I/O Model,异步IO模型

今天我们来分析下IO多路复用机制,在Linux中是通过select/poll/epoll机制来实现的。

先看一下阻塞IO模型与非阻塞IO模型的特点:


  • 阻塞IO模型:在IO访问的时候,如果条件没有满足,会将当前任务切换出去,等到条件满足时再切换回来。

    • 缺点:阻塞IO操作,会让处于同一个线程的执行逻辑都在阻塞期间无法执行,这往往意味着需要创建单独的线程来交互。

  • 非阻塞IO模型:在IO访问的时候,如果条件没有满足,直接返回,不会block该任务的后续操作。

    • 缺点:非阻塞IO需要用户一直轮询操作,轮询可能会来带CPU的占用问题。

对单个设备IO操作时,问题并不严重,如果有多个设备呢?比如,在服务器中,监听多个Client的收发处理,这时候IO多路复用就显得尤为重要了,来张图:


如果这个图,让你有点迷惑,那就像个男人一样,man一下select/poll函数吧:

  • select:

  • poll

简单来说,select/poll能监听多个设备的文件描述符,只要有任何一个设备满足条件,select/poll就会返回,否则将进行睡眠等待。看起来,select/poll像是一个管家了,统一负责来监听处理了。

已经迫不及待来看看原理了,由于底层的机制大体差不多,我将选择select来做进一步分析。

2. 原理

2.1 select系统调用

select的系统调用开始:


  • select系统调用,最终的核心逻辑是在do_select函数中处理的,参考fs/select.c文件;

  • do_select函数中,有几个关键的操作:

  1. 初始化poll_wqueues结构,包括几个关键函数指针的初始化,用于驱动中进行回调处理;

  2. 循环遍历监测的文件描述符,并且调用f_op->poll()函数,如果有监测条件满足,则会跳出循环;

  3. 在监测的文件描述符都不满足条件时,poll_schedule_timeout让当前进程进行睡眠,超时唤醒,或者被所属的等待队列唤醒;

  • do_select函数的循环退出条件有三个:

    1. 检测的文件描述符满足条件;

    2. 超时;

    3. 有信号要处理;

  • 在设备驱动程序中实现的poll()函数,会在do_select()中被调用,而驱动中的poll()函数,需要调用poll_wait()函数,poll_wait函数本身很简单,就是去回调函数p->_qproc(),这个回调函数正是poll_initwait()函数中初始化的__pollwait()

  • 所以,来看看__pollwait()函数喽。

    2.2 __pollwait


    • 驱动中的poll_wait函数回调__pollwait,这个函数完成的工作是向struct poll_wqueue结构中添加一条poll_table_entry

    • poll_table_entry中包含了等待队列的相关数据结构;

    • 对等待队列的相关数据结构进行初始化,包括设置等待队列唤醒时的回调函数指针,设置成pollwake

    • 将任务添加到驱动程序中的等待队列中,最终驱动可以通过wake_up_interruptile等接口来唤醒处理;

    这一顿操作,其实就是驱动向select维护的struct poll_wqueue中注册,并将调用select的任务添加到驱动的等待队列中,以便在合适的时机进行唤醒。所以,本质上来说,这是基于等待队列的机制来实现的。

    是不是还有点抽象,来看看数据结构的组织关系吧。

    2.3 数据结构关系


    • 调用select系统调用的进程/线程,会维护一个struct poll_wqueues结构,其中两个关键字段:

    1. pll_table:该结构体中的函数指针_qproc指向__pollwait函数;

    2. struct poll_table_entry[]:存放不同设备的poll_table_entry,这些条目的增加是在驱动调用poll_wait->__pollwait()时进行初始化并完成添加的;

    2.4 驱动编写启示

    如果驱动中要支持select的接口调用,那么需要做哪些事情呢?如果理解了上文中的内容,你会毫不犹豫的大声说出以下几条:

    1. 定义一个等待队列头wait_queue_head_t,用于收留等待队列任务;

    2. struct file_operations结构体中的poll函数需要实现,比如xxx_poll()

    3. xxx_poll()函数中,当然不要忘了poll_wait函数的调用了,此外,该函数的返回值mask需要注意是在条件满足时对应的值,比如EPOLLIN/EPOLL/EPOLLERR等,这个返回值是在do_select()函数中会去判断处理的;

    4. 条件满足的时候,wake_up_interruptible唤醒任务,当然也可以使用wake_up,区别是:wake_up_interruptible只能唤醒处于TASK_INTERRUPTIBLE状态的任务,而wake_up能唤醒处于TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE状态的任务;

    2.5 select/poll的差异

    • selectpoll本质上基本类似,其中select是由BSD UNIX引入,pollSystemV引入;

    • selectpoll需要轮询文件描述符集合,并在用户态和内核态之间进行拷贝,在文件描述符很多的情况下开销会比较大,select默认支持的文件描述符数量是1024;

    • Linux提供了epoll机制,改进了selectpoll在效率与资源上的缺点,未深入了解;

    3. 示例代码

    3.1 内核驱动

    示例代码中的逻辑:

    1. 驱动维护一个count值,当count值大于0时,表明条件满足,poll返回正常的mask值;

    2. poll函数每执行一次,count值就减去一次;

    3. count的值可以由用户通过ioctl来进行设置;

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/poll.h>
    #include <linux/wait.h>
    #include <linux/cdev.h>
    #include <linux/mutex.h>
    #include <linux/slab.h>
    #include <asm/ioctl.h>#define POLL_DEV_NAME    "poll"#define POLL_MAGIC    'P'
    #define POLL_SET_COUNT      (_IOW(POLL_MAGIC, 0, unsigned int))struct poll_dev {struct cdev cdev;struct class *class;struct device *device;wait_queue_head_t wq_head;struct mutex poll_mutex;unsigned int count;dev_t devno;
    };struct poll_dev *g_poll_dev = NULL;static int poll_open(struct inode *inode, struct file *filp)
    {filp->private_data = g_poll_dev;return 0;
    }static int poll_close(struct inode *inode, struct file *filp)
    {return 0;
    }static unsigned int poll_poll(struct file *filp, struct poll_table_struct *wait)
    {unsigned int mask = 0;struct poll_dev *dev = filp->private_data;mutex_lock(&dev->poll_mutex);poll_wait(filp, &dev->wq_head, wait);if (dev->count > 0) {mask |= POLLIN | POLLRDNORM;/* decrease each time */dev->count--;}mutex_unlock(&dev->poll_mutex);return mask;
    }static long poll_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
    {struct poll_dev *dev = filp->private_data;unsigned int cnt;switch (cmd) {case POLL_SET_COUNT:mutex_lock(&dev->poll_mutex);if (copy_from_user(&cnt, (void __user *)arg, _IOC_SIZE(cmd))) {pr_err("copy_from_user fail:%d\n", __LINE__);return -EFAULT;}if (dev->count == 0) {wake_up_interruptible(&dev->wq_head);}/* update count */dev->count += cnt;mutex_unlock(&dev->poll_mutex);break;default:return -EINVAL;}return 0;
    }static struct file_operations poll_fops = {.owner = THIS_MODULE,.open = poll_open,.release = poll_close,.poll = poll_poll,.unlocked_ioctl = poll_ioctl,.compat_ioctl = poll_ioctl,
    };static int __init poll_init(void)
    {int ret;if (g_poll_dev == NULL) {g_poll_dev = (struct poll_dev *)kzalloc(sizeof(struct poll_dev), GFP_KERNEL);if (g_poll_dev == NULL) {pr_err("struct poll_dev allocate fail\n");return -1;}}/* allocate device number */ret = alloc_chrdev_region(&g_poll_dev->devno, 0, 1, POLL_DEV_NAME);if (ret < 0) {pr_err("alloc_chrdev_region fail:%d\n", ret);goto alloc_chrdev_err;}/* set char-device */cdev_init(&g_poll_dev->cdev, &poll_fops);g_poll_dev->cdev.owner = THIS_MODULE;ret = cdev_add(&g_poll_dev->cdev, g_poll_dev->devno, 1);if (ret < 0) {pr_err("cdev_add fail:%d\n", ret);goto cdev_add_err;}/* create device */g_poll_dev->class = class_create(THIS_MODULE, POLL_DEV_NAME);if (IS_ERR(g_poll_dev->class)) {pr_err("class_create fail\n");goto class_create_err;}g_poll_dev->device = device_create(g_poll_dev->class, NULL,g_poll_dev->devno, NULL, POLL_DEV_NAME);if (IS_ERR(g_poll_dev->device)) {pr_err("device_create fail\n");goto device_create_err;}mutex_init(&g_poll_dev->poll_mutex);init_waitqueue_head(&g_poll_dev->wq_head);return 0;device_create_err:class_destroy(g_poll_dev->class);
    class_create_err:cdev_del(&g_poll_dev->cdev);
    cdev_add_err:unregister_chrdev_region(g_poll_dev->devno, 1);
    alloc_chrdev_err:kfree(g_poll_dev);g_poll_dev = NULL;return -1;
    }static void __exit poll_exit(void)
    {cdev_del(&g_poll_dev->cdev);device_destroy(g_poll_dev->class, g_poll_dev->devno);unregister_chrdev_region(g_poll_dev->devno, 1);class_destroy(g_poll_dev->class);kfree(g_poll_dev);g_poll_dev = NULL;
    }module_init(poll_init);
    module_exit(poll_exit);MODULE_DESCRIPTION("select/poll test");
    MODULE_AUTHOR("LoyenWang");
    MODULE_LICENSE("GPL");
    

    3.2 测试代码

    测试代码逻辑:

    1. 创建一个设值线程,用于每隔2秒来设置一次count值;

    2. 主线程调用select函数监听,当设值线程设置了count值后,select便会返回;

    #include <stdio.h>
    #include <string.h>
    #include <fcntl.h>
    #include <pthread.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/time.h>static void *set_count_thread(void *arg)
    {int fd = *(int *)arg;unsigned int count_value = 1;int loop_cnt = 20;int ret;while (loop_cnt--) {ret = ioctl(fd, NOTIFY_SET_COUNT, &count_value);if (ret < 0) {printf("ioctl set count value fail:%s\n", strerror(errno));return NULL;}sleep(1);}return NULL;
    }int main(void)
    {int fd;int ret;pthread_t setcnt_tid;int loop_cnt = 20;/* for select use */fd_set rfds;struct timeval tv;fd = open("/dev/poll", O_RDWR);if (fd < 0) {printf("/dev/poll open failed: %s\n", strerror(errno));return -1;}/* wait up to five seconds */tv.tv_sec = 5;tv.tv_usec = 0;ret = pthread_create(&setcnt_tid, NULL,set_count_thread, &fd);if (ret < 0) {printf("set_count_thread create fail: %d\n", ret);return -1;}while (loop_cnt--) {FD_ZERO(&rfds);FD_SET(fd, &rfds);ret = select(fd + 1, &rfds, NULL, NULL, &tv);//ret = select(fd + 1, &rfds, NULL, NULL, NULL);if (ret == -1) {perror("select()");break;}else if (ret)printf("Data is available now.\n");else {printf("No data within five seconds.\n");}}ret = pthread_join(setcnt_tid, NULL);if (ret < 0) {printf("set_count_thread join fail.\n");return -1;}close(fd);return 0;
    }
    

    推荐阅读:

    专辑|Linux文章汇总

    专辑|程序人生

    嵌入式Linux

    微信扫描二维码,关注我的公众号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467590.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SSM框架的搭建学习(1)---MyBatis的环境搭建

SSM(SpringSpringMVCMyBatis)框架为当今最为流行的WEB开发框架之一,基本上涉及数据库的一些增删改查操作都可以借用此框架,本尊此前接的一个小公司关于楼宇空调监控指标的项目就是基于此框架,只不过当时框架已经被别人搭建好,只等我去添砖加瓦,并没有从头开始对此框架进行着手搭…

argv python 提示输入_Python解释器

一、Python解释器我们编写的Python代码都要放在Python解释器上运行&#xff0c;解释器是代码与计算机硬件之间的软件逻辑层。当我们在操作系统上安装好Python之后&#xff0c;它就包含了保证Python运行的最小化组件&#xff1a;解释器 和 标准库。根据选用的Python版本的不同&a…

高阶篇:8.1)开模前评审及提交资料;

本章目的&#xff1a;明确开模前需要提交的资料&#xff0c;确保开模一次性成功。 在现有机械行业制作技术的大环境下&#xff0c;除却必要的机加工零件之外&#xff0c;大部分的零件量产都需要开模制作。如塑胶、钣金、压铸、粉末冶金、锻造等。 但是模具是很贵的&#xff08;…

Android系统充电系统介绍-预防手机充电爆炸

1、锂电池介绍锂离子电池由日本索尼公司于1990年最先开发成功。它是把锂离子嵌入碳&#xff08;石油焦炭和石墨&#xff09;中形成负极&#xff08;传统锂电池用锂或锂合金作负极&#xff09;。正极材料常用LixCoO2 ,也用 LixNiO2 &#xff0c;和LixMnO4 &#xff0c;电解液用L…

Linux物理内存初始化

背景Read the fucking source code! --By 鲁迅A picture is worth a thousand words. --By 高尔基说明&#xff1a;Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio1. 介绍让我们思考…

mysql中%3c%3e和=_Grafana+Prometheus 监控 MySQL

架构图环境IP环境需装软件192.168.0.237mysql-5.7.20node_exporter-0.15.2.linux-amd64.tar.gzmysqld_exporter-0.10.0.linux-amd64.tar.gz192.168.0.248grafanaprometheusprometheus-2.1.0.linux-amd64.tar.gznode_exporter-0.15.2.linux-amd64.tar.gzgrafana-4.6.3.linux-x64…

Linux CentOS7.0 (01)在Vmvare Workstation上 安装配置

一、新建虚拟机 1、创建新的虚拟机 -》 默认典型 -》选择安装介质 2、指定虚拟机名称、安装目录、磁盘容量大小 点击 “完成”&#xff0c;创建虚拟机&#xff01; 随后虚拟机将自动启动安装过程。 二、安装linux 1、选择 English、English&#xff08;United States&#xff0…

i=1,为什么 (++i)+(++i)=6?

源码#include "stdio.h"int main(void) {int i 1;printf("%d\n",(i) (i));return 0; }执行weiqifabsp-ubuntu1804:~/c/undif$ gcc g.c && ./a.out 6 weiqifabsp-ubuntu1804:~/c/undif$为什么出现这个鬼现象&#xff1f;原因很简单&#xff0c;C语…

Android-HIDL实例解析

HIDL 简介“HAL interface definition language or HIDL (pronounced “hide-l”) is an interface description language (IDL) to specify the interface between a HAL and its users. It allows specifying types and method calls, collected into interfaces and package…

子矩阵(NOIP2014 普及组第四题)

描述 给出如下定义&#xff1a; 子矩阵&#xff1a;从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵&#xff08;保持行与 列的相对顺序&#xff09;被称为原矩阵的一个子矩阵。 例如&#xff0c;下面左图中选取第 2、4 行和第 2、4、5 列交叉位置的元素得到一个 2*3 …

linux spinlock/rwlock/seqlock原理剖析(基于ARM64)

背景Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio1. 概述吹起并发机制研究的进攻号角了&#xff01;作为第一篇文章&#xff0c;应该提纲挈领的介绍下并发。什么是并发&#xff0c;…

爸爸都老了

今天是父亲节&#xff0c;早上韦泽楠去上绘画课&#xff0c;我睡了个回笼觉&#xff0c;一觉睡到了十一点。起来的时候老婆买了新鲜的荔枝和龙眼&#xff0c;当然我没有马上吃&#xff0c;我不是一个随便的男人&#xff0c;我刷了牙&#xff0c;洗了脸&#xff0c;再回到客厅慢…

java输出日志_java代码中如何正确使用loggger日志输出

java代码中如何正确使用loggger日志输出发布时间&#xff1a;2019-06-28作者&#xff1a;spider阅读(2980)当你遇到问题的时候&#xff0c;只能通过debug功能来确定问题&#xff0c;你应该考虑打日志&#xff0c;良好的系统&#xff0c;是可以通过日志进行问题定为的。使用slf4…

大学的多级放大电路,你交给老师了吗?

第一章 设计任务1.1项目名称&#xff1a;设计三极管多级音频放大电路本项目的主要内容是设计并实现三极管多级音频放大功能。该电路将所学习的三极管基本放大电路与功率放大电路有机结合。1.2项目设计说明&#xff08;1&#xff09;设计任务和要求使用常见的小功率三极管设计一…

第十四节TypeScript 联合类型

1、简介 联合类型可以通过管道&#xff08;|&#xff09;将变量设置多种类型&#xff0c;赋值时可以根据设置的类型来赋值。 注意&#xff1a;只能赋值指定的类型&#xff0c;如果赋值其它类型就会报错的。 2、创建联合类型的语法格式&#xff1a; Type1|Type2|Type3 实例&a…

Linux进程调度器-基础

背景Read the fucking source code! --By 鲁迅A picture is worth a thousand words. --By 高尔基说明&#xff1a;Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio1. 概述从这篇文章…

每天学习点--------第五天(2017-10-9) 摘要: 常用的集合

今天学习 java.util下面的软件包 包含 collection框架、遗留的coolection类、事件模型、日期和时间设施、国际化和各种工具类&#xff08;字符串标记生成器、随机数生成器和位数组&#xff09; 一、Collenction<.E> 接口 转载于:https://www.cnblogs.com/hanxue112253/p/…

Linux内存,先看这篇文章

内存大小计算我们拿32位系统来举个栗子2^32 ‭4,294,967,296‬ bytes‭4,294,967,296‬ bytes / 1024 ‭4,194,304‬ kbytes4,194,304‬ kbytes / 1024 ‭4,096‬ M‭4,096‬ M /1024 4G物理内存如何分页&#xff1f;分段和分页计算机内存管理的两种方式&#xff0c;这里我…

cloudstack java api_CloudStack API编程指引

前言本文阐述为CloudStack编写新API或者更新已存在API时应遵循的约定和编程指引。参考文档(暂略)介绍当你需要为CS添加新的API时&#xff0c;需要创建一个Request类和Response类(或者在扩展CS API功能时它的API Responese已经定义的情况下重用已经存在的API Response类)。编写C…

在ODM公司要不要跳槽到创业公司

读者朋友提问&#xff1a; 发哥&#xff0c;我现在在手机odm公司做指纹模块做了两三个月&#xff0c;基本天天加班到十点以后&#xff0c;后面要被调到camera团队&#xff0c;但是从这几个月的经历来看&#xff0c;感觉学到的不多&#xff0c;代码都是供应商写的&#xff0c;很…