Work queue 工作队列 与Timer 定时器

一、schedule_work 工作队列

是系统延时调度的一个自定义函数, 一般用来处理中断中底半等耗时操作

1、定义struct work_struct irq_queue;

2、初始化INIT_WORK(&irq_queue,do_irq_queuework);

3、调用方法:schedule_work(&irq_queue);

调用完毕后系统会释放此函数,所以如果想再次执行的话,就再次调用schedule_work()即可。

在使用上和 tasklet最大的不同是工作队列的函数可以使用休眠,而tasklet的函数是不允许使用休眠的。

工作队列的使用又分两种情况,一种是利用系统共享的工作队列来添加自己的工作,这种情况处理函数不能消耗太多时间,这样会影响共享队列中其他任务的处理;另外一种是创建自己的工作队列并添加工作。

(一)利用系统共享的工作队列添加工作:

第一步:声明或编写一个工作处理函数

void my_queue_func();

第二步:创建一个工作结构体变量,并将处理函数和参数的入口地址赋给这个工作结构体变量

//编译时创建名为my_queue_work的结构体变量并把函数入口地址和参数地址赋给它;
DECLARE_WORK(my_queue_work,my_queue_func,&data); 

如果不想要在编译时就用DECLARE_WORK()创建并初始化工作结构体变量,也可以在程序运行时再用INIT_WORK()创建

//1. 创建一个名为my_queue_work的结构体变量,创建后才能使用INIT_WORK()
struct work_struct my_queue_work; 
//2. 初始化已经创建的my_queue_work
// 其实就是往这个结构体变量中添加处理函数的入口地址和data的地址,通常在驱动的open函数中完成
INIT_WORK(&my_queue_work,my_queue_func,&data); 

第三步:将工作结构体变量添加入系统的共享工作队列

schedule_work(&my_queue_work); //添加入队列的工作完成后会自动从队列中删除

或使用延时的方法

schedule_delayed_work(&my_queue_work,tick); //延时tick个滴答后再提交工作

(二)创建自己的工作队列来添加工作

第一步:声明指向工作队列的指针

struct workqueue_struct *p_queue;
p_queue=create_workqueue("my_queue"); //创建一个名为my_queue的工作队列并把工作队列的入口地址赋给声明的指针

第二步:创建工作结构体变量和声明工作处理函数(通常在open函数中完成)

void my_queue_func();
struct work_struct my_queue_work;

INIT_WORK(&my_queue_work, my_queue_func, &data); //创建一个工作结构体变量并初始化,和第一种情况的方法一样

第三步:将工作添加入自己创建的工作队列等待执行

queue_work(p_queue, &my_queue_work);
//作用与schedule_work()类似,不同的是将工作添加入p_queue指针指向的工作队列而不是系统共享的工作队列

第四步:删除自己的工作队列

destroy_workqueue(p_queue); //一般是在close函数中删除

二、Timer 定时器

1、相关API

a. init_timer(struct timer_list):定时器初始化函数;
b. add_timer(struct timer_list
):往系统添加定时器;
c. mod_timer(struct timer_list *, unsigned long jiffier_timerout):
修改定时器的超时时间为jiffies_timerout;
(Linux系统中的jiffies是定义在内核里面的一个全局变量,
只是它的单位并不是秒或是毫秒。
通常是250个jiffies为一秒,在内核里面可以直接使用宏定义:HZ
)
d. timer_pending(struct timer_list ):定时器状态查询,
如果在系统的定时器列表中则返回1,否则返回0;
e. del_timer(struct timer_list
):删除定时器。

2.相关API函数源码

a)init_timer函数
路径: kernel-3.18/include/linux/timer.h)

#define init_timer(timer) \
__init_timer((timer), 0)
#define __init_timer(_timer, _flags)\
init_timer_key((_timer), (_flags), NULL, NULL)

可以看出 实际上是调用init_timer_key()函数去初始化
(路径: kernel/time/timer.c)

/*
init_timer_key -初始化一个计时器
@timer:要初始化的计时器。
@flags:定时器的旗帜
@name:计时器名称
@key:用于跟踪计时器 同步锁的依赖关系
*注意: 必须先调用init_timer_key()进行初始化,
然后才能调用其他跟定时器相关的方法,例如add_timer,mod_timer等
*/
void init_timer_key(struct timer_list *timer, unsigned int flags,  
            const char *name, struct lock_class_key *key)
{
    debug_init(timer);
    do_init_timer(timer, flags, name, key);
}

b)add_timer函数
(路径: kernel/time/timer.c)

