驱动中定时器,taskle,工作队列编程

本文深入探讨Linux内核中的定时器、tasklet机制及工作队列原理与应用,包括定时器的初始化、注册与修改,tasklet的定义、使能与调度,以及工作队列的创建、调度与销毁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、定时器

1.内核时间相关转换函数

unsigned long usecs_to_jiffies(const unsigned int u)

功能: 把微秒转换成时钟节拍
参数: u 时间微秒
返回: 对应的时钟节拍数量

unsigned long msecs_to_jiffies(const unsigned int m)

功能: 把毫秒转换成时钟节拍
参数: m 时间毫秒
返回: 对应的时钟节拍数量

2.初始化定时器
1)静态初始化(=定义定时器+动态初始化+设置)

DEFINE_TIMER(_name, _function, _expires, _data)

功能: 定义一个名字为_name 的 struct timer_list 结构的变量, 并且初始化它的 function, expires, data 成员。

2)动态初始化

init_timer (struct timer_list *timer);

功能: 只是对 struct timer_list 结构成员进行一些基础初始化操作, function, expires, data 成员还需要用户自己填充。

3)设置定时器

setup_timer(struct timer_list *timer, void *function, unsigned long data);

功能: 设置定时器中的 function, data 和一些基础成员, expires 并没有初始化, 需要用户自己进行初始化
参数:
timer: struct timer_list 结构变量地址
function:超时函数的指针
expires:时间节拍数,可以通过上面的时间相关转换获得
data :超时函数的参数

2.注册定时器到内核

void add_timer(struct timer_list *timer)

功能: 向内核注册一个定时器, 注册后会马上开始计时。
参数: 是一个 struct timer_list 结构变量地址, 并且要求这个结构变量是已经初始化好必须成员

3.修改定时器时间,重新注册

int mod_timer(struct timer_list *timer, unsigned long expires);

功能: 修改定时器定时时间值, 并且重新注册, 不管这个定时的超时函数是否执行过。 执行完成后会马上启动定时。 功能有点像 add_timer。
参数:
timer 要修改的定时器结构变量地址, 要求这个结构变量的 function, data 已经初始化好的。
expires: 有来填充结构中的 expires 成员, 也就是未来的时间点。

4.从内核注销定时器

int del_timer(struct timer_list * timer);

功能: 从内核定时链表上删除指定的定时器, 删除后就不会再执行绑定的函数
参数: 是一个 struct timer_list 结构变量地址

例子:

#include <linux/module.h>
#include <linux/init.h>

//1)添加头文件 
#include <linux/timer.h>

//3)定义一个timer_list结构变量  
struct timer_list timerlist;

//2)实现一个超时函数  
void timerlist_func(unsigned long data)
{
	printk("%s is call!  data:%d\r\n",__FUNCTION__,(int)data);
	///再次修改本定时器超时时间 为当前时间 后面2秒
	//mod_timer(&timerlist, jiffies + HZ*2);    //循环使用定时器
}

static int __init timerlist_init(void)
{
	//4)静态初始化
	//DEFINE_TIMER(timelist , time_function, 0, 123);
	//4)动态初始化,对timer_list结构变量进行初始
	init_timer(&timerlist);
	setup_timer(&timerlist, timerlist_func, (unsigned long)123);
	
	timerlist.expires = jiffies + HZ*2;//如果不启用定时器,就不用赋值

	//5)注册定时器,启动定时
	add_timer(&timerlist);      //启动定时器
	printk("%s is call!\r\n",__FUNCTION__);
    return 0;
}

static void __exit timerlist_exit(void)  //Module exit function specified by module_exit()
{
	//6)注销定时器
	del_timer(&timerlist);
}

module_init(timerlist_init);
module_exit(timerlist_exit);

MODULE_LICENSE("GPL");

二、tasklet机制

1.tasklet定义以及初始化函数
1)静态初始化

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

作用:定义一个名字为 name 的 tasklet_struct 结构变量,并且初始化这个结构。 所定义的这个 tasklet 是可以被调度,默认是处于被使能状态。

2)静态初始化

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

作用:定义一个名字为 name 的 tasklet_struct 结构变量,并且初始化这个结构。所定义的这个 tasklet 是不能被调度,默认是处于被禁能状态。 要调度这个 tasklet 需要先使能。

3)动态初始化

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

作用:初始化一个 tasklet_struct 结构变量,初始化的结构默认是处于激活状态,可以被调度。
参数:
t:tasklet核心数据结构,已经申明 好(只需定义),如下:

 	struct tasklet_struct {
 		struct tasklet_struct *next;          //链表中的下一个tasklet
		unsigned long state;               //tasklet的状态        
		atomic_t count;                  //引用计数器
		void(*func) (unsigned long data);    //tasklet处理函数 --参数就下面的data          
		unsigned long data;               //给tasklet处理函数的参数     
   };

