Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()【转】

 

转自:http://blog.csdn.net/droidphone/article/details/8104433

我们已经在前面几章介绍了低分辨率定时器和高精度定时器的实现原理,内核为了方便其它子系统,在时间子系统中提供了一些用于延时或调度的API,例如msleep,hrtimer_nanosleep等等,这些API基于低分辨率定时器或高精度定时器来实现,本章的内容就是讨论这些方便、好用的API是如何利用定时器系统来完成所需的功能的。

/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

1.  msleep

msleep相信大家都用过,它可能是内核用使用最广泛的延时函数之一,它会使当前进程被调度并让出cpu一段时间,因为这一特性,它不能用于中断上下文,只能用于进程上下文中。要想在中断上下文中使用延时函数,请使用会阻塞cpu的无调度版本mdelay。msleep的函数原型如下:

 

[cpp] view plaincopy

 

  1. void msleep(unsigned int msecs)  

延时的时间由参数msecs指定,单位是毫秒,事实上,msleep的实现基于低分辨率定时器,所以msleep的实际精度只能也是1/HZ级别。内核还提供了另一个比较类似的延时函数msleep_interruptible:

 

 

[cpp] view plaincopy

 

  1. unsigned long msleep_interruptible(unsigned int msecs)  

延时的单位同样毫秒数,它们的区别如下:

 

 

函数 延时单位 返回值 是否可被信号中断
msleep 毫秒
msleep_interruptible 毫秒 未完成的毫秒数

最主要的区别就是msleep会保证所需的延时一定会被执行完,而msleep_interruptible则可以在延时进行到一半时被信号打断而退出延时,剩余的延时数则通过返回值返回。两个函数最终的代码都会到达schedule_timeout函数,它们的调用序列如下图所示:
                                             

 

                                                                            图1.1  两个延时函数的调用序列

下面我们看看schedule_timeout函数的实现,函数首先处理两种特殊情况,一种是传入的延时jiffies数是个负数,则打印一句警告信息,然后马上返回,另一种是延时jiffies数是MAX_SCHEDULE_TIMEOUT,表明需要一直延时,直接执行调度即可:

 

[cpp] view plaincopy

 

  1. signed long __sched schedule_timeout(signed long timeout)  
  2. {  
  3.     struct timer_list timer;  
  4.     unsigned long expire;  
  5.   
  6.     switch (timeout)  
  7.     {  
  8.     case MAX_SCHEDULE_TIMEOUT:  
  9.         schedule();  
  10.         goto out;  
  11.     default:  
  12.         if (timeout < 0) {  
  13.             printk(KERN_ERR "schedule_timeout: wrong timeout "  
  14.                 "value %lx\n", timeout);  
  15.             dump_stack();  
  16.             current->state = TASK_RUNNING;  
  17.             goto out;  
  18.         }  
  19.     }  

然后计算到期的jiffies数,并在堆栈上建立一个低分辨率定时器,把到期时间设置到该定时器中,启动定时器后,通过schedule把当前进程调度出cpu的运行队列:

 

 

[cpp] view plaincopy

 

  1. expire = timeout + jiffies;  
  2.   
  3. setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);  
  4. __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);  
  5. schedule();  

到这个时候,进程已经被调度走,那它如何返回继续执行?我们看到定时器的到期回调函数是process_timeout,参数是当前进程的task_struct指针,看看它的实现:

 

 

[cpp] view plaincopy

 

  1. static void process_timeout(unsigned long __data)  
  2. {  
  3.     wake_up_process((struct task_struct *)__data);  
  4. }  

噢,没错,定时器一旦到期,进程会被唤醒并继续执行:

 

 

[cpp] view plaincopy

 

  1.     del_singleshot_timer_sync(&timer);  
  2.   
  3.     /* Remove the timer from the object tracker */  
  4.     destroy_timer_on_stack(&timer);  
  5.   
  6.     timeout = expire - jiffies;  
  7.   
  8.  out:  
  9.     return timeout < 0 ? 0 : timeout;  
  10. }  

schedule返回后,说明要不就是定时器到期,要不就是因为其它时间导致进程被唤醒,函数要做的就是删除在堆栈上建立的定时器,返回剩余未完成的jiffies数。

 

说完了关键的schedule_timeout函数,我们看看msleep如何实现:

 