/*
add_timer -启动一个计时器,或者说激活一个定时器。
add_timer用于往系统中添加一个定时器,参数timer为要添加的定时器(timer_list)
,当系统时间经过timer->expires这么多时间,就会去调用timer->function回调方法去完成相应的任务。
注意:必须先初始化timer->expires,timer->function,timer->data这三个成员变量,才能调用add_timer()这个方法
@timer:要添加的定时器
*/
void add_timer(struct timer_list *timer)
{
    BUG_ON(timer_pending(timer));//打印相关log
    mod_timer(timer, timer->expires);//调用mod_timer设置时间
}

c)mod_timer函数
(路径: kernel/time/timer.c)

/*
mod_timer -修改一个timer(定时器)的超时时间
@timer:要修改的计时器。
@expires:新的超时,单位jiffies
分析:mod_timer()是更新活动计时器过期字段(即expires参数)的一种更有效的方法(如果计时器没有被激活,mod_timer会先激活计时器,然后在重新设定超时时间)
*/
int mod_timer(struct timer_list *timer, unsigned long expires)
{
    expires = apply_slack(timer, expires);
    if (timer_pending(timer) && timer->expires == expires)
        return 1;
    return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}

实际上 调用mod_timer(timer, expires)相当于
del_timer(timer); timer->expires = expires; add_timer(timer);
注意:如果有多个未序列化的并发用户使用相同的计时器,则mod_timer()是修改超时的唯一安全方法,因为add_timer()不能修改已经运行的计时器。
该函数返回是否已经修改了一个待定定时器
如果调用mod_timer去修改一个定时器,
如果当前定时器处于非激活状态,则该函数返回0,
如果当前定时器处于激活状态,则该函数返回1
Ps:调用了add_timer(),就表示该定时器处于激活状态

d)timer_pending函数
(路径: include/linux/timer.h)

/*
@timer:给定的定时器
timer_pending会告诉给定的计时器是否正在等待
返回值:如果计时器挂起,则为1;如果不是,则为0
如果timer->entry.next为NULL,表示计时器没有挂起,返回0
如果timer->entry.next不等于NULL,表示计时器挂起,返回1
*/
static inline int timer_pending(const struct timer_list * timer)   
{
    return timer->entry.next != NULL;
}
timer_pending——是否有一个计时器正在等待?

e)del_timer函数
(路径: kernel/time/timer.c)

/*
del_timer -删除(停用)计时器。
*@timer:被停用的计时器
分析:del_timer()禁用计时器——这对激活的和非激活的计时器都有效。
函数返回是否已经禁用了一个待定定时器。
(即。一个非激活计时器的del_timer()返回0,激活计时器返回1)
*/
int del_timer(struct timer_list *timer)
{
    struct tvec_base *base;
    unsigned long flags;
    int ret = 0;
    debug_assert_init(timer);
    timer_stats_timer_clear_start_info(timer);
    if (timer_pending(timer)) {
        base = lock_timer_base(timer, &flags);
        ret = detach_if_pending(timer, base, true);
        spin_unlock_irqrestore(&base->lock, flags);
    }
    return ret;
}

3.使用定时器的一般流程

1)定义timer_list,、初始化timer_list、编写function;
2)为timer的expires、data、function赋值;
3)调用add_timer将timer往系统添加定时器;(或者直接调用mod_timer方法)
4)在定时器到期时,function被运行;
5)在程序中涉及timer控制的地方适当地调用del_timer、mod_timer删除timer或改动timer的expires。

4. Demo:

/*定义计时器*/
static struct timer_list  test_timer;
/*回调函数*/
static void test_timer_callback(unsigned long a)
{
    /*这里添加相应的逻辑*/
    printk("hello word\n");
    /*一般使用场景是调用前面的workqueue 来实现自己的功能逻辑
    *  schedule_work(&my_queue_work); 
    */
    /* 如果这里要使用定时器循环的话可以在这里继续调用 timer
    * 这样子一旦有地方调用就会开启time 循环了
    * mod_timer(&test_timer, jiffies + (10 * HZ));
    */
}

/*主函数*/
void test_timer_init()
{
   /*初始化相关参数*/
    init_timer(&test_timer);//先初始化timer
    test_timer.expires = jiffies + (20 * HZ);//设置超时 20*HZ 表示20秒
    test_timer.function = &test_timer_callback;//设置回调函数
    test_timer.data = ((unsigned long)0);//设置data参数,回调函数的执行的参数,不需要使用传0 即可
    add_timer(&test_timer);//把定时器添加到系统中,激活定时器
    /*这里调用mod_timer 通过add timer 一个执行时间为10s的*/
   mod_timer(&test_timer, jiffies + (10 * HZ));
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345