抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

1.异步通知

1.1异步通知简介

中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。

同样的, Linux 应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。

这两种方式都需要应用程序主动的去查询设备的使用情况,如果能提供一种类似中断的机制,当驱动程序可以访问的时候主动告诉应用程序那就最好了。

“信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。

阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法,没有优劣之分,在实际的工作和学习中,根据自己的实际需求选择合适的处理方法即可。

异步通知的核心就是信号,在 arch/xtensa/include/uapi/asm/signal.h 文件中定义了 Linux 所支持的所有信号 :

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
#define SIGHUP 1 /* 终端挂起或控制进程终止 */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4 /* 非法指令 */
#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT 指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号 1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号 2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO /* #define SIGLOS 29 */
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */
  • 除了 SIGKILL(9)和 SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以忽略。

用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal 函数来设置指定信号的处理函数, signal 函数原型如下所示:

1
sighandler_t signal(int signum, sighandler_t handler)
  • signum:要设置处理函数的信号。

  • handler: 信号的处理函数。

  • 返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回SIG_ERR。

信号处理函数原型如下所示:

1
typedef void (*sighandler_t)(int)

1.2驱动中的信号处理

1.2.1fasync_struct 结构体

首先我们需要在驱动程序中定义一个 fasync_struct 结构体指针变量, fasync_struct 结构体内容如下:

1
2
3
4
5
6
7
8
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
}

一般将 fasync_struct 结构体指针变量定义到设备结构体中 :

1
2
3
4
5
6
7
struct imx6uirq_dev {
struct device *dev;
struct class *cls;
struct cdev cdev;
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};

1.2.2async 函数

如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数,此函数格式如下所示:

1
int (*fasync) (int fd, struct file *filp, int on)

fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针, fasync_helper 函数原型如下:

1
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

fasync_helper 函数的前三个参数就是 fasync 函数的那三个参数,第四个参数就是要初始化的 fasync_struct 结构体指针变量。当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行。

1.2.3kill_fasync函数

当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。 kill_fasync函数负责发送指定的信号, kill_fasync 函数原型如下所示:

1
void kill_fasync(struct fasync_struct **fp, int sig, int band)
  • fp:要操作的 fasync_struct

  • sig: 要发送的信号

  • band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT

  • 返回值: 无

1.2.4应用程序对异步通知的处理

应用程序对异步通知的处理包括以下三步:

  • 注册信号处理函数

应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用

signal 函数来设置信号的处理函数。

  • 将本应用程序的进程号告诉给内核

使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。

  • 开启异步通知

使用如下两行程序开启异步通知:

1
2
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。

  • 要使用fasync_struct定义一个指针结构体函数。

  • 实现file_operations里面的fasync函数,函数原型:

    int (*fasync)(int,struct file *,int);

    fasync还需要借助fasync_helper函数。

  • 驱动里面调用fasync向应用发送信号,函数原型:

    void kill_fasync(struct fasync_struct **fp,int sig,int band)

  • 关闭驱动的时候要删除信号。

2.实验

2.1驱动代码

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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define im6uirq_CNT 1 /* 设备号个数 */
#define im6uirq_NAME "imx6uirq" /* 名字 */

#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0XFF



struct irq_key_dev
{
int gpio; //io编号
int irqnum; //中断号
unsigned char value; //键值
char name[10]; //名字

irqreturn_t (*handler)(int,void *); //中断处理函数


struct tasklet_struct tasklet;
};


struct im6uirq_dev
{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */

struct cdev cdev; /* cdev */

struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */

struct irq_key_dev irq_key[KEY_NUM];

struct timer_list timer;

atomic_t keyvalue;
atomic_t releasekey;

struct fasync_struct *fasync_queue;

};

struct im6uirq_dev im6uirq;




static int im6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &im6uirq;
return 0;
}

static int im6uirq_fasync(int fd, struct file *filp, int on)
{
struct im6uirq_dev *dev = filp->private_data;
return fasync_helper(fd, filp, on, &dev->fasync_queue);
}
static int im6uirq_release(struct inode *inode,struct file *filp)
{

return im6uirq_fasync(-1, filp, 0);
}

static ssize_t im6uirq_write(struct file *filp,const char __user *buf,size_t count,loff_t *offt)
{


return 0;
}

