(十三)linux中断底半部分处理机制

这篇文章介绍一下linux中断的底半部分的tasklet和workquene两种处理机制,其中tasklet中不能有延时函数,workquene的处理函数可以加入延时操作

在Linux中为了提高系统的响应速度及并发能力,将Linux的中断划分为顶半部和底半部两部分。
顶半部(top half):做中断的登记操作,当然也可以做不耗时的中断处理(内核中会创建一个中断登记表)。
顶半部完成的一般是紧急的硬件操作,一般包括读取寄存的中断状态,清除中断标志,将底半部处理程序挂到底半部的执行队列中去,此过程不可被打断
底半部(bottom half):处理耗时操作,把耗时的操作放入底半部执行,这个过程可以被打断,耗时操作推后执行

(一)tasklet小任务处理机制

内核中关于tasklet的介绍:

/* Tasklets --- multithreaded analogue of BHs.

   Main feature differing them of generic softirqs: tasklet
   is running only on one CPU simultaneously.

   Main feature differing them of BHs: different tasklets
   may be run simultaneously on different CPUs.

   Properties:
   * If tasklet_schedule() is called, then tasklet is guaranteed
     to be executed on some cpu at least once after this.
   * If the tasklet is already scheduled, but its execution is still not
     started, it will be executed only once.
   * If this tasklet is already running on another CPU (or schedule is called
     from tasklet itself), it is rescheduled for later.
   * Tasklet is strictly serialized wrt itself, but not
     wrt another tasklets. If client needs some intertask synchronization,
     he makes it with spinlocks.
(1)tasklet相关函数接口

小任务机制相关的数据结构:

struct tasklet_struct
{
	struct tasklet_struct *next;	//用来实现多个tasklet_struct结构链表
	unsigned long state;			//当前这个tasklet是否已经被调度
	atomic_t count;					//值为0的时候用户才可以调度
    /*原子变量操作:指的是操作过程中不允许被打断机制
    typedef struct {
    	int counter;
    } atomic_t;	
    */
   void (*func)(unsigned long);	//指向tasklet绑定的函数指针
   unsigned long data;				//传向tasklet绑定的函数的参数
};

小任务数据结构创建:

#define DECLARE_TASKLET(name, func, data) \        //静态初始化,默认为使能
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \      //静态初始化,默认为失能
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

初始化小任务:

extern void tasklet_init(struct tasklet_struct *t,
			 void (*func)(unsigned long), unsigned long data);

小任务加锁解锁:

//尝试加锁
static inline int tasklet_trylock(struct tasklet_struct *t)
{
	return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}

//解锁
static inline void tasklet_unlock(struct tasklet_struct *t)
{
	smp_mb__before_clear_bit(); 
	clear_bit(TASKLET_STATE_RUN, &(t)->state);
}
/**
 * test_and_set_bit - Set a bit and return its old value
 * @nr: Bit to set
 * @addr: Address to count from
 *
 * This operation is atomic and cannot be reordered.
 * It may be reordered on other architectures than x86.
 * It also implies a memory barrier.
 */
static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
{
	unsigned long mask = BIT_MASK(nr);
	unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
	unsigned long old;
	unsigned long flags;

	_atomic_spin_lock_irqsave(p, flags);
	old = *p;
	*p = old | mask;
	_atomic_spin_unlock_irqrestore(p, flags);

	return (old & mask) != 0;
}

小任务登记:

static inline void tasklet_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_schedule(t);
}

小任务失能:

static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
	atomic_inc(&t->count);
	smp_mb__after_atomic_inc();
}

static inline void tasklet_disable(struct tasklet_struct *t)
{
	tasklet_disable_nosync(t);
	tasklet_unlock_wait(t);
	smp_mb();
}

小任务使能:

static inline void tasklet_enable(struct tasklet_struct *t)
{
	smp_mb__before_atomic_dec();
	atomic_dec(&t->count);		//单纯的将count减一操作
}

结束小任务:

extern void tasklet_kill(struct tasklet_struct *t);
extern void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu);
(2)tasklet使用流程
  1. 定义结构体并初始化
struct tasklet_struct task;
tasklet_init(&task,自定义函数功能名,函数形参);
  1. 在合适的地方(一般在中断里)对tasklet登记
   tasklet_schedule(&task);
(3)tasklet实例代码

chrdev.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>

struct tasklet_struct task;

void tasklet_fun(unsigned long data)
{
  printk("this is tasklet test\n");
}

static int __init  tasklet_module_init(void)
{
   tasklet_init(&task,tasklet_fun,(unsigned long)10);
   //tasklet_disable(&task);//失能后不能卸载该tasklet
   tasklet_schedule(&task);
    return 0;
}

static void __exit  tasklet_module_cleanup(void)
{
   tasklet_kill(&task);
}
module_init(tasklet_module_init);
module_exit(tasklet_module_cleanup);
MODULE_LICENSE("GPL");

(二)workquene工作队列处理机制

工作队列提供了将功能推迟到下半部分的通用方法。核心是工作队列(struct workqueue_struct),这是工作所在的结构。内核中通过work_struct结构标识要延迟的工作和要使用的延迟功能。events / X内核线程(每个CPU一个)从工作队列中提取工作,并激活下半部处理程序之一。

