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的函数原型如下:
- void msleep(unsigned int msecs)
 
延时的时间由参数msecs指定,单位是毫秒,事实上,msleep的实现基于低分辨率定时器,所以msleep的实际精度只能也是1/HZ级别。内核还提供了另一个比较类似的延时函数msleep_interruptible:
- unsigned long msleep_interruptible(unsigned int msecs)
 
延时的单位同样毫秒数,它们的区别如下:
| 函数 | 延时单位 | 返回值 | 是否可被信号中断 | 
|---|---|---|---|
| msleep | 毫秒 | 无 | 否 | 
| msleep_interruptible | 毫秒 | 未完成的毫秒数 | 是 | 
最主要的区别就是msleep会保证所需的延时一定会被执行完,而msleep_interruptible则可以在延时进行到一半时被信号打断而退出延时,剩余的延时数则通过返回值返回。两个函数最终的代码都会到达schedule_timeout函数,它们的调用序列如下图所示:
                                             
图1.1 两个延时函数的调用序列
下面我们看看schedule_timeout函数的实现,函数首先处理两种特殊情况,一种是传入的延时jiffies数是个负数,则打印一句警告信息,然后马上返回,另一种是延时jiffies数是MAX_SCHEDULE_TIMEOUT,表明需要一直延时,直接执行调度即可:
- signed long __sched schedule_timeout(signed long timeout)
 - {
 - struct timer_list timer;
 - unsigned long expire;
 - switch (timeout)
 - {
 - case MAX_SCHEDULE_TIMEOUT:
 - schedule();
 - goto out;
 - default:
 - if (timeout < 0) {
 - printk(KERN_ERR "schedule_timeout: wrong timeout "
 - "value %lx\n", timeout);
 - dump_stack();
 - current->state = TASK_RUNNING;
 - goto out;
 - }
 - }
 
然后计算到期的jiffies数,并在堆栈上建立一个低分辨率定时器,把到期时间设置到该定时器中,启动定时器后,通过schedule把当前进程调度出cpu的运行队列:
- expire = timeout + jiffies;
 - setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
 - __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
 - schedule();
 
到这个时候,进程已经被调度走,那它如何返回继续执行?我们看到定时器的到期回调函数是process_timeout,参数是当前进程的task_struct指针,看看它的实现:
- static void process_timeout(unsigned long __data)
 - {
 - wake_up_process((struct task_struct *)__data);
 - }
 
噢,没错,定时器一旦到期,进程会被唤醒并继续执行:
- del_singleshot_timer_sync(&timer);
 - /* Remove the timer from the object tracker */
 - destroy_timer_on_stack(&timer);
 - timeout = expire - jiffies;
 - out:
 - return timeout < 0 ? 0 : timeout;
 - }
 
schedule返回后,说明要不就是定时器到期,要不就是因为其它时间导致进程被唤醒,函数要做的就是删除在堆栈上建立的定时器,返回剩余未完成的jiffies数。
说完了关键的schedule_timeout函数,我们看看msleep如何实现:
- signed long __sched schedule_timeout_uninterruptible(signed long timeout)
 - {
 - __set_current_state(TASK_UNINTERRUPTIBLE);
 - return schedule_timeout(timeout);
 - }
 - void msleep(unsigned int msecs)
 - {
 - unsigned long timeout = msecs_to_jiffies(msecs) + 1;
 - while (timeout)
 - timeout = schedule_timeout_uninterruptible(timeout);
 - }
 
msleep先是把毫秒转换为jiffies数,通过一个while循环保证所有的延时被执行完毕,延时操作通过schedule_timeout_uninterruptible函数完成,它仅仅是在把进程的状态修改为TASK_UNINTERRUPTIBLE后,调用上述的schedule_timeout来完成具体的延时操作,TASK_UNINTERRUPTIBLE状态保证了msleep不会被信号唤醒,也就意味着在msleep期间,进程不能被kill掉。
看看msleep_interruptible的实现:
- signed long __sched schedule_timeout_interruptible(signed long timeout)
 - {
 - __set_current_state(TASK_INTERRUPTIBLE);
 - return schedule_timeout(timeout);
 - }
 - unsigned long msleep_interruptible(unsigned int msecs)
 - {
 - unsigned long timeout = msecs_to_jiffies(msecs) + 1;
 - while (timeout && !signal_pending(current))
 - timeout = schedule_timeout_interruptible(timeout);
 - return jiffies_to_msecs(timeout);
 - }
 