static ssize_t im6uirq_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
int ret;
unsigned char keyvalue;
unsigned char releasekey;
struct im6uirq_dev *dev = filp->private_data;

keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);

if (releasekey) //有效按键
{
if (keyvalue & 0x80)
{
keyvalue &= ~0x80;
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
}else{
goto data_error;
}

atomic_set(&dev->releasekey,0);
}
else {
goto data_error;
}

return ret;
data_error:
return -EINVAL;

}



/* 1.操作集 */
static const struct file_operations im6uirq_fops =
{
.owner = THIS_MODULE,
.open = im6uirq_open,
.release = im6uirq_release,
.write = im6uirq_write,
.read = im6uirq_read,
.fasync = im6uirq_fasync,
};


//中断处理函数
static irqreturn_t key0_irq_handler(int irq,void *dev_id)
{
struct im6uirq_dev *dev = dev_id;



// dev->timer.data = (volatile unsigned long)dev_id;
// mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms

tasklet_schedule(&dev->irq_key[0].tasklet);

return IRQ_HANDLED;
}


/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
int value = 0;
struct im6uirq_dev *dev = (struct im6uirq_dev *)arg;

value = gpio_get_value(dev->irq_key[0].gpio);

if (value == 0) //按下
{
atomic_set(&dev->keyvalue, dev->irq_key[0].value);
}else if (value == 1)
{
atomic_set(&dev->keyvalue, 0x80 | dev->irq_key[0].value);
atomic_set(&dev->releasekey,1);
}

if(atomic_read(&dev->releasekey)) { //有效按键过程
if (dev->fasync_queue)
kill_fasync(&dev->fasync_queue, SIGIO, POLL_IN);
}
}

static void key_tasklet(unsigned long data)
{
struct im6uirq_dev *dev = (struct im6uirq_dev *)data;

dev->timer.data = (volatile unsigned long)data;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms
}