[cpp] view plaincopy

 

  1. signed long __sched schedule_timeout_uninterruptible(signed long timeout)  
  2. {  
  3.     __set_current_state(TASK_UNINTERRUPTIBLE);  
  4.     return schedule_timeout(timeout);  
  5. }  
  6.   
  7. void msleep(unsigned int msecs)  
  8. {  
  9.     unsigned long timeout = msecs_to_jiffies(msecs) + 1;  
  10.   
  11.     while (timeout)  
  12.         timeout = schedule_timeout_uninterruptible(timeout);  
  13. }  

msleep先是把毫秒转换为jiffies数,通过一个while循环保证所有的延时被执行完毕,延时操作通过schedule_timeout_uninterruptible函数完成,它仅仅是在把进程的状态修改为TASK_UNINTERRUPTIBLE后,调用上述的schedule_timeout来完成具体的延时操作,TASK_UNINTERRUPTIBLE状态保证了msleep不会被信号唤醒,也就意味着在msleep期间,进程不能被kill掉。

 

看看msleep_interruptible的实现:

 

[cpp] view plaincopy

 

  1. signed long __sched schedule_timeout_interruptible(signed long timeout)  
  2. {  
  3.     __set_current_state(TASK_INTERRUPTIBLE);  
  4.     return schedule_timeout(timeout);  
  5. }  
  6.   
  7. unsigned long msleep_interruptible(unsigned int msecs)  
  8. {  
  9.     unsigned long timeout = msecs_to_jiffies(msecs) + 1;  
  10.   
  11.     while (timeout && !signal_pending(current))  
  12.         timeout = schedule_timeout_interruptible(timeout);  
  13.     return jiffies_to_msecs(timeout);  
  14. }  

msleep_interruptible通过schedule_timeout_interruptible中转,schedule_timeout_interruptible的唯一区别就是把进程的状态设置为了TASK_INTERRUPTIBLE,说明在延时期间有信号通知,while循环会马上终止,剩余的jiffies数被转换成毫秒返回。实际上,你也可以利用schedule_timeout_interruptible或schedule_timeout_uninterruptible构造自己的延时函数,同时,内核还提供了另外一个类似的函数,不用我解释,看代码就知道它的用意了:

 

 

[cpp] view plaincopy

 

  1. signed long __sched schedule_timeout_killable(signed long timeout)  
  2. {  
  3.     __set_current_state(TASK_KILLABLE);  
  4.     return schedule_timeout(timeout);  
  5. }  

 

2.  hrtimer_nanosleep

第一节讨论的msleep函数基于时间轮定时系统,只能提供毫秒级的精度,实际上,它的精度取决于HZ的配置值,如果HZ小于1000,它甚至无法达到毫秒级的精度,要想得到更为精确的延时,我们自然想到的是要利用高精度定时器来实现。没错,linux为用户空间提供了一个api:nanosleep,它能提供纳秒级的延时精度,该用户空间函数对应的内核实现是sys_nanosleep,它的工作交由高精度定时器系统的hrtimer_nanosleep函数实现,最终的大部分工作则由do_nanosleep完成。调用过程如下图所示:

 

                 图  2.1  nanosleep的调用过程

与msleep的实现相类似,hrtimer_nanosleep函数首先在堆栈中创建一个高精度定时器,设置它的到期时间,然后通过do_nanosleep完成最终的延时工作,当前进程在挂起相应的延时时间后,退出do_nanosleep函数,销毁堆栈中的定时器并返回0值表示执行成功。不过do_nanosleep可能在没有达到所需延时数量时由于其它原因退出,如果出现这种情况,hrtimer_nanosleep的最后部分把剩余的延时时间记入进程的restart_block中,并返回ERESTART_RESTARTBLOCK错误代码,系统或者用户空间可以根据此返回值决定是否重新调用nanosleep以便把剩余的延时继续执行完成。下面是hrtimer_nanosleep的代码:

