Linux driver waiting queue and poll mechanism

Linux driver waiting queue and poll mechanism

  When we operate the device, we often encounter that when the device cannot obtain resources, the process will be suspended, and when the device resources meet the requirements, the process will be woken up (such as the read function, when the data cannot be read, it will be suspended, read When the data arrives, it can return immediately). This access to the device through blocking can greatly reduce the CPU load, and allow the CPU to execute other resources when the process is suspended. By waiting for the queue, the process can be blocked, and the process can be woken up when the requirements are met.
  Because a blocked process will go to sleep, it is necessary to ensure that there is a place to wake up the sleeping process. The most likely place to wake up the process occurs in the interrupt, because the acquisition of hardware resources is often accompanied by an interrupt.
  In the kernel, the reasonable application of waiting queue can greatly improve CPU execution efficiency, especially in interrupt processing, process synchronization, timing and other occasions. Waking up a blocked process can be done using a wait queue. It uses queues as the basic data structure and is closely integrated with the process scheduling mechanism. It can be used to implement the asynchronous event notification mechanism in the kernel and synchronize access to system resources.
  The waiting queue is a thread management mechanism based on the resource state. It can make the thread sleep when the resource is not satisfied, and give up CPU resources. When the resource state is satisfied, the thread is awakened to continue the business processing.
  The wait queue (wait queue) is used to make threads wait for a specific event to occur without frequent polling. The process sleeps during the waiting period and is automatically woken up by the kernel when something happens. It is based on the double-circular linked list data structure, which is closely related to the sleep wake-up mechanism of the process. It is the underlying technical support for asynchronous event notification, cross-process communication, and synchronous resource access.

1. Waiting queue related interface functions

  In Linux, the waiting queue is managed by the waiting queue head wait_queue_head_t *q , and the structure information is as follows:

struct __wait_queue_head {
    
    
	spinlock_t lock;
	struct list_head task_list;
};

1.1 Waiting for queue head initialization

  Initialization The waiting queue head can be statically initialized or dynamically initialized

#define DECLARE_WAIT_QUEUE_HEAD(name)
function: statically initialize the waiting queue head
parameter: name -- wait queue head structure variable name

#define init_waitqueue_head(q)
function: statically initialize the waiting queue head
parameter: q – wait queue head structure pointer variable

  Note: During dynamic initialization, you need to manually create a waiting queue head structure variable, while static initialization only needs to fill in the variable name of the waiting queue head. Right now:
  DECLARE_WAIT_QUEUE_HEAD(q) is equivalent to the following two lines of code:

wait_queue_head_t q;
init_waitqueue_head(&q);//动态初始化等待队列头

1.2 Sleep process

  The sleep process consists of two types of functions: interruptible sleep and uninterruptible sleep . Interruptible sleep can be woken up by signal; uninterruptible sleep cannot receive signals (such as CTRL+C, CTRL+) during sleep, the signal will be blocked, and the signal must be waited for after the process wakes up.

  • Interruptible sleep function
#define wait_event_interruptible(wq, condition)				\
({
      
      									\
	int __ret = 0;							\
	if (!(condition))						\
		__wait_event_interruptible(wq, condition, __ret);	\
	__ret;								\
})
//不可中断休眠,但可以指定超时时间
#define __wait_event_timeout(wq, condition, ret)			\
do {
      
      									\
	DEFINE_WAIT(__wait);						\
									\
	for (;;) {
      
      							\
		prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);	\
		if (condition)						\
			break;						\
		ret = schedule_timeout(ret);				\
		if (!ret)						\
			break;						\
	}								\
	finish_wait(&wq, &__wait);					\
} while (0)

  wq is the head of the waiting queue;
  condition is the wake-up flag, if the condition is true to wake up the process, if it is false, it will be in a dormant state;
  ret is the timeout time to be specified, and the unit is the clock beat jiffies

  • non-interruptible sleep function
#define wait_event(wq, condition) 					\
do {
      
      									\
	if (condition)	 						\
		break;							\
	__wait_event(wq, condition);					\
} while (0)
//可中断休眠,但可以指定超时时间
#define __wait_event_interruptible_timeout(wq, condition, ret)		\
do {
      
      									\
	DEFINE_WAIT(__wait);						\
									\
	for (;;) {
      
      							\
		prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);	\
		if (condition)						\
			break;						\
		if (!signal_pending(current)) {
      
      				\
			ret = schedule_timeout(ret);			\
			if (!ret)					\
				break;					\
			continue;					\
		}							\
		ret = -ERESTARTSYS;					\
		break;							\
	}								\
	finish_wait(&wq, &__wait);					\
} while (0)

  wq is the head of the waiting queue;
  condition is the wake-up flag, if the condition is true, it will wake up the process, if it is false, it will be in a dormant state.
  ret is the timeout time to be specified, the unit is clock beat jiffies

