威尼斯wns.9778官网活动_vnsc威尼斯城官网

热门关键词: 威尼斯wns.9778官网活动,vnsc威尼斯城官网
当前位置:威尼斯wns.9778官网活动 > 计算机教程 > 转:Linux内部的时钟处理机制全面剖析

转:Linux内部的时钟处理机制全面剖析

文章作者:计算机教程 上传时间:2019-05-11

int mod_timer (struct timer_list *timer, unsigned long expires);

在时钟处理这部分中,内核用到了所谓的"通知链( notification chain )"技术。所以在介绍时钟处理过程之前先来了解下"通知链"技术。

struct timer_list {

   struct list_head entry, /*定时器列表*/
   unsigned long expires, /*定时器到期时间*/
   void (*function) (unsigned long), /*定时器处理函数*/
   unsigned long data,/*作为参数被传入定时器处理函数*/
   struct timer_base_s *base,
   ...

};

3.5 软件时钟的应用

c.触发软中断处理函数

清单3-5 try_to_del_timer_sync 函数

int try_to_del_timer_sync(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = -1;
base = lock_timer_base(timer, &flags);
if (base->running_timer == timer)
goto out;
ret = 0;
if (timer_pending(timer)) {
detach_timer(timer, 1);
ret = 1;
}
out:
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}

2) 初始化定时器

1.  tick_init()

实例化 struct timer_list my_timer;

5.  设置软件时钟的到期时间

a.注册软中断处理函数

威尼斯wns.9778官网活动 1

软件意义上的定时器最终依赖硬件定时器来实现, 内核在时钟中断发生后检测各定时器是否到期 , 到期后的定时器处理函数将作为软中断在底半部执行 。实质上,时钟中断处理程序会 换起TIMER_SOFTIRQ软中断 ,运行当前处理器上到期的所有定时器。

·         初始化本 CPU 上的软件时钟相关的数据结构,参见3.2节

static void run_timer_softirq(struct softirq_action *h)
    -->__run_timers(base);
        -->遍历执行时间到达的timer_list中的定时器处理函数
在Linux设备驱动编程中,可以利用Linux内核中提供的一组函数和数据结构来完成定时触发工作或者完成某种周期性的事务。这组函数和数据结构使得驱动程序师在多数情况下不用关心具体的软件定时器究竟对应着怎样的内核和硬件行为。

函数 cascade 用于调整软件时钟(这个调整过程是指:将马上就要到期的软件时钟从其所在的链表中删除,重新计算到期时间的相对值(到期时间 - timer_jiffies ),然后根据该值重新插入到 base 中)。注意到在软件时钟处理过程中,每次都是从 tv1 中取出一个链表进行处理,而不是从 tv2~tv5 中取,所以对软件时钟就要进行必要的调整。

void add_timer (struct timer_list *timer);

这部分代码表明:如果 index 有0再到0时( index 是对 timer_jiffies 取模),说明时间已经过了256个 tick ,这时要把 tv2 中软件时钟转移到 tv1 中。如果 index 和第一个cascade 函数的返回值都从0再到到0时,说明时间已经过了256*64个 tick ,这时要把 tv3 中软件时钟转移到 tv1 或者 tv2 中。之后的调整过程依次类推。

d.调用软中断处理函数

其中 tv1 的类型为 struct tvec_root ,tv 2~ tv 5的类型为 struct tvec ,清单3-1显示它们的定义清单3-1 struct tvec_root 和 struct tvec 的定义

您可能感兴趣的文章:

函数 process_timeout 直接调用 wake_up_process 将进程唤醒。当内核重新调用该进程执行时,该进程继续执行 schedule_timeout 函数,执行流则从 schedule 函数中返回,之后调用 del_singleshot_timer_sync 函数将软件时钟卸载,然后函数 schedule_timeout 结束。函数 del_singleshot_timer_sync 是实际上就是函数 del_timer_sync (参见3.3.2节),如清单3-14

总结起来还是软中断的流程

1.  检测该软件时钟是否处在 pending 状态(在 base 中,准备运行),如果不是则直接函数返回

void init_timer (struct timer_list *timer);

TIMER_INITIALIZER (_function, _expires, _data)

DEFINE_TIMER (_name, _function, _expires, _data)

setup_timer ();

