linux内核中的延迟和定时器

84 篇文章 0 订阅

内核中的延迟和定时器

1. 关于延时

忙等待

while(time_before(jiffies, j1))
    cpu_relax();

这种代码会占用大量内核cpu运行时间,是不好的方式

超时等待

有两个重要的api

#include <linux/wait.h>
long wait_event_timeout(wait_queue_head_t q, condition, long timeout);
long wait_event_interrupt_timeout((wait_queue_head_t q, condition, long timeout);

这个函数的timeout是jiffies的数量,到达时间会返回0,被中断后会返回timeout的剩余的jiffies数目,我们通过一个demo来坐下实验,实验流程是read后timeout 然后wakeup再次观察结果,动手实验记录:

关于超时的实验步骤:

首先在入口处初始化一个等待队列:

static int __init  hello_init(void)
{
        dev_t dev = 0;

        init_waitqueue_head(&wait_head);

        scull_p_init(dev);
        return 0;

}

读取处进行超时等待

ssize_t scull_p_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{

        long ret = wait_event_interruptible_timeout(wait_head, 0, 10000);
        printk("scull_read:%ld\n", ret);
        return ret;
}

如果说超时,那么返回错误码

[ 2774.971526] scull_read:0

关于唤醒的实验步骤:

文中说过唤醒的话会返回剩余的jiffies,我们read等待,write唤醒,看返回值

我们写一个write的例子

read 检查 程里条件

int flag = 0;
ssize_t scull_p_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
        printk("scull_read\n");
        long ret = wait_event_interruptible_timeout(wait_head, flag != 0, 10000);
        printk("scull_read:%ld\n", ret);
        return ret;
}

write 唤醒:

static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{

        wake_up_interruptible(&wait_head);
        flag = 1;
        printk("wake up\n");
        return 0;

}

linux 定时器

常见的定时API

注意事项:

1.没有current指针,不能访问用户空间。因为没有进程上下文,相关代码和被中断没有任何来联系

2.不能进行休眠和调度

3.访问数据结构要加锁保护

4.定时器由注册的cpu运行

代码

#include <linux/timer.h>
struct time_list {
    /*........*/
    unsigned long expires;
    void (*function)(unsigned long data);
    unsigned long data
}

void init_timer(struct time_list* timer);
struct time_list TIMER_INITIALIZER(_function, _expires, _data);

void add_timer(struct time_list* timer);
void del_timer(struct time_list* timer);

extern void add_timer_on(struct timer_list *timer, int cpu);
extern int del_timer(struct timer_list * timer);
extern int mod_timer(struct timer_list *timer, unsigned long expires);
extern int mod_timer_pending(struct timer_list *timer, unsigned long expires);
extern int timer_reduce(struct timer_list *timer, unsigned long expires);

注意书上写的很老了,我们可以用setup_timer,数据结构也不一样

struct timer_list {
        /*
         * All fields that change during normal runtime grouped to the
         * same cacheline
         */
        struct hlist_node       entry;
        unsigned long           expires;
        void                    (*function)(struct timer_list *);
        u32                     flags;

#ifdef CONFIG_LOCKDEP
        struct lockdep_map      lockdep_map;
#endif
};


#define timer_setup(timer, callback, flags)                     \
        __init_timer((timer), (callback), (flags))

写一个简单例子:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h> /* copy_*_user */
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/timer.h>

struct timer_list timer;

static void timer_handle(struct timer_list* ptr)
{
    printk("timer handle\n");
    mod_timer(&timer, jiffies + 2 * HZ);
}


static void __exit  hello_exit(void)
{
    del_timer(&timer);

    return;
}

static int __init  hello_init(void)
{
    timer.expires  = jiffies + 2 * HZ;
    timer_setup(&timer, timer_handle, 0);
    printk("hahhaha");
    add_timer(&timer);
    return 0;

}


module_init(hello_init);
module_exit(hello_exit);

dmesg查看结果

