kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

Linux时间子系统之三:时间的维护者:timekeeper

http://blog.csdn.net/droidphone/article/details/7989566

本系列文章的前两节讨论了用于计时的时钟源:clocksource,以及内核内部时间的一些表示方法,但是对于真实的用户来说,我们感知的是真实世界的真实时间,也就是所谓的墙上时间,clocksource只能提供一个按给定频率不停递增的周期计数,如何把它和真实的墙上时间相关联?本节的内容正是要讨论这一点。

1. 时间的种类

内核管理着多种时间,它们分别是:

1
2
3
4
5
RTC时间
wall time:墙上时间
monotonic time
raw monotonic time
boot time:总启动时间

RTC时间 在PC中,RTC时间又叫CMOS时间,它通常由一个专门的计时硬件来实现,软件可以读取该硬件来获得年月日、时分秒等时间信息,而在嵌入式系统中,有使用专门的RTC芯片,也有直接把RTC集成到Soc芯片中,读取Soc中的某个寄存器即可获取当前时间信息。一般来说,RTC是一种可持续计时的,也就是说,不管系统是否上电,RTC中的时间信息都不会丢失,计时会一直持续进行,硬件上通常使用一个后备电池对RTC硬件进行单独的供电。因为RTC硬件的多样性,开发者需要为每种RTC时钟硬件提供相应的驱动程序,内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。

xtime xtime和RTC时间一样,都是人们日常所使用的墙上时间,只是RTC时间的精度通常比较低,大多数情况下只能达到毫秒级别的精度,如果是使用外部的RTC芯片,访问速度也比较慢,为此,内核维护了另外一个wall time时间:xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自1970年1月1日24时到当前时刻所经历的纳秒数。

monotonic time 该时间自系统开机后就一直单调地增加,它不像xtime可以因用户的调整时间而产生跳变,不过该时间不计算系统休眠的时间,也就是说,系统休眠时,monotoic时间不会递增。

raw monotonic time 该时间与monotonic时间类似,也是单调递增的时间,唯一的不同是:raw monotonic time“更纯净”,他不会受到NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。

boot time 与monotonic时间相同,不过会累加上系统休眠的时间,它代表着系统上电后的总时间。

1
2
3
4
5
6
时间种 类     精度(统计单位)     访问速度     累计休眠时间     受NTP调整的影响
RTC           低                   慢           Yes              Yes
xtime         高                   快           Yes              Yes
monotonic     高                   快           No               Yes
raw monotonic 高                   快           No               No
boot time     高                   快           Yes              Yes

2. struct timekeeper

内核用timekeeper结构来组织与时间相关的数据,它的定义如下:

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
28
29
struct timekeeper {  
	struct clocksource *clock;    /* Current clocksource used for timekeeping. */  
	u32 mult;    /* NTP adjusted clock multiplier */  
	int shift;  /* The shift value of the current clocksource. */  
	cycle_t cycle_interval; /* Number of clock cycles in one NTP interval. */  
	u64 xtime_interval; /* Number of clock shifted nano seconds in one NTP interval. */  
	s64 xtime_remainder;    /* shifted nano seconds left over when rounding cycle_interval */  
	u32 raw_interval;   /* Raw nano seconds accumulated per NTP interval. */  
  
	u64 xtime_nsec; /* Clock shifted nano seconds remainder not stored in xtime.tv_nsec. */  
	/* Difference between accumulated time and NTP time in ntp 
	 * shifted nano seconds. */  
	s64 ntp_error;  
	/* Shift conversion between clock shifted nano seconds and 
	 * ntp shifted nano seconds. */  
	int ntp_error_shift;  
  
	struct timespec xtime;  /* The current time */  
  
	struct timespec wall_to_monotonic;  
	struct timespec total_sleep_time;   /* time spent in suspend */  
	struct timespec raw_time;   /* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */  
  
	ktime_t offs_real;  /* Offset clock monotonic -> clock realtime */  
  
