Linux内核 定时器 用法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Qidi_Huang/article/details/51318157
【前言】
    最近在工程中接触到调用 Linux 内核函数配置定时器实现 LED 闪烁效果的代码。对定时器的使用方法写个简单小结。


【概述】
    定时器的用法不复杂。调用过程分为以下几步:
    1、声明一个 timer。
    2、初始化 timer。
    3、完善定时中断服务函数。
    4、注册 timer 到定时器链表。
    5、重新注册 timer。
    6、提前停止定时器 timer。(非必需步骤)


【函数调用】
    1、声明一个新的定时器可以使用语句 struct timer_list newTimer;

    2、初始化定时器就是要为 timer 设定中断服务函数及其参数、超时时间等值。
        代码示例:
        setup_timer(newTimer, fn, data);    // 实际上是一个宏。timer 是要设置的定时器,fn 是定时中断服务函数,data 是传递给 fn 的参数。
        newTimer.expires = jiffies + 10 * HZ;    // 设定 10 秒后发生定时中断
        工程代码示例:setup_timer(&trigger_data->timer, netdev_trig_timer, (unsigned long)trigger_data);

    3、向中断服务函数中添加的代码应当位于上锁/解锁操作之间。
        代码示例:
        static void netdev_trig_timer(unsigned long arg)    // 我的工程中实际使用的回调函数,即 setup_timer 函数中的 fn 参数。根据需要不同,这个函数可以不同。中断服务在运行时需要上锁,运行结束时解锁。
        {
            struct led_netdev_data *trigger_data = (struct led_netdev_data *)arg;
            ...    // 变量声明
            write_lock(&trigger_data->lock);    // 写操作上锁
            ...    // 中断服务函数主体
            mod_timer(&trigger_data->timer, jiffies + trigger_data->interval);    // 为定时器 timer 重新注册。
            write_unlock(&trigger_data->lock);
        }

    4、配置好定时器后,需要将定时器添加到定时器链表中才会生效。一旦将定时器添加进链表,定时器便开始工作了。
        函数原型:
        void add_timer(struct timer_list *timer);    // 一般只在初始化结束后调用,之后的周期性添加工作一般使用 mod_timer() 函数完成

    5、定时器配在置好后只运行一次,执行完中断服务函数后定时器就会自动销毁。
        所以,如果想要实现周期性定时中断就必须在中断服务程序的结尾重新给定时器写入超时值。

        常用 mod_timer() 函数来完成这一步骤,其源码如下:
        int mod_timer(struct timer_list *timer, unsigned long expires)
        {
            int ret;
            unsigned long flags;
            spin_lock_irqsave(&timerlist_lock, flags);
            timer->expires = expires;    // 重新写入超时值
            ret = detach_timer(timer);    // 停止定时器运行,相当于 del_timer()
            internal_add_timer(timer);    // 对定时器进行注册,相当于 add_timer()
            spin_unlock_irqrestore(&timerlist_lock, flags);
            return ret;
        }
        需要注意,上面的源码意味着该函数也会将一个处于未激活状态的定时器激活。因此,使用了 mod_timer() 就无需再单独调用 add_timer() 了。
        代码示例:
        mod_timer(&trigger_data->timer, jiffies + trigger_data->interval);

    6、如果想要在定时器中断发生前停止定时器,可以使用 del_timer() 函数。
        函数原型:
        int del_timer(struct timer_list *timer);


        在多 CPU 平台上可能发生要停止的定时器正在其它 CPU 上运行的情况,此时应该使用 del_timer_sync() 函数。
        函数原型:
        int del_timer_sync(struct timer_list *timer);

