- 软中断
- tasklet
- 工做队列
- 内核定时器,也能够将工做退后一段时间(精准的)执行
tasklet是中断处理下半部最多见的一种方式,驱动程序通常先申请中断,在中断处理函数内完成中断上半部分的工做以后再调用tasklet,tasklet有以下优势:linux
HI_SOFTIRQ
和ACKLET_SOFTIRQ
,本质上没什么区别,只不过HI_SOFTIRQ
的优先级更高一些/* tasklet结构体 */ struct tasklet_struct { struct tasklet_struct *next; //链表节点 unsigned long state; //属性 atomic_t count; //计数,用于禁止使能,每禁止一次计数加1,每使能一次计数减1,只有禁止次数和使能次数同样(count=0)的时候tasklet才会执行调用函数 void (*func)(unsigned long); //中断下半部实现方法,不能阻塞,不能睡眠 unsigned long data; //中断服务函数的参数 }; /* tasklet结构体变量是tasklet_vec链表下的一个节点,next是链表下一个节点,status使用了2个位,以下:*/ enum { TASKLET_STATE_SCHED, //1表示已经被调度,0表示还没调度 TASKLET_STATE_RUN //1表示tasklet正在执行,0表示还没有执行,只针对SMP有效,单处理器无心义 }; /* tasklet初始化 */ /* 1.定义时初始化:定义变量名为name的tasklet_struct结构体变量,并初始化调用函数func,参数为data,使能tasklet */ DECLARE_TASKLET(name, func, data); #define DECLARE_TASKLET(name, func, data) struct tasklet_struct name = {NULL, 0, ATOMIC_INIT(0), func, data} /* 2.定义时初始化:定义变量名为name的tasklet_struct结构体变量,并初始化调用函数func,参数为data,禁止tasklet */ DECLARE_TASKLET_DISABLED(name, func, data); #define DECLARE_TASKLET_DISABLED(name, func, data) struct tasklet_struct name = {NULL, 0, ATOMIC_INIT(1), func, data} /* 3.运行中初始化,先定义struct tasklet_struct name,后初始化 */ void tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data) { t->next = NULL; t->state = 0; //设置为未调度,未执行 atomic_set(&t->count, 0); //默认使能 t->func = func; t->data = data; } /* 启动中断下半部 */ static inline void tasklet_schedule(struct tasklet_struct * t); static inline int tasklet_trylock(struct tasklet_struct * t); static inline void tasklet_unlock(struct tasklet_struct * t); static inline void tasklet_unlock_wait(struct tasklet_struct * t); /* 使能以前一个被disable的tasklet;若这个tasklet已经被调度,他会很快运行。tasklet_enable和task_disable必须匹配使用,由于内核跟踪每一个tasklet的“禁止次数” */ static inline void tasklet_enable(struct tasklet_struct * t); /* 与tasklet_schedule相似,只是在更高优先级执行。当软中断处理运行时,它处理高优先级tasklet,在其余软中断以前,只有具备低响应周期要求的驱动(实时性不强)才应使用这个函数,可避免其余软件中断处理引入的附加周期 */ void tasklet_hi_schedule(struct tasklet_struct * t); /* 确保了tasklet不会被再次调度运行,即执行此函数以后若再次调用tasklet_schedule函数,中断下半部也不会执行。一般当一个设备正在被关闭或者模块卸载时被调用。若是tasklet正在运行,那么这个函数需等待直到tasklet执行完毕。若tasklet从新调度它本身,则必须阻止在调用tasklet_kill以前它从新调度本身,如同使用del_timer_sync。注意:该函数不是真的去杀掉被调度的tasklet,而是保证tasklet再也不调用 */ void tasklet_kill(struct tasklet_struct *t);
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ cat tasklet.c #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/interrupt.h> struct message { int id; int val; }; struct tasklet_struct mytask; void mytask_func(unsigned long data) { struct message *msg = (struct message *)data; printk("id = %d, val = %d\n", msg->id, msg->val); kfree(msg); msg = NULL; } static int __init task_init(void) { struct message *msg; printk(KERN_INFO "%s start\n", __func__); msg = (struct message *)kzalloc(sizeof(struct message), GFP_KERNEL); if (msg == NULL) { printk(KERN_ERR "failed to kmalloc\n"); return -ENOMEM; } msg->id = 7; msg->val = 77; tasklet_init(&mytask, mytask_func, (unsigned long)msg); tasklet_schedule(&mytask); printk(KERN_INFO "%s end\n", __func__); return 0; } static void __exit task_exit(void) { printk(KERN_INFO "%s start\n", __func__); tasklet_kill(&mytask); printk(KERN_INFO "%s end\n", __func__); } module_init(task_init); module_exit(task_exit); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ sudo insmod tasklet.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ sudo rmmod tasklet.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ dmesg [ 583.990890] task_init start [ 583.990893] task_init end [ 583.990897] id = 7, val = 77 [ 593.107581] task_exit start [ 593.107582] task_exit end
工做队列是一种将工做退后执行的形式,交由一个内核线程去执行进在程上下文执行,其不能访问用户空间。最重要的特色是工做队列容许从新调度甚至是睡眠。工做队列子系统提供了一个默认的工做线程来处理这些工做。默认的工做者线程叫作/events/n
,这里的n是处理器编号,每一个处理器对应一个线程,也能够本身建立工做者线程编程
/* 工做队列work_struct结构体 */ struct work_struct { /* atomic_long_t data; */ unsigned long 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); /* 初始化工做队列 */ INIT_WORK(struct work_struct *work, work_func_t func); DECLARE_WORK(n, f); /* 开启中断下半部,即将给定工做的处理函数提交给缺省的工做队列和工做线程 */ int schedule_work(struct work_struct *work); /* 确保没有工做队列入口在系统中任何地方运行 */ void flush_scheduled_word(void); /* 延时执行一个任务 */ int schedule_delayed_work(struct delayed_struct *work, unsigned long delay); /* 从一个工做队列中去除入口 */ int cancel_dalayed_work(struct delayed_struct *work);
workqueue_struct *
指针,并建立了工做线程。三个宏主要区别在于后面两个参数singlethread
和freezable
,singlethread
为0时会为每一个CPU上建立一个工做者线程,为1时只在当前运行的cpu上建立一个工做者线程。freezable
会影响内核线程结构体thread_info
的PF_NOFREEZE
标记/* 多处理器时会为每一个cpu建立一个工做线程 */ #define create_workqueue(name) __create_workqueue((name), 0, 0) /* 只建立一个工做线程,系统挂起时线程也挂起 */ #define create_freezeable_workqueue(name) __create_workqueue((name), 1, 1) /* 只建立一个工做线程,系统挂起时线程不会挂起 */ #define create_singlethread_workqueue(name) __create_workqueue((name), 1, 0) /* 释放建立的工做队列资源 */ void destroy_workqueue(struct workqueue_struct *wq); /* 延时调用指定工做队列的工做 */ queue_delayed_work(struct workqueue_struct *wq, struct delay_struct *work, unsigned long delay); /* 取消指定工做队列的延时工做 */ cancel_delayed_work(struct delay_struct *work); /* 将工做加入到工做队列中进行调度 */ queue_work(struct workqueue_struct *wq, struct work_struct *work); /* 等待队列中的任务所有执行完毕 */ void flush_workqueue(struct workqueue_struct *wq);
#include <linux/init.h> #include <linux/module.h> #include <linux/workqueue.h> void my_func(struct work_struct *ws); DECLARE_WORK(my_work, my_func); void my_func(struct work_struct *ws) { printk(KERN_INFO "doing work\n"); } static int __init work_queue_init(void) { schedule_work(&my_work); return 0; } static void __exit work_queue_exit(void) { } module_init(work_queue_init); module_exit(work_queue_exit); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ sudo insmod work_queue.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ dmesg [ 4097.029387] doing work
#include <linux/init.h> #include <linux/module.h> #include <linux/workqueue.h> struct work_struct my_work; struct workqueue_struct *my_wq; void my_func(struct work_struct *ws) { printk(KERN_INFO "doing work\n"); } static int __init work_queue_init(void) { /* create my work queue */ my_wq = create_workqueue("my_wq"); if (!my_wq) { printk(KERN_ERR "failed to create workqueue\n"); return -1; } /* init my work */ INIT_WORK(&my_work, my_func); /* schedule my work to my work queue */ queue_work(my_wq, &my_work); return 0; } static void __exit work_queue_exit(void) { /* destroy my work queue */ destroy_workqueue(my_wq); my_wq = NULL; } module_init(work_queue_init); module_exit(work_queue_exit); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ sudo insmod work_queue.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ dmesg \ [ 5472.402008] doing work
find -name "*.c" | xargs grep "create_workqueue"
mod_timer()
函数动态的改变定时器到达时间struct timer_list
实现,须要的头文件包括#include <linux/timer.h>
,可是实际开发过程当中不须要包含该头文件,由于在sched.h
中包含了该头文件struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; }
expires
:定义定时器到期时间,一般采用jiffies
和HZ
这个两个全局变量来配合设置该参数的值,如expires = jiffies + n * HZ
,其中jiffies
是自启动以来的滴答数,HZ
是一秒钟的滴答数。假设系统1秒的滴答数为1,咱们但愿定时10秒钟,并且咱们在启动定时器的时候jiffies
数值为5,那么10秒后滴答总数将为5 + 10 * 1 = 15
function
:即一个函数指针,就是定时器处理函数,当定时时间到达以后就会触发该函数的执行data
:一般实现参数的传递,从function
的参数类型值咱们能够看到,data
能够做为定时器处理函数的参数,其余的元素可经过内核的函数来初始化
/* 定时器初始化 */ init_timer(struct timer_list *timer); /* 定时器初始化 */ #define DEFINE_TIMER(_name, _function, _expires, _data) struct timer_list_name = TIMER_INITIALIZER(_function, _expires, _data) /* 添加定时器到内核 */ void add_timer(struct timer_list *timer) { BUG_ON(timer_pending(timer)); mod_timer(timer, timer->expires; } /* 删除定时器,若是定时器的定时时间尚未到达,那么才能够删除定时器 */ int del_timer(struct timer_list *timer); /* 修改定时器到达时间,该函数特色是:无论定时器是否到达定时时间,都会从新添加一个定时器到内核,因此能够在定时器出路函数中调用该函数去修改新的定时时间 */ int mode_timer(struct timer_list *timer, unsigned long expires);
#include <linux/module.h> #include <linux/init.h> #include <linux/timer.h> struct timer_list g_timer; void timer_func(unsigned long data) { printk(KERN_INFO "%s->current jiffies = %ld\n", __func__, jiffies); mod_timer(&g_timer, jiffies + HZ); } static int __init timer_init(void) { /* init timer old kernel can use init_timer(), but new kernel can not use this function */ init_timer(&g_timer); // DEFINE_TIMER(g_timer, timer_func); /* set timer parameters */ printk(KERN_INFO "%s->current jiffies = %ld\n", __func__, jiffies); g_timer.expires = jiffies + 2 * HZ; g_timer.function = timer_func; /* add timer */ add_timer(&g_timer); return 0; } static void __exit timer_exit(void) { del_timer(&g_timer); } module_init(timer_init); module_exit(timer_exit); MODULE_LICENSE("GPL");