o    内核利用其获取系统当前时间和日期

/*/linux/kernel.timer.c*/
void __init init_timers(void)
  -->open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);

3.  再次检测软件时钟是否处于 pending 状态(该软件时钟可能被卸载了),不是则释放锁然后函数返回

1) 一个timer_list 结构体的实例对应一个定时器,其定义如下:

·         再次获得锁

b.添加timer_list到某个链表

2.  如果该软件时钟处在 pending 状态(在 base 中,准备执行),则卸载该软件时钟

void add_timer (struct timer_list *timer);

接下来的几节会详细介绍 Linux2.6.25 是怎么实现软件时钟的。

4) 删除定时器

在 TCP 协议具体的一次数据包发送中,函数 tcp_write_xmit 用来将数据包从 TCP 层发送到网络层,如清单3-16。

void irq_exit(void)
  -->tick_nohz_stop_sched_tick();
    -->raise_softirq_irqoff(TIMER_SOFTIRQ);

4.  如果 base 和 new_base 不一样,也就是说软件时钟发生了迁移(从一个 CPU 中移到了另一个 CPU 上),那么如果该软件时钟的处理函数当前没有在迁移之前的那个 CPU 上运行,则先将软件时钟的 base 设置为 NULL ,然后再将该软件时钟的 base 设置为 new_base 。否则,跳到5。

3) 增加定时器

初始化硬件时钟这个过程主要包括以下两个过程(参见 hpet_enable 的实现):

int del_timer (struct timer_list *timer);

·         struct tvec_base :用于组织、管理软件时钟的结构。在 SMP 系统中,每个 CPU 有一个。具体的成员以及含义参见表3-2。

5) 修改定时器的expire

·         Programmable Interval Timer ( PIT ):

3.4 自我激活

2.  如果 jiffies 大于等于 timer_jiffies (当前正要处理的软件时钟的到期时间,说明可能有软件时钟到期了),就一直运行3~7,否则跳转至8

Linux 内核维护了两个链表,分别存储了系统中所有时钟源的信息和时钟事件设备的信息。这两个链表的表头在内核中分别是 clocksource_list 和 clockevent_devices 。图2-1显示了这两个链表。

3.  取出相应的软件时钟链表

·         struct clocksource :对硬件设备的抽象,描述时钟源信息

由定义可知:函数 timer_event_interrupt 为时钟中断处理函数,其定义如清单2-5

这部分内容不是本文的重点,这里仅仅简单介绍几种,更多内容参见参考文献:

2.  更新 xtimer 和当前时钟源信息等

函数 hpet_enable 检测系统是否可以使用 hpet 时钟,如果可以则初始化 hpet 时钟。否则初始化 pit 时钟。最后设置硬件时钟发生时的处理函数(参见2.4节)。

需要注意的是在初始化时钟事件设备时,全局变量 global_clock_event 被赋予了相应的值。该变量保存着系统中当前正在使用的时钟事件设备(保存了系统当前使用的硬件时钟中断发生时,要执行的中断处理函数的指针)。

这里所说的硬件时钟处理特指的是硬件计时器时钟中断的处理过程。

包括以下过程:

·         Real Timer Clock ( RTC ):

清单3-8 run_timer_softirq函数

static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = __get_cpu_var(tvec_bases);

hrtimer_run_pending();
if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base);
}

软件时钟的处理是在时钟的软中断中进行的。

这里有必要详细说明一下软件时钟如何被添加到软件时钟的 base 中的(添加到本 CPU base 的 tv1~tv5 里面),因为这是软件时钟处理的基础。来看函数 internal_add_timer 函数的实现,如清单3-3

该函数检测当前运行的软件时钟是不是该软件时钟,如果是,则函数返回-1,表明目前不能删除该软件时钟;如果不是检测该软件时钟是否处于 pending 状态,如果不是,则函数返回0,表明软件时钟已经被卸载,如果处于 pending 状态再把软件时钟卸载,函数返回1,表明成功卸载该软件时钟。

·         struct blocking_notifier_head :可阻塞通知链的链头