	ktime_t offs_boot;  /* Offset clock monotonic -> clock boottime */  
  
	seqlock_t lock; /* Seqlock for all timekeeper values */  
};  

其中的xtime字段就是上面所说的墙上时间,它是一个timespec结构的变量,它记录了自1970年1月1日以来所经过的时间,因为是timespec结构,所以它的精度可以达到纳秒级,当然那要取决于系统的硬件是否支持这一精度。

内核除了用xtime表示墙上的真实时间外,还维护了另外一个时间:monotonic time,可以把它理解为自系统启动以来所经过的时间,该时间只能单调递增,可以理解为xtime虽然正常情况下也是递增的,但是毕竟用户可以主动向前或向后调整墙上时间,从而修改xtime值。但是monotonic时间不可以往后退,系统启动后只能不断递增。奇怪的是,内核并没有直接定义一个这样的变量来记录monotonic时间,而是定义了一个变量wall_to_monotonic,记录了墙上时间和monotonic时间之间的偏移量,当需要获得monotonic时间时,把xtime和wall_to_monotonic相加即可,因为默认启动时monotonic时间为0,所以实际上wall_to_monotonic的值是一个负数,它和xtime同一时间被初始化,请参考timekeeping_init函数。

计算monotonic时间要去除系统休眠期间花费的时间,内核用total_sleep_time记录休眠的时间,每次休眠醒来后重新累加该时间,并调整wall_to_monotonic的值,使其在系统休眠醒来后,monotonic时间不会发生跳变。因为wall_to_monotonic值被调整。所以如果想获取boot time,需要加入该变量的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void get_monotonic_boottime(struct timespec *ts)  
{  
		......  
	do {  
		seq = read_seqbegin(&timekeeper.lock);  
		*ts = timekeeper.xtime;  
		tomono = timekeeper.wall_to_monotonic;  
		<span style="color:#ff0000;">sleep = timekeeper.total_sleep_time;</span>  
		nsecs = timekeeping_get_ns();  
  
	} while (read_seqretry(&timekeeper.lock, seq));  
  
	set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec + sleep.tv_sec,  
			ts->tv_nsec + tomono.tv_nsec + sleep.tv_nsec + nsecs);  
}  

raw_time字段用来表示真正的硬件时间,也就是上面所说的raw monotonic time,它不受时间调整的影响,monotonic时间虽然也不受settimeofday的影响,但会受到ntp调整的影响,但是raw_time不受ntp的影响,他真的就是开完机后就单调地递增。xtime、monotonic-time和raw_time可以通过用户空间的clock_gettime函数获得,对应的ID参数分别是 CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW。

clock字段则指向了目前timekeeper所使用的时钟源,xtime,monotonic time和raw time都是基于该时钟源进行计时操作,当有新的精度更高的时钟源被注册时,通过timekeeping_notify函数,change_clocksource函数将会被调用,timekeeper.clock字段将会被更新,指向新的clocksource。

早期的内核版本中,xtime、wall_to_monotonic、raw_time其实是定义为全局静态变量,到我目前的版本(V3.4.10),这几个变量被移入到了timekeeper结构中,现在只需维护一个timekeeper全局静态变量即可:

1
static struct timekeeper timekeeper;  

3. timekeeper的初始化