1.3 Wake up the process

  The functions for waking up dormant processes are divided into two categories: one is to wake up interruptible and uninterruptible dormant processes; the other is to only wake up interruptible dormant processes.
  The wake-up process function is generally called when the device obtains resources, and the calling location is often in the interrupt processing function.

//可唤醒可中断和不可中断休眠进程
#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)  //随机唤醒一个休眠进程
#define wake_up_nr(x, nr)		__wake_up(x, TASK_NORMAL, nr, NULL)  //唤醒多个休眠进程
#define wake_up_all(x)			__wake_up(x, TASK_NORMAL, 0, NULL)  //唤醒所有休眠进程
//只能唤醒可中断休眠进程
#define wake_up_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL) //随机唤醒一个休眠进程
#define wake_up_interruptible_nr(x, nr)	__wake_up(x, TASK_INTERRUPTIBLE, nr, NULL) //唤醒多个休眠进程
#define wake_up_interruptible_all(x)	__wake_up(x, TASK_INTERRUPTIBLE, 0, NULL) //唤醒所有休眠进程

1.4 Waiting queue application example

  Let’s take the button as an example to realize the button detection in the interrupt mode, process the bottom half of the code through the work queue, and realize the device registration with the miscellaneous device framework. Wake up the sleeping process in the key's work function. Put the process to sleep when the key information is obtained.
  Linux interrupt programming reference: https://blog.csdn.net/weixin_44453694/article/details/126812705

#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/suspend.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#define KEY_CNT sizeof(key_info)/sizeof(struct key_info)  //按键个数

//static wait_queue_head_t key_q;/*等待队列头(动态初始化时需要定义)*/
DECLARE_WAIT_QUEUE_HEAD(key_q);//静态初始化等待队列头
struct key_info
{
    
    
	unsigned int gpio;//gpio口
	int irq;//中断号
	char key_name[20];//注册中断名字
	int key_num;//按键编号
};
//按键信息保存
static struct key_info key_info[]=
{
    
    
	{
    
    EXYNOS4_GPX3(2),0,"key1",1},
	{
    
    EXYNOS4_GPX3(3),0,"key2",2},
	{
    
    EXYNOS4_GPX3(4),0,"key3",3},
	{
    
    EXYNOS4_GPX3(5),0,"key4",4}
};
static struct key_info *key_p;
static struct work_struct key_work;/*工作结构体*/
static int key_val;
static int condition=0;/*唤醒标志*/
/*工作处理函数*/
void work_func(struct work_struct *work)
{
    
    
	msleep(10);//按键消抖
	if(gpio_get_value(key_p->gpio)==0)
	{
    
    
		//printk("KEY %d 按下\n",key_p->key_num);
		key_val=key_p->key_num;
	}
	condition=1;//将唤醒标志置位
	wake_up(&key_q);
	
}
/*中断服务函数*/
static irqreturn_t key_exit_work(int irq, void *dev)
{
    
    
	key_p=(struct key_info *)dev;
	schedule_work(&key_work);//工作调度
	return IRQ_HANDLED;
}

static int key_open(struct inode *inode, struct file *file)
{
    
    
	printk("设备打开成功\n");
	return 0;
}
static ssize_t key_read(struct file *file, char __user *data, size_t size, loff_t *offset)
{
    
    
	int ret;
	int key;
	//wait_event(key_q, condition);//休眠进程(不可中断休眠)
	wait_event_interruptible(key_q, condition);//休眠进程(可中断休眠)
	key=key_val;
	condition=0;//清除唤醒标志
	ret=copy_to_user(data,&key,sizeof(key));
	return sizeof(key)-ret;
}
static int key_release(struct inode *inode, struct file *file)
{
    
    
	printk("设备关闭成功\n");
	return 0;
}
/*文件操早集合*/
static struct file_operations key_fops=
{
    
    
	.open=		key_open,
	.read=		key_read,
	.release=	key_release
};