函数 schedule_timeout 定义了一个软件时钟变量 timer ,在计算到期时间后初始化这个软件时钟:设置软件时钟当时间到期时的处理函数为 process_timeout ,参数为当前进程描述符,设置软件时钟的到期时间为 expire 。之后调用 schedule() 函数。此时当前进程睡眠,交出执行权,内核调用其它进程运行。但内核在每一个时钟中断处理结束后都要检测这个软件时钟是否到期。如果到期,将调用 process_timeout 函数,参数为睡眠的那个进程描述符。 process_timeout 函数的代码如清单3-13。

内核初始化部分( start_kernel 函数)和时钟相关的过程主要有以下几个:

3.  hrtimers_init()

2.  init_timers()

清单3-9中调整软件时钟的代码如下:

int index = base->timer_jiffies & TVR_MASK;
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));

在 Linux 操作系统中,很多活动都和时间有关,例如:进程调度和网络处理等等。所以说,了解 Linux 操作系统中的时钟处理机制有助于更好地了解 Linux 操作系统的运作方式。本文分析了 Linux 2.6.25 内核的时钟处理机制,首先介绍了在计算机系统中的一些硬件计时器,然后重点介绍了 Linux 操作系统中的硬件时钟和软件时钟的处理过程以及软件时钟的应用。最后对全文进行了总结。

1.被通知者:对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。

清单3-11 mod_timer 函数

int mod_timer(struct timer_list *timer, unsigned long expires)
{
……
if (timer->expires == expires && timer_pending(timer))
return 1;

return __mod_timer(timer, expires);
}

4.  如果还是 pending 状态,则将其卸载,之后释放锁,函数返回

·         struct clock_event_device :时钟的事件信息,包括当硬件时钟中断发生时要执行那些操作(实际上保存了相应函数的指针)。本文将该结构称作为"时钟事件设备"。

2.2.1 数据结构:

4.SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体

函数 tick_init() 很简单,调用 clockevents_register_notifier 函数向 clockevents_chain 通知链注册元素: tick_notifier。这个元素的回调函数指明了当时钟事件设备信息发生变化(例如新加入一个时钟事件设备等等)时,应该执行的操作,该回调函数为 tick_notify (参见2.4节)。

通知链有四种类型,

·         struct atomic_notifier_head :原子通知链的链头

   

3.  根据 tick 计算 avenrun 负载

3.3.3 软件时钟调整过程

3.3.2 删除软件时钟

链头中保存着指向元素链表的指针。通知链元素结构则保存着回调函数的类型以及优先级,参见 notifier.h 文件。

·         计算该软件时钟的到期时间和 timer_jiffies (当前正在处理的软件时钟的到期时间)的差值,作为索引保存到 idx 变量中。

接下来,再来看看函数 del_timer_sync 定义,如清单3-6

单2-1 init_timers 函数
void __init init_timers(void)
{
int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE, 
(void *)(long)smp_processor_id());
……
register_cpu_notifier(&timers_nb);
open_softirq(TIMER_SOFTIRQ,run_timer_softirq, NULL);
}

2.1 数据结构

·         仅仅激活一次

由于以后的讲解经常要提到每个 CPU 相关的 struct tvec_base 变量,所以为了方便,称保存软件时钟的 struct tvec_base 变量为该软件时钟的 base ,或称 CPU 的 base 。

威尼斯wns.9778官网活动,4.  time_init()

o    [ 2^26 ,  2^32-1 ),则将要添加到 tv5 中,但实际上最大值为 0xffffffffUL

注:本文中所有代码均来自于Linux2.6.25 源代码

实现软件时钟原理也比较简单:每一次硬件时钟中断到达时,内核更新的 jiffies ,然后将其和软件时钟的到期时间进行比较。如果 jiffies 等于或者大于软件时钟的到期时间,内核就执行软件时钟指定的函数。

3.1 相关数据结构

如果在 SMP 系统中,则需使用 del_timer_sync 函数来删除软件时钟。在讲解 del_timer_sync 函数之前,先来看下 try_to_del_timer_sync 函数的实现(该函数被del_timer_sync 函数使用),其代码如清单3-5

清单3-10 cascade 函数

static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
struct timer_list *timer, *tmp;
struct list_head tv_list;
list_replace_init(tv->vec index, &tv_list);
list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
……
internal_add_timer(base, timer);
}
return index;
}

int index = base->timer_jiffies & TVR_MASK;

