04、IO 多路复用实验 *
一、IO 多路复用简介
IO多路复用就像一个人同时盯着多个信号源,等有信号来了再处理。
Linux系统里主要有三种实现方式:
- select
- poll
- epoll
它们的区别可以这样理解:
1.1、核心区别
监控方式不同
- select/poll:像"挨个检查" 它们会主动轮询所有文件描述符(比如网络连接、文件),每隔一段时间挨个查看哪个有数据可读/可写。 ➡️ 类比:你每隔5分钟去检查10个鱼塘有没有鱼上钩。
- epoll:像"直接通知" 提前注册监听需要关注的文件描述符,当有数据时内核会主动通知。 ➡️ 类比:每个鱼塘装了铃铛,鱼上钩时自动响铃提醒你。
性能表现不同
select/poll:
- 低效:每次都要遍历所有监控对象,连接越多耗时越长。
- 有上限:select默认最多监控1024个连接(可修改),poll理论上无上限但效率下降。
epoll:
- 高效:只处理触发事件的连接,几乎不浪费CPU资源。
- 无数量限制:能轻松处理成千上万个连接。
实现原理不同
- select/poll:应用层主动询问内核"哪些连接有数据"。
- epoll:内核维护一个事件表,当连接有数据时自动推送事件给应用。
1.2、简单对比总结
特性 | select/poll | epoll |
---|---|---|
监控方式 | 主动轮询(不断检查所有连接) | 被动通知(内核主动推送事件) |
性能 | 连接多时效率低 | 连接多时仍保持高性能 |
描述符限制 | select默认1024,poll无硬限制但效率低 | 无数量限制,扩展性极强 |
适用场景 | 小规模连接 | 高并发服务器(如Web、游戏服务器) |
1.3、为什么epoll更快?
- epoll的"预注册"机制: 提前告诉内核"这些连接我要监听",当数据到达时内核直接通知,无需反复扫描所有连接。
- 只处理触发事件: 比如1000个连接中只有3个有数据,epoll只需处理这3个,而select/poll需要检查全部1000个。
1.4、举个简单的例子
- select/poll: 你站在鱼塘边,每隔1分钟去每个鱼竿旁看一眼:"这条有鱼吗?那条有鱼吗?"
- epoll: 每个鱼竿装了感应器,鱼上钩时自动发消息:"第5号鱼竿有鱼!"
这样,epoll就像拥有"智能报警系统",而select/poll只能靠"人工巡查"。这就是为什么epoll更适合高并发场景的原因。
二、poll函数介绍
2.1、接口介绍
作用: 同时监控多个文件描述符的状态变化,等待它们有活动发生(比如数据可读、可写或出错)。
参数说明:
fds
:一个结构体数组,每个元素包含要监控的文件描述符和事件类型。nfds
:数组中有多少个文件描述符需要监控。timeout
:等待超时时间(毫秒),-1表示无限等待,0表示立即返回。
每个文件描述符的结构(pollfd):
C
struct pollfd {
int fd; // 要监控的文件描述符(如打开的文件、网络套接字等)
short events; // 我们想监控的事件类型(如数据可读、可写等)
short revents; // 实际发生的事件(由系统填充)
};
1
2
3
4
5
2
3
4
5
可监控的事件类型(events):
POLLIN
:有数据可读(比如网络数据到达或文件可读)。POLLOUT
:文件描述符可写(比如网络连接可用或缓冲区有空间)。POLLERR
:文件描述符出错。POLLHUP
:文件描述符挂起(比如对方关闭了连接)。POLLNVAL
:无效的请求(比如文件描述符不存在)。
2.2、驱动程序中的poll函数
当应用程序调用poll()
时,Linux会调用设备驱动中对应的poll
函数,告诉驱动“用户想监控这个文件描述符的哪些状态变化”。
驱动poll函数原型:
C
unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait);
1
参数:
filp
:应用程序打开的文件描述符对应的文件对象。wait
:系统提供的参数,驱动需将其传递给poll_wait()
函数。
功能步骤:
注册等待队列: 通过调用
poll_wait(filp, 队列头, wait)
,将驱动内部的等待队列注册到系统中。 (例如:如果设备有数据可读时,需要通知应用程序。)返回状态码: 根据设备当前状态,返回一个数字(如
POLLIN
表示可读,POLLOUT
表示可写)。 常见返回值:POLLIN
:数据可读。POLLOUT
:可写入数据。
关键函数:poll_wait的作用:
C
void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);
1
- 将驱动的
queue
队列与当前poll
调用关联。 - 当队列被触发(如设备有数据到达)时,系统会唤醒应用程序。
- 注意:
poll_wait()
不会让程序阻塞,只是“注册监听”。
2.3、总结流程
- 应用程序调用
poll(fds, ...)
,告诉系统要监控哪些文件描述符。 - 系统调用驱动的
poll
函数,驱动注册自己的等待队列。 - 当设备状态变化(如数据到达),驱动通知系统,系统标记对应
pollfd.revents
。 poll()
返回,应用程序根据revents
处理事件。
二、实验
2.1、驱动代码
C
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/wait.h>
#include <linux/poll.h>
struct device_test{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_test; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
int flag; //标志位
};
struct device_test dev1;
DECLARE_WAIT_QUEUE_HEAD(read_wq); //定义并初始化等待队列头
/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;//设置私有数据
return 0;
}
/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test *)file->private_data;
if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
{
printk("copy_from_user error\r\n");
return -1;
}
test_dev->flag=1;
wake_up_interruptible(&read_wq);
return 0;
}
/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test *)file->private_data;
if(file->f_flags & O_NONBLOCK ){
if (test_dev->flag !=1)
return -EAGAIN;
}
wait_event_interruptible(read_wq,test_dev->flag);
if (copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据
{
printk("copy_to_user error\r\n");
return -1;
}
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)
{
return 0;
}
static __poll_t cdev_test_poll(struct file *file, struct poll_table_struct *p){
struct device_test *test_dev=(struct device_test *)file->private_data; //设置私有数据
__poll_t mask=0;
poll_wait(file,&read_wq,p); //应用阻塞
if (test_dev->flag == 1)
{
mask |= POLLIN;
}
return mask;
}
/*设备操作函数*/
struct file_operations cdev_test_fops = {
.owner = THIS_MODULE, //将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = cdev_test_open, //将open字段指向chrdev_open(...)函数
.read = cdev_test_read, //将open字段指向chrdev_read(...)函数
.write = cdev_test_write, //将open字段指向chrdev_write(...)函数
.release = cdev_test_release, //将open字段指向chrdev_release(...)函数
.poll = cdev_test_poll, //将poll字段指向chrdev_poll(...)函数
};
static int __init chr_fops_init(void) //驱动入口函数
{
/*注册字符设备驱动*/
int ret;
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号
if (ret < 0)
{
goto err_chrdev;
}
printk("alloc_chrdev_region is ok\n");
dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号
printk("major is %d \r\n", dev1.major); //打印主设备号
printk("minor is %d \r\n", dev1.minor); //打印次设备号
/*2 初始化cdev*/
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_fops);
/*3 添加一个cdev,完成字符设备注册到内核*/
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if(ret<0)
{
goto err_chr_add;
}
/*4 创建类*/
dev1. class = class_create(THIS_MODULE, "test");
if(IS_ERR(dev1.class))
{
ret=PTR_ERR(dev1.class);
goto err_class_create;
}
/*5 创建设备*/
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if(IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_device_create;
}
return 0;
err_device_create:
class_destroy(dev1.class); //删除类
err_class_create:
cdev_del(&dev1.cdev_test); //删除cdev
err_chr_add:
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
err_chrdev:
return ret;
}
static void __exit chr_fops_exit(void) //驱动出口函数
{
/*注销字符设备*/
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
cdev_del(&dev1.cdev_test); //删除cdev
device_destroy(dev1.class, dev1.dev_num); //删除设备
class_destroy(dev1.class); //删除类
}
module_init(chr_fops_init);
module_exit(chr_fops_exit);
MODULE_LICENSE("GPL v2");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
2.2、应用实验代码
读
C
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
int main(int argc, char *argv[])
{
int fd;//要监视的文件描述符
char buf1[32] = {0};
char buf2[32] = {0};
struct pollfd fds[1];
int ret;
fd = open("/dev/test", O_RDWR); //打开/dev/test设备,阻塞式访问
if (fd < 0)
{
perror("open error \n");
return fd;
}
fds[0] .fd =fd;
fds[0].events = POLLIN;
printf("read before \n");
while (1)
{
ret = poll(fds,1,3000);
if(!ret){
printf("time out !!\n");
}else if(fds[0].revents == POLLIN)
{
read(fd,buf1,sizeof(buf1)); //从/dev/test文件读取数据
printf("buf is %s \n",buf1);
sleep(1);
}
}
printf("read after\n");
close(fd); //关闭文件
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
写
C
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd;
char buf1[32] = {0};
char buf2[32] = "nihao";
fd = open("/dev/test",O_RDWR); //打开led驱动
if (fd < 0)
{
perror("open error \n");
return fd;
}
printf("write before \n");
write(fd,buf2,sizeof(buf2)); //向/dev/test文件写入数据
printf("write after\n");
close(fd); //关闭文件
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25