linux时间管理,时钟中断,系统节拍

目录

1 时钟中断/系统节拍

1.1 简介

1.2 系统时钟中断需要处理的事情

2 HZ

2.1 简介

2.2 动态调节时钟中断 / CONFIG_NO_HZ / 降低功耗/tickless mode

2.3 获取当前运行系统的HZ值 / USER_HZ

3 jiffies和jiffies_64(记录系统启动以来产生的节拍数)

3.1 简介

3.2 访问jiffies和jiffies_64

3.3 jiffies和毫秒微秒之间的转换

4 uptime(系统启动到现在的时间;系统空闲时间;平均负载)

4.1 简介

4.2 uptime命令

4.3 /proc/uptime文件

5 内核态的延时操作

5.1 长延时:让出处理器(schedule)

5.2 长延时:超时(wait_event_timeout / schedule_timeout)

5.3 长延时:调度其它任务(cond_resched)

5.4 短延时:udelay / ndelay (忙等待)

6 动态定时器 / 低分辨率定时器(struct timer_list)

6.1 简介

6.2 注意点

参考资料:


1 时钟中断/系统节拍

1.1 简介

系统定时器以某种频率自行触发或射中时钟中断,该频率可以通过编程预定,称作节拍率。当时钟中断发生时,内核就通过一种特殊的中断处理程序对其进行处理。      《Linux内核设计与实现》P166

时钟中断的频率取决于硬件体系结构。较慢的机器,其节拍大约为 10ms(每秒产生 100 次时钟中断),而较快的机
器的节拍大约 1ms(每秒产生 1000 或 1024 次时钟中断)。《深入理解 LINUX 内核》P230

1.2 系统时钟中断需要处理的事情

更新时间和日期

更新系统统计数:更新本地CPU统计数

更新系统统计数:记录系统负载

更新系统统计数:监管内核代码

检查非屏蔽中断(NMI)监视器

《深入理解LINUX内核》P243

2 HZ

2.1 简介

产生每秒时钟中断的近似个数,也就是时钟频率。    《深入理解LINUX内核》P230

时钟中断由系统定时器硬件以周期性的间隔产生,这个间隔由内核根据HZ的值设定,HZ是一个体系结构有关的常熟。如果想改变系统时钟中断发生的频率,可以通过修改HZ值来进行。但是,如果修改了头文件里的HZ值,则必须使用新的值重新编译内核以及所有模块。      《LINUX设备驱动程序》(第三版)P183

2.2 动态调节时钟中断 / CONFIG_NO_HZ / 降低功耗/tickless mode

内核在联编时,可以选择支持或不支持动态时钟。如果启用了动态时钟,将设置预处理器常数CONFIG_NO_HZ。《深入LINUX内核架构》P727

linux内核支持“无节拍操作”这样的选项。当编译内核时设置了CONFIG_NO_HZ(书上本来写的是CONFIG_HZ)配置选项,系统就是根据这个选项动态调度时钟中断。并不是每隔固定的时间间隔(比如1ms)触发时钟中断,而是按需动态调整和重新设置。如果一个时钟频率设置为3ms,就每3ms触发一个时钟中断。之后,如果50ms内都无事可做,内核以50ms重新调度时钟中断。减少开销总是受欢迎的,但是实质性的受益还是省电,特别是在系统空闲时。                          《Linux内核设计与实现》P170

如果运行队列没有活动进程,内核将选择一个特别的idle进程来运行。此时,动态时钟将开始发挥作用。每当idle进程运行时,都将禁用周期时钟,直至下一个定时器即将到期为止。                                                    《深入LINUX内核架构》P747

2.3 获取当前运行系统的HZ值 / USER_HZ

对用户来讲,如果想知道定时器中断的确切HZ值,只能通过/proc/interrupts获得。例如,通过/proc/interrupts获得的计数值除以/proc/uptime文件中报告的系统运行时间,即可获得内核确切的HZ值。          《LINUX设备驱动程序》(第三版)P186

内核定义了USER_HZ来代表用户空间看到的HZ值。                              《Linux内核设计与实现》P173

3 jiffies和jiffies_64(记录系统启动以来产生的节拍数)

3.1 简介

jiffies变量是一个计数器,用来记录自系统启动以来产生的节拍数。每次时钟中断发生时(每个节拍)它便加1。《深入理解LINUX内核》P235

jiffies提供了内核一种简单形式的低分辨率时间管理方式。         《深入LINUX内核架构》P719

jiffies 是unsigned long类型。当HZ取值1000时,jiffies每过大约50天该计数器才会溢出一次。《LINUX设备驱动程序》(第三版)P184