[  163.007938] hahhaha
[  187.181738] hahhaha
[  189.187063] timer handle
[  251.055807] hahhaha
[  253.059268] timer handle
[  255.075894] timer handle
[  257.091296] timer handle
[  259.107259] timer handle
[  261.123795] timer handle
[  263.140004] timer handle
[  265.154878] timer handle
[  267.171715] timer handle
[  269.187201] timer handle
[  271.207680] timer handle
[  273.219831] timer handle
[  275.235527] timer handle
[  277.251688] timer handle
[  279.267137] timer handle
[  281.282927] timer handle
[  283.299361] timer handle
[  285.315257] timer handle
[  287.332112] timer handle
[  289.347368] timer handle
[  291.363303] timer handle
[  293.379972] timer handle
[  295.395127] timer handle
[  297.411604] timer handle
[  299.427758] timer handle
[  301.442900] timer handle
[  303.458954] timer handle
[  305.475460] timer handle
[  307.495112] timer handle
[  309.507765] timer handle
[  311.523080] timer handle

tasklet

tasklet 是和定时器相关的另一个内核设施,他也是运行在中断期间的,中断管理大量使用了这项技术,和内核定时器类似,tasklet也会在软件中断原子模式执行。软件中断是打开硬件中断的同时执行某些异步任务的一些机制。

tasklist的特性

1.一个tasklet在稍后被禁止或者重新启用,只有在启用和禁止次数相同的时候tasklet才会被执行

2.和定时器类似,tasklet可以注册自身

3.tasklet优先级越高,越被先执行

4.如果系统负荷不重,tasklet会立马被执行

下半部和退后执行的工作,软中断的使用只在那些执行频率很高和连续性要求很高的情况下才需要。在大多数情况下,为了控制一个寻常的硬件设备,tasklet机制都是实现自己下半部的最佳选择。其实tasklet是利用软中断实现的一种下半部机制。tasklet和软中断在本质上很相似,行为表现也相近。tasklet有两类中断代表:HISOFTIRQ和TASKLETSOFTIRQ。这两者之间唯一的区别在于HISOFTIRQ类型的软中断先于TASKLETSOFTIRQ类型的软中断执行

tasklet的定义:

struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};

1.next:链表中的下一个tasklet; 2.state:tasklet的状态。有三个取值:0,TASKLETSTATESCHED和TASKLETSTATERUN之间的取值。TASKLETSTATESCHED表明tasklet已经被调度,正准备投入运行,TASKLETSTATERUN表示该tasklet正在运行。 3.count:tasklet的引用计数,如果它不为0,则tasklet被禁止,不允许执行;只有当它为0时,tasklet才被激活,并且在被设置为挂起(TASKLETSTATESCHED)状态时,该tasklet才能够被执行。 4.结构体中的func成员是tasklet的处理程序,data是它唯一的参数。

内核使用taskletschedule()函数来执行tasklet的调度,已调度的tasklet存放在两个单处理器数据结构:taskletvec(普通的tasklet)和tasklethivec(高优先级的tasklet)。这两个数据结构都是由taskletstruct结构体构成的链表。链表中的每个taskletstruct 代表一个不同的tasklet。 tasklet是由taskletschedule()和tasklethi_shedule()函数进行调度的,它们接受一个指向taskletstruct结构的指针作为参数。两个函数非常相似(区别在于一个使用TASKLETSOFTIRQ,一个使用HISOFTIRQ()),接下来我们来看下taskletschedule()的内核源码:

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


void __tasklet_schedule(struct tasklet_struct *t)
{
    unsigned long flags;

    local_irq_save(flags);   //保存IF标志的状态,并禁用本地中断
    t->next = NULL;
    *__get_cpu_var(tasklet_vec).tail = t;   //下面的这两行代码就是为该tasklet分配per_cpu变量
    __get_cpu_var(tasklet_vec).tail = &(t->next);
    raise_softirq_irqoff(TASKLET_SOFTIRQ);  //触发软中断,让其在下一次do_softirq()的时候,有机会被执行
    local_irq_restore(flags);  //恢复前面保存的标志
}

由上面的代码可以看出,每次中断只会向其中的一个cpu注册,而不是所有的cpu。完成注册后的tasklet由tasklet_action()函数来执行。

执行tasklet