**注意:**是一个引用定时器,只有其值为0的时候,tasklet才回被激活;否则被禁止,不能被执行。但是,实际上并没有什么用,因为基本都在激活状态,只是在调度的时候才跑到指针函数那里。
func:是一个指针函数,指向tasklet处理函数,这个处理函数的唯一参数为data
data:func指针函数的参数。

2.tasklet 机制使能、禁能函数
1)使能tasklet

void tasklet_disable(struct tasklet_struct *t) 

功能:激活tasklet ,可以调度;实质是就是让count加1。

2)失能tasklet

void tasklet_enable (struct tasklet_struct *t)

功能:禁止tasklet ,不可以调度;实质是就是让count减1。
注意:如果对一个 tasklet_struct 结构变量调用了两次 tasklet_disable 函数,则 count 会增加 1两次,这时你再调用一次tasklet_enable ,那实际 count 值还是 1,还是处于非激活状态。

**3.tasklet 调度函数 **

tasklet_schedule (struct tasklet_struct *t)

功能:tasklet_struct 结构中绑定了一个函数,当用户需要执行这个函数时候,需要自己调用 tasklet_schedule 函数去通知内核帮我们调度所绑定的函数,但是具体什么时候执行就有内核来处理。(要是绑定函数还没有执行,无论调度多少次,效果等同一次。)

4.tasklet取消调度函数

void tasklet_kill(struct tasklet_struct *t)

函数功能:该函数确保指定的tasklet不会被再次调度运行。如果tasklet正被调度执行,该函数会等待其退出。一般来说,设备要被关闭或者模块要被移除时,我们通常调用这个函数。(注意:不能在tasklet的处理函数中调用这个函数,否则会系统异常)

例子:

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

#define  mode   1   //0:静态初始化  1:动态初始化
    
static void my_tasklet_function(unsigned long data);
        
char my_tasklet_data[]="my_tasklet_function was  called.";
        
#if mode    
    //1.1)动态定义结构体变量,用于动态初始化
   	struct tasklet_struct my_tasklet;
#else
    //1)静态定义并初始化一个tasklet 内核微线程
  	DECLARE_TASKLET(my_tasklet,my_tasklet_function,(unsigned long)my_tasklet_data);
#endif	    
 //2)定义tasklet工作函数
static void my_tasklet_function(unsigned long data)
{
       	printk("%s\r\n",(char *)data);
      	udelay(2000);	  //延时2ms,而不是休眠
}
        
static int __init tasklet_module_init(void)
{
	printk("mondule insmod\r\n");

#if mode    
		//1.2)动态初始化
    	tasklet_init(&my_tasklet, my_tasklet_function,(unsigned long) my_tasklet_data);
#endif	    
    	//3)调度tasklet
    	tasklet_schedule(&my_tasklet);
    	return 0;
}
    
static void __exit tasklet_cleanup(void)
{
	 printk("mondule remove\r\n");
	//4)销毁tasklet 
	tasklet_kill(&my_tasklet);
}

module_init(tasklet_module_init);   
module_exit(tasklet_cleanup);  
MODULE_LICENSE("GPL");

三、工作队列

1.共享工作队列调度
1)静态初始化

void work_func(struct work_struct * dat)
{
 		printk(“%p:”,dat);
}
 DECLARE_WORK(mywork,work_func)

2)动态初始化

struct work_struct work;
void work_func(struct work_struct * dat)
{
 		printk(“%p:”,dat);
}
INIT_WORK(&work, work_func);

功能:定义一个工作结构变量,初始化工作结构。
参数:
work:类型如下:

struct work_struct {
		atomic_long_t data;    
		struct list_head entry;
		work_func_t func;      /* 工作函数指针 */
	#ifdef CONFIG_LOCKDEP
		struct lockdep_map lockdep_map;
	#endif
	};

3)调度

int schedule_work(struct work_struct *work)

功能:调度共享工作队列,将工作结构体变量添加入系统的共享工作队列中,添加入队列的工作完成后会自动从队列中删除。
例子:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
//1)添加头文件 
#include <linux/workqueue.h>

//2)定义一个工作函数
void  mywork_func(struct work_struct *work)
{
	printk("befor:%s is call!  work:%p\r\n",__FUNCTION__,work);
	ssleep(1);  //内核休眠
	printk("after:%s is call!  work:%p\r\n",__FUNCTION__,work);
}

//3)定义一个 work_struct 结构变量,并且进行初始化
DECLARE_WORK(mywork, mywork_func);   //定义并且初始化了