timekeeper的初始化由timekeeping_init完成,该函数在start_kernel的初始化序列中被调用,timekeeping_init首先从RTC中获取当前时间:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void __init timekeeping_init(void)  
{  
	struct clocksource *clock;  
	unsigned long flags;  
	struct timespec now, boot;  
  
	read_persistent_clock(&now);  
	read_boot_clock(&boot);  

然后对锁和ntp进行必要的初始化:

	seqlock_init(&timekeeper.lock);  
  
	ntp_init();  

接着获取默认的clocksource,如果平台没有重新实现clocksource_default_clock函数,
默认的clocksource就是基于jiffies的clocksource_jiffies,
然后通过timekeeper_setup_inernals内部函数把timekeeper和clocksource进行关联:

	write_seqlock_irqsave(&timekeeper.lock, flags);  
	clock = clocksource_default_clock();  
	if (clock->enable)  
		clock->enable(clock);  
	timekeeper_setup_internals(clock);  

利用RTC的当前时间,初始化xtime,raw_time,wall_to_monotonic等字段:

	timekeeper.xtime.tv_sec = now.tv_sec;  
	timekeeper.xtime.tv_nsec = now.tv_nsec;  
	timekeeper.raw_time.tv_sec = 0;  
	timekeeper.raw_time.tv_nsec = 0;  
	if (boot.tv_sec == 0 && boot.tv_nsec == 0) {  
		boot.tv_sec = timekeeper.xtime.tv_sec;  
		boot.tv_nsec = timekeeper.xtime.tv_nsec;  
	}  
	set_normalized_timespec(&timekeeper.wall_to_monotonic,  
			-boot.tv_sec, -boot.tv_nsec);  

最后,初始化代表实时时间和monotonic时间之间偏移量的offs_real字段,total_sleep_time字段初始化为0:


	update_rt_offset();  
	timekeeper.total_sleep_time.tv_sec = 0;  
	timekeeper.total_sleep_time.tv_nsec = 0;  
	write_sequnlock_irqrestore(&timekeeper.lock, flags);  

}

xtime字段因为是保存在内存中,系统掉电后无法保存时间信息,所以每次启动时都要通过timekeeping_init从RTC中同步正确的时间信息。其中,read_persistent_clock和read_boot_clock是平台级的函数,分别用于获取RTC硬件时间和启动时的时间,不过值得注意到是,到目前为止(我的代码树基于3.4版本),ARM体系中,只有tegra和omap平台实现了read_persistent_clock函数。如果平台没有实现该函数,内核提供了一个默认的实现:

1
2
3
4
5
void __attribute__((weak)) read_persistent_clock(struct timespec *ts)  
{  
	ts->tv_sec = 0;  
	ts->tv_nsec = 0;  
}  
1
2
3
4
5
void __attribute__((weak)) read_boot_clock(struct timespec *ts)  
{  
	ts->tv_sec = 0;  
	ts->tv_nsec = 0;  
}  

那么,其他ARM平台是如何初始化xtime的?答案就是CONFIG_RTC_HCTOSYS这个内核配置项,打开该配置后,driver/rtc/hctosys.c将会编译到系统中,由rtc_hctosys函数通过do_settimeofday在系统初始化时完成xtime变量的初始化:

1
2
3
4
5
6
7
8
9
10
11
static int __init rtc_hctosys(void)   
{   
		......   
		err = rtc_read_time(rtc, &tm);   
		......  
		rtc_tm_to_time(&tm, &tv.tv_sec);   
		do_settimeofday(&tv);   
		......   
		return err;   
}   
late_initcall(rtc_hctosys);  

4. 时间的更新

xtime一旦初始化完成后,timekeeper就开始独立于RTC,利用自身关联的clocksource进行时间的更新操作,根据内核的配置项的不同,更新时间的操作发生的频度也不尽相同,如果没有配置NO_HZ选项,通常每个tick的定时中断周期,do_timer会被调用一次,相反,如果配置了NO_HZ选项,可能会在好几个tick后,do_timer才会被调用一次,当然传入的参数是本次更新离上一次更新时相隔了多少个tick周期,系统会保证在clocksource的max_idle_ns时间内调用do_timer,以防止clocksource的溢出:

1
2
3
4
5
6
void do_timer(unsigned long ticks)  
{  
	jiffies_64 += ticks;  
	update_wall_time();  
	calc_global_load(ticks);  
}  

在do_timer中,jiffies_64变量被相应地累加,然后在update_wall_time中完成xtime等时间的更新操作,更新时间的核心操作就是读取关联clocksource的计数值,累加到xtime等字段中,其中还设计ntp时间的调整等代码,详细的代码就不贴了。