[cpp] view plaincopy

 

  1. long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp,  
  2.                const enum hrtimer_mode mode, const clockid_t clockid)  
  3. {  
  4.     struct restart_block *restart;  
  5.     struct hrtimer_sleeper t;  
  6.     int ret = 0;  
  7.     unsigned long slack;  
  8.   
  9.     slack = current->timer_slack_ns;  
  10.     if (rt_task(current))  
  11.         slack = 0;  
  12.   
  13.     hrtimer_init_on_stack(&t.timer, clockid, mode);  
  14.     hrtimer_set_expires_range_ns(&t.timer, timespec_to_ktime(*rqtp), slack);  
  15.     if (do_nanosleep(&t, mode))  
  16.         goto out;  
  17.   
  18.     /* Absolute timers do not update the rmtp value and restart: */  
  19.     if (mode == HRTIMER_MODE_ABS) {  
  20.         ret = -ERESTARTNOHAND;  
  21.         goto out;  
  22.     }  
  23.   
  24.     if (rmtp) {  
  25.         ret = update_rmtp(&t.timer, rmtp);  
  26.         if (ret <= 0)  
  27.             goto out;  
  28.     }  
  29.   
  30.     restart = ¤t_thread_info()->restart_block;  
  31.     restart->fn = hrtimer_nanosleep_restart;  
  32.     restart->nanosleep.clockid = t.timer.base->clockid;  
  33.     restart->nanosleep.rmtp = rmtp;  
  34.     restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);  
  35.   
  36.     ret = -ERESTART_RESTARTBLOCK;  
  37. out:  
  38.     destroy_hrtimer_on_stack(&t.timer);  
  39.     return ret;  
  40. }  

接着我们看看do_nanosleep的实现代码,它首先通过hrtimer_init_sleeper函数,把定时器的回调函数设置为hrtimer_wakeup,把当前进程的task_struct结构指针保存在hrtimer_sleeper结构的task字段中:

[cpp] view plaincopy

 

  1. void hrtimer_init_sleeper(struct hrtimer_sleeper *sl, struct task_struct *task)  
  2. {  
  3.     sl->timer.function = hrtimer_wakeup;  
  4.     sl->task = task;  
  5. }  
  6. EXPORT_SYMBOL_GPL(hrtimer_init_sleeper);  
  7.   
  8. static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode)  
  9. {  
  10.     hrtimer_init_sleeper(t, current);  

然后,通过一个do/while循环内:启动定时器,挂起当前进程,等待定时器或其它事件唤醒进程。这里的循环体实现比较怪异,它使用hrtimer_active函数间接地判断定时器是否到期,如果hrtimer_active返回false,说明定时器已经过期,然后把hrtimer_sleeper结构的task字段设置为NULL,从而导致循环体的结束,另一个结束条件是当前进程收到了信号事件,所以,当因为是定时器到期而退出时,do_nanosleep返回true,否则返回false,上述的hrtimer_nanosleep正是利用了这一特性来决定它的返回值。以下是do_nanosleep循环体的代码:

[cpp] view plaincopy

 

  1.     do {  
  2.         set_current_state(TASK_INTERRUPTIBLE);  
  3.         hrtimer_start_expires(&t->timer, mode);  
  4.         if (!hrtimer_active(&t->timer))  
  5.             t->task = NULL;  
  6.   
  7.         if (likely(t->task))  
  8.             schedule();  
  9.   
  10.         hrtimer_cancel(&t->timer);  
  11.         mode = HRTIMER_MODE_ABS;  
  12.   
  13.     } while (t->task && !signal_pending(current));  
  14.   
  15.     __set_current_state(TASK_RUNNING);  
  16.   
  17.     return t->task == NULL;  
  18. }  

除了hrtimer_nanosleep,高精度定时器系统还提供了几种用于延时/挂起进程的api:

  • schedule_hrtimeout    使得当前进程休眠指定的时间,使用CLOCK_MONOTONIC计时系统;
  • schedule_hrtimeout_range    使得当前进程休眠指定的时间范围,使用CLOCK_MONOTONIC计时系统;
  • schedule_hrtimeout_range_clock    使得当前进程休眠指定的时间范围,可以自行指定计时系统;
  • usleep_range 使得当前进程休眠指定的微妙数,使用CLOCK_MONOTONIC计时系统;

它们之间的调用关系如下:

                                                          图 2.2  schedule_hrtimeout_xxxx系列函数

最终,所有的实现都会进入到schedule_hrtimeout_range_clock函数。需要注意的是schedule_hrtimeout_xxxx系列函数在调用前,最好利用set_current_state函数先设置进程的状态,在这些函数返回前,进城的状态会再次被设置为TASK_RUNNING。如果事先把状态设置为TASK_UNINTERRUPTIBLE,它们会保证函数返回前一定已经经过了所需的延时时间,如果事先把状态设置为TASK_INTERRUPTIBLE,则有可能在尚未到期时由其它信号唤醒进程从而导致函数返回。主要实现该功能的函数schedule_hrtimeout_range_clock和前面的do_nanosleep函数实现原理基本一致。大家可以自行参考内核的代码,它们位于:kernel/hrtimer.c。