前面说过tasklet被放在一个全局的taskletvec的链表中,链表中的元素是taskletstruct结构体。内核中有个ksoftirqd()的内核线程,它会周期的遍历软中断的向量列表,如果发现哪个软中断向量被挂起了(pending),就执行相应的处理函数。tasklet对应的处理函数就是taskletaction,这个函数在系统启动初始化软中断时,就在软中断向量表中注册。taskletaction()遍历全局的taskletvec链表。链表中的元素为taskletstruct结构体

tasklet 的api

#include <linux/interrupt.h>

struct tasklet_struct
{
        struct tasklet_struct *next;
        unsigned long state;
        atomic_t count;
        void (*func)(unsigned long);
        unsigned long data;
};

extern void tasklet_kill(struct tasklet_struct *t);
extern void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu);
extern void tasklet_init(struct tasklet_struct *t,
                         void (*func)(unsigned long), unsigned long data);

#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 }

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

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();
        atomic_dec(&t->count);
}

编写一个测试例子

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h> /* copy_*_user */
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
void my_tasklet_function( unsigned long data )
{
  printk( "%s\n", (char *)data );
}

DECLARE_TASKLET( my_tasklet, my_tasklet_function, ((unsigned long) &my_tasklet_data));


static void __exit  hello_exit(void)
{
    tasklet_kill( &my_tasklet );

    return;
}

static int __init  hello_init(void)
{

       tasklet_disable(&my_tasklet);
       tasklet_enable(&my_tasklet);
      tasklet_schedule( &my_tasklet );
    return 0;

}


module_init(hello_init);
module_exit(hello_exit);

工作队列

工作队列和tasklet的区别

1.tasklet运行在中断上下文,而工作队列运行在进程上下文,tasklet应有原子性

2.tasklet的提交和运行总是在同一个处理器上

3.工作队列可以进行延迟执行

工作队列api

#include <linux/workqueue.h>
struct workqueue_struct;
struct work_struct;

struct workqueue_struct* create_workqueue(const char* name);
struct workqueue_struct* create_singlethread_workqueue(const char* name);
void destroy_workqueue(struct workqueue_struct *queue);

创建和销毁销毁工作队列函数。调用create_workqueue创建一个队列,且系统中每个处理器都会运行一个工作线程;相反,create_singlethread_workqueue 只会创建单个工作进程

#define DECLARE_WORK(n, f)                                              \
    struct work_struct n = __WORK_INITIALIZER(n, f) 
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data); 
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data); 
声明和初始化工作队列入口的宏. 

int queue_work(struct workqueue_struct *queue, struct work_struct *work); 
int  queue_delayed_work(struct  workqueue_struct  *queue,  struct  work_struct  *work,  unsigned 
long delay); 
从一个工作队列对工作进行排队执行的函数.

int cancel_delayed_work(struct work_struct *work); 
void flush_workqueue(struct workqueue_struct *queue); 
使用 cancel_delayed_work 来从一个工作队列中去除入口; flush_workqueue 确保没有工作队列入口在系统中任何地方运行. 

int schedule_work(struct work_struct *work); 
int schedule_delayed_work(struct work_struct *work, unsigned long delay); 
void flush_scheduled_work(void); 
使用共享队列的函数.

如何使用工作队列

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h> /* copy_*_user */
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

void myfunc(struct work_struct*ws);

struct workqueue_struct *wqueue;

DECLARE_WORK(mywork,myfunc);


void myfunc(struct work_struct*ws)
{
   printk(KERN_ALERT "myfunc current->pid %d\n",current->pid);
   ssleep(1);
   printk(KERN_ALERT "myfunc current->pid %d\n",current->pid);
   ssleep(1);
   printk(KERN_ALERT "myfunc current->pid %d\n",current->pid);
   ssleep(1);
}

static void __exit  hello_exit(void)
{

        return;
}

static int __init  hello_init(void)
{

         wqueue=create_workqueue("myqueue111");
        queue_work(wqueue,&mywork);
        printk(KERN_ALERT "main current->pid  %d\n" ,current->pid);

        return 0;

}


module_init(hello_init);
module_exit(hello_exit);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值