Introduce a work queue
I have talked about the Linux interrupt mechanism divided into the upper half interrupt (hard interrupt) and the lower half. The top half interrupt is used to complete the more urgent function. It is usually just to read the interrupt status in the register and clear the interrupt flag. After that, the lower half is started, and the lower half needs to complete most of the tasks of the interrupt event. We often use the tasklet mechanism (soft interrupt latency mechanism) to achieve the lower half of the work, and the tasklet mechanism is a traditional bottom half processing mechanism, its execution timing is often released in the top half of the return, tasklet is based on soft The interrupt is implemented, so it is run in the soft interrupt context, and the soft interrupt context is also the interrupt context. The interrupt context is characterized by the inability to sleep or delay internal operations. Then this situation is very suitable for using the work queue to complete the work. A work queue is another form of post-execution work that is different from a tasklet. The work queue can push the work back to a kernel thread to execute, that is, the lower half can be executed in the process context. In this way, the code executed through the work queue can take advantage of all the advantages of the process context. The most important thing is that the work queue allows for rescheduling or even sleep.

So, when do you use a work queue and when to use a tasklet. If the post-pushing task requires sleep, then the work queue is selected. If the post-pushing task does not require sleep, then select the tasklet. In addition, if you need to perform a lower half of processing with a reschedulable entity, you should also use a work queue. It is the only mechanism that can be implemented in the lower half of the process context, and only it can sleep. This means that it is useful when you need to get a lot of memory, when you need to get a semaphore, when you need to perform blocking I/O operations. If you don’t need a kernel thread to push back the work, consider using a tasklet.

Two work queue overview
As an important basic component of the kernel, workqueue is widely used in the kernel. Through the work queue, it is convenient to refer to a certain task (ie, function + context parameter) that we want to execute to the kernel, and the kernel executes it for us. When the kernel is initialized, the kernel has created a default work queue for us, so we don’t need to create a work queue when we use it. We just need to create a job and add the work to the work queue. Of course, we can also create our own work queues without using the kernel to create a good work queue. Simple understanding, the work queue is implemented by the kernel thread + linked list + wait queue, that is, a kernel thread continuously reads the work from the linked list, and then executes the working function of the work! The system default worker thread is events, and you can create your own worker thread.
The work queue is another form of pushing the work back. The work queue can push the work back to a kernel thread to execute, that is, the lower half can be executed in the process context. The most important thing is that the work queue allows for rescheduling or even sleep.

We call the post-execution task a work and describe its data structure as work_struct.

struct work_struct {
    atomic_long_t data;       
#define WORK_STRUCT_PENDING 0        /* T if work item pending execution */
#define WORK_STRUCT_STATIC 1        /* static initializer (debugobjects) */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
    struct list_head entry;       
    work_func_t func;              
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

These structures are connected into a linked list. When a worker thread is woken up, it performs all the work on its linked list. After the work is completed, it removes the corresponding work_struct object from the linked list. When there are no more objects on the list, it will continue to sleep.

These jobs are organized into work queues in a queue structure, and their data structure is workqueue_struct

struct workqueue_struct {
 struct cpu_workqueue_struct *cpu_wq;
 struct list_head list;
 const char *name;   /*workqueue name*/
 int singlethread;  
 int freezeable;  /* Freeze threads during suspend */
 int rt;
}; 

If it is multi-threaded, Linux creates cpu_workqueue_struct based on the number of CPUs in the current system.

truct cpu_workqueue_struct {
 spinlock_t lock;
 struct list_head worklist;
 wait_queue_head_t more_work;
 struct work_struct *current_work; 
 struct workqueue_struct *wq;   
 struct task_struct *thread;
} ____cacheline_aligned;
2.1 Common Interfaces for Work Queues – Creating Initial Work_struct Work

create:

//method 1
#define __DELAYED_WORK_INITIALIZER(n, f) {          \
    .work = __WORK_INITIALIZER((n).work, (f)),      \
    .timer = TIMER_INITIALIZER(NULL, 0, 0),         \
    }

//method 2
#define DECLARE_WORK(n, f)                  \
    struct work_struct n = __WORK_INITIALIZER(n, f)
    
//method 3
#define INIT_WORK(_work, _func)                     \
    do {                                \
        (_work)->data = (atomic_long_t) WORK_DATA_INIT();   \
        INIT_LIST_HEAD(&(_work)->entry);            \
        PREPARE_WORK((_work), (_func));             \
    } while (0)

initialization:

void work_handler(void*data)

