【北京迅为】i.MX6ULL终结者Linux阻塞和非阻塞IO实验阻塞IO实验教程
文章目录
我们在按键中断实验的基础上来完成阻塞IO的实验,在上一章的按键中断实验中,我们通过read函数while循环的方式读取按键状态,这种方法缺点很明显,会导致有很高的CPU占用率。我们来看一下通过while循环的方式CPU的使用率,使用上一章的key\_irq.ko模块和key\_irq\_test应用程序,先加载驱动模块,然后执行应用程序:
modprobe key_irq
./key_irq_test /dev/imx6uirq &
使用“top”命令查看key\_irq\_test程序的CPU使用率,如图 1所示:
图 1
可以看到key\_irq\_test程序的CPU使用率为97.2%,仅仅是一个按键程序就会导致这么高的CPU使用率效率是非常低的。所以一般是不会采用这种方法的,只是在实验中使用一下,在本节中我们就是用阻塞IO来实现一下。
1 驱动程序编写
本实验例程路径:i.MX6UL终结者光盘资料/06\_Linux驱动例程/11\_key\_waitqueue
创建key\_waitqueue.c文件,在key\_irq.c文件的基础上进行修改,添加阻塞相关的代码。具体内容如下:
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <linux/semaphore.h>
15 #include <linux/timer.h>
16 #include <linux/of_irq.h>
17 #include <linux/irq.h>
18 #include <asm/mach/map.h>
19 #include <asm/uaccess.h>
20 #include <asm/io.h>
21
22 #define IMX6UIRQ_CNT 1 /* 设备号个数 */
23 #define IMX6UIRQ_NAME "blockio" /* 名字 */
24 #define KEY0VALUE 0X01 /* KEY0 按键值 */
25 #define INVAKEY 0XFF /* 无效的按键值 */
26 #define KEY_NUM 1 /* 按键数量 */
27
28 /* 中断 IO 描述结构体 */
29 struct irq_keydesc {
30 int gpio; /* gpio */
31 int irqnum; /* 中断号 */
32 unsigned char value; /* 按键对应的键值 */
33 char name[10]; /* 名字 */
34 irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
35 };
36
37 /* imx6uirq 设备结构体 */
38 struct imx6uirq_dev{
39 dev_t devid; /* 设备号 */
40 struct cdev cdev; /* cdev */
41 struct class *class; /* 类 */
42 struct device *device; /* 设备 */
43 int major; /* 主设备号 */
44 int minor; /* 次设备号 */
45 struct device_node *nd; /* 设备节点 */
46 atomic_t keyvalue; /* 有效的按键键值 */
47 atomic_t releasekey; /* 标记是否完成一次完成的按键 */
48 struct timer_list timer; /* 定义一个定时器*/
49 struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键 init 述数组 */
50 unsigned char curkeynum; /* 当前 init 按键号 */
51
52 wait_queue_head_t r_wait; /* 读等待队列头 */
53 };
54
55 struct imx6uirq_dev imx6uirq; /* irq 设备 */
56
57 /* @description : 中断服务函数,开启定时器
58 * 定时器用于按键消抖。
59 * @param - irq : 中断号
60 * @param - dev_id : 设备结构。
61 * @return : 中断执行结果
62 */
63 static irqreturn_t key0_handler(int irq, void *dev_id)
64 {
65 struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;
66
67 dev->curkeynum = 0;
68 dev->timer.data = (volatile long)dev_id;
69 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
70 return IRQ_RETVAL(IRQ_HANDLED);
71 }
72
73 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
74 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
75 * @param - arg : 设备结构变量
76 * @return : 无
77 */
78 void timer_function(unsigned long arg)
79 {
80 unsigned char value;
81 unsigned char num;
82 struct irq_keydesc *keydesc;
83 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
84
85 num = dev->curkeynum;
86 keydesc = &dev->irqkeydesc[num];
87
88 value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
89 if(value == 0){ /* 按下按键 */
90 atomic_set(&dev->keyvalue, keydesc->value);
91 }
92 else{ /* 按键松开 */
93 atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
94 atomic_set(&dev->releasekey, 1);
95 }
96
97 /* 唤醒进程 */
98 if(atomic_read(&dev->releasekey)) { /* 完成一次按键过程 */
99 /* wake_up(&dev->r_wait); */
100 wake_up_interruptible(&dev->r_wait);
101 }
102 }
103
104 /*
105 * @description : 按键 IO 初始化
106 * @param : 无
107 * @return : 无
108 */
109 static int keyio_init(void)
110 {
111 unsigned char i = 0;
112 char name[10];
113 int ret = 0;
114
115 imx6uirq.nd = of_find_node_by_path("/key");
116 if (imx6uirq.nd== NULL){
117 printk("key node not find!\r\n");
118 return -EINVAL;
119 }
120
121 /* 提取 GPIO */
122 for (i = 0; i < KEY_NUM; i++) {
123 imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,
124 "key-gpio", i);
125 if (imx6uirq.irqkeydesc[i].gpio < 0) {
126 printk("can't get key%d\r\n", i);
127 }
128 }
129
130 /* 初始化 key 所使用的 IO,并且设置成中断模式 */
131 for (i = 0; i < KEY_NUM; i++) {
132 memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name));
133 sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
134 gpio_request(imx6uirq.irqkeydesc[i].gpio, name);
135 gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
136 imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(
137 imx6uirq.nd, i);
138 #if 0
139 imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(
140 imx6uirq.irqkeydesc[i].gpio);
141 #endif
142 printk("key%d:gpio=%d, irqnum=%d\r\n",i,
143 imx6uirq.irqkeydesc[i].gpio,
144 imx6uirq.irqkeydesc[i].irqnum);
145 }
145 }
146 /* 申请中断 */
147 imx6uirq.irqkeydesc[0].handler = key0_handler;
148 imx6uirq.irqkeydesc[0].value = KEY0VALUE;
149
150 for (i = 0; i < KEY_NUM; i++) {
151 ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
152 imx6uirq.irqkeydesc[i].handler,
153 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
154 imx6uirq.irqkeydesc[i].name, &imx6uirq);
155 if(ret < 0){
156 printk("irq %d request failed!\r\n",
157 imx6uirq.irqkeydesc[i].irqnum);
158 return -EFAULT;
159 }
160 }
161
162 /* 创建定时器 */
163 init_timer(&imx6uirq.timer);
164 imx6uirq.timer.function = timer_function;
165
166 /* 初始化等待队列头 */
167 init_waitqueue_head(&imx6uirq.r_wait);
168 return 0;
169 }
170
171 /*
172 * @description : 打开设备
173 * @param – inode : 传递给驱动的 inode
174 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
175 * 一般在 open 的时候将 private_data 指向设备结构体。
176 * @return : 0 成功;其他 失败
177 */
178 static int imx6uirq_open(struct inode *inode, struct file *filp)
179 {
180 filp->private_data = &imx6uirq; /* 设置私有数据 */
181 return 0;
182 }
183
184 /*
185 * @description : 从设备读取数据
186 * @param - filp : 要打开的设备文件(文件描述符)
187 * @param - buf : 返回给用户空间的数据缓冲区
188 * @param - cnt : 要读取的数据长度
189 * @param - offt : 相对于文件首地址的偏移
190 * @return : 读取的字节数,如果为负值,表示读取失败
191 */
192 static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
193 size_t cnt, loff_t *offt)
194 {
195 int ret = 0;
196 unsigned char keyvalue = 0;
197 unsigned char releasekey = 0;
198 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)
199 filp->private_data;
200
201 #if 0
202 /* 加入等待队列,等待被唤醒,也就是有按键按下 */
203 ret = wait_event_interruptible(dev->r_wait,
204 atomic_read(&dev->releasekey));
205 if (ret) {
206 goto wait_error;
207 }
208 #endif
209
210 DECLARE_WAITQUEUE(wait, current); /* 定义一个等待队列 */
211 if(atomic_read(&dev->releasekey) == 0) { /* 没有按键按下 */
212 add_wait_queue(&dev->r_wait, &wait); /* 添加到等待队列头 */
213 __set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */
214 schedule(); /* 进行一次任务切换 */
215 if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */
216 ret = -ERESTARTSYS;
217 goto wait_error;
218 }
219 }
220 remove_wait_queue(&dev->r_wait, &wait);/* 唤醒以后将等待队列移除 */
221
222 keyvalue = atomic_read(&dev->keyvalue);
223 releasekey = atomic_read(&dev->releasekey);
224
225 if (releasekey) { /* 有按键按下 */
226 if (keyvalue & 0x80) {
227 keyvalue &= ~0x80;
228 ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
229 } else {
230 goto data_error;
231 }
232 atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
233 } else {
234 goto data_error;
235 }
236 return 0;
237
238 wait_error:
239 set_current_state(TASK_RUNNING); /* 设置任务为运行态 */
240 remove_wait_queue(&dev->r_wait, &wait); /* 将等待队列移除 */
241 return ret;
242
243 data_error:
244 return -EINVAL;
245 }
246
247 /* 设备操作函数 */
248 static struct file_operations imx6uirq_fops = {
249 .owner = THIS_MODULE,
250 .open = imx6uirq_open,
251 .read = imx6uirq_read,
252 };
253
254 /*
255 * @description : 驱动入口函数
256 * @param : 无
257 * @return : 无
258 */
259 static int __init imx6uirq_init(void)
260 {
261 /* 1、构建设备号 */
262 if (imx6uirq.major) {
263 imx6uirq.devid = MKDEV(imx6uirq.major, 0);
264 register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,
265 IMX6UIRQ_NAME);
266 } else {
267 alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,
268 IMX6UIRQ_NAME);
269 imx6uirq.major = MAJOR(imx6uirq.devid);
270 imx6uirq.minor = MINOR(imx6uirq.devid);
271 }
272
273 /* 2、注册字符设备 */
274 cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
275 cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
276
277 /* 3、创建类 */
278 imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
279 if (IS_ERR(imx6uirq.class)) {
280 return PTR_ERR(imx6uirq.class);
281 }
282
283 /* 4、创建设备 */
284 imx6uirq.device = device_create(imx6uirq.class, NULL,
285 imx6uirq.devid, NULL, IMX6UIRQ_NAME);
286 if (IS_ERR(imx6uirq.device)) {
287 return PTR_ERR(imx6uirq.device);
288 }
289
290 /* 5、始化按键 */
291 atomic_set(&imx6uirq.keyvalue, INVAKEY);
292 atomic_set(&imx6uirq.releasekey, 0);
293 keyio_init();
294 return 0;
295 }
296
297 /*
298 * @description : 驱动出口函数
299 * @param : 无
300 * @return : 无
301 */
302 static void __exit imx6uirq_exit(void)
303 {
304 unsigned i = 0;
305 /* 删除定时器 */
306 del_timer_sync(&imx6uirq.timer); /* 删除定时器 */
307
308 /* 释放中断 */
309 for (i = 0; i < KEY_NUM; i++) {
310 free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
311 }
312 cdev_del(&imx6uirq.cdev);
313 unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
314 device_destroy(imx6uirq.class, imx6uirq.devid);
315 class_destroy(imx6uirq.class);
316 }
317
318 module_init(imx6uirq_init);
319 module_exit(imx6uirq_exit);
320 MODULE_LICENSE("GPL");
321 MODULE_AUTHOR("topeet");
第 23 行,修改设备文件名字为“blockio”,当驱动程序加载成功以后就会在根文件系统中出现一个名为“/dev/blockio”的文件。
第 52 行,在设备结构体中添加一个等待队列头 r\_wait,因为在 Linux 驱动中处理阻塞 IO需要用到等待队列。
第 78~102 行,定时器中断处理函数执行,表示有按键按下,先在 98 行判断一下是否是一次有效的按键,如果是的话就通过 wake\_up 或者 wake\_up\_interruptible 函数来唤醒等待队列r\_wait。
第 167 行,调用 init\_waitqueue\_head 函数初始化等待队列头 r\_wait。
第 201~208 行,采用等待事件来处理 read 的阻塞访问,wait\_event\_interruptible 函数等待releasekey 有效,也就是有按键按下。如果按键没有按下的话进程就会进入休眠状态,因为采用了 wait\_event\_interruptible 函数,因此进入休眠态的进程可以被信号打断。
第 210~220 行,首先使用 DECLARE\_WAITQUEUE 宏定义一个等待队列,如果没有按键按下的话就使用 add\_wait\_queue 函数将当前任务的等待队列添加到等待队列头r\_wait 中。随后调用\_\_set\_current\_state 函数设置当前进程的状态为 TASK\_INTERRUPTIBLE,也就是可以被信号打断。接下来调用 schedule 函数进行一次任务切换,当前进程就会进入到休眠态。如果有按键按下,那么进入休眠态的进程就会唤醒,然后接着从休眠点开始运行。在这里也就是从第 215行开始运行,首先通过 signal\_pending 函数判断一下进程是不是由信号唤醒的,如果是由信号唤醒的话就直接返回-ERESTARTSYS 这个错误码。如果不是由信号唤醒的(也就是被按键唤醒的)那么就在 220 行调用 remove\_wait\_queue 函数将进程从等待队列中删除。
使用等待队列实现阻塞访问重点注意两点:
①、将任务或者进程加入到等待队列头,
②、在合适的点唤醒等待队列,一般都是中断处理函数里面。
2 编写应用测试程序
应用测试程序直接使用41.2.3小节的应用测试程序key\_irq\_test.c即可,将key\_irq\_test.c文件复制到本实验的目录下,修改为key\_waitqueue\_test.c。
3 运行测试
1、编译驱动程序
和前面章节中驱动测试程序一样需要一个Makefile文件,只是将obj-m的值改为key\_waitqueue.o,Makefile文件内容如下:
KERNELDIR := /home/topeet/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := key_waitqueue.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
首先我们在终端输入两个命令(设置两个环境变量):
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
然后执行“make”命令编译模块,编译完成生成key\_waitqueue.ko模块文件。
2、 编译应用测试程序
输入如下命令编译应用测试程序:
arm-linux-gnueabihf-gcc -o key_waitqueue_test key_waitqueue_test.c
编译完成后,会生成key\_waitqueue\_test可执行文件。
3、 运行测试
启动开发板,将编译好的key\_waitqueue.ko驱动模块文件和key\_waitqueue\_test应用测试程序拷贝到/lib/modules/4.1.15目录下(检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话需要自行创建一下。开发板中使用的是光盘资料里面提供的busybox文件系统,光盘资料的“i.MX6UL终结者光盘资料\08\_开发板系统镜像\03\_文件系统镜像\01\_Busybox文件系统”目录下)。输入下面命令加载模块:
depmod
modprobe key_waitqueue
驱动加载成功后,使用key\_waitqueue\_test应用测试程序测试一下驱动文件,命令如下:
./key_waitqueue_test /dev/blockio &
然后按下开发板上的KEY0按键,会有如下信息打印:
图 3.1
说明按键驱动程序正常,这时可以使用“top”命令来看一下key\_waitqueue\_test程序CPU的使用率,如图 3.2所示:
图 3.2
在按键驱动程序中加入阻塞访问后,key\_waitqueue\_test程序的CPU使用率几乎接近0%,和在按键中断的实验中97.2%的CPU使用率相比,效率提高了很多。使用“q”或者“ctrl + c”退出top命令。
在卸载模块驱动之前先使用“ps”加“kill”命令关闭key\_waitqueue\_test应用进程,然后卸载模块:
rmmod key_waitqueue