Linux内核中断检测按键输入一文中,应用程序在while中通过read函数循环读取按键值,导致CPU使用率居高不下。本文使用阻塞式I/O方式进行按键的读取,并比较两种不同方式的CPU使用率,那么如何查看CPU使用率:
加载驱动后,使用后台模式(加‘&’)打开应用程序
使用top命令查看CPU使用率
1. 阻塞式I/O按键检测
阻塞访问是指当设备文件不可操作时,进程可进入休眠态,从而将CPU资源让出来;当设备文件可操作时,再唤醒进程;一般在中断函数里完成唤醒工作。Linux内核提供了等待队列来实现阻塞进程的唤醒工作,其使用方法如下:
⏩ 等待队列头:若要在驱动中使用等待队列,需创建并初始化一个等待队列头
//等待队列头定义
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
/* 创建并初始化等待队列头 */
typedef struct __wait_queue_head wait_queue_head_t;
init_waitqueue_head(wait_queue_head_t);
⏩ 等待队列项:每个访问设备的进程都是一个队列项
//等待队列项定义
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
/* 定义并初始化等待队列项 */
typedef struct __wait_queue wait_queue_t;
DECLARE_WAITQUEUE(name, tsk);
//参数name:等待队列项的名字
//参数tsk:表示该等待队列项属于哪个任务(进程),一般为current,表示当前进程
⏩ 将队列项添加到等待队列头:设备不可访问时,将进程对应的等待队列项添加到等待队列头中,只有添加到等待队列头中后,进程才能进入休眠态
void add_wait_queue( wait_queue_head_t *q, wait_queue_t *wait)
//参数q:等待队列项要加入的等待队列头
//参数wait:要加入的等待队列项
⏩ 将队列项移除出等待队列头:设备可以访问后,将进程对应的等待队列项从等待队列头中移除
void remove_wait_queue( wait_queue_head_t *q, wait_queue_t *wait)
//参数q:要删除的等待队列项所处的等待队列头
//参数wait:要删除的等待队列项
⏩ 等待唤醒:设备可以使用时,唤醒进入休眠态的进程
//可以唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程
void wake_up(wait_queue_head_t *q)
//只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程
void wake_up_interruptible(wait_queue_head_t *q)
⏩ 等待事件:除主动唤醒以外,也可设置等待队列等待某个事件,当该事件满足以后就自动唤醒等待队列中的进程
//等待以wq为等待队列头的等待队列被唤醒,前提是condition条件必须满足,否则一直阻塞
//函数会将进程设置为TASK_UNINTERRUPTIBLE状态
wait_event(wq, condition)
wait_event_timeout(wq, condition, timeout)
//等待以wq为等待队列头的等待队列被唤醒,前提是condition条件必须满足,否则一直阻塞
//函数会将进程设置为TASK_INTERRUPTIBLE状态(即可被信号打断)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq, condition, timeout)
2. 阻塞式I/O程序编写
在按键中断实验的代码基础上进行改编,设备树文件和应用程序无需修改,只需修改驱动程序里的部分代码即可
⏩ 定义并初始化等待队列头
......
#define IMX6UIRQ_NAME "blockio"
......
/* imx6uirq 设备结构体 */
struct imx6uirq_dev{
..........
unsigned char curkeynum; /* 当前的按键号 */
wait_queue_head_t r_wait; /* 读等待队列头 */
};
......
......
static int keyio_init(void) {
......
/* 创建定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
/* 初始化等待队列头 */
init_waitqueue_head(&imx6uirq.r_wait);
return 0;
}
⏩ 定义一个等待队列,当按键没有按下时,阻塞等待,然后进行任务切换,交出CPU的使用权;当按键按下时,有信号唤醒该等待,并将键值返回给应用层程序
......
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
......
......
#if 0
/* 加入等待队列,等待被唤醒,也就是有按键按下 */
ret = wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
#endif
DECLARE_WAITQUEUE(wait, current); /* 定义一个等待队列 */
if(atomic_read(&dev->releasekey) == 0) { /* 没有按键按下 */
add_wait_queue(&dev->r_wait, &wait); /* 添加到等待队列头 */
__set_current_state(TASK_INTERRUPTIBLE); /* 设置任务状态 */
schedule(); /* 进行一次任务切换 */
if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */
ret = -ERESTARTSYS;
goto wait_error;
}
__set_current_state(TASK_RUNNING); /*设置为运行状态 */
remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */
}
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
......
return 0;
wait_error:
set_current_state(TASK_RUNNING); /* 设置任务为运行态 */
remove_wait_queue(&dev->r_wait, &wait); /* 将等待队列移除 */
return ret;
data_error:
return -EINVAL;
}
⏩ 定时器去抖函数中,读取到按键后,触发唤醒
......
void timer_function(unsigned long arg) {
......
......
/* 唤醒进程 */
if(atomic_read(&dev->releasekey)) { /* 完成一次按键过程 */
wake_up_interruptible(&dev->r_wait);
}
}
3. 编译测试
⏩ 编译驱动程序:当前目录下创建Makefile文件,并make编译
KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := blockio.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
⏩ 编译测试程序:无需内核参与,直接编译即可
arm-linux-gnueabihf-gcc blockioApp.c -o blockioApp
⏩ 将驱动文件和测试文件拷贝至rootfs/lib/modules/4.1.15后加载驱动
depmod #第一次加载驱动时,需使用“depmod”命令
modprobe blockio.ko
⏩ 使用./blockioApp /dev/blockio &命令运行应用程序,此时按下按键,应用程序会打印出按键值
./blockioApp /dev/blockio &
⏩ 使用top命令查看blockioApp的CPU使用率:虽然应用程序中仍使用循环读取的方式,但由于无按键值时read被阻塞,应用程序也就被阻塞住了,CPU的使用权被让出,如下图示此时CPU使用率为0