This function is executed by a worker thread, so the function will run in the process context. By default, response interrupts are allowed and no locks are held. The function can sleep if needed. It should be noted that although the function runs in the process context, it cannot access user space because the kernel thread has no associated memory map in user space. Usually when a system call occurs, the kernel will run on behalf of the user space process, at which point it can access the user space, and only then will it map the memory of the user space.

2.2 Common Interfaces for Work Queues – Scheduling Work, Entering Teams

int schedule_work(struct work_struct *work);

1
schedule_work() will cause the work to be dispatched immediately, and will be executed once the worker thread on its processor is woken up. In most cases, you don’t need to create a work queue yourself. Instead, you only define the work, hook the work structure to the kernel’s predefined event work queue, and define a static global quantity in kernel/workqueue.c. Work Queue static struct workqueue_struct *keventd_wq; The default worker thread is called events/n, where n is the processor number and each processor corresponds to a thread. For example, a single-processor system has only one thread, events/0. A dual-processor system will have one more event/1 thread.

Schedule_work adds the work structure to the global event work queue keventd_wq, which is maintained, created, and destroyed by the kernel itself. This work will be dispatched immediately, and once the worker thread on the processor it is on is awakened, it will be executed.

2
Sometimes I don’t want the job to be executed right away, but I hope it will be executed after a delay. In this case, the timer can also be used for delay scheduling, which is executed at the specified time, and is registered by the default timer callback function after expiration. After the delay is delayed, it is woken up by the timer and the work is added to the work queue wq.

schedule_delayed_work(&work,delay);

The work queue has no priority and is processed in a FIFO manner.

Three work queues are simple to use
In the Workqueue mechanism, a system default workqueue queue is provided, keventd_wq, which is created by the Linux system at initialization time. The user can directly initialize a work_struct object and then schedule it in the queue, which is more convenient to use.

When the user calls the initialization API of the workqueue queue: create_workqueue or create_singlethread_workqueue to initialize the workqueue queue, the kernel begins to assign a workqueue object to the user and chain it to a global workqueue queue. Then Linux allocates the cpu_workqueue_struct object with the same number of CPUs as the workqueue object according to the current CPU situation. Each cpu_workqueue_struct object will have a task queue. Next, Linux assigns a kernel thread to each cpu_workqueue_struct object, which is the kernel daemon to handle the tasks in each queue. At this point, the user calls the initialization interface to initialize the workqueue and returns a pointer to the workqueue.

Routine:
Experiment 1 Use the system default queue: For kernel ready-made queues, we initialize the work and directly join the system default workqueue queue with queue_schedule: keventd_wq and schedule execution

Demo 2 Re-creating the queue: For our newly created work queue, we need to create a work_queue with create_queue, then initialize the work. Finally, we need to use queue_work to join the work queue we created and schedule execution.

Demo 1: Using the system default queue

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/workqueue.h>

#define ENTER() printk(KERN_DEBUG "%s() Enter", __func__)
#define EXIT() printk(KERN_DEBUG "%s() Exit", __func__)
#define ERR(fmt, args...) printk(KERN_ERR "%s()-%d: " fmt "\n", __func__, __LINE__, ##args)
#define DBG(fmt, args...) printk(KERN_DEBUG "%s()-%d: " fmt "\n", __func__, __LINE__, ##args)

struct test_work {
	struct work_struct w;
	unsigned long data;
};

static struct test_work my_work;

static void my_work_func(struct work_struct *work)
{
	struct test_work *p_work;
	ENTER();
	p_work = container_of(work, struct test_work, w);
	while (p_work->data) {
		DBG("data: %lu", p_work->data--);
		msleep_interruptible(1000);
	}

	EXIT();
}

static int __init wq_demo_init(void)
{
	INIT_WORK(&my_work.w, my_work_func);
	my_work.data = 30;

	msleep_interruptible(1000);
	DBG("schedule work begin:");
	if (schedule_work(&my_work.w) == 0) {
		ERR("schedule work fail");
		return -1;
	}

	DBG("success");
	return 0;
}

static void __exit wq_demo_exit(void)
{
	ENTER();
	while (my_work.data) {
		DBG("waiting exit");
		msleep_interruptible(2000);
	}
	EXIT();
}

MODULE_LICENSE("GPL");
module_init(wq_demo_init);
module_exit(wq_demo_exit);

Practice of Experiment 1:
Scene: Android7.1 rk3288 motherboard receives the interrupt signal of the external MCU small board. The work of the dispatch function processing function in the interrupt function is added to the system default queue, and once the worker thread on the processor it is on is awakened, it will be executed. The execution content is obtained by the i2c bus to obtain the value in the peripheral board register, and the value is pushed to the corresponding node in the sys/chensai path in the form of the sysfs attribute file, and the value can be obtained by the adb shell cat gpion.

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/mm.h>
#include <asm/uaccess.h>
#include <linux/blkdev.h>
 
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/string.h>