在1ms一个节拍(当HZ取值1000时)的情况下,jiffies_64变量会在十亿年后才会发生回绕(溢出)。《深入理解LINUX内核》P235

3.2 访问jiffies和jiffies_64

由于jiffes_64在32位系统上是一个复合变量,它不能直接读取,而只能用辅助函数get_jiffies_64访问。这确保在所有系统上都能返回正确的值。              《深入LINUX内核架构》P719

对jiffies_64的访问不像对jiffies的访问那样直接。在64位计算机架构上,这2个变量其实是同一个;但在32位处理器上,对64位值的访问不是原子的。这意味着,在我们读取64位的高32位及低32位时,可能会发生更新,从而获得错误的值。因此对应该使用内核导出的一个特殊的辅助函数,该函数完成了设当的锁定:u64 get_jiffies_64(void);        《LINUX设备驱动程序》(第三版)P185

3.3 jiffies和毫秒微秒之间的转换

unsigned int jiffies_to_msecs(const unsigned long j);

unsigned int jiffies_to_usecs(const unsigned long j);

unsigned long msecs_to_jiffies(const unsigned int m);

unsigned long usecs_to_jiffies(const unsigned int u);

《深入LINUX内核架构》P720

4 uptime(系统启动到现在的时间;系统空闲时间;平均负载)

4.1 简介

什么时平均负载。简单来说,平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和CPU使用率并没有直接关系。

负载多大最理想:

就是刚好每个CPU上都刚好运行着一个进程,这样每个CPU都得到了充分的利用。比如平均负载为4时意味着什么呢?

  • 在只有4个CPU的系统上,意味着所有的CPU都刚好被完全占用。

  • 在8个CPU的系统上,意味着CPU有50%的空闲。

  • 而在有2个CPU系统上,意味着有一半的进程竞争不到CPU。

参考:https://blog.csdn.net/datuzijean/article/details/86565184

 

平均负载在单处理器系统上,值为0意味着没有活跃的进程(除了swapper进程0)在运行,而1意味着一个单独的进程100%占有CPU,值大于1说明几个运行着的进程共享CPU。    《深入理解LINUX内核》P242

4.2 uptime命令

每当系统变慢时,我们做的第一件事就是执行top或uptime,来了解下负载情况。

root@ubuntu:/home/user# uptime 
 16:55:57 up  7:40,  4 users,  load average: 1.15, 0.60, 0.34 
root@ubuntu:/home/user#
命令输出的结果解释:
16:55:57			//系统的当前时间
7:40				//系统运行的时间
4 users				//正在登录的用户数
load average: 1.15, 0.60, 0.34	//最后3个数依次代表1分钟内、5分钟内和15分钟内系统的平均负载。

 

参考:https://blog.csdn.net/datuzijean/article/details/86565184

4.3 /proc/uptime文件

root@ubuntu:/home/user# cat /proc/uptime 
29550.60    54260.02 
root@ubuntu:/home/user#

第一列输出的是,系统启动到现在的时间(以秒为单位),这里简记为num1;
第二列输出的是,系统空闲的时间(以秒为单位),这里简记为num2。

注意,很多很多人都知道第二个是系统空闲的时间,但是可能你不知道是,在SMP系统里,系统空闲的时间有时会是系统运行时间的几倍,这是怎么回事呢?因为系统空闲时间的计算,是把SMP算进去的,就是所你有几个逻辑的CPU(包括超线程)。

系统的空闲率(%) = num2/(num1*N) 其中N是SMP系统中的CPU个数。

参考:https://www.cnblogs.com/frydsh/p/3887357.html

5 内核态的延时操作

5.1 长延时:让出处理器(schedule)

在不需要CPU时主动释放CPU,这可以通过调用schedule()函数实现。延时代码如下

while(time_before(jiffies, j1)){
	schedule();
}

《LINUX设备驱动程序》(第三版)P192

5.2 长延时:超时(wait_event_timeout / schedule_timeout)

long wait_event_timeout(wait_queue_head q, condition, long timeout);
long wait_event_interruptible_timeout(wait_queue_head q, condition, long timeout);

《LINUX设备驱动程序》(第三版)P193

schedule_timeout(delay);不等待特定事件而延时。用法如下:

#include <linux/sched.h>
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(delay);

在上面的代码中,第一行调用set_current_state设置当前进程状态,这样,调度器只会在超时到期且其状态变成TASK_RUNNING时才会运行这个进程。如果要实现不可中断的延迟,可以使用TASK_UNINTERRUPTIBLE。如果我们忘记改变当前进程的状态,则对schedule_timeout的调用和对schedule的调用一样。           《LINUX设备驱动程序》(第三版)P195