static int __init myworkqueue_init(void)
{
	printk("%s is call!\r\n",__FUNCTION__);
	printk("&mywork:%p\r\n",&mywork);    //输出 mywork变量地址
	//3)如果动态初始化,定义好全局变量后,在这里初始化,可以参考下面的例子
	//4)开始调度 
	schedule_work(&mywork);
    return 0;
}

static void __exit myworkqueue_exit(void)   
{
	
}

module_init(myworkqueue_init);
module_exit(myworkqueue_exit);
MODULE_LICENSE("GPL");

2.自定义工作队列
1)创建工作队列

create_workqueue(name)

作用: 创建一个名字为 name 的工作队列
参数:
name:工作队列的名字,是一个字符串。
返回值:成功:返回工作队列指针,即struct workqueue_struct 的结构体(需自己定义);失败:NULL。

2)往工作队列加入工作节点

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

功能:往 wq 工作队列中加入工作节点,并调度;
参数:
wq:自定义的工作队列结构变量指针
work:要添加的工作节点

3)销毁工作队列

void destroy_workqueue(struct workqueue_struct *wq)

功能: 销毁一个自定义工作队列
参数:
wq:要销毁的工作队列指针

例子:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
//0)添加头文件
#include <linux/workqueue.h>

struct mydata {
    int x;
    int y;
    struct work_struct work;
    int z;
};

struct mydata g_var;

//1)定义一个工作队列指针
struct workqueue_struct * mywq;

//2)定义一个工作函数
void  mywork_func(struct work_struct *mywork)
{
    struct mydata  *p = (struct mydata*)container_of(mywork, struct mydata, work);
    printk("%s is call!  work:%p\r\n", __FUNCTION__, mywork);
    //这个使用全局变量来访问x,y,z
    printk("g_var.x:%d,g_var.y:%d,g_var.z:%d,\r\n", g_var.x, g_var.y, g_var.z);
    //这个使用函数的形式参数来访问x,y,z
    printk("p->x:%d,p->y:%d,p->z:%d,\r\n", p->x, p->y, p->z);
}

static int __init myworkqueue_init(void)
{
    printk("%s is call!\r\n", __FUNCTION__);
    printk("&g_var.work:%p\r\n", &g_var.work);   //输出 mywork变量地址

    g_var.x = 123;
    g_var.y = 456;
    g_var.z = 789;

    //3) 创建工作队列
    mywq = create_workqueue("mywq");
    if(mywq == NULL) {
        printk("create_workqueue error\r\n");
        return -ENOMEM;
    }
    printk("create_workqueue ok\r\n");
    //4)使用动态初始化工作队列
    INIT_WORK(&g_var.work, mywork_func);

    //5)开始调度
    //schedule_work(&g_var.work);    //调度共享工作队列
    queue_work(mywq, &g_var.work);   //调度自定义工作队列

    return 0;
}

static void __exit myworkqueue_exit(void)
{
    //6) 销毁工作队列
    destroy_workqueue(mywq);
}

module_init(myworkqueue_init);
module_exit(myworkqueue_exit);
MODULE_LICENSE("GPL");

3.延时工作队列
注意:
相同点:和上面的内核工作队列一样,可以实现中断的底部代码功能。
不同点:和内核工作队列惟一区别就是延后的时间可控制。

1)延时调度函数
int schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)
功能:延时调度工作函数。登记延时工作,一旦登记完成,CPU在适当的时间去执行延时工作的延后处理函数。
参数:
dwork:要调度的延时工作结构指针。
delay:延后调度的时间,单位是时钟节拍。和内核定时器定时时间单位相同,但是不是到期时间,而是定时时间。

例子:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
//0.添加头文件
#include <linux/workqueue.h>

//1)实现一个工作函数
void  mywork_func(struct work_struct *mywork)
{
    printk("%s is call!  work:%p\r\n", __FUNCTION__, mywork);
}

//2)定义一个延时工作
//struct delayed_work mydelayed_work;            //动态定义
DECLARE_DELAYED_WORK(mydelayed_work, mywork_func); //静态定义

static int __init myworkqueue_init(void)
{
    printk("%s is call!\r\n", __FUNCTION__);
    //2)动态初始化延时工作
    // INIT_DELAYED_WORK(&mydelayed_work, mywork_func);
    //3)一安装模块就开始调度,1秒后开始调度工作函数
    schedule_delayed_work(&mydelayed_work, msecs_to_jiffies(1000));   //调度延时共享工作队列
    return 0;
}

static void __exit myworkqueue_exit(void)
{

}

module_init(myworkqueue_init);
module_exit(myworkqueue_exit);
MODULE_LICENSE("GPL");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小坚学Linux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值