5. 获取时间

timekeeper提供了一系列的接口用于获取各种时间信息。

1
2
3
4
5
6
7
8
9
void getboottime(struct timespec *ts);    获取系统启动时刻的实时时间
void get_monotonic_boottime(struct timespec *ts);     获取系统启动以来所经过的时间,包含休眠时间
ktime_t ktime_get_boottime(void);   获取系统启动以来所经过的c时间,包含休眠时间,返回ktime类型
ktime_t ktime_get(void);    获取系统启动以来所经过的c时间,不包含休眠时间,返回ktime类型
void ktime_get_ts(struct timespec *ts) ;   获取系统启动以来所经过的c时间,不包含休眠时间,返回timespec结构
unsigned long get_seconds(void);    返回xtime中的秒计数值
struct timespec current_kernel_time(void);    返回内核最后一次更新的xtime时间,不累计最后一次更新至今clocksource的计数值
void getnstimeofday(struct timespec *ts);    获取当前时间,返回timespec结构
void do_gettimeofday(struct timeval *tv);    获取当前时间,返回timeval结构

Linux时间子系统之二:表示时间的单位和结构

http://blog.csdn.net/droidphone/article/details/7979295

1. jiffies

内核用jiffies变量记录系统启动以来经过的时钟滴答数,它的声明如下:

1
2
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

可见,在32位的系统上,jiffies是一个32位的无符号数,系统每过1/HZ秒,jiffies的值就会加1,最终该变量可能会溢出,所以内核同时又定义了一个64位的变量jiffies_64,链接的脚本保证jiffies变量和jiffies_64变量的内存地址是相同的,通常,我们可以直接访问jiffies变量,但是要获得jiffies_64变量,必须通过辅助函数get_jiffies_64来实现。jiffies是内核的低精度定时器的计时单位,所以内核配置的HZ数决定了低精度定时器的精度,如果HZ数被设定为1000,那么,低精度定时器(timer_list)的精度就是1ms=1/1000秒。因为jiffies变量可能存在溢出的问题,所以在用基于jiffies进行比较时,应该使用以下辅助宏来实现:

1
2
3
4
5
time_after(a,b)
time_before(a,b)
time_after_eq(a,b)
time_before_eq(a,b)
time_in_range(a,b,c)

同时,内核还提供了一些辅助函数用于jiffies和毫秒以及纳秒之间的转换:

1
2
3
4
unsigned int jiffies_to_msecs(const unsigned long j);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned long msecs_to_jiffies(const unsigned int m);
unsigned long usecs_to_jiffies(const unsigned int u);

2. struct timeval

timeval由秒和微秒组成,它的定义如下:

1
2
3
4
struct timeval {
	__kernel_time_t    tv_sec;         /* seconds */
	__kernel_suseconds_t  tv_usec; /* microseconds */
};

__kernel_time_t__kernel_suseconds_t 实际上都是long型的整数。gettimeofday和settimeofday使用timeval作为时间单位。

3. struct timespec

timespec由秒和纳秒组成,它的定义如下:

1
2
3
4
struct timespec {
	__kernel_time_t tv_sec;          /* seconds */
	long      tv_nsec;         /* nanoseconds */
};

同样地,内核也提供了一些辅助函数用于jiffies、timeval、timespec之间的转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static inline int timespec_equal(const struct timespec *a, const struct timespec *b);
static inline int timespec_compare(const struct timespec *lhs, const struct timespec *rhs);
static inline int timeval_compare(const struct timeval *lhs, const struct timeval *rhs);
extern unsigned long mktime(const unsigned int year, const unsigned int mon,
				const unsigned int day, const unsigned int hour,
				const unsigned int min, const unsigned int sec);
extern void set_normalized_timespec(struct timespec *ts, time_t sec, s64 nsec);
static inline struct timespec timespec_add(struct timespec lhs, struct timespec rhs);
static inline struct timespec timespec_sub(struct timespec lhs, struct timespec rhs);

