首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Linux 时钟处理机制(3)软件时钟处理-4

Linux 时钟处理机制(3)软件时钟处理-4

3.4 自我激活软件时钟可分为两种类型:
  • 仅仅激活一次
  • 激活多次或者周期性激活
多次激活的实现机制就是要在软件时钟处理函数中重新设置软件时钟的到期时间为将来的一个时间,这个过程通过调用 mod_timer 函数来实现。该函数的实现如清单3-11
清单3-11 mod_timer 函数
1
2
3
4
5
6
7
8
int mod_timer(struct timer_list *timer, unsigned long expires)
{
    ……
    if (timer->expires == expires && timer_pending(timer))
        return 1;

    return __mod_timer(timer, expires);
}




从代码中可以看出,该函数实际上调用 __mod_timer 函数(参见3.3.1节)来调整软件时钟的到期时间。
3.5 软件时钟的应用软件时钟的处理是在处理软中断时触发的,而软中断的处理又会紧接着硬件中断处理结束而进行,并且系统会周期地产生时钟中断(硬件中断),这样,软件时钟的处理至少会在系统每一次时钟中断处理完成后触发(如果软件时钟的到期时间大于系统当前的 jiffies ,表明时间未到期,则不会调用保存在软件时钟中的函数,但此时的确提供了处理软件时钟的时机)。从这点上看,软件时钟会有较快的相应——一旦时间到期,保存在软件时钟中的函数会将快地被调用(在时钟软中断中被调用,参见3.3.2节)。所以内核中凡是需要隔一段时间间隔后作指定操作的过程都通过软件时钟完成。例如大部分设备驱动程序使用软件时钟探测异常条件、软盘驱动程序利用软件时钟关闭有一段时间没有被访问软盘的设备马达、进程的定时睡眠( schedule_timeout 函数)和网络超时重传等等。
本节主要通过介绍进程的定时睡眠( schedule_timeout 函数)和网络超时重传来说明软件时钟的应用。
3.5.1 进程的定时睡眠函数 schedule_timeout 的代码如清单3-12
清单3-12 函数 schedule_timeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
signed long __sched schedule_timeout(signed long timeout)
{
    struct timer_list timer;
    unsigned long expire;

    ……
    expire = timeout + jiffies;

    setup_timer(&timer, process_timeout, (unsigned long)current);
    __mod_timer(&timer, expire);
    schedule();
    del_singleshot_timer_sync(&timer);

    timeout = expire - jiffies;

out:
    return timeout < 0 ? 0 : timeout;
}




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




函数 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
清单3-14 函数del_singleshot_timer_sync
1
#define del_singleshot_timer_sync(t) del_timer_sync(t)




以上就是进程定时睡眠的实现过程。接下来介绍的是软件时钟在网络超时重传上的应用。
3.5.2 网路超时重传对于 TCP 协议而言,如果某次发送完数据包后,并超过一定的时间间隔还没有收到这次发送数据包的 ACK 时, TCP 协议规定要重新发送这个数据包。
在 Linux2.6.25 的内核中,这种数据的重新发送使用软件时钟来完成。这个软件时钟保存在面向连接的套接字(对应内核中 inet_connection_sock 结构)中。对这个域的初始在函数 tcp_init_xmit_timers 中,如清单3-15
清单3-15 函数 tcp_init_xmit_timers 、函数 inet_csk_init_xmit_timers 和函数 setup_timer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void tcp_init_xmit_timers(struct sock *sk)
{
    inet_csk_init_xmit_timers(sk,
                                   &tcp_write_timer, &tcp_delack_timer,
                                   &tcp_keepalive_timer);
}

void inet_csk_init_xmit_timers(struct sock *sk,
                                   void (*retransmit_handler)(unsigned long),
                                   void (*delack_handler)(unsigned long),
                                   void (*keepalive_handler)(unsigned long))
{
    struct inet_connection_sock *icsk = inet_csk(sk);

    setup_timer(&icsk->icsk_retransmit_timer, retransmit_handler,
                    (unsigned long)sk);
    ……
}

static inline void setup_timer(struct timer_list * timer,
                                    void (*function)(unsigned long),
                                    unsigned long data)
{
    timer->function = function;
    timer->data = data;
    init_timer(timer);
}




在函数 inet_csk_init_xmit_timers 中,变量 icsk 就是前面提到的面向连接的套接字,其成员 icsk_retransmit_timer 则为实现超时重传的软件时钟。该函数调用 setup_timer 函数将函数 tcp_write_timer (参考函数 tcp_init_xmit_timers )设置为软件时钟 icsk->icsk_retransmit_timer 当时间到期后的处理函数。初始化的时候并没有设置该软件时钟的到期时间。
在 TCP 协议具体的一次数据包发送中,函数 tcp_write_xmit 用来将数据包从 TCP 层发送到网络层,如清单3-16。
清单3-16 tcp_write_xmit 函数
1
2
3
4
5
6
7
8
9
10
11
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);
}




注意该函数中加粗的函数,其中 tcp_transmit_skb 函数是真正将数据包由 TCP 层发送到网络层中的函数。数据发送后,将调用函数 tcp_event_new_data_sent ,而后者又会调用函数 inet_csk_reset_xmit_timer 来设置超时软件时钟的到期时间。
当函数 tcp_event_new_data_sent 结束之后,处理超时的软件时钟已经设置好了。内核会在每一次时钟中断处理完成后检测该软件时钟是否到期。如果网络真的超时,没有 ACK 返回,那么当该软件时钟到期后内核就会执行函数 tcp_write_timer 。函数 tcp_write_timer 将进行数据包的重新发送,并重新设置超时重传软件时钟的到期时间。
返回列表