Reduce CPU usage with blocking I/O model

In the article "Linux Kernel Interrupt Detecting Key Input" , the application reads key values ​​cyclically through the read function in while, resulting in high CPU usage. This article uses the blocking I/O method to read the keys, and compares the CPU usage of the two different methods, so how to check the CPU usage:

  • After loading the driver, open the application in background mode (add '&')
  • Use the top command to view CPU usage

pic

1. Blocking I/O button detection

Blocking access means that when the device file is inoperable, the process can enter the dormant state, thereby giving up CPU resources; when the device file is operable, the process is woken up; generally, the wake-up work is completed in the interrupt function. The Linux kernel provides a waiting queue to realize the wake-up work of the blocked process, which is used as follows:

  • Waiting queue head: If you want to use the waiting queue in the driver, you need to create and initialize a waiting queue head
//等待队列头定义
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);

  • Waiting for queue entry: each process accessing the device is a queue entry
//等待队列项定义
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,表示当前进程
  • Add the queue item to the head of the waiting queue: when the device is inaccessible, add the corresponding waiting queue item of the process to the head of the waiting queue. Only after adding it to the head of the waiting queue, the process can enter the dormant state
void add_wait_queue( wait_queue_head_t *q,
					 wait_queue_t *wait)
//参数q:等待队列项要加入的等待队列头
//参数wait:要加入的等待队列项
  • Remove the queue item from the head of the waiting queue: After the device is accessible, remove the waiting queue item corresponding to the process from the head of the waiting queue
void remove_wait_queue( wait_queue_head_t *q,
						wait_queue_t *wait)
//参数q:要删除的等待队列项所处的等待队列头
//参数wait:要删除的等待队列项
  • Waiting to wake up: When the device is ready to use, wake up the process that entered the dormant state
//可以唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程
void wake_up(wait_queue_head_t *q)
//只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程
void wake_up_interruptible(wait_queue_head_t *q) 
  • Waiting for events: In addition to actively waking up, you can also set the waiting queue to wait for an event. When the event is satisfied, the process in the waiting queue will be automatically woken up
//等待以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. Blocking I/O programming

Based on the code of the button interrupt experiment , the device tree file and test file do not need to be modified, only part of the code in the driver can be modified

  • Define and initialize the waiting queue head
......
#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;
}
  • Define a waiting queue, when the button is not pressed, block waiting, then perform task switching, hand over the CPU usage right; when the button is pressed, there is a signal to wake up the waiting, and return the key value to the application layer program
......
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;
}
  • In the timer debounce function, after reading the key, trigger the wake-up
......
void timer_function(unsigned long arg) {
    
    
 ......
    ......
    /* 唤醒进程 */
    if(atomic_read(&dev->releasekey)) {
    
       /* 完成一次按键过程 */
        wake_up_interruptible(&dev->r_wait);
    }
}

3. Compile and test

  • Compile the driver: create a Makefile in the current directory and makecompile it
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
  • Copy the driver files and test files torootfs/lib/modules/4.1.15, load the driver
depmod                     #第一次加载驱动的时候需要运行此命令
modprobe blockio.ko        #加载驱动
  • Use ./blockioApp /dev/blockio &the command to run the application, press the key at this time, the application will print out the key value
./blockioApp /dev/blockio &

insert image description here

  • Use topthe command to view the CPU usage of blockioApp: Although the cyclic reading method is still used in the application, because the read is blocked when there is no key value, the application is also blocked, and the right to use the CPU is given up, as shown in the figure below Indicates that the CPU usage is 0 at this time

insert image description here

Guess you like

Origin blog.csdn.net/Chuangke_Andy/article/details/126158367