时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据 HZ 值来设定,HZ 是一个体系依赖的值,在中定义或该文件包含的某个子平台相关文件中。作为通用的规则,即便如果知道 HZ 的值,在编程时应当不依赖这个特定值,而始终使用HZ。对于当前版本,我们应完全信任内核开发者,他们已经选择了最适合的HZ值,最好保持 HZ 的默认值。
对用户空间,内核HZ几乎完全隐藏,用户 HZ 始终扩展为 100。当用户空间程序包含 param.h,且每个报告给用户空间的计数器都做了相应转换。对用户来说确切的 HZ 值只能通过 /proc/interrupts 获得:/proc/interrupts 的计数值除以 /proc/uptime 中报告的系统运行时间。
每次发生一个时钟中断,内核内部计数器的值就加一。这个计数器在系统启动时初始化为 0, 因此它代表本次系统启动以来的时钟嘀哒数。这个计数器是一个 64-位 变量( 即便在 32-位的体系上)并且称为 “jiffies_64”。但是驱动通常访问 jiffies 变量(unsigned long)(根据体系结构的不同:可能是 jiffies_64 ,可能是jiffies_64 的低32位)。使用 jiffies 是首选,因为它访问更快,且无需在所有的体系上实现原子地访问 64-位的 jiffies_64 值。
访问jiffies_64 对于 32-位 处理器不是原子的,这意味着如果这个变量在你正在读取它们时被更新你可能读到错误的值。若需要访问jiffies_64,内核有一个特别的辅助函数,为你完成适当的锁定:
若需测量非常短时间间隔或需非常高的精度,可以借助平台依赖的资源。许多现代处理器包含一个随时钟周期不断递增的计数寄存器,他是进行高精度的时间管理任务唯一可靠的方法。最有名的计数器寄存器是 TSC ( timestamp counter), 在 x86 的 Pentium 处理器开始引入并在之后所有的 CPU 中出现(包括 x86_64 平台)。它是一个 64-位 寄存器,计数 CPU 的时钟周期,可从内核和用户空间读取。在包含了 asm/msr.h (一个 x86-特定的头文件, 它的名子代表machine-specific registers)的代码中可使用这些宏:
驱动一般无需知道时钟时间(用年月日、小时、分钟、秒来表达的时间),只对用户程序才需要,如 cron 和 syslogd。 内核提供了一个将时钟时间转变为秒数值的函数:
全局变量xtime:它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对秒数值。
结构timeval是Linux内核表示时间的一种格式(Linux内核对时间的表示有多种格式,每种格式都有不同的时间精度),其时间精度是微秒。该结构是内核表示时间时最常用的一种格式,它定义在头文件include/linux/time.h中,如下所示:
Linux内核通过timeval结构类型的全局变量xtime来维持当前时间,该变量定义在kernel/timer.c文件中,如下所示:
但是,全局变量xtime所维持的当前时间通常是供用户来检索和设置的,而其他内核模块通常很少使用它(其他内核模块用得最多的是jiffies),因此对xtime的更新并不是一项紧迫的任务,所以这一工作通常被延迟到时钟中断的底半部(bottom half)中来进行。由于bottom half的执行时间带有不确定性,因此为了记住内核上一次更新xtime是什么时候,Linux内核定义了一个类似于jiffies的全局变量wall_jiffies,来保存内核上一次更新xtime时的jiffies值。时钟中断的底半部分每一次更新xtime的时侯都会将wall_jiffies更新为当时的jiffies值。全局变量wall_jiffies定义在kernel/timer.c文件中:
若想延迟执行若干个时钟嘀哒,精度要求不高。最容易的( 尽管不推荐 ) 实现是一个监视 jiffy 计数器的循环。这种忙等待实现的代码如下:
对 cpu_relex 的调用将以体系相关的方式执行,在许多系统中它根本不做任何事,这个方法应当明确地避免。对于ARM体系来说:
这种忙等待严重地降低了系统性能。如果未配置内核为抢占式, 这个循环在延时期间完全锁住了处理器,计算机直到时间 j1 到时会完全死掉。如果运行一个可抢占的内核时会改善一点,但是忙等待在可抢占系统中仍然是浪费资源的。更糟的是, 当进入循环时如果中断碰巧被禁止, jiffies 将不会被更新, 并且 while 条件永远保持真,运行一个抢占的内核也不会有帮助, 唯一的解决方法是重启。
忙等待加重了系统负载,必须找出一个更好的技术:不需要CPU时释放CPU 。 这可通过调用schedule函数实现(在 linux/sched.h 中声明):
在计算机空闲时运行空闲任务(进程号 0, 由于历史原因也称为swapper)可减轻处理器工作负载、降低温度、增加寿命。
(2)为了实现进程在超时到期时被唤醒而又不等待特定事件(避免声明和使用一个多余的等待队列头),内核提供了 schedule_timeout 函数:
当一个设备驱动需要处理硬件的延迟(latency潜伏期), 涉及到的延时通常最多几个毫秒,在这个情况下, 不应依靠时钟嘀哒,而是内核函数 ndelay, udelay和 mdelay ,他们分别延后执行指定的纳秒数, 微秒数或者毫秒数,定义在 asm/delay.h,原型如下:
重要的是记住这 3 个延时函数是忙等待; 其他任务在时间流失时不能运行。每个体系都实现 udelay, 但是其他的函数可能未定义; 如果它们没有定义, linux/delay.h 提供一个缺省的基于 udelay 的版本。在所有的情况中, 获得的延时至少是要求的值, 但可能更多。udelay 的实现使用一个软件循环, 它基于在启动时计算的处理器速度和使用整数变量 loos_per_jiffy确定循环次数。
为避免在循环计算中整数溢出, 传递给udelay 和 ndelay的值有一个上限,如果你的模块无法加载和显示一个未解决的符号:__bad_udelay, 这意味着你调用 udleay时使用太大的参数。
作为一个通用的规则:若试图延时几千纳秒, 应使用 udelay 而不是 ndelay; 类似地, 毫秒规模的延时应当使用 mdelay 完成而不是一个更细粒度的函数。
有另一个方法获得毫秒(和更长)延时而不用涉及到忙等待的方法是使用以下函数(在linux/delay.h 中声明):
当需要调度一个以后发生的动作, 而在到达该时间点时不阻塞当前进程, 则可使用内核定时器。内核定时器用来调度一个函数在将来一个特定的时间(基于时钟嘀哒)执行,从而可完成各类任务。
内核定时器是一个数据结构, 它告诉内核在一个用户定义的时间点使用用户定义的参数执行一个用户定义的函数,函数位于linux/timer.h 和 kernel/timer.c。被调度运行的函数几乎确定不会在注册它们的进程在运行时运行,而是异步运行。实际上, 内核定时器通常被作为一个软件中断的结果而实现。当在进程上下文之外(即在中断上下文)中运行程序时, 必须遵守下列规则:
(3)不能进行睡眠或者调度. 例如:调用 kmalloc(..., GFP_KERNEL) 是非法的,信号量也不能使用因为它们可能睡眠。
通过调用函数 in_interrupt()能够告知是否它在中断上下文中运行,无需参数并如果处理器当前在中断上下文运行就返回非零。
通过调用函数 in_atomic()能够告知调度是否被禁止,若调度被禁止返回非零; 调度被禁止包含硬件和软件中断上下文以及任何持有自旋锁的时候。
在后一种情况, current 可能是有效的,但是访问用户空间是被禁止的,因为它能导致调度发生. 当使用 in_interrupt()时,都应考虑是否真正该使用的是 in_atomic 。他们都在 asm/hardirq.h 中声明。
内核定时器的另一个重要特性是任务可以注册它本身在后面时间重新运行,因为每个 timer_list 结构都会在运行前从激活的定时器链表中去连接,因此能够立即链入其他的链表。一个重新注册它自己的定时器一直运行在同一个 CPU.
即便在一个单处理器系统,定时器是一个潜在的态源,这是异步运行直接结果。因此任何被定时器函数访问的数据结构应当通过原子类型或自旋锁被保护,避免并发访问。
unsignedlongdata;
intdel_timer_sync(structtimer_list*timer);
一个内核定时器还远未完善,因为它受到 jitter 、硬件中断,还有其他定时器和其他异步任务的影响。虽然一个简单数字 I/O关联的定时器对简单任务是足够的,但不合适在工业环境中的生产系统,对于这样的任务,你将最可能需要实时内核扩展(RT-Linux).
另一个有关于定时的内是 tasklet。它类似内核定时器:在中断时间运行且运行同一个 CPU 上, 并接收一个 unsigned long 参数。不同的是:无法要求在一个指定的时间执行函数,只能简单地要求它在以后的一个由内核选择的时间执行。它对于中断处理特别有用:硬件中断必须尽快处理, 但大部分的数据管理可以延后到以后安全的时间执行。 实际上, 一个 tasket, 就象一个内核定时器, 在一个软中断的上下文中执行(以原子模式)。软件中断是在使能硬件中断时执行异步任务的一个内核机制。
tasklet 以一个数据结构形式存在,使用前必须被初始化。初始化能够通过调用一个特定函数或者通过使用某些宏定义声明结构:
(1)一个 tasklet 能够被禁止并且之后被重新使能; 它不会执行,直到它被使能与被禁止相同的的次数;
(5)一个 tasklet 可能和其他 tasklet 并发, 但是它自己是严格地串行的 ,且tasklet 从不同时运行在不同处理器上,通常在调度它的同一个 CPU 上运行。
(1)tasklet 在软件中断上下文中运行,所以 tasklet 代码必须是原子的。而工作队列函数在一个特殊内核进程上下文运行,有更多的灵活性,且能够休眠。
(4)tasklet 执行的很快, 短时期, 并且在原子态, 而工作队列函数可能是长周期且不需要是原子的,两个机制有它适合的情形。
每个工作队列有一个或多个专用的进程(内核线程), 这些进程运行提交给这个队列的函数。 若使用 create_workqueue, 就得到一个工作队列它在系统的每个处理器上有一个专用的线程。在很多情况下,过多线程对系统性能有影响,如果单个线程就足够则使用 create_singlethread_workqueue 来创建工作队列。
提交一个任务给一个工作队列,在这里《LDD3》介绍的内核2.6.10和我用的新内核2.6.22.2已经有不同了,老接口已经不能用了,编译会出错。这里我只讲2.6.22.2的新接口,至于老的接口我想今后内核不会再有了。从这一点我们可以看出内核发展。
在将来的某个时间, 这个工作函数将被传入给定的 data 值来调用。这个函数将在工作线程的上下文运行, 因此它可以睡眠 (你应当知道这个睡眠可能影响提交给同一个工作队列的其他任务) 工作函数不能访问用户空间,因为它在一个内核线程中运行, 完全没有对应的用户空间来访问。
如果这个入口在它开始执行前被取消,则返回非零。内核保证给定入口的执行不会在调用 cancel_delay_work 后被初始化. 如果 cancel_delay_work 返回 0, 但是, 这个入口可能已经运行在一个不同的处理器, 并且可能仍然在调用 cancel_delayed_work 后在运行. 要绝对确保工作函数没有在 cancel_delayed_work 返回 0 后在任何地方运行, 你必须跟随这个调用来调用:
在 flush_workqueue 返回后, 没有在这个调用前提交的函数在系统中任何地方运行。
在许多情况下, 设备驱动不需要它自己的工作队列。如果你只偶尔提交任务给队列, 简单地使用内核提供的共享的默认的队列可能更有效。若使用共享队列,就必须明白将和其他人共享它,这意味着不应当长时间独占队列(不能长时间睡眠), 并且可能要更长时间才能获得处理器。
因为不知道谁可能使用这个队列,因此不可能知道 flush_schduled_work 返回需要多长时间。
在一些场景下需要取得一个相对时间值,比如取1994年1月1号到当前时间的值,这个值是一个相对值,即:不管在哪个时区,如果是同一个时间获取的值是一样的;
今天,再次碰到一个由于系统时间调整导致的bug。不得不再次查找完美的问题解决方案。 虽然,按道理来说,系统时间进行重置之后,一些应用程序的行为出现异常是合乎情理,并且是...对
虽然,按道理来说,系统时间进行重置之后,一些应用程序的行为出现异常是合乎情理,并且是可以被理解并接受的,最重要的是这样的问题都可以通过重新启动操作系统来解决(微软大哥解决问题之道),但是,对于软件开发人员来说,应该尽量避免非必要的重启。对自己严格要求点儿似乎没什么过错!?
对绝对时间的依赖似乎不怎么容易摆脱,但是我们的应用程序又有多少是严格依赖于系统的绝对时间呢?很多时候,我们只要求能够求出时间间隔就可以了。时间间隔实际上是可以抛开绝对时间求得的,我们可以采用相对于某个时间点的相对时钟来计时,比如说系统启动时间。
自然地,我们就想到了proc伪文件系统下的/proc/uptime文件。通过读这个文件,我们能够获得相对于系统启动时刻的相对时间,其精度为百分之一秒:
读/proc/uptime简单易行,也适合shell操作,但是只为了一个简单的相对时间就进行三次系统调用(open, read, close)和一次字符串解析,这样的复杂度显然不能满足吹毛求疵的我这种人的非礼要求。
联想到Linux内核空间的精度为(1/HZ)秒的计时变量jiffies,如果有哪个系统调用把它导到用户空间,其效率可见是比较高的,再次扒到了系统调用times:
times的返回值是相对于某个固定时刻的时间。这个固定时刻,在2.4内核及其之前是系统启动,以后就改成了系统启动((2^32/HZ) - 300)秒之前(这个对jiffies初始值的改动是为了使Linux内核能够尽快地暴露有关jiffies变量回卷的bug)。所以,应用程序不要依赖于这个开始时刻,如果非要依赖,可以通过其他的方式先得到系统的启动时间,然后与其做差值,求得偏移量,以后就能参考这个偏差求得系统的启动时间。这个函数的返回值clock_t的单位为(1/sysconf(_SC_CLK_TCK);)秒(sysconf(_SC_CLK_TCK)通常为100),并且clock_t可能溢出(在2.6内核的Linux系统上,系统启动5分钟就会溢出),所以需要特别小心。
times虽然高效,但是他的精度还是略显不够,所以我继续挖掘。最终找到了clock_gettime这个系统调用:
clk_id为CLOCK_MONOTONIC的单调时钟能够满足我们的需求,并且它的精度一般都会很高,略微地浏览了一遍它的内核实现,也较为轻量,可谓完美!
路径,就是根目录,前面加/ cd .. 返回上一级目录!! touch触摸一个文件,若不存在,则创建,若创建了,则修改末次修改的日期 我们在...
复杂的目录,很多应用会安装到此目录 /usr/local 用户自己安装软件的目录 /sbin超级管理员ROOT的可执行文件目录 /proc 这个目录是系统内存的映射,会保留进程运行的一些信息...
内核中占有非常重要的作用。 相对于事件驱动而言,内核中有大量函数是基于时间驱动的。... *
显示器可以分离 Gnome KDE Xface 结构: 软件 (IDE)shell 软件 库(系统调用)library:API 内核(平台)kernel 底层(硬件) IDE:集成的开发环境,可以...
时间管理在内核中占有非常重要的...而另外一些函数,比如需要推后执行的磁盘I/O操作等,则需要等待一个
后才运行——比如说,内核会在500ms后再执行某个任务。除了上述两种函数需要内核提供时间外,内核还...
的概念,比如间隔100ms。那么应该使用哪个时间函数更准确呢?1、time 该函数返回的是自1970年以来的秒数,显然精度...
内核中,常见的时间类型有以下两种:系统时间(system time)和实时时间(real time),其实,方便理解,可以将二者分别认为是
下运行,而要解决这两个环境运行的兼容性问题,有两点需要注意:一是路径问题及路径符号,二是字符集编码,解决了这两个问题,我们就能将Windows下调试通过的...
时区 如果有人问你说现在几点? 你看了看表回答他说晚上8点了. 这样回答看上去没有什么问题,但是如果问你的这个人在欧洲的话那么你的回答就会让他很疑惑,因为他那里还太阳当空呢. 这里就有产生了一个如何...
链接文件有关。我觉得很有意思,特意来复现了一下。这复现过程可真是难受死了,好了,话不多说,下面是我复现的过程。 第一步 首先打开我们的...
,也许你发现不管是在X-window下面,还是在Console下面,鼠标都是可以操作的。那么你有没有考虑过这些鼠标都是从哪来的?不错!他们都是从/dev/input/mice这个文件...
类型恐怕是time_t这个类型了吧。它出现在C语言的标准库。但ISO C中并没有规定time_t是什么类型...
清单 7. pthread_cond_timedwait() 函数定义 int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct ...参数 abstime 在这里用来表示
本地时间。GMT时间叫做格林威治时间,也就是UTC时间,这个时间有点特殊,它所在时区为0,在这个时区内,本地

