poll机制可实现有数据的时候就去读,没有数据的时候,如果超过规定一个时间,就表示超时时间。poll机制需要应用程序主动去读,而异步通知并不需要,一旦设备就绪,则主动通知应用程序,应用程序不需要主动查询设备状态,类似于中断的概念,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来 等待信号的到达。
在linux中,异步通知是使用信号来实现的,而在linux,大概有30种信号,比如大家熟悉的ctrl+c的SIGINT信号,进程能够忽略或者捕获除过SIGSTOP和SIGKILL的全部信号,当信号背捕获以后,有相应的函数来处理它。
实现异步通知的四个要素:
一、应用程序要实现:注册信号处理函数,使用signal函数;
二、谁来发?驱动来发;
三、发给谁?发给应用程序,但应用程序必须告诉驱动PID;
四、怎么发?驱动程序使用kill_fasync函数;
问:应该在驱动的哪里调用kill_fasync函数?
答:kill_fasync函数的作用是,当有数据时去通知应用程序,理所当然的应该在用户终端处理函数里调用。
问:file_operations需要添加什么函数指针成员吗?
答:要的,需要添加fasync函数指针,要实现这个函数指针,幸运的是,这个函数仅仅调用了fasync_helper函数,而且这个函数是内核帮我们实现好了,驱动工程师不用修改,fasync_helper函数的作用是初始化/释放fasync_struct
详细请参考驱动源码
#include #include #include #include #include #include #include #include #include #include //class_create#include //S3C2410_GPF1//#include #include //#include #include //wait_event_interruptible#include //poll#include /* 定义并初始化等待队列头 */static DECLARE_WAIT_QUEUE_HEAD(button_waitq);static struct class *fifthdrv_class;static struct device *fifthdrv_device;static struct pin_desc { unsigned int pin; unsigned int key_val;};static struct pin_desc pins_desc[4] = { {S3C2410_GPF1, 0x01}, {S3C2410_GPF4, 0x02}, {S3C2410_GPF2, 0x03}, {S3C2410_GPF0, 0x04},};static int ev_press = 0;/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 *//* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */static unsigned char key_val;int major;static struct fasync_struct *button_fasync;/* 用户中断处理函数 */static irqreturn_t buttons_irq(int irq, void *dev_id){ struct pin_desc *pindesc = (struct pin_desc *)dev_id; unsigned int pinval; pinval = s3c2410_gpio_getpin(pindesc->pin); if(pinval) { /* 松开 */ key_val = 0x80 | (pindesc->key_val); } else { /* 按下 */ key_val = pindesc->key_val; } ev_press = 1;/* 表示中断已经发生 */ wake_up_interrupTIble(&button_waitq); /* 唤醒休眠的进程 */ /* 用kill_fasync函数告诉应用程序,有数据可读了 * button_fasync结构体里包含了发给谁(PID指定) * SIGIO表示要发送的信号类型 * POLL_IN表示发送的原因(有数据可读了) */ kill_fasync(&button_fasync, SIGIO, POLL_IN); return IRQ_HANDLED;}staTIc int fifth_drv_open(struct inode * inode, struct file * filp){ /* K1 ---- EINT1,K2 ---- EINT4,K3 ---- EINT2,K4 ---- EINT0 * 配置GPF1、GPF4、GPF2、GPF0为相应的外部中断引脚 * IRQT_BOTHEDGE应该改为IRQ_TYPE_EDGE_BOTH */ request_irq(IRQ_EINT1, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K1",&pins_desc[0]); request_irq(IRQ_EINT4, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K2",&pins_desc[1]); request_irq(IRQ_EINT2, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K3",&pins_desc[2]); request_irq(IRQ_EINT0, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K4",&pins_desc[3]); return 0;}staTIc ssize_t fifth_drv_read(struct file *file, char __user *user, size_t size,loff_t *ppos){ if (size != 1) return -EINVAL; /* 当没有按键按下时,休眠。 * 即ev_press = 0; * 当有按键按下时,发生中断,在中断处理函数会唤醒 * 即ev_press = 1; * 唤醒后,接着继续将数据通过copy_to_user函数传递给应用程序 */ wait_event_interrupTIble(button_waitq, ev_press); copy_to_user(user, &key_val, 1); /* 将ev_press清零 */ ev_press = 0; return 1;}static int fifth_drv_close(struct inode *inode, struct file *file){ free_irq(IRQ_EINT1,&pins_desc[0]); free_irq(IRQ_EINT4,&pins_desc[1]); free_irq(IRQ_EINT2,&pins_desc[2]); free_irq(IRQ_EINT0,&pins_desc[3]); return 0;}static unsigned int fifth_drv_poll(struct file *file, poll_table *wait){ unsigned int mask = 0; /* 该函数,只是将进程挂在button_waitq队列上,而不是立即休眠 */ poll_wait(file, &button_waitq, wait); /* 当没有按键按下时,即不会进入按键中断处理函数,此时ev_press = 0 * 当按键按下时,就会进入按键中断处理函数,此时ev_press被设置为1 */ if(ev_press) { mask |= POLLIN | POLLRDNORM; /* 表示有数据可读 */ } /* 如果有按键按下时,mask |= POLLIN | POLLRDNORM,否则mask = 0 */ return mask;}/* 当应用程序调用了fcntl(fd, F_SETFL, Oflags | FASYNC); * 则最终会调用驱动的fasync函数,在这里则是fifth_drv_fasync * fifth_drv_fasync最终又会调用到驱动的fasync_helper函数 * fasync_helper函数的作用是初始化/释放fasync_struct */static int fifth_drv_fasync(int fd, struct file *filp, int on){ return fasync_helper(fd, filp, on, &button_fasync);}/* File operations struct for character device */static const struct file_operations fifth_drv_fops = { .owner = THIS_MODULE, .open = fifth_drv_open, .read = fifth_drv_read, .release = fifth_drv_close, .poll = fifth_drv_poll, .fasync = fifth_drv_fasync,};/* 驱动入口函数 */static int fifth_drv_init(void){ /* 主设备号设置为0表示由系统自动分配主设备号 */ major = register_chrdev(0, "fifth_drv", &fifth_drv_fops); /* 创建fifthdrv类 */ fifthdrv_class = class_create(THIS_MODULE, "fifthdrv"); /* 在fifthdrv类下创建buttons设备,供应用程序打开设备*/ fifthdrv_device = device_create(fifthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); return 0;}/* 驱动出口函数 */static void fifth_drv_exit(void){ unregister_chrdev(major, "fifth_drv"); device_unregister(fifthdrv_device); //卸载类下的设备 class_destroy(fifthdrv_class); //卸载类}module_init(fifth_drv_init); //用于修饰入口函数module_exit(fifth_drv_exit); //用于修饰出口函数MODULE_AUTHOR("LWJ");MODULE_DESCRIPTION("Just for Demon");MODULE_LICENSE("GPL"); //遵循GPL协议
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
179
180
181
182
183
184
185
186
应用测试程序源码
#include #include #include #include #include //sleep#include #include #include int fd;void mysignal_fun(int signum){ unsigned char key_val; read(fd, &key_val, 1); printf("key_val = 0x%x\n", key_val);}int main(int argc ,char *argv[]){ int flag; signal(SIGIO, mysignal_fun); fd = open("/dev/buttons", O_RDWR); if (fd < 0) { printf("open error\n"); } /* F_SETOWN: Set the process ID * 告诉内核,发给谁 */ fcntl(fd, F_SETOWN, getpid()); /* F_GETFL :Read the file status flags * 读出当前文件的状态 */ flag = fcntl(fd, F_GETFL); /* F_SETFL: Set the file status flags to the value specified by arg * int fcntl(int fd, int cmd, long arg); * 修改当前文件的状态,添加异步通知功能 */ fcntl(fd, F_SETFL, flag | FASYNC); while(1) { /* 为了测试,主函数里,什么也不做 */ sleep(1000); } 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
45
46
47
48
49
50
51
52
53
当无按键按下时,应用测试程序一直在sleep,当有按键按下时,signal会被调用,最终会调用mysignal_fun,在此函数里read(fd, &key_val, 1);会去读出按键值,这样一来,应用程序就相当于不用主动去读数据了,每当驱动里有数据时,就会告诉应用程序有数据了,此时read函数才会被调用。
总结
为了使设备支持异步通知机制,驱动程序中涉及以下3项工作:
支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。 不过此项工作已由内核完成,设备驱动无须处理。
支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。驱动中应该实现fasync()函数。
在设备资源可获得时,调用kill_fasync()函数激发相应的信号
应用程序:
fcntl(fd, F_SETOWN, getpid()); // 告诉内核,发给谁Oflags = fcntl(fd, F_GETFL);fcntl(fd, F_SETFL, Oflags | FASYNC); // 改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_s