更新系统统计数

更新时间和日期

用户程序从xtime变量中获得当前时间和日期。内核必须周期性地更新该变量,才能使它的值保持相当的精确。

全局时钟中断处理程序调用update_times()函数更新xtime变量的值,代码如下:

voidupdate_times(void)

{

unsigned longticks;

ticks = jiffies –wall_jiffies;

if(ticks){

wall_jiffies+=ticks

update_wall_time(ticks);

}

calc_load(ticks);

}

我们回忆一下前面对时钟中断处理程序的描述,当执行该函数时,已经获得用于写操作xtime_lock顺序锁。

wall_jiffies变量存放xtime变量最后更新的时间。观察一下,wall_jiffies的值可以小于jiffies-1,因为一些定时器中断会丢失,例如当中断被禁止了很长一段时间,换句话说,内核不必每个时钟节拍更新xtime变量。然而,最后不会有时钟节拍丢失,因此,xtime最终存放正确的系统时间。对丢失的定时器中断的检查在cur_timermark_offset中完成,参见前面的”单处理器系统上的计时结构”一节。

update_wall_time()函数连续调用update_wall_time_one_tick()函数ticks次,每次调用都给xtime.tv_nsec字段加上1000000。如果xtime.tv_nsec的值大于999999999,那么update_wall_time()函数还会更新xtimetv_sec字段,如果系统发出adjtimex()系统调用,那么函数可能会稍微调整1000000这个值使时钟稍快或稍慢一点。

calc_load()函数的描述请参见本章后面的”记录系统负载”一节。

更新系统统计数

内核在与定时相关的其它任务中必须周期性地收集若干数据用于:

  1. 检查运行进程的CPU资源限制

  2. 更新与本地CPU工作负载有关的统计数

  3. 计算平均系统负载

  4. 监管内核代码

更新本地CPU统计数

我们曾经提到过,单处理器系统上的全局时钟中断处理程序或多处理器系统上的本地时钟中断处理程序调用update_process_times()函数来更新一些内核统计数。该函数执行以下步骤:

  1. 检查当前进程运行了多长时间。当时钟中断发生时,根据当前进程运行在用户态还是内核态,选择调用account_user_time()还是account_system_time()。每个函数基本上执行如下步骤。

a.更新当前进程描述符的utime字段或stime字段。在进程描述符中提供两个被称作cutimecstime的附加字段,分别用来统计子进程在用户态和内核态下所经过的CPU节拍数。由于效率的原因,update_process_times()并不更新这些字段,而只是当父进程询问它的其中一个子进程的状态时才对其进行更新。

b.检查是否已达到总的CPU时限,如果是,向current进程发送SIGXCPUSIGKILL信号,在第三章的”进程资源限制”一节中,讲述了限制是如何被每个进程描述符的signal->rlim[RLIMIT_CPU].rlim_cur字段所控制的。

c.调用account_it_virt()account_it_prof()来检查进程定时器。

d.更新一些内核统计数,这些统计数存放在每CPU变量kstat中。

  1. 调用raise_softirq()来激活本地CPU上的TIMER_SOFTIRQ任务队列。

  2. 如果必须回收一些老版本的,受RCU保护的数据结构,那么检查本地CPU是否经历了静止状态并调用tasklet_schedule()来激活本地CPUrcu_tasklet任务队列。

  3. 调用scheduler_tick()函数,该函数使当前进程的时间片计数器减1,并检查计数器是否已减到0。我们将在第七章的”scheduler_tick()函数”一节深入讨论这些操作。

记录系统负载

任何Unix内核都要记录系统进行了多少CPU活动。这些统计数据由各种管理实用程序来使用。用户输入uptime命令后可以看到一些统计数据:如相对于最后1分钟,5分钟,15分钟的”平均负载”。在单处理器系统上,值0意味着没有活跃的进程在运行,而值1意味着一个单独的进程100%占有CPU,值大于1说明几个运行着的进程共享CPU

update_times()在每个节拍都要调用calc_load()函数来计算处于TASK_RUNNINGTASK_UNINTERRUPTIBLE状态的进程数,并用这个数据更新平均系统负载。

监管内核代码

Linux包含一个被称作readprofiler的最低要求的代码监管器,Linux开发者用其发现内核在内核态的什么地方花费时间。监管器确定内核的”热点”----执行最频繁的内核代码片段。确定内核”热点”是非常重要的。因为这可以指出应当进一步优化的内核函数。

监管器基于非常简单的蒙特卡洛算法;在每次时钟中断发生时,内核确定该中断是否发生在内核态;如果是,内核从堆栈取回中断发生前的eip寄存器的值。并用这个值揭示中断发生前内核正在做什么。最后,采样数据积聚在”热点”上。

profile_tick()函数为代码监管器采集数据。这个函数在单处理器系统上是由do_timer_interrup()调用的,在多处理器系统上是由smp_local_timer_interrup()函数调用的。

为了激活代码监管器,在Linux内核启动时必须传递字符串参数”profile=N”,这里2N的次方表示要监管的代码段的大小,采集的数据可以从/proc/profile文件中读取。可以通过修改这个文件来重置计数器;在多处理器系统上,修改这个文件还可以改变抽样频率。不过,内核开发者并不直接访问/proc/profile文件,而是用readprofile系统命令。

Linux2.6内核还包含了另一个监管器,叫做oprofile.比如readprofileoprofile除了更灵活,更可定制外,还能用于发现内核代码,用户态应用程序及系统库中的热点。当使用ofrofile时,profile_tick()调用timer_notify()函数来收集这个新监管器所使用的数据。


检查非屏蔽中断监视器

在多处理器系统上,Linux为内核开发者还提供了另外一种功能;看门狗系统,这对于探测引起系统冻结的内核bug可能相当有用。为了激活这样的看门狗,必须在内核启动时传递nmi_watchdog参数。

看门狗基于本地和I/OAPIC一个巧妙的硬件特性;它们能在每个CPU上产生周期性的NMI中断,因为NMI中断是不能用汇编语言指令cli屏蔽的,所以,即使禁止中断,看门狗也能检测到死锁。

因而,一旦每个时钟节拍到来,所有的CPU,不管其正在做什么,都开始执行NMI中断处理程序;该中断处理程序又调用do_nmi()。这个函数获得CPU的逻辑号n,然后检查irq_stat数组第n项的apic_timer_irqs字段。如果该CPU字段工作正常,那么,第n项的值必定不同于在前一个NMI中断中读出的值。当CPU正常运行时,第n项的apic_timer_irq字段就会被本地时钟中断处理程序增加,如果计数器没有增加,说明本地时钟中断处理程序在整个时钟节拍期间根本就没有被执行,你可以想到,这可不是什么好事。

NMI中断处理程序检测到一个CPU冻结时,就会敲响所有的钟:它把引起恐慌的信息记录在系统日志文件中,转储该CPU寄存器的内容和内核栈的内容。最后杀死当前进程,这就为内核开发者提供了发现错误的机会。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值