时间: 2024-09-24 21:03:52

Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()【转】的相关文章

Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)【转】

转自:http://blog.csdn.net/droidphone/article/details/8112948 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 数据结构 低分辨率下的动态时钟 1  切换至动态时钟模式 2  低分辨率动态时钟下的事件中断处理函数 3  动态时钟停止周期tick时钟事件 3  动态时钟重新开启周期tick时钟事件 高精度模式下的动态时钟 动态时钟对中断的影响   在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由

Linux时间子系统之一:clock source(时钟源)【转】

转自:http://blog.csdn.net/droidphone/article/details/7975694 clock source用于为linux内核提供一个时间基线,如果你用linux的date命令获取当前时间,内核会读取当前的clock source,转换并返回合适的时间单位给用户空间.在硬件层,它通常实现为一个由固定时钟频率驱动的计数器,计数器只能单调地增加,直到溢出为止.时钟源是内核计时的基础,系统启动时,内核通过硬件RTC获得当前时间,在这以后,在大多数情况下,内核通过选定

linux 时间管理——概念、注意点(一)【转】

转自:http://www.cnblogs.com/openix/p/3324243.html 参考:1.http://bbs.eyeler.com/thread-69-1-1.html                                                                                    2.<Linxu Kernel Development>3ed_CN p166~p185         3.<Professional

select-ARM9 + Linux多线程精确的定时器

问题描述 ARM9 + Linux多线程精确的定时器 最近公司有个项目平台是ARM9 + linux, 在开发过程中遇到一个问题: 有一部分CAN通讯,需要250ms定时发送一帧数据包.于是我用select做了一个定时器发送.但是当这个多线程的程序真正跑起来,在接受端进行检测.结果收到的该帧的时间间隔竟然是330ms左右.虽然预先我知道有偏差,但是330的时间间隔确实大大超出了预期!如果用setitimer的话,一是资源少,二是信号如果加入程序中,可能会带来很多不必要的BUG.哪位大神有更好的定

Linux输入子系统

 Linux输入子系统(Input Subsystem)         Linux 的输入子系统不仅支持鼠标.键盘等常规输入设备,而且还支持蜂鸣器.触摸屏等设备.本章将对 Linux 输 入子系统进行详细的分析. 一    前言                  输入子系统又叫 input 子系统.其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现 给应用程序.                                           二   设备驱动层    

浅析linux内核中timer定时器的生成和sofirq软中断调用流程【转】

转自:http://blog.chinaunix.net/uid-20564848-id-73480.html 浅析linux内核中timer定时器的生成和sofirq软中断调用流程   mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(&base->lock);禁止cpu中断,所以我们的timer回调处理函数handler工作在irq关闭的环境中,所以需要作很多考虑,比如在handler中尽量不要执行会引起pending

linux时间函数详解

我们在编程中可能会经常用到时间,比如取得系统的时间(获取系统的年.月.日.时.分.秒,星期等 ),或者是隔一段时间去做某事,那么我们就用到一些时间函数. linux下存储时间常见的有两种存储 方式,一个是从1970年到现在经过了多少秒,一个是用一个结构来分别存储年月日时分秒的. time_t 这种类型就是用来存储从1970年到现在经过了多少秒,要想更精确一点,可以用结构struct timeval,它精确 到微妙. struct timeval { long tv_sec ; /*秒*/ lon

Linux时间转化方法

  Linux时间转化方法: (1)date -d"2008年 12月 17日 星期三 17:27:22 CST" +"%s" 该命令将2008年 12月 17日 星期三 17:27:22 CST转化为时间戳 结果:1229515680 (2)将时间戳1123495443 换算成可以识别的年月日分秒 date -d '1970-01-01 UTC 1123495443 seconds' 结果:2005年 08月 08日 星期一 18:04:03 CST (3)dat

Linux input子系统分析

 输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见.同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分析很有意义. 一.input子系统知识点 完整的input子系统分析包括以下几方面: 1) 软件层次 2) 输入子系统分层(input_handler,input_core, input_device) 3) 输入设备(TS)驱动开发 4) evdev handler分析 5) input设备模型