更理想的延迟执行方法是使用schedule_timeout()函数,该方法让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再执行。但该方法也不能保证睡眠时间正好等于指定的延迟时间,只能尽量使用睡眠时间接近指定的延迟时间。当指定的时间到期后,内核唤醒被延迟的任务并将其重新放回运行队列。注意,由于schedule_timeout()函数需要调用调度程序所以调用它的代码必须保证能够睡眠。简而言之,调用代码必须处于进程上下文中,并且不能持有锁。schedule_timeout()函数的用法相当简单、直接。其实,它是内核定时器的一个简单应用。             《Linux内核设计与实现》P183

5.3 长延时:调度其它任务(cond_resched)

unsigned long  delay  =  jiffies + 5*HZ;	//延时5秒
while(time_before(jiffies, delay))
	cond_resched();

cond_resched()函数将调度一个新程序投入运行,但它只有在设置完need_resched标志后才能生效。换句话说,该方法有效的条件是系统中存在更重要的任务需要运行。注意,因为该方法需要调度程序,所以它不能在中断上下文中使用——只能在进程上下文中使用。           《Linux内核设计与实现》P181

5.4 短延时:udelay / ndelay (忙等待)

#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

这些函数的实际实现在<asm/delay.h>中,其实现和具体的体系结构相关。所有的体系架构都会实现udelay,但其它函数可能未被定义。

udelay(以及可能的ndelay)的实现使用了软件循环。

ndelay / udelay / mdelay这三个延迟函数均是忙等待的函数,因而在延迟过程中无法运行其他任何。

《LINUX设备驱动程序》(第三版)P195

 

void udelay(unsigned long usecs)
{
	unsigned long loops;
	loops = (usecs * HZ * current_cpu_data, loops_per_jiffy) / 1000000;
	cur_timer->delay(loops);
}

void ndelay(unsigned long nsecs)
{
	unsigned long loops;
	loops = (nsecs * HZ * current_cpu_data, loops_per_jiffy) / 1000000000;
	cur_timer->delay(loops);
}

《深入理解LINUX内核》P251

 

ndelay和udelay两个函数都依赖于cur_timer定时器对象的延时方法,它接收“loops”中的时间间隔作为参数。不过每一次”loop”精确的持续时间取决于cur_timer涉及的定时器对象,具体结束如下:

1. 如果cur_timer指向timer_hpettimer_pmtmrtimer_tsc对象,一次loop对应于一个CPU循环——也就是两个连续CPU时钟信号间的时间间隔。

2. 如果cur_timer指向timer_nonetimer_pit对象,一次loop对应于一条紧凑指令循环在一次单独循环中所花费的时间。

《深入理解LINUX内核》P252

 

6 动态定时器 / 低分辨率定时器(struct timer_list)

6.1 简介

动态定时器通常有很大的设置开销和一个相当大的最小等待时间(1ms);  《深入理解LINUX内核》P251

内核在时钟中断发生后执行定时器,定时器作为软中断在下半部上下文中执行。《Linux内核设计与实现》P180

内核定时器常常是作为“软中断”的结构而运行的。在这种原子性的上下文中运行时,代码会受到许多限制。 《LINUX设备驱动程序》(第三版)P197

在SMP系统中,定时器函数会由注册它的同一个CPU执行,这样可以尽可能获得缓存的局域性(locality)。因此,一个注册自己的定时器始终会在同一CPU上运行。   《LINUX设备驱动程序》(第三版)P198

2.3版本前内核也存在静态定时器。这种定时器在编译时创建,而不是实时创建。由于静态定时器存在缺陷,已经被淘汰了。《Linux内核设计与实现》P178

6.2 注意点

一般来说,定时器都在超时后马上就会执行,但是也有可能推迟到下一次时钟节拍时才执行,所以不能用定时器来实现任何硬实时任务。 《Linux内核设计与实现》P179

定时器会是竞态的潜在来源。这是由于其异步执行的特点直接导致的。因此,任何通过定时器函数访问的数据结构都应该针对并发访问进行保护,可以使用原子类型,或者自旋锁。《LINUX设备驱动程序》(第三版)P198

 

参考资料:

《Linux内核设计与实现》原书第三版,机械工业出版社

《深入理解LINUX内核》第三版,中国电力出版社

《LINUX设备驱动程序》第三版,中国电力出版社

《深入LINUX内核架构》,人民邮电出版社

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值