十一、【工作队列】tasklet、上半部和下半部

一、概述

1、工作队列(workqueue)

  • 工作队列,将一个work提交到workqueue上,而这个workqueue是挂到一个特殊内核进程上,当这个特殊内核进程被调度时,会从workqueue上取出work来执行。当然这里的work是与函数联系起来的。这个过程表现为,此刻先接下work,但不立刻执行这个work,等有时间再执行,而这个时间是不确定的。
  • 工作队列运行在进程上下文,可以睡眠
  • 如果进程上下文中有非常耗时的工作,尽量交给工作队列,不使用tasklet

2、Tasklet
  Tasklet,同样,也是先接下任务,但不立刻做任务,与work很类似。tasklet运行在软中断上下文。

  • 软中断:有这样三句话理解”硬中断是外部设备对CPU的中断”,”软中断通常是硬中断服务程序对内核的中断”,”信号则是由内核(或其他进程)对某个进程的中断

这三句话,是比较笼统的理解,现在回到linux具体来理解:

1)软中断触发时机:

  • 中断上下文触发(在中断服务程序中),在中断服务程序退出后,软中断会得到立马处理。
  • 非中断上下文(也可以理解进程上下文),通过唤醒守护进程ksoftirqd,只有当守护进程得到调度后,软中断才会得到处理。

不管是中断上下文,还是非中断上下文,最终都是调用__do_softirq实现的软中断,在这个函数里面是打开硬件中断,关闭内核抢占。这就是软中断上下文,即开硬件中断,关闭抢占

(2)tasklet是基于软中断实现的,用在中断服务程序触发tasklet,则就是中断下半部分,也是用得最多的情况。用在进程上下文触发tasklet,则很类似workqueue,但是tasklet不能有睡眠(因为关闭抢占的,不考虑硬件中断,就是原子性的),也不适合做非常耗时的如果是非常耗时的,尽量交给workqueue(可以在tasklet回调里面用work,把更耗时,时间要求更不高的,交给workqueue)。

3、中断上半部和下半部

  由于中断随时可能发生,所以必须保证中断处理程序可以快速执行;但是中断处理程序可能又会处理大量的任务,两者之间存在矛盾,所以一般会把中断处理的过程分成两部分:上半部和下半部

  • 上半部也叫硬中断,是通常意义上的中断处理程序,用来接收中断,和简单的、有时限的处理工作,例如对中断接收后进行应答或者复位硬件等需要在所有中断被禁止的情况下完成的工作。——对时间要求严格的工作
  • 下半部允许稍后完成的工作,则会推迟到下半部在合适的时间完成。Linux有多种机制来实现下半部,其中一种就是软中断。——对时间要求没那么严格,可以稍微推后执行

  对于网卡的中断处理,上半部会执行通知硬件、拷贝网络数据报到内存并继续读取新数据包,这些重要、紧急且与硬件相关的工作,因为网卡接收的网络数据包的缓存大小通常是固定的、有限的,一旦被延迟可能造成缓存溢出。而数据包的处理等操作,则由下半部来完成。

二、tasklet的接口函数

1、定义一个tasklet

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

struct tasklet_struct
{
	struct tasklet_struct *next; 
	unsigned long state;   
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

参数:

  • name: tasklet的名字
  • func:函数指针,指向tasklet系统调度时执行的函数
  • data:作为实参传递给 void (*func)(unsigned long);

2、tasklet小任务的处理函数

void key_tasklet_func(unsigned long data)//下半部
{
//做一些对时间要求不严格的工作

}

3、调度tasklet

static inline void tasklet_schedule(struct tasklet_struct *t)

三、工作队列(wrokqueue)的接口函数

1、定义一个工作队列

#define DECLARE_WORK(n, f)					\
	struct work_struct n = __WORK_INITIALIZER(n, f)

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;-------函数指针,指向的函数就是工作队列中的work被调度时,执行的函数
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

2、工作队列work调用时执行的函数

static void key_work_func(struct work_struct *work)

3、调度work

int schedule_work(struct work_struct *work)

四、使用tasklet实例 和workqueue分别实现按键中断处理