static inline s64 timespec_to_ns(const struct timespec *ts);
static inline s64 timeval_to_ns(const struct timeval *tv);
extern struct timespec ns_to_timespec(const s64 nsec);
extern struct timeval ns_to_timeval(const s64 nsec);
static __always_inline void timespec_add_ns(struct timespec *a, u64 ns);
1
2
3
4
unsigned long timespec_to_jiffies(const struct timespec *value);
void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(const struct timeval *value);
void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value);

timekeeper中的xtime字段用timespec作为时间单位。

4. struct ktime

linux的通用时间架构用ktime来表示时间,为了兼容32位和64位以及big-little endian系统,ktime结构被定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
union ktime {
	s64 tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
	struct {
# ifdef __BIG_ENDIAN
	s32 sec, nsec;
# else
	s32 nsec, sec;
# endif
	} tv;
#endif
};

64位的系统可以直接访问tv64字段,单位是纳秒,32位的系统则被拆分为两个字段:sec和nsec,并且照顾了大小端的不同。高精度定时器通常用ktime作为计时单位。下面是一些辅助函数用于计算和转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ktime_t ktime_set(const long secs, const unsigned long nsecs); 
ktime_t ktime_sub(const ktime_t lhs, const ktime_t rhs); 
ktime_t ktime_add(const ktime_t add1, const ktime_t add2); 
ktime_t ktime_add_ns(const ktime_t kt, u64 nsec); 
ktime_t ktime_sub_ns(const ktime_t kt, u64 nsec); 
ktime_t timespec_to_ktime(const struct timespec ts); 
ktime_t timeval_to_ktime(const struct timeval tv); 
struct timespec ktime_to_timespec(const ktime_t kt); 
struct timeval ktime_to_timeval(const ktime_t kt); 
s64 ktime_to_ns(const ktime_t kt); 
int ktime_equal(const ktime_t cmp1, const ktime_t cmp2); 
s64 ktime_to_us(const ktime_t kt); 
s64 ktime_to_ms(const ktime_t kt);
ktime_t ns_to_ktime(u64 ns);

Linux时间子系统之一:clock source(时钟源)

http://blog.csdn.net/droidphone/article/details/7975694

clock source用于为Linux内核提供一个时间基线,如果你用linux的date命令获取当前时间,内核会读取当前的clock source,转换并返回合适的时间单位给用户空间。在硬件层,它通常实现为一个由固定时钟频率驱动的计数器,计数器只能单调地增加,直到溢出为止。时钟源是内核计时的基础,系统启动时,内核通过硬件RTC获得当前时间,在这以后,在大多数情况下,内核通过选定的时钟源更新实时时间信息(墙上时间),而不再读取RTC的时间。本节的内核代码树基于V3.4.10。

1. struct clocksource结构

内核用一个clocksource结构对真实的时钟源进行软件抽象,现在我们从clock source的数据结构开始,它的定义如下:

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
28
29
30
31
32
33
struct clocksource {  
	/* 
	 * Hotpath data, fits in a single cache line when the 
	 * clocksource itself is cacheline aligned. 
	 */  
	cycle_t (*read)(struct clocksource *cs);  
	cycle_t cycle_last;  
	cycle_t mask;  
	u32 mult;  
	u32 shift;  
	u64 max_idle_ns;  
	u32 maxadj;  
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA  
	struct arch_clocksource_data archdata;  
#endif  
  
	const char *name;  
	struct list_head list;  
	int rating;  
	int (*enable)(struct clocksource *cs);  
	void (*disable)(struct clocksource *cs);  
	unsigned long flags;  
	void (*suspend)(struct clocksource *cs);  
	void (*resume)(struct clocksource *cs);  
  
	/* private: */  
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG  
	/* Watchdog related data, used by the framework */  
	struct list_head wd_list;  
	cycle_t cs_last;  
	cycle_t wd_last;  
#endif  
} ____cacheline_aligned;  