msleep_interruptible通过schedule_timeout_interruptible中转,schedule_timeout_interruptible的唯一区别就是把进程的状态设置为了TASK_INTERRUPTIBLE,说明在延时期间有信号通知,while循环会马上终止,剩余的jiffies数被转换成毫秒返回。实际上,你也可以利用schedule_timeout_interruptible或schedule_timeout_uninterruptible构造自己的延时函数,同时,内核还提供了另外一个类似的函数,不用我解释,看代码就知道它的用意了:
- signed long __sched schedule_timeout_killable(signed long timeout)
 - {
 - __set_current_state(TASK_KILLABLE);
 - return schedule_timeout(timeout);
 - }
 
2. hrtimer_nanosleep

- long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp,
 - const enum hrtimer_mode mode, const clockid_t clockid)
 - {
 - struct restart_block *restart;
 - struct hrtimer_sleeper t;
 - int ret = 0;
 - unsigned long slack;
 - slack = current->timer_slack_ns;
 - if (rt_task(current))
 - slack = 0;
 - hrtimer_init_on_stack(&t.timer, clockid, mode);
 - hrtimer_set_expires_range_ns(&t.timer, timespec_to_ktime(*rqtp), slack);
 - if (do_nanosleep(&t, mode))
 - goto out;
 - /* Absolute timers do not update the rmtp value and restart: */
 - if (mode == HRTIMER_MODE_ABS) {
 - ret = -ERESTARTNOHAND;
 - goto out;
 - }
 - if (rmtp) {
 - ret = update_rmtp(&t.timer, rmtp);
 - if (ret <= 0)
 - goto out;
 - }
 - restart = ¤t_thread_info()->restart_block;
 - restart->fn = hrtimer_nanosleep_restart;
 - restart->nanosleep.clockid = t.timer.base->clockid;
 - restart->nanosleep.rmtp = rmtp;
 - restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);
 - ret = -ERESTART_RESTARTBLOCK;
 - out:
 - destroy_hrtimer_on_stack(&t.timer);
 - return ret;
 - }
 
接着我们看看do_nanosleep的实现代码,它首先通过hrtimer_init_sleeper函数,把定时器的回调函数设置为hrtimer_wakeup,把当前进程的task_struct结构指针保存在hrtimer_sleeper结构的task字段中:
- void hrtimer_init_sleeper(struct hrtimer_sleeper *sl, struct task_struct *task)
 - {
 - sl->timer.function = hrtimer_wakeup;
 - sl->task = task;
 - }
 - EXPORT_SYMBOL_GPL(hrtimer_init_sleeper);
 - static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode)
 - {
 - hrtimer_init_sleeper(t, current);
 
然后,通过一个do/while循环内:启动定时器,挂起当前进程,等待定时器或其它事件唤醒进程。这里的循环体实现比较怪异,它使用hrtimer_active函数间接地判断定时器是否到期,如果hrtimer_active返回false,说明定时器已经过期,然后把hrtimer_sleeper结构的task字段设置为NULL,从而导致循环体的结束,另一个结束条件是当前进程收到了信号事件,所以,当因为是定时器到期而退出时,do_nanosleep返回true,否则返回false,上述的hrtimer_nanosleep正是利用了这一特性来决定它的返回值。以下是do_nanosleep循环体的代码:
- do {
 - set_current_state(TASK_INTERRUPTIBLE);
 - hrtimer_start_expires(&t->timer, mode);
 - if (!hrtimer_active(&t->timer))
 - t->task = NULL;
 - if (likely(t->task))
 - schedule();
 - hrtimer_cancel(&t->timer);
 - mode = HRTIMER_MODE_ABS;
 - } while (t->task && !signal_pending(current));
 - __set_current_state(TASK_RUNNING);
 - return t->task == NULL;
 - }
 
除了hrtimer_nanosleep,高精度定时器系统还提供了几种用于延时/挂起进程的api:
- schedule_hrtimeout 使得当前进程休眠指定的时间,使用CLOCK_MONOTONIC计时系统;
 - schedule_hrtimeout_range 使得当前进程休眠指定的时间范围,使用CLOCK_MONOTONIC计时系统;
 - schedule_hrtimeout_range_clock 使得当前进程休眠指定的时间范围,可以自行指定计时系统;
 - usleep_range 使得当前进程休眠指定的微妙数,使用CLOCK_MONOTONIC计时系统;
 