/*杂项设备结构体*/
static struct miscdevice key_misc=
{
    
    
	.minor=MISC_DYNAMIC_MINOR,//次设备号,255,有内核分配
	.name="key_exit",//在/dev下生成的设备节点名字
	.fops=&key_fops
};
static int __init wbyq_key_exit_init(void)
{
    
    
	int i=0;
	int ret;
	/*初始化等待队列头*/
	//init_waitqueue_head(&key_q);
	/*初始化工作*/
	INIT_WORK(&key_work, work_func);
	for(i=0;i<KEY_CNT;i++)
	{
    
    
		key_info[i].irq=gpio_to_irq(key_info[i].gpio);/*获取中断号*/
		/*注册中断*/
		ret=request_irq(key_info[i].irq,key_exit_work,IRQF_TRIGGER_FALLING,key_info[i].key_name,&key_info[i]);
		if(ret)
		{
    
    	
			disable_irq(key_info[i].irq);//禁止中断
			free_irq(key_info[i].irq,&key_info[i]);//注销中断
			printk("注册中断失败irq=%d,i=%d\n",key_info[i].irq,i);
			return ret;
		}
	}
	/*注册杂项设备*/
	misc_register(&key_misc);
	return 0;
	
}
/*驱动释放*/
static void __exit wbyq_key_exit_cleanup(void)
{
    
    
	int i=0;
    printk("驱动出口,驱动注销成功\n");
	/*注销中断*/
	for(i=0;i<KEY_CNT;i++)
	{
    
    
		disable_irq(key_info[i].irq);//禁止中断
		free_irq(key_info[i].irq,&key_info[i]);//注销中断
	}
	/*注销杂项设备*/
	misc_deregister(&key_misc);
}
module_init(wbyq_key_exit_init);//驱动入口函数
module_exit(wbyq_key_exit_cleanup);//驱动出口函数

MODULE_LICENSE("GPL");//驱动注册协议
MODULE_AUTHOR("it_ashui");
MODULE_DESCRIPTION("Exynos4 key_exit Driver");

2. Driver layer and application layer poll call mechanism

  When using the waiting queue, when the device cannot obtain resources, it will be blocked, and the process cannot be woken up until the resources are obtained, which makes it impossible to do other events in the main process. If we want to achieve process sleep without blocking the process, we will The poll mechanism can be used to monitor the file descriptor. The poll mechanism can set the timeout period of monitoring, allowing a process to decide whether it can read or write one or more files without blocking. Of course, the poll call can also block the process until any given set of file descriptors is available for reading or writing. Therefore, they are often used in applications that use multiple input and output streams.
  Corresponding to event monitoring, the application layer has three mechanisms: select, poll, and epoll. The poll and epoll mechanisms are upgraded versions of the select mechanism.

2.1 Poll mechanism driver layer related function interface

  • poll mechanism interface function

  The three mechanisms of select, poll, and epoll in the application layer share a driver layer interface.

unsigned int (*poll) (struct file *, struct poll_table_struct *);
  • wait event trigger function
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
    
    
	if (p && p->_qproc && wait_address)
		p->_qproc(filp, wait_address, p);
}
  • poll wakeup function
//唤醒休眠进程
#define wake_up_poll(x, m)				\
	__wake_up(x, TASK_NORMAL, 1, (void *) (m))
#define wake_up_locked_poll(x, m)				\
	__wake_up_locked_key((x), TASK_NORMAL, (void *) (m))
#define wake_up_interruptible_poll(x, m)			\
	__wake_up(x, TASK_INTERRUPTIBLE, 1, (void *) (m))
#define wake_up_interruptible_sync_poll(x, m)				\
	__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))
//或者直接使用等待队列唤醒函数
#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)	

2.2 poll mechanism application layer related function interface

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
Parameters: fds --poll structure
 struct pollfd {    int fd; /*File descriptor to be monitored*/    short events; /*Events to be monitored*/   short revents; /*Return event*/   };     nfds --The number of descriptors to monitor     timeout --Timeout time, <0 wait forever; ==0 return immediately; >0 timeout return Value: >0 event is generated; ==0 timeout; <0 function error








  • listen event
  • POLLIN -- has data to read
  • POLLRDNORM -- has normal data to read
  • POLLRDBAND -- has priority data to read
  • POLLPRI -- has urgent data to read
  • POLLOUT -- data can be written
  • POLLERR -- an error occurred
  • POLLHUP -- A hangup occurs
  • POLLNVAL -- The specified file descriptor is illegal.
  • Application layer poll mechanism example
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
int main()
{
    
    
	int fd=open("/dev/key_exit",2);
	if(fd<0)
	{
    
    
		printf("设备打开失败\n");
		return 0;
	}
	int key_val;
	int ret;
	struct pollfd fds=
	{
    
    
		.fd=fd,//要监听的文件描述符
		.events=POLLIN,//监听读事件
		.revents=0,//返回事件
	};
	while(1)
	{
    
    
		ret=poll(&fds,1, 1000);
		if(ret>0)
		{
    
    
			if(fds.revents & POLLIN)
			{
    
    
				read(fd,&key_val,4);
				printf("KEY %d 按下\n",key_val);
			}
		}
		else if(ret==0)
		{
    
    
			printf("超时\n");
		}
		else 
		{
    
    
			printf("poll函数出错\n");
		}
	}
	close(fd);
}
  • Driver layer poll interface implementation
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/suspend.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/poll.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#define KEY_CNT sizeof(key_info)/sizeof(struct key_info)  //按键个数