ocksource中的几个重要的字段。

1.1 rating:时钟源的精度

同一个设备下,可以有多个时钟源,每个时钟源的精度由驱动它的时钟频率决定,比如一个由10MHz时钟驱动的时钟源,他的精度就是100nS。clocksource结构中有一个rating字段,代表着该时钟源的精度范围,它的取值范围如下:

1
2
3
4
5
1--99: 不适合于用作实际的时钟源,只用于启动过程或用于测试;
100--199:基本可用,可用作真实的时钟源,但不推荐;
200--299:精度较好,可用作真实的时钟源;
300--399:很好,精确的时钟源;
400--499:理想的时钟源,如有可能就必须选择它作为时钟源;
1.2 read回调函数

时钟源本身不会产生中断,要获得时钟源的当前计数,只能通过主动调用它的read回调函数来获得当前的计数值,注意这里只能获得计数值,也就是所谓的cycle,要获得相应的时间,必须要借助clocksource的mult和shift字段进行转换计算。

1.3 mult和shift字段

因为从clocksource中读到的值是一个cycle计数值,要转换为时间,我们必须要知道驱动clocksource的时钟频率F,一个简单的计算就可以完成:

1
t = cycle/F;

可是clocksource并没有保存时钟的频率F,因为使用上面的公式进行计算,需要使用浮点运算,这在内核中是不允许的,因此,内核使用了另外一个变通的办法,根据时钟的频率和期望的精度,事先计算出两个辅助常数mult和shift,然后使用以下公式进行cycle和t的转换:

1
t = (cycle * mult) >> shift;

只要我们保证:

1
F = (1 << shift) / mult;

内核内部使用64位进行该转换计算:

1
2
3
4
static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)  
{  
	return ((u64) cycles * mult) >> shift;  
}  

从转换精度考虑,mult的值是越大越好,但是为了计算过程不发生溢出,mult的值又不能取得过大。为此内核假设cycle计数值被转换后的最大时间值:10分钟(600秒),主要的考虑是CPU进入IDLE状态后,时间信息不会被更新,只要在10分钟内退出IDLE,clocksource的cycle计数值就可以被正确地转换为相应的时间,然后系统的时间信息可以被正确地更新。当然最后的结果不一定是10分钟,它由clocksource_max_deferment进行计算,并保存max_idle_ns字段中,tickless的代码要考虑这个值,以防止在NO_HZ配置环境下,系统保持IDLE状态的时间过长。在这样,由10分钟这个假设的时间值,我们可以推算出合适的mult和shift值。

2. clocksource的注册和初始化

通常,clocksource要在初始化阶段通过clocksource_register_hz函数通知内核它的工作时钟的频率,调用的过程如下:

由上图可见,最终大部分工作会转由__clocksource_register_scale完成,该函数首先完成对mult和shift值的计算,然后根据mult和shift值,最终通过clocksource_max_deferment获得该clocksource可接受的最大IDLE时间,并记录在clocksource的max_idle_ns字段中。clocksource_enqueue函数负责按clocksource的rating的大小,把该clocksource按顺序挂在全局链表clocksource_list上,rating值越大,在链表上的位置越靠前。

每次新的clocksource注册进来,都会触发clocksource_select函数被调用,它按照rating值选择最好的clocksource,并记录在全局变量curr_clocksource中,然后通过timekeeping_notify函数通知timekeeping,当前clocksource已经变更,关于timekeeping,我将会在后续的博文中阐述。

3. clocksource watchdog

系统中可能同时会注册对个clocksource,各个clocksource的精度和稳定性各不相同,为了筛选这些注册的clocksource,内核启用了一个定时器用于监控这些clocksource的性能,定时器的周期设为0.5秒:

1
2
#define WATCHDOG_INTERVAL (HZ >> 1)  
#define WATCHDOG_THRESHOLD (NSEC_PER_SEC >> 4)  