  在按键中断处理程序中,我们将键值的获取归为比较紧急的任务(上半部),对于时间要求比较严格。对于等待队列的唤醒归为可以稍微退后执行的工作,所以归为下半部

1、下半部用tasklet实现。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <cfg_type.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
struct key_gpio_t{
	unsigned int irq;
	char irqname[20];
	unsigned char keyvalue;
};
static DECLARE_WAIT_QUEUE_HEAD(key_wq) ;
static bool flag = false;
static struct key_gpio_t key_gpio[]=
{
	{IRQ_GPIO_A_START+28,"KEY2_GPIOA28",2},
	{IRQ_GPIO_B_START+30,"KEY3_GPIOB30",3},
	{IRQ_GPIO_B_START+31,"KEY4_GPIOB31",4},
	{IRQ_GPIO_B_START+9,  "KEY6_GPIOB9",6},
};
static char keyvalue = 0;

static void key_tasklet_func(unsigned long data)//下半部
{
//做一些对时间要求不严格的工作
	 printk(KERN_INFO"key_tasklet_func\n");
	flag = true;  //设置flag为true
	wake_up_interruptible(&key_wq); //按键按下时,唤醒等待队列
 
}
static  DECLARE_TASKLET(key_tasklet, key_tasklet_func, 0); 
static ssize_t gec6818_key_read(struct file *filp, char __user * buf, size_t size, loff_t *oft)
{
	   
       int ret;
	  wait_event_interruptible(key_wq, flag);
	  flag = false;  //唤醒一次队列后要复位flag的值
	  if(size !=1)
	  {
	  	return -EINVAL;
	  }
	  ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
	  if(ret != 0)
	  {
	  	return (size -ret);
	  }
	  keyvalue=0;
	  return size;
}
static irqreturn_t gec6818_key_handler(int irq, void * dev)
{
   	
	struct key_gpio_t keytmp=*(struct key_gpio_t *)dev;
	keyvalue =keytmp.keyvalue;
	mdelay(400);  //按键防抖
	tasklet_schedule(&key_tasklet);
	return IRQ_HANDLED;
}	

struct file_operations key_misc_fops=
{
	.read = gec6818_key_read,
};
static struct miscdevice key_misc={
	.minor = MISC_DYNAMIC_MINOR,
	.name = "key_misc",
	.fops =  &key_misc_fops,
	};
static int __init  gec6818_key_init(void)
{
      int ret,i;
       printk(KERN_INFO"gec6818_key_init\n");
      ret = misc_register(&key_misc);
      if(ret < 0)
      {
      		printk(KERN_INFO"key misc register fail.\n");
		goto misc_register_err;	
      }
	for(i=0;i<4;i++)
	{
		 ret = request_irq(key_gpio[i].irq, gec6818_key_handler,IRQF_TRIGGER_FALLING,key_gpio[i].irqname,(void*)&key_gpio[i]);
		 if(ret < 0)
		 {
		 	printk(KERN_INFO"request_irq fail.\n");
			goto irq_request_err;
		 }
	}
	return 0;

irq_request_err:
	while(i--) 
	{
		free_irq(key_gpio[i].irq,NULL);
	}
misc_register_err:
		return 0;
}

static void __exit gec6818_key_exit(void)
{
	int i;
    printk(KERN_INFO"gec6818_key_exit\n");
    misc_deregister(&key_misc);	  
      for(i=0;i<4;i++) 
     {
	free_irq(key_gpio[i].irq,(void *)&key_gpio[i]);
     }
  
}

module_init(gec6818_key_init);
module_exit(gec6818_key_exit);
MODULE_LICENSE("GPL");

2、用工作队列来实现

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <cfg_type.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
struct key_gpio_t{
	unsigned int irq;
	char irqname[20];
	unsigned char keyvalue;
};
static DECLARE_WAIT_QUEUE_HEAD(key_wq) ;
static bool flag = false;
static struct key_gpio_t key_gpio[]=
{
	{IRQ_GPIO_A_START+28,"KEY2_GPIOA28",2},
	{IRQ_GPIO_B_START+30,"KEY3_GPIOB30",3},
	{IRQ_GPIO_B_START+31,"KEY4_GPIOB31",4},
	{IRQ_GPIO_B_START+9,  "KEY6_GPIOB9",6},
};
static char keyvalue = 0;
//用工作队列实现下半部
static void key_work_func(struct work_struct *work)
{
 	printk(KERN_INFO"key_work_func\n");
	flag = true;  //设置flag为true
	wake_up_interruptible(&key_wq); //按键按下时,唤醒等待队列

}
static  DECLARE_WORK(key_work, key_work_func);	

static ssize_t gec6818_key_read(struct file *filp, char __user * buf, size_t size, loff_t *oft)
{
	   
       int ret;
	  wait_event_interruptible(key_wq, flag);
	  flag = false;  //唤醒一次队列后要复位flag的值
	  if(size !=1)
	  {
	  	return -EINVAL;
	  }
	  ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
	  if(ret != 0)
	  {
	  	return (size -ret);
	  }
	  keyvalue=0;
	  return size;
}
static irqreturn_t gec6818_key_handler(int irq, void * dev)
{
   	
	struct key_gpio_t keytmp=*(struct key_gpio_t *)dev;
	keyvalue =keytmp.keyvalue;
	mdelay(400);  //按键防抖
	schedule_work(&key_work);
	return IRQ_HANDLED;
}	

struct file_operations key_misc_fops=
{
	.read = gec6818_key_read,
};
static struct miscdevice key_misc={
	.minor = MISC_DYNAMIC_MINOR,
	.name = "key_misc",
	.fops =  &key_misc_fops,
	};
static int __init  gec6818_key_init(void)
{
      int ret,i;
       printk(KERN_INFO"gec6818_key_init\n");
      ret = misc_register(&key_misc);
      if(ret < 0)
      {
      		printk(KERN_INFO"key misc register fail.\n");
		goto misc_register_err;	
      }
	for(i=0;i<4;i++)
	{
		 ret = request_irq(key_gpio[i].irq, gec6818_key_handler,IRQF_TRIGGER_FALLING,key_gpio[i].irqname,(void*)&key_gpio[i]);
		 if(ret < 0)
		 {
		 	printk(KERN_INFO"request_irq fail.\n");
			goto irq_request_err;
		 }
	}
	return 0;

irq_request_err:
	while(i--) 
	{
		free_irq(key_gpio[i].irq,NULL);
	}
misc_register_err:
		return 0;
}

static void __exit gec6818_key_exit(void)
{
	int i;
    printk(KERN_INFO"gec6818_key_exit\n");
    misc_deregister(&key_misc);	  
      for(i=0;i<4;i++) 
     {
		free_irq(key_gpio[i].irq,(void *)&key_gpio[i]);
     }
  
}

module_init(gec6818_key_init);
module_exit(gec6818_key_exit);
MODULE_LICENSE("GPL");

  

  

相关链接Linux中断原理、上半部和下半部、硬中断和软中断

原文地址:https://www.cnblogs.com/yuanqiangfei/p/15707993.html