#include <linux/major.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/seq_file.h>

#include <linux/kobject.h>
#include <linux/kobj_map.h>
#include <linux/cdev.h>
#include <linux/mutex.h>
#include <linux/backing-dev.h>
#include <linux/tty.h> 
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/async.h>
#include <linux/irq.h>
#include <linux/workqueue.h>
#include <linux/proc_fs.h>
#include <linux/input/mt.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/device.h>

#include <linux/module.h> 
#include <linux/moduleparam.h> 
#include <linux/init.h> 
#include <linux/kmod.h> 
#include <linux/sched.h> 
#include <linux/delay.h>

#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/kthread.h>
#include <asm/unaligned.h>

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/l2cap.h>
#include <net/bluetooth/rfcomm.h>


static int debug = 4;
module_param(debug, int, S_IRUGO|S_IWUSR);

#define dbg_codec(level, fmt, arg...)		\
	do {					\
		if (debug >= level)		\
			printk(fmt , ## arg);	\
	 } while (0)

#define	DBG(fmt, ...)	dbg_codec(0, fmt, ## __VA_ARGS__)


#define CLONE_KERNEL    (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)


static struct i2c_client *chensai_client = NULL;
unsigned int gpio_num; //= 230; //GPIO7_A6
unsigned char register_addr = 0x00;
char chensai_iic_addr = 0x09;
int chensai_irq_num;
uint8_t key_status[1];
int keys_val[5];
int key_status_val;
//static struct task_struct *task;


struct test_work {
	struct work_struct mhr_work;
	int val;
};

struct test_work my_work;

static int chensai_i2c_read( struct i2c_client* client,unsigned char reg,uint8_t *data, char device_addr)
{  
	int ret;
	struct i2c_msg msgs[] = {
			{
				 .addr = device_addr,
				 .flags = 0,
				// .len = 1,
				 .len = sizeof(reg),
				 .buf = &reg,//reg addr
			 },
			{
				 .addr = device_addr,
				// .flags = I2C_M_RD,0x01
				 .flags = I2C_M_RD,
				 .len = sizeof(data),
				 .buf = data,// reg value
			 },
		};

		ret = i2c_transfer(client->adapter, msgs, 2);
			if (ret < 0)
			{
				printk("i2c read error\n");
			}
		DBG("%s  i2c_transfer_ret=%d\n", __func__, ret);
	return ret;
		
} 

/*
static int chensai_thread(void *arg)
{ 
	int i;
	DBG("%s : chensai_thread\n", __func__);

	
	while(1)
	{
		
		if(irq_status)
		{		
			chensai_i2c_read(chensai_client, register_addr, key_status, chensai_iic_addr);
			key_status_val = key_status[0];
			irq_status = 0;
			
			for( i=0; i < 5; i++)
			{
				if(key_status_val & (1 << i))
				{
					keys_val[i] = 1;
				}else{
					keys_val[i] = 0;
				}
			}
		}

		ssleep(1);
		
	}
	
	return 0;
}
*/

static ssize_t gpio0_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{  
	int a = keys_val[0];
    return sprintf(buf, "%d\n", a);
}

static ssize_t gpio1_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{ 
	int a = keys_val[1];
	return sprintf(buf, "%d\n", a);
}

static ssize_t gpio2_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{ 
	int a = keys_val[2];
	return sprintf(buf, "%d\n", a);
}

static ssize_t gpio3_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{ 
	int a = keys_val[3];
	return sprintf(buf, "%d\n", a);
}

static ssize_t gpio4_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{ 
	int a = keys_val[4];
	return sprintf(buf, "%d\n", a);
}

static struct kobject *chensai_kobj = NULL;

struct  chensai_control_attribute {
	
	struct attribute	attr;
	ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
	ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n);
};

static struct  chensai_control_attribute chensai_attribute[] = {
	__ATTR(gpio0, 0777,	gpio0_show,	NULL),
	__ATTR(gpio1, 0777,	gpio1_show,	NULL),
	__ATTR(gpio2, 0777,	gpio2_show,	NULL),
	__ATTR(gpio3, 0777,	gpio3_show,	NULL),
	__ATTR(gpio4, 0777,	gpio4_show,	NULL),
};

