Linux's Heartbeat

系统的心跳无疑就是“时钟”,心跳稳定,系统才稳定,有心脏病的系统可要不得。


app最常见使用的用法:

 1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/time.h>
4 #include<signal.h>
5
6 int func(void)
7 {
8 printf("hello timer\n");
9 alarm(2);
10 return 0;
11 }
12
13
14 int main(void)
15 {
16 signal(SIGALRM, func);
17 alarm(2);
18 while(1)
19 ;
20 return 0;
21 }



app更精确的定时方式: 

  1 #include<stdio.h>
2 #include<signal.h>
3 #include<sys/time.h>
4
5 int limit = 10;
6
7 int timeout_info(int signo)
8 {
9 if (limit == 0)
10 {
11 printf("time limit reached!\n");
12 return 0;
13 }
14
15 printf("only %d sec left...\n", limit--);
16
17 return 0;
18 }
19
20
21
22 int init_sigaction(void)
23 {
24 struct sigaction act;
25
26 /*
27 * struct sigaction {
28 * __sighandler_t sa_handler;
29 * unsigned long sa_flags;
30 * __sigrestore_t sa_restorer;
31 * sigset_t sa_mask;
32 * };
33 */
34
35
36 /*初始化各项*/
37 act.sa_handler = timeout_info;
38 act.sa_flags = 0;
39 sigemptyset(&act.sa_mask);
40
41 //sigaction(SIGPREAL, &act, NULL);
42 sigaction(SIGVTALRM, &act, NULL);
43 //sigaction(SIGPROF, &act, NULL);
44
45 return 0;
46 }
47
48
49
50
51
52 int init_timer(void)
53 {
54 struct itimerval val;
55
56
57 /*
58 * struct itimerval
59 * {
60 * struct timeval it_interval; //时间间隔
61 * struct timeval it_value; //当前时间计数
62 * };
63 */
64
65
66 /*距火箭发射(定时器工作)还有几秒*/
67 val.it_value.tv_sec = 2;
68 val.it_value.tv_usec = 0;
69
70
71
72 /*火箭发射!(定时器工作)*/
73 val.it_interval.tv_sec = 1;
74 val.it_interval.tv_usec = 0;
75
76
77 /*linux内置的三个定时器的细微差别*/
78 //setitimer(ITIMER_REAL, &val, NULL); //SIGALRM信号
79 setitimer(ITIMER_VIRTUAL, &val, NULL); //SIGPVTPARLM信号
80 //setitimer(ITIMER_PROF, &val, NULL); //SIGPROF信号
81
82 /*定时发送或者采用 (while+sleep)+sigqueue 方式*/
83
84
85 return 0;
86 }
87
88
89
90 int main(void)
91 {
92 init_sigaction(); //设置一个定时信号接收器
93 init_timer(); //设置一个定时器发送定时信号
94
95 printf("10 sec...\n");
96
97 while(1)
98 ;
99
100 return 0;
101 }



再来个简单的内核定时测试程序:

 1 #include <linux/module.h>
2 #include <linux/init.h>
3 #include <linux/delay.h>
4 #include <linux/timer.h>
5
6 struct timer_list my_timer;
7
8 void timer_func(unsigned long args)
9 {
10 printk("boom [%d]\n", args);
11
12 /* 定时任务执行时可以顺便设置下一次定时
13 * my_timer.expires = jiffies + 2*HZ;
14 * add_timer(&my_timer);
15 */
16 }
17 int init_test(void)
18 {
19 my_timer.expires = jiffies + 5*HZ;
20 my_timer.function = timer_func;
21 my_timer.data = 99;
22
23 init_timer(&my_timer); //初始化定时器
24
25 printk("start\n");
26 add_timer(&my_timer); //激活定时器,也就是加入上述相应的定时链表中
27
28 return 0;
29 }
30
31
32 void exit_test(void)
33 {
34 del_timer(&my_timer);
35 printk("bye\n");
36 }
37
38
39 module_init(init_test);
40 module_exit(exit_test);
41
42 MODULE_LICENSE("GPL");
43
44 MODULE_AUTHOR("Jesse");
45 MODULE_DESCRIPTION("this is test module");
46 MODULE_VERSION("v0.1");

 

linux非实时系统,实时性有待提高,低负荷、毫秒级以上的操作还是比较理想。先来看看这个几乎是个驱动就会涉及到的 异常常见的 结构体 timer_list

struct timer_list {
    struct list_head entry;
    unsigned long expires;
  
    void (*function)(unsigned long);
    unsigned long data;

    struct tvec_base *base;   //系统内有许多timer_list,此指针指向它们共同的管理者
#ifdef CONFIG_TIMER_STATS
    void *start_site;
    char start_comm[16];
    int start_pid;
#endif  
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map; 
#endif  
};

 

来看看这个struct tvec_base

struct tvec_base {
spinlock_t
lock;
struct timer_list *running_timer;
unsigned
long timer_jiffies;
unsigned long next_timer;
/*级联表*/

struct tvec_root tv1;
struct tvec tv2;
struct tvec tv3;
struct tvec tv4;
struct tvec tv5;
} ____cacheline_aligned;

 

关键就在于对struct tvec的理解。至于tvec_root,其实和tvec一摸一样。

struct tvec {
    struct list_head vec[TVN_SIZE];
};

struct tvec_root {
    struct list_head vec[TVR_SIZE];
};

 

   

有了上述的了解,开始看一些关键函数:

cascade(base, &base->tv2, (base->timer_jiffies >> 8) & 63);


static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
/* cascade all the timers from tv up one level */
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) {
BUG_ON(tbase_get_base(timer->base) != base);
internal_add_timer(base, timer);  //-->
}

return index;
}

 

internal_add_timer()

