linux kernel时钟就是系统定时器以某种频率自行触发,当时钟中断发生时,内核就通过一种特殊的中断处理程序对其进行处理。
利用时钟中断做的事情:
- 更新系统运行时间 update_wall_time(ticks)
- 更新实际时间
- 在SMP系统上,均衡调度各处理器上的运行队列
- 检查自己时间片是否用尽,耗尽的花,就是用schedule()调度
- 更新资源消耗负载,比如top右上方统计值
在include/asm-generic/param.h 规定了节拍值。
比如HZ定义的是1000Hz 也就意味着1/1000 s中断一次,除此之外还有USER_HZ这个是在用户空间下使用的,二者相同。
不同的HZ有不同的优缺点,较高的HZ可以对poll()select()超时精度的提高,会对系统性能提高带来极大好处。较高的HZ=1000可以使得一个正在运行的程序即使只有2ms时间片,也可以尽快调度。但是HZ=100的话,程序只有在10ms后才可以调度。
jiffies定义在include/linux/jiffies.h中
extern unsigned long volatile jiffies
在32位机上市32位,64位机是64位。有时候我们不care高位,所以我们使用强转。C语言限定符volatile表示jiffies是一个易该变的变量,因此编译器将使对该变量的访问从不通过CPU内部cache来进行。
* The 64-bit value is not atomic - you MUST NOT read it * without sampling the sequence number in jiffies_lock. * get_jiffies_64() will do this for you as appropriate. * extern u64 __jiffy_data jiffies_64; extern unsigned long volatile __jiffy_data jiffies; #if (BITS_PER_LONG < 64) u64 get_jiffies_64(void); #else static inline u64 get_jiffies_64(void) { return (u64)jiffies; } #endif
要记住,这个数字只要kernel初始化后,就开始一直自增。所以很有可能溢出,在<linux/jiffies.h>中
定义了比较时间的宏,防止了jiffies值得溢出。
全局变量xtime:它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对秒数值。结构timeval是Linux内核表示时间的一种格式(Linux内核对时间的表示有多种格式,每种格式都有不同的时间精度),其时间精度是微秒。
#ifndef _STRUCT_TIMESPEC #define _STRUCT_TIMESPEC struct timespec { __kernel_time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; #endif struct timeval { __kernel_time_t tv_sec; /* seconds */ __kernel_suseconds_t tv_usec; /* microseconds */ }; struct timezone { int tz_minuteswest; /* minutes west of Greenwich */ int tz_dsttime; /* type of dst correction */ };
时钟中断服务程序主要是调用do_timer(),首先进行jiffies_64++,然后调用update_process_times();update_times()
对于实际时间,更新xtime我们要使用seqlock锁。
seqlock的实现思路是,用一个递增的整型数表示sequence。写操作进入临界区时,sequence++;退出临界区时,sequence再++。写操作还需要获得一个锁(比如mutex),这个锁仅用于写写互斥,以保证同一时间最多只有一个正在进行的写操作。
当sequence为奇数时,表示有写操作正在进行,这时读操作要进入临界区需要等待,直到sequence变为偶数。读操作进入临界区时,需要记录下当前sequence的值,等它退出临界区的时候用记录的sequence与当前sequence做比较,不相等则表示在读操作进入临界区期间发生了写操作,这时候读操作读到的东西是无效的,需要返回重试。
seqlock写写是必须要互斥的。但是seqlock的应用场景本身就是读多写少的情况,写冲突的概率是很低的。所以这里的写写互斥基本上不会有什么性能损失。
而读写操作是不需要互斥的。seqlock的应用场景是写操作优先于读操作,
write_seqlock(&xtime_lock); .............. write_sequnlock(&xtime_lock); //读 do { seq = read_seqbegin(&seq_lock); // 进入临界区 do_something(); } while (read_seqretry(&seq_lock, seq)); // 尝试退出临界区,存在冲突则重试
从内核空间取得时间的函数式sys_gettimeofday().从用户空间获取时间是gettimeofday().
定时器也是内核编程中的比较重要的一个项目,timer_list的定义在include/linux/timer.h
struct timer_list { struct list_head entry; unsigned long expires; struct tvec_base *base; void (*function)(unsigned long); unsigned long data; int slack; #ifdef CONFIG_TIMER_STATS int start_pid; void *start_site; char start_comm[16]; #endif #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
使用timer比较简单
- struct timer_list my_timer;
- init_timer(&my_timer);
- 填充my_timer data,expires = jiffies+delay,function
- 定义自己的My_timer_function()
- add_timer(&my_timer);mod_timer();del_timer()
要区分del_timer()在SMP环境下会出现在别的CPU上运行。用del_timer_sync()解决,但不能再中断上下文中运行。
短延迟使用linux/delay.h中的void udelay()与void mdelay().
如果长延迟,让任务在我们规定的时刻wakeup就使用schedule_timeout()
参考:http://blog.csdn.net/axman/article/details/8484583
http://www.ibm.com/developerworks/cn/linux/1307_liuming_linuxtime1/