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. switch (timeout)
  6. {
  7. case MAX_SCHEDULE_TIMEOUT:
  8. schedule();
  9. goto out;
  10. default:
  11. if (timeout < 0) {
  12. printk(KERN_ERR "schedule_timeout: wrong timeout "
  13. "value %lx\n", timeout);
  14. dump_stack();
  15. current->state = TASK_RUNNING;
  16. goto out;
  17. }
  18. }

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

[cpp] view plaincopy

  1. expire = timeout + jiffies;
  2. setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
  3. __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
  4. 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. /* Remove the timer from the object tracker */
  3. destroy_timer_on_stack(&timer);
  4. timeout = expire - jiffies;
  5. out:
  6. return timeout < 0 ? 0 : timeout;
  7. }

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. void msleep(unsigned int msecs)
  7. {
  8. unsigned long timeout = msecs_to_jiffies(msecs) + 1;
  9. while (timeout)
  10. timeout = schedule_timeout_uninterruptible(timeout);
  11. }

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. unsigned long msleep_interruptible(unsigned int msecs)
  7. {
  8. unsigned long timeout = msecs_to_jiffies(msecs) + 1;
  9. while (timeout && !signal_pending(current))
  10. timeout = schedule_timeout_interruptible(timeout);
  11. return jiffies_to_msecs(timeout);
  12. }

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. slack = current->timer_slack_ns;
  9. if (rt_task(current))
  10. slack = 0;
  11. hrtimer_init_on_stack(&t.timer, clockid, mode);
  12. hrtimer_set_expires_range_ns(&t.timer, timespec_to_ktime(*rqtp), slack);
  13. if (do_nanosleep(&t, mode))
  14. goto out;
  15. /* Absolute timers do not update the rmtp value and restart: */
  16. if (mode == HRTIMER_MODE_ABS) {
  17. ret = -ERESTARTNOHAND;
  18. goto out;
  19. }
  20. if (rmtp) {
  21. ret = update_rmtp(&t.timer, rmtp);
  22. if (ret <= 0)
  23. goto out;
  24. }
  25. restart = ¤t_thread_info()->restart_block;
  26. restart->fn = hrtimer_nanosleep_restart;
  27. restart->nanosleep.clockid = t.timer.base->clockid;
  28. restart->nanosleep.rmtp = rmtp;
  29. restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);
  30. ret = -ERESTART_RESTARTBLOCK;
  31. out:
  32. destroy_hrtimer_on_stack(&t.timer);
  33. return ret;
  34. }

接着我们看看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. static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode)
  8. {
  9. 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. if (likely(t->task))
  7. schedule();
  8. hrtimer_cancel(&t->timer);
  9. mode = HRTIMER_MODE_ABS;
  10. } while (t->task && !signal_pending(current));
  11. __set_current_state(TASK_RUNNING);
  12. return t->task == NULL;
  13. }

除了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-10-11 18:21:59

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

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

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

Linux时间子系统专题汇总

DroidPhone关于Linux时间子系统专题: http://blog.csdn.net/DroidPhone/article/category/1263459 Linux时间子系统之一:clock source(时钟源) Linux时间子系统之二:表示时间的单位和结构 Linux时间子系统之三:时间的维护者:timekeeper Linux时间子系统之四:定时器的引擎:clock_event_device Linux时间子系统之五:低分辨率定时器的原理和实现 Linux时间子系统之六:高精

Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现

上一篇文章,我介绍了传统的低分辨率定时器的实现原理.而随着内核的不断演进,大牛们已经对这种低分辨率定时器的精度不再满足,而且,硬件也在不断地发展,系统中的定时器硬件的精度也越来越高,这也给高分辨率定时器的出现创造了条件.内核从2.6.16开始加入了高精度定时器架构.在实现方式上,内核的高分辨率定时器的实现代码几乎没有借用低分辨率定时器的数据结构和代码,内核文档给出的解释主要有以下几点: 低分辨率定时器的代码和jiffies的关系太过紧密,并且默认按32位进行设计,并且它的代码已经经过长时间的优化

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

在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工作于低分辨率模式,还是高精度模式,内核都竭尽所能,用不同的方式提供周期时钟,以产生定期的tick事件,tick事件或者用于全局的时间管理(jiffies和时间的更新),或者用于本地cpu的进程统计.时间轮定时器框架等等.周期性时钟虽然简单有效,但是也带来了一些缺点,尤其在系统的功耗上,因为就算系统目

Linux时间子系统(十七) ARM generic timer驱动代码分析

一.前言 关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程.在单核时代,各个SOC vendor厂商购买ARM core的IP,然后自己设计SOC上的peripherals,这里面就包括了timer的硬件.由于没有统一的标准,各个厂商的设计各不相同,这给驱动工程师带来了工作量.然而,如果仅仅是工作量的话就还好,实际上,不仅仅如此.linux的时间子系统要求硬件t

Linux时间子系统(一) 基本概念

本文使用Q & A的方式来和大家以前探讨一下时间的基本概念 一.什么是时间? 这个问题实在是太复杂了,我都不知道这是一个物理学.宇宙学.还是热力学异或是哲学问题,我只是想从几个侧面来了解一下时间这个概念.本节内容都是我坐在公交车上瞎想的,对物理学有兴趣的人可以指出我的错误(一个搞linux kernel的人不会有太深刻的物理学知识的),对Linux时间子系统有兴趣的人还是忽略这个小节吧. 1.时间和空间以及相对性 有没有绝对时间的概念呢?时间是否是独立于一切存在的呢?相信有绝对时间的存在比较符合

Linux时间子系统(十五) clocksource

一.前言 和洋葱一样,软件也是有层次的,内核往往需要对形形色色的某类型的驱动进行抽象,屏蔽掉其具体的特质,获取该类驱动共同的逻辑,而又根据这些逻辑撰写该类驱动的抽象层.嵌入式系统总是会提供timer的硬件block,软件需要对timer硬件提供的功能进行抽象:linux kernel将timer类型的硬件抽象成两个组件,一是free running的counter,另外一个是指定的counter值上产生中断的能力.本文主要描述第一个组件,在内核中被称作clock source. 二.什么是clo

Linux时间子系统之四:定时器的引擎:clock_event_device

早期的内核版本中,进程的调度基于一个称之为tick的时钟滴答,通常使用时钟中断来定时地产生tick信号,每次tick定时中断都会进行进程的统计和调度,并对tick进行计数,记录在一个jiffies变量中,定时器的设计也是基于jiffies.这时候的内核代码中,几乎所有关于时钟的操作都是在machine级的代码中实现,很多公共的代码要在每个平台上重复实现.随后,随着通用时钟框架的引入,内核需要支持高精度的定时器,为此,通用时间框架为定时器硬件定义了一个标准的接口:clock_event_devic

Linux时间子系统之四:定时器的引擎:clock_event_device【转】

本文转载自:http://blog.csdn.net/droidphone/article/details/8017604 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 早期的内核版本中,进程的调度基于一个称之为tick的时钟滴答,通常使用时钟中断来定时地产生tick信号,每次tick定时中断都会进行进程的统计和调度,并对tick进行计数,记录在一个jiffies变量中,定时器的设计也是基于jiffies.这时候的内核代码中,几乎所有关于时钟的操作都是在machine级