static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
{
    unsigned long expires = timer->expires;
    unsigned long idx = expires - base->timer_jiffies;
    struct list_head *vec;

    if (idx < TVR_SIZE) {	//idx < 256
        int i = expires & TVR_MASK;
        vec = base->tv1.vec + i; 
    } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
        int i = (expires >> TVR_BITS) & TVN_MASK;
        vec = base->tv2.vec + i; 
    } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
        int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
        vec = base->tv3.vec + i; 
    } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
        int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
        vec = base->tv4.vec + i; 

    } else if ((signed long) idx < 0) { 
        /*   
         * Can happen if you add a timer with expires == jiffies,
         * or you set a timer to go off in the past
         */
        vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
    } else {	
        int i;
        /* If the timeout is larger than 0xffffffff on 64-bit
         * architectures then we use the maximum timeout:
         */
        if (idx > 0xffffffffUL) {
            idx = 0xffffffffUL;
            expires = idx + base->timer_jiffies;
        }    
        i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
        vec = base->tv5.vec + i;
    }
    /*
     * Timers are FIFO:
     */
    list_add_tail(&timer->entry, vec);	
}

 

 

从一堆的if,if else中,基本可以看出个这样的眉目:

内核将不同时长的定时分了类,从第一个if可以看出,8个字节的为一类,也就是256种状况(0~255jiffies)。

第二类多了6位,也就是64种。以此类推。

至于那个idx > 0xffffffffUL,源于内核更多考虑的是32位构架,至于64位能表示的时长,一般人儿在一般情况下,几乎是用不到di。

最后便是,内核搞了512个链表来实现一个定时器(256个短期链表+4组64个的长期链表),占用了4kb的空间,大么?

 

 上个图比较直观:

 

这便是定时器的底层实现。多个定时器在同一条链表上,需要合理的排序,使查找更具效率。
      从cascade可以看出,目前使用时间轮方式(个人更倾向叫hash);
      pjlib 中的定时器在内部使用数组的方式实现最小堆也是一种选择。

 

定时器又如如何计算剩余时间的呢?可以查看udelay的实现:

#define udelay(n)                           \
(__builtin_constant_p(n) ? \
((n) > (MAX_UDELAY_MS * 1000) ? __bad_udelay() : \
__const_udelay((n) * ((2199023U*HZ)>>11))) : \
__udelay(n))

#endif /* defined(_ARM_DELAY_H) */

 __udelay()与平台相关,进入arch/arm

<arch/arm/lib/delay.S>


ENTRY(__udelay)
ldr r2, .LC1
mul r0, r2, r0
ENTRY(__const_udelay) @ 0 <= r0 <= 0x7fffff06
mov r1, #-1
ldr r2, .LC0
ldr r2, [r2] @ max = 0x01ffffff
add r0, r0, r1, lsr #32-14
mov r0, r0, lsr #14 @ max = 0x0001ffff
add r2, r2, r1, lsr #32-10
mov r2, r2, lsr #10 @ max = 0x00007fff
mul r0, r2, r0 @ max = 2^32-1
add r0, r0, r1, lsr #32-6
movs r0, r0, lsr #6
moveq pc, lr

/*
* loops = r0 * HZ * loops_per_jiffy / 1000000
*
* Oh, if only we had a cycle counter...
*/

@ Delay routine

ENTRY(__delay)
subs r0, r0, #1
#if 0
movls pc, lr
subs r0, r0, #1
movls pc, lr
subs r0, r0, #1
movls pc, lr
subs r0, r0, #1
movls pc, lr
subs r0, r0, #1
movls pc, lr
subs r0, r0, #1
movls pc, lr
subs r0, r0, #1
movls pc, lr
subs r0, r0, #1
#endif
bhi __delay
mov pc, lr
ENDPROC(__udelay)

看汇编耗查克拉,以下是等价的c语言形式:

/*
* loops = usecs * HZ * loops_per_jiffy / 1000000
*
* Oh, if only we had a cycle counter...
*/
void __delay(unsigned long loops)
{
asm volatile(
"1: subs %0, %0, #1 \n"
" bhi 1b \n"
: /* No output */
: "r" (loops)
);
}
EXPORT_SYMBOL(__delay);

/*
* 0 <= xloops <= 0x7fffff06
* loops_per_jiffy <= 0x01ffffff (max. 3355 bogomips)
*/
void __const_udelay(unsigned long xloops)
{
unsigned long mask = ULONG_MAX;
unsigned long lpj = loops_per_jiffy;
unsigned long loops;

xloops += mask >> (32 - 14);
xloops >>= 14; /* max = 0x01ffffff */

lpj += mask >> (32 - 10);
lpj >>= 10; /* max = 0x0001ffff */

loops = lpj * xloops; /* max = 0x00007fff */
loops += mask >> (32 - 6);
loops >>= 6; /* max = 2^32-1 */

if (likely(loops))
__delay(loops);
}
EXPORT_SYMBOL(__const_udelay);

/*
* usecs <= 2000
* HZ <= 1000
*/
void __udelay(unsigned long usecs)
{
__const_udelay(usecs * ((2199023*HZ)>>11));
}

 
最后,2.6内核由于增强了实时性,对于app也引入了POSIX 1003.1b标准的定时器。相应的在app层增加了一些POSIX接口,更好的支持多线程和实时应用程序。

总之,一堆小闹钟挂在一簇簇的条儿上,过一个time irq,调整这些条儿,同时处理些其他的系统操作,比如增加jiffies,软中断处理等。Linux的arm平台时钟精度应用一般限于微秒级,或者增加外部时钟,达到更加精确的目的,不过,目前似乎没什么必要。

 

End.

posted @ 2011-06-26 15:14  郝壹贰叁  阅读(1496)  评论(0编辑  收藏  举报