05、信号驱动 IO 实验 *
信号驱动 IO 是最后一个 IO 模型:
一、信号驱动 IO 简介
1.1、信号驱动IO的比喻理解
想象小马想吃鱼但不想自己守着钓鱼。他请助手帮忙:
- 不等鱼上钩:小马立刻去忙其他事,程序不会卡在等待状态;
- 自动通知:一旦有鱼上钩,助手会立刻把鱼收拾好并放到指定位置,然后立刻通知小马来取;
- 处理数据:小马收到通知后回来取鱼(处理数据),全程无需主动查看鱼竿状态。
1.2、实现信号驱动IO的3个步骤
步骤1:注册信号处理函数
用 signal()
函数告诉系统:"当收到 SIGIO
信号时,请执行我指定的某个函数"。 (比如:定义一个 handle_fish()
函数,专门用来处理"取鱼"动作)
步骤2:指定接收信号的进程
通过 fcntl(fd, F_SETOWN, pid)
告诉系统:"当设备有数据了,要给进程ID是 pid
的程序发信号"。 (类似设置"紧急联系人":信号只能发给特定进程)
步骤3:开启异步IO模式
用 fcntl(fd, F_SETFL, O_ASYNC)
打开设备的异步通知功能。 (就像告诉助手:"开始工作,有鱼就立刻通知我")
1.3、fcntl
函数详解
fcntl
是用来操作文件描述符(如设备、网络连接)的工具,常用命令如下:
命令名 | 作用 |
---|---|
F_SETOWN | 设置接收 SIGIO 信号的目标进程ID或进程组ID |
F_SETFL | 开启/关闭文件状态标志(如 O_ASYNC 异步模式) |
F_GETOWN | 查询当前接收信号的目标ID |
F_GETFL | 查询文件当前的模式标志(如是否启用异步) |
1.4、fasync
函数详解
以下是更直白简洁的改写版本:
实现驱动程序的 fasync
方法步骤
步骤 1:在驱动中注册 **fasync**
函数
当应用程序开启“信号驱动IO”模式时,系统会调用驱动中的 fasync
函数。 你需要在驱动的 file_operations
结构体中定义这个函数,函数格式如下:
c
int (*fasync)(int fd, struct file *filp, int on);
1
fd
:文件描述符filp
:指向文件的指针on
:非零值表示启用异步通知,0表示禁用
步骤 2:用 **fasync_helper**
管理异步通知
在 fasync
函数内部,直接调用内核提供的 fasync_helper
来管理异步通知列表。它的格式是:
c
int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp);
1
- 这个函数会自动帮你维护
fasync_struct
结构体(存储需要通知的进程信息)。 - 你只需传入自己的
fapp
指针(通常在驱动中定义为全局或设备私有变量)。
步骤 3:设备就绪时通知应用程序
当设备数据可读或可写时,调用 kill_fasync
发送信号给应用程序:
c
void kill_fasync(struct fasync_struct **fp, int sig, int band);
1
fp
:指向之前用fasync_helper
管理的fasync_struct
列表sig
:发送的信号(通常是SIGIO
)- 设备可读时填
POLLIN
- 设备可写时填
POLLOUT
- 设备可读时填
二、实验
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>
#include <linux/fcntl.h>
#include <linux/signal.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 fasync_struct *fasync;
};
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);
kill_fasync(&test_dev->fasync,SIGIO,POLLIN);
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;
}
static int cdev_test_fasync (int fd, struct file *file, int on)
{
struct device_test *test_dev=(struct device_test *)file->private_data; //设置私有数据
return fasync_helper(fd,file,on,&test_dev->fasync);
}
/*设备操作函数*/
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(...)函数
.fasync = cdev_test_fasync, //将fasync字段指向cdev_test_fasync(...)函数
};
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
168
169
170
171
172
173
174
175
176
177
178
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
168
169
170
171
172
173
174
175
176
177
178
2.2、应用
Read
C
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#include <fcntl.h>
#include <signal.h>
int fd;
char buf1[32] = {0};
//SIGIO信号的信号处理函数
static void func(int signum)
{
read(fd,buf1,32);
printf ("buf is %s\n",buf1);
}
int main(int argc, char *argv[])
{
int ret;
int flags;
fd = open("/dev/test", O_RDWR); //打开led驱动
if (fd < 0)
{
perror("open error \n");
return fd;
}
signal(SIGIO,func); //步骤一:使用signal函数注册SIGIO信号的信号处理函数
//步骤二:设置能接收这个信号的进程
//fcntl函数用来操作文件描述符,
//F_SETOWN 设置当前接收的SIGIO的进程ID
fcntl(fd,F_SETOWN,getpid());
flags = fcntl(fd,F_GETFD); //获取文件描述符标志
//步骤三 开启信号驱动IO 使用fcntl函数的F_SETFL命令打开FASYNC标志
fcntl(fd,F_SETFL,flags| FASYNC);
while(1);
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
Write
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