【北京迅为】i.MX6ULL终结者Linux阻塞和非阻塞IO实验阻塞IO实验教程

2020-12-21 10:01:34

文章目录

我们在按键中断实验的基础上来完成阻塞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

当前页面是本站的「Baidu MIP」版。发表评论请点击:完整版 »