static void my_work_func(struct work_struct *work)
{
	struct test_work *p_work;
	int i;
	p_work = container_of(work, struct test_work, mhr_work);
	if(p_work->val) {
		DBG("%s : container_of ok\n", __func__);
		
		chensai_i2c_read(chensai_client, register_addr, key_status, chensai_iic_addr);
		key_status_val = key_status[0];
			
		for( i=0; i < 5; i++)
		{
			if(key_status_val & (1 << i))
			{
				keys_val[i] = 1;
			}else{
				keys_val[i] = 0;
			}
		}
		
	}
	
}

static irqreturn_t chensai_irq_handler(int irq, void *dev_id)
{
	
	DBG("%s :enter\n", __func__);
	disable_irq_nosync(chensai_irq_num);
	
	//irq_status = 1;
	if (schedule_work(&my_work.mhr_work) == 0) {
		DBG("schedule work fail\n");
	}
	
	enable_irq(chensai_irq_num);

	return IRQ_HANDLED;
}


static int chensai_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int error;
	struct device_node *np = client->dev.of_node;
	enum of_gpio_flags flags;
	int i;
	
	DBG("%s : chensai_probe\n", __func__);
	
	for(i=0; i < 5; i++)
	{
		error = sysfs_create_file(chensai_kobj, &chensai_attribute[i].attr);
	}	
	
	
	gpio_num =of_get_named_gpio_flags(np, "irq-gpio", 0, &flags);
	DBG("%s  gpio_num=%d\n", __func__, gpio_num);
	
	
	if (!gpio_is_valid(gpio_num)){
        DBG("%s  gpio_is_unvalid \n", __func__);
    } 
	
	if (gpio_request(gpio_num, "irq-gpio")) {
		DBG("%s  failed to request  irq-gpio, gpio_num =%d\n", __func__, gpio_num);
    }

	gpio_direction_input(gpio_num);

	chensai_irq_num = gpio_to_irq(gpio_num);    //gpio to interrupt ID
	DBG("%s  chensai_irq_num=%d\n", __func__, chensai_irq_num);
	
	error = request_irq(chensai_irq_num, chensai_irq_handler, IRQ_TYPE_EDGE_BOTH, "chensai_irq", NULL);
    if (error) {
			printk("request_irq error\n");
    }
	
	
	
	INIT_WORK(&my_work.mhr_work, my_work_func);
	my_work.val = 1;
	
	
	chensai_client = client;
	
	return 0;
}


static const struct i2c_device_id chensai_id[] = {
	{"chensai_keyboard", 0},
	{}
};
MODULE_DEVICE_TABLE(i2c, chensai_id);

static struct i2c_driver chensai_drv = { 
    .driver     = { 
        .name   = "chensai",
        .owner  = THIS_MODULE,
    },
	.probe = chensai_probe,
	.id_table = chensai_id,
};

static int chensai_init(void)
{
	chensai_kobj = kobject_create_and_add("chensai", NULL);
	i2c_add_driver(&chensai_drv);
    return 0;
}

static void chensai_exit(void)
{
    i2c_del_driver(&chensai_drv);
    free_irq(chensai_irq_num, chensai_irq_handler);
    //kthread_stop(task);   //send signal to task to exit
}

module_init(chensai_init);
module_exit(chensai_exit);
MODULE_LICENSE("GPL");

Demo 2: Recreating the queue

Template of experiment 2:

#include <linux/init.h>

#include <linux/kernel.h>

#include <linux/module.h>

MODULE_AUTHOR("Mike Feng");



struct my_data

{

         structwork_struct my_work;

         intvalue; 

};

struct workqueue_struct *wq=NULL;

struct work_struct work_queue;



struct my_data* init_data(structmy_data *md)

{

         md=(structmy_data*)kmalloc(sizeof(struct my_data),GFP_KERNEL);

         md->value=1;

         md->my_work=work_queue;

         returnmd;

}



static void work_func(struct work_struct *work)

{

         structmy_data *md=container_of(work,structmy_data,my_work);

         printk("<2>""Thevalue of my data is:%d\n",md->value);

}

static __init intwork_init(void)

{

         structmy_data *md=NULL;

         structmy_data *md2=NULL;

         md2=init_data(md2);

         md=init_data(md);     

         md2->value=20;

         md->value=10;



         INIT_WORK(&md->my_work,work_func);

         schedule_work(&md->my_work);

 



         wq=create_workqueue("test");

         INIT_WORK(&md2->my_work,work_func);

         queue_work(wq,&md2->my_work);    

         return0;

}

static void work_exit(void)

{


         destroy_workqueue(wq);

}

module_init(work_init);

module_exit(work_exit);