【原创】Linux select/poll机制原理分析
前言
Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基
1. 概述
Linux系统在访问设备的时候,存在以下几种IO模型:
Blocking IO Model,阻塞IO模型
;Nonblocking I/O Model,非阻塞IO模型
;I/O Multiplexing Model,IO多路复用模型
;Signal Driven I/O Model,信号驱动IO模型
;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
函数中,有几个关键的操作:- 初始化
poll_wqueues
结构,包括几个关键函数指针的初始化,用于驱动中进行回调处理; - 循环遍历监测的文件描述符,并且调用
f_op->poll()
函数,如果有监测条件满足,则会跳出循环; - 在监测的文件描述符都不满足条件时,
poll_schedule_timeout
让当前进程进行睡眠,超时唤醒,或者被所属的等待队列唤醒;
- 初始化
do_select
函数的循环退出条件有三个:- 检测的文件描述符满足条件;
- 超时;
- 有信号要处理;
- 在设备驱动程序中实现的
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
结构,其中两个关键字段:pll_table
:该结构体中的函数指针_qproc
指向__pollwait
函数;struct poll_table_entry[]
:存放不同设备的poll_table_entry
,这些条目的增加是在驱动调用poll_wait->__pollwait()
时进行初始化并完成添加的;
2.4 驱动编写启示
如果驱动中要支持select
的接口调用,那么需要做哪些事情呢?
如果理解了上文中的内容,你会毫不犹豫的大声说出以下几条:
- 定义一个等待队列头
wait_queue_head_t
,用于收留等待队列任务; struct file_operations
结构体中的poll
函数需要实现,比如xxx_poll()
;xxx_poll()
函数中,当然不要忘了poll_wait
函数的调用了,此外,该函数的返回值mask
需要注意是在条件满足时对应的值,比如EPOLLIN/EPOLL/EPOLLERR
等,这个返回值是在do_select()
函数中会去判断处理的;- 条件满足的时候,
wake_up_interruptible
唤醒任务,当然也可以使用wake_up
,区别是:wake_up_interruptible
只能唤醒处于TASK_INTERRUPTIBLE
状态的任务,而wake_up
能唤醒处于TASK_INTERRUPTIBLE
和TASK_UNINTERRUPTIBLE
状态的任务;
2.5 select/poll
的差异
select
与poll
本质上基本类似,其中select
是由BSD UNIX
引入,poll
由SystemV
引入;select
与poll
需要轮询文件描述符集合,并在用户态和内核态之间进行拷贝,在文件描述符很多的情况下开销会比较大,select
默认支持的文件描述符数量是1024;- Linux提供了
epoll
机制,改进了select
与poll
在效率与资源上的缺点,未深入了解;
3. 示例代码
3.1 内核驱动
示例代码中的逻辑:
- 驱动维护一个count值,当count值大于0时,表明条件满足,poll返回正常的mask值;
- poll函数每执行一次,count值就减去一次;
- 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 测试代码
测试代码逻辑:
- 创建一个设值线程,用于每隔2秒来设置一次count值;
- 主线程调用
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 select/poll机制原理分析的更多相关文章
- Linux select/poll和epoll实现机制对比
关于这个话题,网上已经介绍的比较多,这里只是以流程图形式做一个简单明了的对比,方便区分. 一.select/poll实现机制 特点: 1.select/poll每次都需要重复传递全部的监听fd进来,涉 ...
- 【转载】Select函数实现原理分析
Select函数实现原理分析 <原文> select需要驱动程序的支持,驱动程序实现fops内的poll函数.select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用 ...
- 阿里系产品Xposed Hook检测机制原理分析
阿里系产品Xposed Hook检测机制原理分析 导语: 在逆向分析android App过程中,我们时常用的用的Java层hook框架就是Xposed Hook框架了.一些应用程序厂商为了保护自家a ...
- Linux之poll机制分析
应用程序访问1个设备文件时可用阻塞/非阻塞方式.如果是使用阻塞方式,则直接调用open().read().write(),但是在驱动程序层会判断是否可读/可写,如果不可读/不可写,则将当前进程休眠,直 ...
- select/poll/epoll原理探究及总结
select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.但select ...
- Redis 发布/订阅机制原理分析
Redis 通过 PUBLISH. SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能. 这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播.实时 ...
- ScrollView嵌套ListView,ListView完全展开及makeMeasureSpec测量机制原理分析
在实际应用中,经常会碰到非常规的布局要求,比如说在ScrollView里嵌套ListView,ScrollView和ListView都是可以滚动的控件,这样布局看似很奇怪,但是有些效果又不得不这样做. ...
- Linux身份鉴别机制原理
传统的UNIX身份鉴别机制原理 传统的UNIX身份鉴别即口令认证方式,它主要通过识别用户的用户名或者UID号获取在/etc/shadow中存放的对应用户密码密文等信息,然后获取用户输入密码并采用cry ...
- linux select poll and epoll
这里以socket文件来阐述它们之间的区别,假设现在服务器端有100 000个连接,即已经创建了100 000个socket. 1 select和poll 在我们的线程中,我们会弄一个死循环,在循环里 ...
随机推荐
- k8s环境部署本地.net core web项目
上一篇文章,我们部署了docker+k8s环境,简单测试通过,但是,还没能将我们自己的项目部署上去,继续记录部署踩坑过程. 一.准备工作 1.当然是docker+k8s环境了,详情请看上一篇文档 ht ...
- 某图片站反爬加密字段x-api-key破解
前言 此次逆向的是某“你们都懂”领域的图片站,目前此站限制注册,非会员无法访问:前两天偶然搞到了份邀请码,进入后发现质量还可以,于是尝试爬取,在爬虫编写过程中发现此站点采用了不少手段来阻止自动化脚本( ...
- 攻防世界Mobile6 app1 XCTF详解
XCTF_app1 先安装看看 点击芝麻开门之后会弹出“年轻人不要耍小聪明噢” 这大概就能看懂是点击之后进行判断,那就直接去看JEB,看看判断条件是什么 V1是输入的字符串,V2获取包信息(百度的), ...
- CSS+DIV自适应布局
CSS+DIV自适应布局 1.两列布局(左右两侧,左侧固定宽度200px;右侧自适应占满) 代码如下: <!doctype html> <html> <head> ...
- Django开发框架知识点
一.什么是web服务器(了解) 当我们在浏览器输入URL后,浏览器会先请求DNS服务器,获得请求站点的 IP 地址.然后发送一个HTTP Request(请求)给拥有该 IP 的主机,接着就会接收到服 ...
- 内存:你跑慢点行不行?CPU:跑慢点你养我吗?内存:我不管!(内附超全思维导图)
主存(RAM) 是一件非常重要的资源,必须要认真对待内存.虽然目前大多数内存的增长速度要比 IBM 7094 要快的多,但是,程序大小的增长要比内存的增长还快很多.不管存储器有多大,程序大小的增长速度 ...
- Js逆向-滑动验证码图片还原
本文列举两个例子:某象和某验的滑动验证 一.某验:aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby9zbGlkZS1mbG9hdC5odG1s 未还原图像: 还原后的图: ...
- springcloud gateway整合sentinel
1.引入依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spri ...
- Linux下MongoDB单实例的安装和配置详解
推荐网站 MongoDB官网:http://www.mongodb.org/ MongoDB学习网站:http://www.runoob.com/mongodb 一.创建MongoDB的资源目录和安装 ...
- Simulink仿真入门到精通(八) M语言对Simulink模型的自动化操作及配置
8.1 M语言控制模型的仿真 M语言与Simulink结合的方式: 在Simulink模型或模块中使用回调函数 在M语言中调用与模型相关的命令,控制模型的建立,设置模块的属性,增删信号线,以及运行模型 ...