【相关概念】
    jiffies    是一个类型为 unsigned long 的全局变量,表示系统启动之后时钟芯片产生的节拍总数。
    Hz    每秒钟 jiffies 增加的数值。从 2.5版本内核开始这个参数的内核空间默认值修改为 1000,而以前的版本里该值是 100。可以在内核源码根目录下的 linux-4.3/.config 文件中查看 Hz 的内核默认值,命令 cat .config | grep -i config_hz。文件 /linux-4.3/include/asm-generic/param.h 中则有更详细的记录。

    所以,如果想要知道系统已经运行了多长时间,可以按照 jiffies/Hz 的方式进行计算,从而获得系统运行了多少秒。
    在 64位 系统中,直接在代码里使用 jeffies 变量只能访问到 低32位 的值。要获取完全 64位 数值需要调用 get_jiffies_64() 函数。

【延伸阅读】
    [1] 《linux 内核定时器 timer_list》
    [2] 《linux驱动之内核定时器驱动设计》
    [3] 《定时器使用和延迟执行》
展开阅读全文

linux内核定时器

06-24

我做的程序需要两个定时器,一个用于自动备份,一个用于自动清理所以我觉得用alarm()的话,统一发送SIG_ALRM信号是不行的,所以我采用了struct timer_list和add_timer机制,但是发现要包含linux/timer.h,所以不得不改用模块编程(正好可以深入一下内核),可是发现在内核里包含time.h并调用localtime(),mktime()等函数都不行,后来又改包含linux/time.h,发现那个里面的函数跟time.h不一样,请各位指点一下怎样做。rnrn另外,我对内核模块的编译也很迷糊,希望各位大侠可以耐心指点一下。不胜感谢。我的模块代码和makefile如下,请指正:rnrn#include rn#include rn#include rn#include rn#include rnrnstatic int backup_time=0; //模块参数,自动备份时间rnstatic int clean_time=0; //模块参数,自动清理时间rnstruct timer_list backup_timer[2]; //全局变量,两个计时器rnint DB_BACKUP()rnrn return 0;rnrnrnint DB_CLEAN()rnrn return 0;rnrnrn//自动备份函数rnvoid auto_backup(unsigned long data)rn rn struct timeval hour;rn hour.tv_sec=86400;rn hour.tv_usec=0;rn int jiffies_t=tvtojiffies(&hour);rnrn //设置第一个计时器rn backup_timer[0].expires=jiffies_t;rn backup_timer[0].data=1;rn backup_timer[0].function=DB_BACKUP;rn add_timer(&backup_timer[0]);rnrn //实现备份功能 rn DB_BACKUP();rnrn return;rnrnrn//自动清理rnvoid auto_clean(unsigned long data)rnrn struct timeval hour;rn hour.tv_sec=86400;rn hour.tv_usec=0;rn int jiffies_t=tvtojiffies(&hour);rnrn //设置自动清理定时器rn backup_timer[1].expires=jiffies_t;rn backup_timer[1].data=0;rn backup_timer[1].function=DB_CLEAN;rn add_timer(&backup_timer[1]);rnrn DB_CLEAN();rnrn return;rnrnrn//初始化两个定时器rnint set_backup_timer()rnrn printk(KERN_ALERT "Auto Backup:%d,Auto Clean:%d",backup_time,clean_time);rnrn struct tm tm_v;rn time_t time1,time2;rn struct timeval hour;rn hour.tv_usec=0;rn time1=time(NULL); //姹傚嚭褰撳墠鏃堕棿rn memcpy((char *)&tm_v,(char *)localtime(&time1),sizeof(struct tm)); rn tm_v.tm_min=0;rn tm_v.tm_sec=0; //鍚屼笂rnrn tm_v.tm_hour=backup_time;rn time2=mktime(&tm_v);rn if((hour.tv_sec=difftime(time2,time1))<0)rn rn hour.tv_sec+=86400;rn rn int jiffies_t=tvtojiffies(&hour);rnrn //浠ヤ笅鏄嚜鍔ㄥ浠藉畾鏃跺櫒rn del_timer(&backup_timer[0]);rn backup_timer[0].expires=jiffies_t;rn backup_timer[0].data=0;rn backup_timer[0].function=auto_backup;rn add_timer(&backup_timer[0]);rnrn //姹傚嚭浠庡綋鍓嶆椂闂寸偣鍒扮涓€娆¤嚜鍔ㄥ浠界殑鏃堕棿锛屽苟杞寲鎴恓iffies鏁?rn tm_v.tm_hour=clean_time;rn time2=mktime(&tm_v);rn if((hour.tv_sec=difftime(time2,time1))<0)rn rn hour.tv_sec+=86400;rn rn jiffies_t=tvtojiffies(&hour);rnrn //浠ヤ笅鏄嚜鍔ㄥ浠藉畾鏃跺櫒rn del_timer(&backup_timer[1]);rn backup_timer[1].expires=jiffies_t;rn backup_timer[1].data=0;rn backup_timer[1].function=auto_clean;rn add_timer(&backup_timer[1]);rnrn return 0;rnrnrnstatic int __init backup_init(void)rnrn init_timer(&backup_timer[0]);rn init_timer(&backup_timer[1]);rnrn //初始化定时器rn set_backup_timer(); rnrn return 0;rnrnrnstatic void __exit backup_exit(void)rnrn del_timer(&backup_timer[0]);rn del_timer(&backup_timer[1]);rnrn return;rnrnrnmodule_init(backup_init);rnmodule_exit(backup_exit);rnrnrnrnrnrnrnMAKEFILE:rnrnifneq ($(KERNELRELEASE),)rn obj-m := backup_timer.ornelsern KDIR := /lib/modules/$(shell uname -r)/buildrn PWD := $(shell pwd)rnrndefault:rn $(MAKE) -C $(KDIR) M=$(PWD) modules -I/usr/src/rnclean:rn rm -f *.ko *.mod.c *.orninstall:rn insmod hook.kornuninstall:rn rmmod hook.kornendifrnrnrnrnrn 论坛