它们之间的调用关系如下:

Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()【转】的更多相关文章
- Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()
		
我们已经在前面几章介绍了低分辨率定时器和高精度定时器的实现原理,内核为了方便其它子系统,在时间子系统中提供了一些用于延时或调度的API,例如msleep,hrtimer_nanosleep等等,这些A ...
 - Linux时间子系统专题汇总
		
关于Linux时间子系统有两个系列文章讲的非常好,分别是WowoTech和DroidPhone. 还有两本书分别是介绍: Linux用户空间时间子系统<Linux/UNIX系统编程手册>的 ...
 - Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现
		
转自:http://blog.csdn.net/droidphone/article/details/8074892 上一篇文章,我介绍了传统的低分辨率定时器的实现原理.而随着内核的不断演进,大牛们已 ...
 - Linux时间子系统之五:低分辨率定时器的原理和实现
		
专题文档汇总目录 Notes:低精度timer在内核中的数据结构以及API接口:低精度timer精巧高效的分组,使用cascade进行定时器移位,组内Timer FIFO:低精度Timer的初始化流程 ...
 - Linux时间子系统之(三):用户空间接口函数
		
专题文档汇总目录 Notes:用户空间时间相关接口函数: 类型 API 精度 说明 时间 time stime time_t 精度为秒级 逐渐要被淘汰.需要定义__ARCH_WANT_SYS_TIME ...
 - Linux时间子系统之(五):POSIX Clock
		
专题文档汇总目录 Notes: 本章主要介绍了若干种类的静态时钟,这些时钟都可以通过k_clock表示,注册到posix_clocks中.这些都是静态时钟,可以分为三大类:各种REALTIME时钟.带 ...
 - Linux时间子系统之(六):POSIX timer
		
专题文档汇总目录 Notes:首先讲解了POSIX timer的标识(唯一识别).POSIX Timer的组织(管理POSIX Timer).内核中如何抽象POSIX Timer:然后分析了POSIX ...
 - Linux时间子系统之(十七):ARM generic timer驱动代码分析
		
专题文档汇总目录 Notes:ARM平台Clock/Timer架构:System counter.Timer以及两者之间关系:Per cpu timer通过CP15访问,System counter通 ...
 - Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)
		
在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工 ...
 
随机推荐
- Just Another Problem UVA - 11490(枚举)
			
题意: 你有s个士兵,并打算把他们排成一个r行c列,但有两个"洞"的矩形方队,以迷惑敌人(从远处看,敌人可能误以为一共有r*c个士兵).洞是两个大小相同的正方形,为了隐蔽性更强,方 ...
 - 【转】一口气读懂NB-IoT
			
在过去的一年多,NB-IoT真的可以说是大红大紫.在通信圈里,除了说5G,就是说物联网.如果说物联网,八成就是在说NB-IoT. 在目前5G还没来的情况下,NB-IoT基本上是独领风骚.风光无限. 各 ...
 - BZOJ2789 [Poi2012]Letters  【树状数组】
			
题目链接 BZOJ 题解 如果我们给\(A\)中所有字母按顺序编号,给\(B\)中所有字母编上相同的号码 对于\(B\)中同一种,显然号码应该升序 然后求逆序对即可 #include<algor ...
 - java随机数的有趣用法
			
直接用代码说明,比较容易理解 package com.wz.other; import java.util.Random;import java.util.concurrent.ThreadLocal ...
 - k最近邻算法(kNN)
			
from numpy import * import operator from os import listdir def classify0(inX, dataSet, labels, k): d ...
 - Linux查看动态库.so导出函数列表
			
https://blog.csdn.net/chrisnotfound/article/details/80662923
 - 利用机器学习实现微信小程序-加减大师自动答题
			
之前有看到微信小程序<跳一跳>别人用python实现自动运行,后来看到别人用hash码实现<加减大师>的自动答题领取娃娃,最近一直在研究深度学习,为啥不用机器学习实现呢?不就是 ...
 - 共享内存shm*(生产者和消费者)
			
//heads.h #ifndef HEAD_H #define HEAD_H #include <iostream> #include <sys/shm.h> //share ...
 - Matlab ------ 打开MATLAB,设置默认打开的文件夹
 - Python 不定参数函数
			
1. 元组形式 def test1(*args): print('################test1################') print(type(args)) print(arg ...