当有新的clocksource被注册时,除了会挂在全局链表clocksource_list外,还会同时挂在一个watchdog链表上:watchdog_list。定时器周期性地(0.5秒)检查watchdog_list上的clocksource,WATCHDOG_THRESHOLD的值定义为0.0625秒,如果在0.5秒内,clocksource的偏差大于这个值就表示这个clocksource是不稳定的,定时器的回调函数通过clocksource_watchdog_kthread线程标记该clocksource,并把它的rate修改为0,表示精度极差。

4. 建立clocksource的简要过程

在系统的启动阶段,内核注册了一个基于jiffies的clocksource,代码位于kernel/time/jiffies.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct clocksource clocksource_jiffies = {  
	.name       = "jiffies",  
	.rating     = 1, /* lowest valid rating*/  
	.read       = jiffies_read,  
	.mask       = 0xffffffff, /*32bits*/  
	.mult       = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */  
	.shift      = JIFFIES_SHIFT,  
};  
......  
  
static int __init init_jiffies_clocksource(void)  
{  
	return clocksource_register(&clocksource_jiffies);  
}  
  
core_initcall(init_jiffies_clocksource);  

它的精度只有1/HZ秒,rating值为1,如果平台的代码没有提供定制的clocksource_default_clock函数,它将返回该clocksource:

1
2
3
4
struct clocksource * __init __weak clocksource_default_clock(void)  
{  
	return &clocksource_jiffies;  
}  

然后,在初始化的后段,clocksource的代码会把全局变量curr_clocksource设置为上述的clocksource:

1
2
3
4
5
6
7
8
9
10
11
12
static int __init clocksource_done_booting(void)  
{  
		......  
	curr_clocksource = clocksource_default_clock();  
		......  
	finished_booting = 1;  
		......  
	clocksource_select();  
		......  
	return 0;  
}  
fs_initcall(clocksource_done_booting);  

当然,如果平台级的代码在初始化时也会注册真正的硬件clocksource,所以经过clocksource_select()函数后,curr_clocksource将会被设为最合适的clocksource。如果clocksource_select函数认为需要切换更好的时钟源,它会通过timekeeping_notify通知timekeeping系统,使用新的clocksource进行时间计数和更新操作。

pssh、pscp命令

http://blog.csdn.net/kumu_linux/article/details/8562320

pssh是一个python编写可以在多台服务器上执行命令的工具,同时支持拷贝文件,是同类工具中很出色的,类似pdsh,个人认为相对pdsh更为简便,使用必须在各个服务器上配置好密钥认证访问。

项目地址: https://code.google.com/p/parallel-ssh/

PSSH provides parallel versions of OpenSSH and related tools. Included are pssh, pscp, prsync, pnuke, and pslurp. The project includes psshlib which can be used within custom applications. The source code is written in Python and can be cloned from:

git clone http://code.google.com/p/parallel-ssh/

PSSH is supported on Python 2.4 and greater (including Python 3.1 and greater). It was originally written and maintained by Brent N. Chun. Due to his busy schedule, Brent handed over maintenance to Andrew McNabb in October 2009.

下载安装

下载

wget http://parallel-ssh.googlecode.com/files/pssh-2.3.1.tar.gz

本地下载 pssh-2.3.1.tar.gz

安装
1
2
3
tar xf pssh-2.3.1.tar.gz  
cd pssh-2.3.1/  
python setup.py install  
参数命令介绍

pssh 在多个主机上并行地运行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   -h 执行命令的远程主机列表  或者 -H user@ip:port  文件内容格式[user@]host[:port]

   -l 远程机器的用户名

   -P 执行时输出执行信息
   -p 一次最大允许多少连接
   -o 输出内容重定向到一个文件
   -e 执行错误重定向到一个文件
   -t 设置命令执行的超时时间
   -A 提示输入密码并且把密码传递给ssh
   -O 设置ssh参数的具体配置,参照ssh_config配置文件
   -x 传递多个SSH 命令,多个命令用空格分开,用引号括起来
   -X 同-x 但是一次只能传递一个命令
   -i 显示标准输出和标准错误在每台host执行完毕后