Linux内核信号量和定时器问题请教:

01-14

rn正在学习用户态线程和内核态定时器的使用,有个问题请教大家:rnrn我在用户空间建立了一个线程,内核做一个字符驱动模块,通过ioctl调用来不断读取内核定时器有没有rnrn到期。在设备结构体里面加了一个信号量,在open调用的时候把信号量拿到,使用一个内核定时器,在内rnrn核定时器到期的处理函数里面释放信号量,这样用户线程调ioctl的时候就获取不到信号量了,等定时器rnrn超时了,就会释放信号量,这样ioctl就返回到用户空间了,可是下次再调用ioctl的时候就不等待信号量了,不知道为什么??rnrn不知道这样做行不行,看书上好像都是两个内核线程间用信号量来通信,请大家指教,谢谢!rnrnvoid timer_handle(void)rnrn int i = 0;rn mod_timer(&KDA_devp->s_timer,jiffies + HZ * 5); /*重新改变定时器的超时时间*/ rn up(&KDA_devp->sem); /*释放信号量,ioctl调用可以获取到该信号量*/rnrnrnrnint KDA_open(struct inode *inode, struct file *filp)rnrn rn struct KDA_dev *dev; /* device information */rnrn dev = container_of(inode->i_cdev, struct KDA_dev, cdev);rn filp->private_data = dev; /* for other methods */rn rn init_timer(&KDA_devp->s_timer);rn KDA_devp->s_timer.function = &timer_handle;rn KDA_devp->s_timer.expires = jiffies + HZ * 5;rn add_timer(&KDA_devp->s_timer); /*添加(注册)定时器*/rn down_interruptible(&dev->sem); /*获取信号量*/rn rn return 0;rnrnrnrnrnint KDA_ioctl(struct inode *inode, struct file *filp,rn unsigned int cmd, unsigned long arg)rnrn int err = 0;rn int isr_num = 100;rn rn struct KDA_dev *dev = filp->private_data; rn rn printk("want to get sem \n");rn rn down_interruptible(&dev->sem);rn rn switch(cmd)rn rn case 1 :rn printk("the case 1 \n");rn break;rn rn case 2 :rn printk("the case 2 \n"); rn break;rn rn case 3 :rn __put_user(KDA_devp->ISR_flag, (int __user *)arg); rn break; rn default:rn printk("the default case \n"); rn rn rn 论坛

没有更多推荐了,返回首页