加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
    • 1. 阻塞式I/O按键检测
    • 2. 阻塞式I/O程序编写
    • 3. 编译测试
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

用阻塞式IO模型降低CPU使用率

2022/08/05
1718
阅读需 14 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

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

相关推荐

电子产业图谱

公众号:嵌入式攻城狮;专注于分享和记录嵌入式开发技术,主要包含C语言、STM32、STM32CubeMX、lwIP、FreeRTOS、Linux、Zigbee、WIFI、BLE、LoRa、NB-loT、PCB电路设计、QT等等。