static int keyio_init(struct im6uirq_dev *dev)
{
int ret;
int i;
/* 1.按键初始化 */
dev->nd = of_find_node_by_path("/key");
if (dev->nd == NULL)
{
printk("fail find nd\r\n");
ret = -EINVAL;
goto fail_nd;
}

for (i = 0; i < KEY_NUM; i++)
{
ret = dev->irq_key[i].gpio = of_get_named_gpio(dev->nd,"key-gpios",i);
if (ret < 0){
printk("can't get key%d\r\n",i);
goto fail_get_gpio;
}
}

for (i = 0; i < KEY_NUM; i++)
{
memset(dev->irq_key[i].name,0,sizeof(dev->irq_key[i].name)); /* 缓冲区清零 */
sprintf(dev->irq_key[i].name,"key%d",i);
gpio_request(dev->irq_key[i].gpio,dev->irq_key[i].name);
gpio_direction_input(dev->irq_key[i].gpio);

dev->irq_key[i].irqnum = gpio_to_irq(dev->irq_key[i].gpio); /* 获取中断号 */
#if 0
dev->irq_key[i].irqnum = irq_of_parse_and_map(dev->nd,i);
#endif
}

dev->irq_key[0].handler = key0_irq_handler;
dev->irq_key[0].value = KEY0VALUE;
/* 2.按键中断初始化 */
for(i = 0; i < KEY_NUM; i++)
{
ret = request_irq(dev->irq_key[i].irqnum,dev->irq_key[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irq_key[i].name,&im6uirq);

if (ret)
{
printk("request %d irq failed\r\n",dev->irq_key[i].irqnum);
ret = -EINVAL;
goto fail_requese_irq;
}
tasklet_init(&dev->irq_key[0].tasklet,key_tasklet,(unsigned long)dev);
}


init_timer(&dev->timer);
dev->timer.function = timer_func;

return 0;
fail_requese_irq:
for (i = 0; i < KEY_NUM; i++){
gpio_free(dev->irq_key[i].gpio);
}
fail_get_gpio:
fail_nd:
return ret;
}


static int __init im6uirq_init(void)
{
int ret;

/* 1.注册字符设备驱动*/
im6uirq.major = 0;
if (im6uirq.major) /* 定义了设备号 */
{
im6uirq.devid = MKDEV(im6uirq.major, 0);
register_chrdev_region(im6uirq.devid,im6uirq_CNT,im6uirq_NAME);
}
else{ /* 没有定义设备号 */
ret = alloc_chrdev_region(&im6uirq.devid,0,im6uirq_CNT,im6uirq_NAME);
im6uirq.major = MAJOR(im6uirq.devid); /* 获取分配号的主设备号 */
im6uirq.minor = MINOR(im6uirq.devid); /* 获取分配号的次设备号 */
}
printk("major = %d,minor = %d\r\n",im6uirq.major,im6uirq.minor);

if(ret < 0)
{
printk("fail devid\r\n");
goto fail_devid;
}

/* 2.初始化cdev*/
im6uirq.cdev.owner = THIS_MODULE;
cdev_init(&im6uirq.cdev,&im6uirq_fops);

/* 3.添加设备 */
ret = cdev_add(&im6uirq.cdev,im6uirq.devid,im6uirq_CNT);
if (ret < 0)
{
printk("fail cdevadd\r\n");
goto fail_cdevadd;
}

/* 4.创建类 */
im6uirq.class = class_create(THIS_MODULE,im6uirq_NAME);
if (IS_ERR(im6uirq.class))
{
printk("fail class\r\n");
ret = PTR_ERR(im6uirq.class);
goto fail_class;
}

/* 5.创建设备 */
im6uirq.device = device_create(im6uirq.class,NULL,im6uirq.devid,NULL,im6uirq_NAME);
if (IS_ERR(im6uirq.device))
{
printk("fail device\r\n");
ret = PTR_ERR(im6uirq.device);
goto fail_device;
}

//初始按键
ret = keyio_init(&im6uirq);
if (ret)
{
printk("fail keyio_init\r\n");
goto fail_keyio_init;
}

//初始化原子变量
atomic_set(&im6uirq.keyvalue,INVAKEY);
atomic_set(&im6uirq.releasekey,0);



printk("all init suggess!!!\r\n");

return 0;

fail_keyio_init:

fail_device:
class_destroy(im6uirq.class);
fail_class:
cdev_del(&im6uirq.cdev);
fail_cdevadd:
unregister_chrdev_region(im6uirq.devid,im6uirq_CNT);
fail_devid:
return ret;
}

static void __exit im6uirq_exit(void)
{
int i = 0;
//1.释放中断
for (i = 0; i < KEY_NUM; i++)
{
free_irq(im6uirq.irq_key[i].irqnum,&im6uirq);
}

//2.释放gpio
for (i = 0; i < KEY_NUM; i++){
gpio_free(im6uirq.irq_key[i].gpio);
}

//3.删除定时器
del_timer_sync(&im6uirq.timer);

//注销字符设备驱动
cdev_del(&im6uirq.cdev);
unregister_chrdev_region(im6uirq.devid,im6uirq_CNT);
device_destroy(im6uirq.class,im6uirq.devid);
class_destroy(im6uirq.class);

printk("im6uirq exit!\r\n");
}


module_init(im6uirq_init);
module_exit(im6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zwl");

2.2应用代码

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
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include "signal.h"

#define CLOSE_CMD (_IO(0XEF, 0x1))
#define OPEN_CMD (_IO(0XEF, 0x2))
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /*设置周期*/

/* argc:应用参数个数
* argv[]:具体参数内容,字符串形式

* ./irqAPP /dev/imx6uirq
*/
int fd;
static void sig_handler(int num)
{
int err;
unsigned int keyvalue = 0;

err = read(fd,&keyvalue,sizeof(keyvalue));

if (err < 0)
{

}else{
printf("read irq key value: %d\n",keyvalue);
}

}
int main(int argc, char *argv[])
{
int ret;
int flags = 0;
char *filename;
unsigned char data;
if (argc != 2)
{
printf("Error Usage!!!\r\n");
return -1;
}

filename = argv[1];

fd = open(filename,O_RDWR);
if (fd < 0){
printf("file %s open failed!!!\r\n",filename);
return -1;
}

//设置信号处理函数
signal(SIGIO,sig_handler);

fcntl(fd, F_SETOWN, getpid()); /* 设置当前进程接收SIGIO信号 */
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 设置进程启用异步通知功能 */

while(1) {
sleep(2);
}

close(fd);
return 0;
}

评论