T13 中断与时钟

1 中断概念

  • 参考T6
  • 一句话,中断上半部要求快进快出,耗时操做则可放在中断下半部执行
  • 下半部实现方式:
  • 软中断
  • tasklet
  • 工做队列
  • 内核定时器,也能够将工做退后一段时间(精准的)执行

2 tasklet

tasklet是中断处理下半部最多见的一种方式,驱动程序通常先申请中断,在中断处理函数内完成中断上半部分的工做以后再调用tasklet,tasklet有以下优势:linux

  • 一个tasklet只能够在一个CPU上同步执行,不一样的tasklet能够在不一样的CPU上同步执行
  • tasklet的实现是创建在两个软件中断的基础之上的,即HI_SOFTIRQACKLET_SOFTIRQ,本质上没什么区别,只不过HI_SOFTIRQ的优先级更高一些
  • 因为tasklet是在软中断上实现的,因此像软中断同样不能睡眠、不能阻塞、处理函数内不能含有致使睡眠的动做,如:减少信号量、从用户空间拷贝数据或者手动分配内存等
  • 一个tasklet可以被禁止而且以后被从新使能,它不会执行直到它被使能的次数与被禁止的次数相同
  • tasklet的串行化使tasklet函数没必要是可重入的(不须要考虑并发),所以简化了设备驱动程序开发者的工做
  • 每一个cpu拥有一个tasklet_vec链表,具体是哪一个cpu的tasklet_vec链表,是根据当前线程是运行在哪一个cpu来决定的

2.1 tasklet API

/* 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);

2.2 tasklet使用示例

  • 驱动代码,因为目前没有开发板,故而缺乏具体的硬件中断触发源,因此将会没法触发中断下半部份的执行。在本示例中直接将中断下半部份触发放在了模块初始化函数里面,也就是说当模块一旦被安装,那么就会触发中断下半部分的执行
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

3 工做队列

工做队列是一种将工做退后执行的形式,交由一个内核线程去执行进在程上下文执行,其不能访问用户空间。最重要的特色是工做队列容许从新调度甚至是睡眠。工做队列子系统提供了一个默认的工做线程来处理这些工做。默认的工做者线程叫作/events/n,这里的n是处理器编号,每一个处理器对应一个线程,也能够本身建立工做者线程编程

3.1 工做队列 API

  • 系统默认的工做队列
/* 工做队列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);
  • 使用自定义工做队列:建立工做队列使用3个宏,成功后返回workqueue_struct *指针,并建立了工做线程。三个宏主要区别在于后面两个参数singlethreadfreezablesinglethread为0时会为每一个CPU上建立一个工做者线程,为1时只在当前运行的cpu上建立一个工做者线程。freezable会影响内核线程结构体thread_infoPF_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);

3.2 工做队列使用示例

  • 使用系统的工做队列
#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"

4 内核定时器

  • 软件意义上的定时器最终依赖硬件定时器来实现,内核在时钟中断发生后检测检测一个定时器释放到期,到期后的定时器处理函数将做为软中断底半部执行。驱动编程中,能够利用一组函数和数据结构来完成定时器触发工做或某些周期性任务
  • 定时器在linux内核中主要是采用一个数据结构来实现的,可是须要注意的是定时器是一个只运行一次的对象,即当一个定时器结束之后,还须要从新添加定时器。可是能够采用mod_timer()函数动态的改变定时器到达时间

4.1 原理

  • 这个驱动主要实现内核定时器的基本操做,内核定时器主要是经过下面结构体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:定义定时器到期时间,一般采用jiffiesHZ这个两个全局变量来配合设置该参数的值,如expires = jiffies + n * HZ,其中jiffies是自启动以来的滴答数,HZ是一秒钟的滴答数。假设系统1秒的滴答数为1,咱们但愿定时10秒钟,并且咱们在启动定时器的时候jiffies数值为5,那么10秒后滴答总数将为5 + 10 * 1 = 15
  • function:即一个函数指针,就是定时器处理函数,当定时时间到达以后就会触发该函数的执行
  • data:一般实现参数的传递,从function的参数类型值咱们能够看到,data能够做为定时器处理函数的参数,其余的元素可经过内核的函数来初始化

4.2 定时器API

/* 定时器初始化 */
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);

4.3 定时器示例(老版本内核)

#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");
相关文章
相关标签/搜索