用阻塞式I/O模型降低CPU使用率

Linux内核中断检测按键输入一文中,应用程序在while中通过read函数循环读取按键值,导致CPU使用率居高不下。本文使用阻塞式I/O方式进行按键的读取,并比较两种不同方式的CPU使用率,那么如何查看CPU使用率:

  • 加载驱动后,使用后台模式(加‘&’)打开应用程序
  • 使用 top 命令查看CPU使用率

pic

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
  • 编译测试程序:测试程序与按键中断实验一文相同, 复制该文件,重新命名为blockioApp.c即可
arm-linux-gnueabihf-gcc blockioApp.c -o blockioApp
  • 将驱动文件和测试文件拷贝至rootfs/lib/modules/4.1.15中,加载驱动
depmod                     #第一次加载驱动的时候需要运行此命令
modprobe blockio.ko        #加载驱动
  • 使用./blockioApp /dev/blockio &命令运行应用程序,此时按下按键,应用程序会打印出按键值
./blockioApp /dev/blockio &

在这里插入图片描述

  • 使用top命令查看blockioApp的CPU使用率:虽然应用程序中仍使用循环读取的方式,但由于无按键值时read被阻塞,应用程序也就被阻塞住了,CPU的使用权被让出,如下图示此时CPU使用率为0

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Chuangke_Andy/article/details/126158367
今日推荐