其他命令

1
2
3
4
5
6
7
pscp     传输文件到多个hosts,类似scp

pslurp   从多台远程机器拷贝文件到本地

pnuke    并行在远程主机杀进程

prsync   使用rsync协议从本地计算机同步到远程主机

实例

pssh
1
2
3
4
5
$ pssh -h ip.txt -l root chkconfig --level 2345 snmpd on  
[1] 10:59:29 [SUCCESS] ... ...  
[2] 10:59:29 [SUCCESS] ... ...  
[3] 10:59:29 [SUCCESS] ... ...  
... ...  
pscp
1
2
3
4
5
$ pscp -h ip.txt -l root /etc/snmp/snmpd.conf /etc/snmp/snmpd.conf  
[1] 11:00:42 [SUCCESS] ... ...  
[2] 11:00:42 [SUCCESS] ... ...  
[3] 11:00:42 [SUCCESS] ... ...  
... ...  

linux 中断下半部

http://blog.chinaunix.net/uid-24203478-id-3111803.html

与Linux中断息息相关的一个重要概念是Linux中断分为两个半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。

在Linux2.6的内核中存在三种不同形式的下半部实现机制:软中断,tasklet和工作队列。

Tasklet基于Linux softirq,其使用相当简单,我们只需要定义tasklet及其处理函数并将二者关联:

1
2
void my_tasklet_func(unsigned long); //定义一个处理函数:
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); //定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联

然后,在需要调度tasklet的时候引用一个简单的API就能使系统在适当的时候进行调度运行:

1
tasklet_schedule(&my_tasklet);

此外,Linux还提供了另外一些其它的控制tasklet调度与运行的API:

1
2
3
4
5
DECLARE_TASKLET_DISABLED(name,function,data); //与DECLARE_TASKLET类似,但等待tasklet被使能
tasklet_enable(struct tasklet_struct *); //使能tasklet
tasklet_disble(struct tasklet_struct *); //禁用tasklet
tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long); //类似DECLARE_TASKLET()
tasklet_kill(struct tasklet_struct *); // 清除指定tasklet的可调度位,即不允许调度该tasklet

我们先来看一个tasklet的运行实例,这个实例没有任何实际意义,仅仅为了演示。它的功能是:在globalvar被写入一次后,就调度一个tasklet,函数中输出"tasklet is executing":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//定义与绑定tasklet函数
void test_tasklet_action(unsigned long t);
DECLARE_TASKLET(test_tasklet, test_tasklet_action, 0);

void test_tasklet_action(unsigned long t)
{
	printk("tasklet is executing\n");
}

...

ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
	...
	if (copy_from_user(&global_var, buf, sizeof(int)))
	{
		return - EFAULT;
	}

	//调度tasklet执行
	tasklet_schedule(&test_tasklet);
	return sizeof(int);
}

下半部分的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。

在Linux2.6的内核中存在三种不同形式的下半部实现机制:软中断,tasklet和工作队列。

下面将比较三种机制的差别与联系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
软中断:    1、软中断是在编译期间静态分配的。
           2、最多可以有32个软中断。
           3、软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。
           4、可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),
              因此也需要使用自旋锁来保护其数据结构。
           5、目前只有两个子系直接使用软中断:网络和SCSI。
           6、执行时间有:从硬件中断代码返回时、在ksoftirqd内核线程中和某些显示检查并执行软中断的代码中。

tasklet:   1、tasklet是使用两类软中断实现的:HI_SOFTIRQ和TASKLET_SOFTIRQ。
           2、可以动态增加减少,没有数量限制。
           3、同一类tasklet不能并发执行。
           4、不同类型可以并发执行。
           5、大部分情况使用tasklet。

工作队列:  1、由内核线程去执行,换句话说总在进程上下文执行。
           2、可以睡眠,阻塞。