对于 TCP 协议而言,如果某次发送完数据包后,并超过一定的时间间隔还没有收到这次发送数据包的 ACK 时, TCP 协议规定要重新发送这个数据包。

清单3-6 del_timer_sync 函数

int del_timer_sync(struct timer_list *timer)
{
for (;;) {
int ret = try_to_del_timer_sync(timer);
if (ret >= 0)
return ret;
cpu_relax();
}
}

o    [0,  2^8-1 ]或者( -无穷, 0)(该软件时钟已经到期),则将要添加到 tv1 中

1.  获得 base 的同步锁

·         向 cpu_chain 通知链注册元素 timers_nb ,该元素的回调函数用于初始化指定 CPU 上的软件时钟相关的数据结构

威尼斯wns.9778官网活动 2

·         计算所要加入的具体位置(哪个链表中,即 tv1~tv5 的哪个子链表,参考图3-1)

3.3.2 处理过程

·         struct timer_list :软件时钟,记录了软件时钟的到期时间以及到期后要执行的操作。具体的成员以及含义见表3-1。

3.3 添加或删除软件时钟

1.原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞

o    内核使用的产生时钟中断的设备,产生的时钟中断依赖于硬件的体系结构,慢的为 10 ms 一次,快的为 1 ms 一次

在函数 inet_csk_init_xmit_timers 中,变量 icsk 就是前面提到的面向连接的套接字,其成员 icsk_retransmit_timer 则为实现超时重传的软件时钟。该函数调用 setup_timer函数将函数 tcp_write_timer (参考函数 tcp_init_xmit_timers )设置为软件时钟 icsk->icsk_retransmit_timer 当时间到期后的处理函数。初始化的时候并没有设置该软件时钟的到期时间。

·         局部处理(每个 CPU 都要运行):

·         释放锁,执行软件时钟处理程序

本节主要通过介绍进程的定时睡眠( schedule_timeout 函数)和网络超时重传来说明软件时钟的应用。

软中断的一个重要的处理时机是在每个硬件中断处理完成后(参见 irq_exit 函数),且由2.4节的内容可知:在硬件时钟中断处理中,会唤醒时钟的软中断,所以每次硬件时钟中断处理函数执行完成后都要进行时钟的软中断处理。和时钟相关的软中断是 TIMER_SOFTIRQ ,其处理函数为 run_timer_softirq ,该函数用来处理所有的软件时钟。这部分初始化代码在函数init_timers 中进行,如清单3-7

清单3-2 __mod_timer 函数

这里所说"软件时钟"指的是软件定时器(Software Timers),是一个软件上的概念,是建立在硬件时钟基础之上的。它记录了未来某一时刻要执行的操作(函数),并使得当这一时刻真正到来时,这些操作(函数)能够被按时执行。举个例子说明:它就像生活中的闹铃,给闹铃设定振铃时间(未来的某一时间)后,当时间(相当于硬件时钟)更新到这个振铃时间后,闹铃就会振铃。这个振铃时间好比软件时钟的到期时间,振铃这个动作好比软件时钟到期后要执行的函数,而闹铃时间更新好比硬件时钟的更新。

多次激活的实现机制就是要在软件时钟处理函数中重新设置软件时钟的到期时间为将来的一个时间,这个过程通过调用 mod_timer 函数来实现。该函数的实现如清单3-11

清单3-9 __run_timers函数

static inline void __run_timers(struct tvec_base *base)
{
……
spin_lock_irq(&base->lock);
while (time_after_eq(jiffies, base->timer_jiffies)) {
……
int index = base->timer_jiffies & TVR_MASK;
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
base->timer_jiffies;
list_replace_init(base->tv1.vec index, &work_list);
while (!list_empty(head)) {
……
timer = list_first_entry(head, struct timer_list,entry);
fn = timer->function;
data = timer->data;
……
set_running_timer(base, timer);
detach_timer(timer, 1);
spin_unlock_irq(&base->lock);
{
int preempt_count = preempt_count();
fn(data);
……
}
spin_lock_irq(&base->lock);
}
}
set_running_timer(base, NULL);
spin_unlock_irq(&base->lock);
}

2.  局部处理:局部于本地 CPU 的处理

·         High Precision Event Timer ( HPET ):

函数 init_tsc_clocksource 初始化 tsc 时钟源。choose_time_init 实际是函数 hpet_time_init ,其代码清单2-3