//static wait_queue_head_t key_q;/*等待队列头(动态初始化时需要定义)*/
DECLARE_WAIT_QUEUE_HEAD(key_q);//静态初始化等待队列头
struct key_info
{
    
    
	unsigned int gpio;//gpio口
	int irq;//中断号
	char key_name[20];//注册中断名字
	int key_num;//按键编号
};
//按键信息保存
static struct key_info key_info[]=
{
    
    
	{
    
    EXYNOS4_GPX3(2),0,"key1",1},
	{
    
    EXYNOS4_GPX3(3),0,"key2",2},
	{
    
    EXYNOS4_GPX3(4),0,"key3",3},
	{
    
    EXYNOS4_GPX3(5),0,"key4",4}
};
static struct key_info *key_p;
static struct work_struct key_work;/*工作结构体*/
static int key_val;
/*工作处理函数*/
void work_func(struct work_struct *work)
{
    
    
	msleep(10);//按键消抖
	if(gpio_get_value(key_p->gpio)==0)
	{
    
    
		//printk("KEY %d 按下\n",key_p->key_num);
		key_val=key_p->key_num;
	}
	wake_up_poll(&key_q, POLLIN);//唤醒进程
}
/*中断服务函数*/
static irqreturn_t key_exit_work(int irq, void *dev)
{
    
    
	key_p=(struct key_info *)dev;
	schedule_work(&key_work);//工作调度
	return IRQ_HANDLED;
}

static int key_open(struct inode *inode, struct file *file)
{
    
    
	printk("设备打开成功\n");
	return 0;
}
static ssize_t key_read(struct file *file, char __user *data, size_t size, loff_t *offset)
{
    
    
	int ret;
	int key;
	key=key_val;
	key_val=0;
	ret=copy_to_user(data,&key,sizeof(key));
	return sizeof(key)-ret;
}
static unsigned int key_poll(struct file *file, struct poll_table_struct *wait)
{
    
    
	int mask = 0;
	poll_wait(file,&key_q, wait);//等待事件产生
	if(key_val)mask|=POLLIN;
	return mask;
		
	
}
static int key_release(struct inode *inode, struct file *file)
{
    
    
	printk("设备关闭成功\n");
	return 0;
}
/*文件操早集合*/
static struct file_operations key_fops=
{
    
    
	.open=		key_open,
	.read=		key_read,
	.poll=		key_poll,
	.release=	key_release
};


/*杂项设备结构体*/
static struct miscdevice key_misc=
{
    
    
	.minor=MISC_DYNAMIC_MINOR,//次设备号,255,有内核分配
	.name="key_exit",//在/dev下生成的设备节点名字
	.fops=&key_fops
};
static int __init wbyq_key_exit_init(void)
{
    
    
	int i=0;
	int ret;
	/*初始化等待队列头*/
	//init_waitqueue_head(&key_q);
	/*初始化工作*/
	INIT_WORK(&key_work, work_func);
	for(i=0;i<KEY_CNT;i++)
	{
    
    
		key_info[i].irq=gpio_to_irq(key_info[i].gpio);/*获取中断号*/
		/*注册中断*/
		ret=request_irq(key_info[i].irq,key_exit_work,IRQF_TRIGGER_FALLING,key_info[i].key_name,&key_info[i]);
		if(ret)
		{
    
    	
			disable_irq(key_info[i].irq);//禁止中断
			free_irq(key_info[i].irq,&key_info[i]);//注销中断
			printk("注册中断失败irq=%d,i=%d\n",key_info[i].irq,i);
			return ret;
		}
	}
	/*注册杂项设备*/
	misc_register(&key_misc);
	return 0;
	
}
/*驱动释放*/
static void __exit wbyq_key_exit_cleanup(void)
{
    
    
	int i=0;
    printk("驱动出口,驱动注销成功\n");
	/*注销中断*/
	for(i=0;i<KEY_CNT;i++)
	{
    
    
		disable_irq(key_info[i].irq);//禁止中断
		free_irq(key_info[i].irq,&key_info[i]);//注销中断
	}
	/*注销杂项设备*/
	misc_deregister(&key_misc);
}
module_init(wbyq_key_exit_init);//驱动入口函数
module_exit(wbyq_key_exit_cleanup);//驱动出口函数

MODULE_LICENSE("GPL");//驱动注册协议
MODULE_AUTHOR("it_ashui");
MODULE_DESCRIPTION("Exynos4 key_exit Driver");
  • Execution effect
    insert image description here

Guess you like

Origin blog.csdn.net/weixin_44453694/article/details/126826448