工作队列是更新的延迟机制,已在2.5 Linux内核版本中添加。工作队列不是通用的延迟机制,不像Tasklet那样提供一站式的延迟方案,在该机制中,工作队列的处理函数可以休眠(在Tasklet模型中是不可能的),工作队列的延迟可能比任务小,但包含更丰富的API以进行工作延迟,延迟之前是通过keventd任务队列管理,现在由名为events / X的内核工作线程管理。

在这里插入图片描述

内核中有两种工作队列,一种是共享工作队列,另一种是自定义工作队列

共享工作队列 :内核提供,用户可直接使用,秩序调用对应的接口即可,更多的时候选择共享消息队列

自定义工作队列:需要用户手动创建,并手动销毁

共享工作队列自定义工作队列
内核启动期间会创建一个工作全局的工作队列,所有的驱动都可以把自己延后执行的工作函数挂到这个共享工作队列中。当你要执行工作不希望受到其他工作的影响时,可以自己创建一个工作队列,然后把自己的工作放在自定义的工作队列调度。
优点:不需要自己创建工作队列,简单,快捷,方便。优点:不会受到其他工作的影响,工作函数执行有保障。
缺点:可能会受到其他工作的影响,前面的工作阻塞,影响到后面的工作的执缺点:造成系统巨大开销大,如果过多创建自定义工作队列,会严重影响系统实时
(1)workqueue相关函数接口

工作队列数据结构:

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

typedef void (*work_func_t)(struct work_struct *work);

声明并初始化工作队列:

 1.静态方式:
#define DECLARE_WORK(n, f)					\
	struct work_struct n = __WORK_INITIALIZER(n, f)

#define DECLARE_DELAYED_WORK(n, f)				\
	struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f)

 2. 动态形式初始化:
#define INIT_WORK(_work, _func)					\
	do {							\
		__INIT_WORK((_work), (_func), 0);		\
	} while (0)

创建自定义工作队列的时候使用:

extern int queue_work(struct workqueue_struct *wq, struct work_struct *work);

工作队列登记:

extern int schedule_work(struct work_struct *work);
/**
 * schedule_work - put work task in global workqueue
 * @work: job to be done
 *
 * Returns zero if @work was already on the kernel-global workqueue and
 * non-zero otherwise.
 *
 * This puts a job in the kernel-global workqueue if it was not already
 * queued and leaves it in the same position on the kernel-global
 * workqueue otherwise.
 */
int schedule_work(struct work_struct *work)
{
	return queue_work(system_wq, work);
}

根据结构体成员找到结构体首地址:

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

container_of使用示例:

struct mywork{
    int m;
    int n;
    struct work_struct works;
}test;
  container_of根据结构体内部的某一成员获取结构的首地址
  container_of(ptr, type, member) 
  @ptr:	指向结构体成员的指针.  如 struct work_struct *works;
 * @type:	the type of the container struct this is embedded in.  结构体类型 struct mywork
 * @member: the name of the member within the struct.  works
(2)共享工作队列使用流程

1.定义共享工作队列结构体并初始化

struct work_struct works;
INIT_WORK(&works,workqueue_fun);

2.在合适位置(一般为中断)对工作队列登记

 schedule_work(&works);
(3)自定义工作队列使用流程

1、创建工作队列

struct workqueue_struct  my_workqueue;
struct workqueue_struct *create_workqueue(&my_workqueue);  
//struct workqueue_struct *create_singlethread_workqueue(const char *name);  
//create_workqueue函数会在系统中的每个处理器上创建一个线程(多线程),而create_singlethread_workqueue只是创建一个单一的线程,如果单个线程足够使用,那么应该使用create_singlethread_workqueue函数。

2、创建任务

struct work_struct works;
INIT_WORK(&works,workqueue_fun);

3、提交任务,要将任务提交到工作队列中,内核提供了下面两个API:

int queue_work(struct workqueue_struct *wq, struct work_struct *work);  
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);  

这两个函数都会将任务提交到工作队列中,使用queue_delayed_work函数,则提交的任务至少延时由参数delay指定的时间才被执行。

如果要取消工作队列中的某个任务,使用cancel_delayed_work,原型如下:

int cancel_delayed_work(struct work_struct *work);  

如果任务在被执行之前取消,那么cancel_delayed_work函数返回非零值,调用该函数之后内核会确保被取消的任务不被执行。但是返回0,则表示任务已经被执行,因此调用cancel_delayed_work函数后,任务有可能仍在运行,所以为了确保任务测地被取消,需要调用flush_workqueue函数,与方法1中的不同。

void flush_workqueue(struct workqueue_struct *wq);  

4、销毁工作队列, 使用完工作队列之后,可以使用destroy_workqueue销毁工作队列:

void destroy_workqueue(struct workqueue_struct *wq); 
(4)共享workqueue实例代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>

struct work_struct works;
void workqueue_fun(struct work_struct * work)
{
  printk("this is workqueue test\n");
}
static int __init  workqueue_module_init(void)
{
    INIT_WORK(&works,workqueue_fun);//初始化共享工作队列结构体
    schedule_work(&works);//将工作队列进行登记
    return 0;
}
static void __exit  workqueue_module_cleanup(void)
{
     printk("module is exit\n");
}
module_init(workqueue_module_init);
module_exit(workqueue_module_cleanup);
MODULE_LICENSE("GPL");

本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作

微信公众号:zhjj0729

微博:文艺to青年

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值