2.可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞

3.3.1 添加软件时钟

代码解释:

◆2、硬件时钟处理

当函数 tcp_event_new_data_sent 结束之后,处理超时的软件时钟已经设置好了。内核会在每一次时钟中断处理完成后检测该软件时钟是否到期。如果网络真的超时,没有 ACK 返回,那么当该软件时钟到期后内核就会执行函数 tcp_write_timer 。函数 tcp_write_timer 将进行数据包的重新发送,并重新设置超时重传软件时钟的到期时间。

接下来看一下函数 __run_timers 都作了些什么,如清单3-9

 

清单2-6 tick_notify 函数

static int tick_notify(struct notifier_block *nb, unsigned long reason, void *dev)
{
switch (reason) {
case CLOCK_EVT_NOTIFY_ADD:
return tick_check_new_device(dev);
……
return NOTIFY_OK;
}

为了说明这个问题,不妨假设系统中使用的是 hpet 时钟。由2.3.3节可知 global_clock_event 指向 hpet 时钟事件设备( hpet_clockevent )。查看 hpet_enable 函数的代码并没有发现有对 event_handler 成员的赋值。所以继续查看时钟事件设备加入事件的处理函数 tick_notify ,该函数记录了当时钟事件设备发生变化(例如,新时钟事件设备的加入)时,执行那些操作(参见2.3.1节),代码如清单2-6

5.  profile_tick 函数调用

2.  释放锁

在 Linux 内核中,各个子系统之间有很强的相互关系,一些被一个子系统生成或者被探测到的事件,很可能是另一个或者多个子系统感兴趣的,也就是说这个事件的获取者必须能够通知所有对该事件感兴趣的子系统,并且还需要这种通知机制具有一定的通用性。基于这些, Linux 内核引入了"通知链"技术。

清单3-14 函数del_singleshot_timer_sync
#define del_singleshot_timer_sync(t) del_timer_sync(t)

单2-4 变量irq0定义

static struct irqaction irq0 = {
.handler = timer_event_interrupt,
.flags  = IRQF_DISABLED | IRQF_IRQPOLL | IRQF_NOBALANCING,
.mask  = CPU_MASK_NONE,
.name  = "timer"
};

注: hrtimer_run_pending() 函数是高精度时钟的处理。本文暂没有涉及高精度时钟相关的内容。

1.  取得软件时钟所在 base 上的同步锁( struct tvec_base 变量中的自旋锁),并返回该软件时钟的 base ,保存在 base 变量中

软件时钟的处理是在处理软中断时触发的,而软中断的处理又会紧接着硬件中断处理结束而进行,并且系统会周期地产生时钟中断(硬件中断),这样,软件时钟的处理至少会在系统每一次时钟中断处理完成后触发(如果软件时钟的到期时间大于系统当前的 jiffies ,表明时间未到期,则不会调用保存在软件时钟中的函数,但此时的确提供了处理软件时钟的时机)。从这点上看,软件时钟会有较快的相应——一旦时间到期,保存在软件时钟中的函数会将快地被调用(在时钟软中断中被调用,参见3.3.2节)。所以内核中凡是需要隔一段时间间隔后作指定操作的过程都通过软件时钟完成。例如大部分设备驱动程序使用软件时钟探测异常条件、软盘驱动程序利用软件时钟关闭有一段时间没有被访问软盘的设备马达、进程的定时睡眠( schedule_timeout 函数)和网络超时重传等等。

注意该函数中加粗的函数,其中 tcp_transmit_skb 函数是真正将数据包由 TCP 层发送到网络层中的函数。数据发送后,将调用函数 tcp_event_new_data_sent ,而后者又会调用函数inet_csk_reset_xmit_timer 来设置超时软件时钟的到期时间。

清单3-16 tcp_write_xmit 函数

static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
……
if (unlikely(tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC)))
break;
tcp_event_new_data_sent(sk, skb);
……
return !tp->packets_out && tcp_send_head(sk);
}

3.2 数据结构之间的关系

o    比起 PIT,TSC 可以提供更精确的时间测量

本文由威尼斯wns.9778官网活动发布于计算机教程,转载请注明出处:转:Linux内部的时钟处理机制全面剖析

关键词: