kk Blog —— 通用基础

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

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、可以睡眠,阻塞。

gdb线程

GDB多线程调试的基本命令。

info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程。

thread ID 切换当前调试的线程为指定ID的线程。

break thread_test.c:123 thread all 在所有线程中相应的行上设置断点

thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command。

thread apply all command 让所有被调试线程执行GDB命令command。

set scheduler-locking off|on|step 估计是实际使用过多线程调试的人都可以发现,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。off 不锁定任何线程,也就是所有线程都执行,这是默认值。 on 只有当前被调试程序会执行。 step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。

理解Ksplice执行过程

http://m.blog.chinaunix.net/uid-29280350-id-4717510.html

http://m.blog.chinaunix.net/uid-29280350-id-4906197.html


注:在Linux-3.0.0 到 linux-3.8.0上能够正常运行,超过3.8.13就会导致系统桌面崩溃

1.Ksplice-create

Ksplice-create用于创建补丁文件,根据用户提供的不同的更新文件,ksplice-create有三种不同的途径:
1)Patch文件
2)Diffext指定新文件的后缀
3)使用git指定新的标记

同时,ksplice-create还需要指定orig_config_dir(指定config的目录),在该目录下要有以下几个文件:
1)当前run内核的System.map
2)当前run内核的.config
3)当前run内核的modules库下的build链接
以上三项缺一不可。

1.1配置

根据配置变量,组织make命令:

1
make -rR

如果定义了jobs

1
-jn

如果定义了verbose level

1
V=1 否则 -s

make_ksplice 变量:

1
@make -f $datadir/Makefile.ksplice @kbuild_flags

如果定义了build_modules

1
KSPLICE_BUILD_MODULES=1

1.2 Revert

配置变量完成后,ksplice-create会查找linux内核代码目录下是否会存在*.KSPLICE_presrc文件,存在该类型的文件则表明在该linux内核目录下曾制作过补丁文件,因此需要先将代码恢复为原始代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
my @revert_flags=("KSPLICE_MODE=revert");
	Revert_orig()
		Find出*.KSPLICE_presrc的文件,将之恢复为原始文件
		执行命令:make -rR -f Makefile.ksplice KSPLICE_MODE=revert

进入Makefile.ksplice文件:
	Makefile.ksplice默认目标是__ksplice,
	__ksplice: $(ksplice-deps) $(ksplice-dirs)
		@:

	目标只是依赖两个dirs,没有具体的执行命令,所有的执行命令都是在依赖中执行的。对于ksplice-dirs的命令:

	$(ksplice-dirs):
		$(Q) $(MAKE) $(build)=$(@:_ksplice_%=%)
		其中
		build := -f $(ksplice-makefile) obj

		所以命令展开就是:
		make -f /usr/local/share/ksplice/Makefile.ksplice obj=arch/x86/crypto

		又再次进入makefile.ksplice,这次传入了obj。

revert的作用就是把.ksplice_pre的文件执行cmd_ksplice-revert。

最后通过$(call cmd, ksplice-revert)调用

1
2
cmd_ksplice-revert = touch -r ksplice-revert-stamp $(@:_ksplice-revert_%=%); \
	mv $(@:_ksplice-revert_%=%) $(@:_ksplice-revert_%.KSPLICE_pre=%)

在然后根据ksplice-clean-files把ksplice生成的文件clean掉。

1.3 SNAP

执行完revert之后,重新回到ksplice-create文件中继续执行

1
2
@snap_flags = (“KSPLICE_MODE=snap”);
runval_raw(@make_ksplice,@snap_flags)

展开即为:

1
执行命令:make -rR -f Makefile.ksplice KSPLICE_MODE=snap

进入Makefile.ksplice文件:

1
2
3
4
5
6
7
8
ifeq ($(KSPLICE_MODE),snap)
$(obj)/%.o.KSPLICE: $(obj)/%.o FORCE
	$(if $(strip $(wildcard $<.KSPLICE_pre) $(filter $<, $?)), \
		$(call cmd, ksplice-snap))
	else
		$(obj)/%. o.KSPLICE:$(obj)/%.o
		$(call cmd, ksplice-diff)
	endif

其中

1
2
3
cmd_ksplice-snap = $(ksplice-script) snap $@
cmd_ksplice-diff = $(ksplice-script) diff $@
ksplice-scrript = $(dir $(ksplice-makefile))ksplice-obj.pl

进入ksplice-obj.pl文件中:

1
2
3
4
5
6
7
sub do_snap {
	my ($out) = @_;
	my ($obj) = $out = ~ /^(.*)\.KSPLICE$/ or die;
	die if (!-e $obj);
	unlink "$obj.KSPLICE_pre" if (-e "$obj.KSPLICE_pre");
	empty_diff($out);
}
1
2
3
4
5
6
7
8
9
sub empty_diff {
	my ($out) = @_;
	my ($obj) = $out =~ /^(.*)\.KSPLICE$/ or die;
	unlink "$obj.KSPLICE_new_code" if (-e "$obj.KSPLICE_new_code");
	unlink "$obj.KSPLICE_old_code" if (-e "$obj.KSPLICE_old_code");
	open OUT, '>', "$out.tmp";
	close OUT;
	rename "$out.tmp", $out;
}

snap的工作就是生成一个.o.KSPLICE空文件,函数empty_diff就是用来生成空文件的。.o.KSPLICE文件用来作为一个标志位,只是为了后续diff阶段,如果有不同的.o就会把.o.KSPLICE中写入1,最后遍历所有的.o.KSPLICE,哪些为1就知道哪些有差异了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sub do_diff {
	my ($out) = @_;
	my ($obj) = $out =~ /^(.*)\.KSPLICE$/ or die;
	my $obj_pre = "$obj.KSPLICE_pre";
	die if (!-e $obj);
	die "Patch creates new object $obj" if (!-e $obj_pre);
	if (system('cmp', '-s', '--', $obj_pre, $obj) == 0) {
		unlink $obj_pre;
		return empty_diff($out);
	}

	runval("$libexecdir/ksplice-objmanip", $obj, "$obj.KSPLICE_new_code", "keep-new-code", "$obj.KSPLICE_pre", $ENV{KSPLICE_KID});
	return empty_diff($out) if (!-e "$obj.KSPLICE_new_code");

	open OUT, '>', "$out.tmp";
	print OUT "1\n";
	close OUT;
	rename "$out.tmp", $out;
	runval("$libexecdir/ksplice-objmanip", $obj_pre, "$obj.KSPLICE_old_code", "keep-old-code");
}

无论snap还是diff都是要创建目标.o.KSPLICE, 但是动作不一样,并且snap是FORCE,diff不是强制的,最关键的就是打了patch之后,就会重新生成patch对应的.o,此时依赖条件更新了,就会执行diff命令。

1.4 创建ksplice模块

将kmodsrc目录拷贝到tmp目录下,执行命令:

1
@make_kmodsrc = (@make, "-C", $kernel_headers_dir, "M=$kmodsrc", "KSPLICE_KID=$kid", "KSPLICE_VERSION=1.0", "map_printk=$map_printk");

编译内核模块,然后make modules_install,

1
@make_kmodsrc_install = (@make_kmodsrc, qw(modules_install --old-file=_modinst_post --old-file=_emodinst_post), "MAKE=make --old-file=_modinst_post --old-file=_emodinst_post", "INSTALL_MOD_STRIP=1", "MODLIB=$tmpdir/ksplice-modules");

1.5 PATCH

将准备的patch文件更新到内核中:

1
runval_infile($patchfile, "patch", @patch_opt, "-bz", ".KSPLICE_presrc");

-bz的意思:
-b 备份原始文件
-z 是用.KSPLICE_presrc为后缀备份原始文件。

要注意patch文件中各个文件的行号等内容要对齐,不然patch文件无法更新到内核源码中(要每个文件都要检查,并确认patch文件可用)。

打上补丁后,执行:

1
make_ksplice KSPLICE_MODE=diff

1.6 DIFF

1
2
my @diff_flags = ("KSPLICE_MODE=diff")
runval_raw(@make_ksplice, @diff_flags);

即执行命令:

1
make -rR -f Makefile.ksplice KSPLICE_MODE=diff

进入Makefile.ksplice文件:

1
2
3
4
5
6
7
8
9
10
11
ifeq ($(KSPLICE_MODE),diff)
	define ksplice-cow-check
		$(if $(strip $(1)),$(if $(filter-out %.KSPLICE,$@),$(if $(wildcard $@),$(if $(wildcard $@.KSPLICE_pre),,$(call cmd,ksplice-cow)))))$(1)
	endef

	define ksplice-add-cow-check
		$(v) = $$(call ksplice-cow-check,$(value $(v)))
	endef

	ksplice-cow-eval += $(foreach v,if_changed if_changed_dep if_changed_rule,$(ksplice-add-cow-check))
endif   # KSPLICE_MODE

其中

1
cmd_ksplice-cow = cp -a $@ $@.KSPLICE_pre

diff比较的是.o.KSPLICE_pre 和 新编译的.o,从do_diff的实现来看,在diff之前,KSPLICE_pre就已经生成了,生成KSPLICE_pre的命令只有cmd-ksplice-cow, 即diff操作的结果。

1
2
3
4
5
6
7
8
9
$KSPLICE_MODE ?= diff
ifeq ($(KSPLICE_MODE),snap)
	$(obj)/%.o.KSPLICE: $(obj)/%.o FORCE
		$(if $(strip $(wildcard $<.KSPLICE_pre) $(filter $<, $?)), \
			$(call cmd, ksplice-snap))
else
	$(obj)/%. o.KSPLICE:$(obj)/%.o
	$(call cmd, ksplice-diff)
endif

在此处调用

1
cmd_ksplice-diff=$(ksplice-script) diff $@

进入ksplice-obj.pl中调用函数do_diff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sub do_diff {
	my ($out) = @_;
	my ($obj) = $out =~ /^(.*)\.KSPLICE$/ or die;
	my $obj_pre = "$obj.KSPLICE_pre";
	die if (!-e $obj);
	die "Patch creates new object $obj" if (!-e $obj_pre);
	if (system('cmp', '-s', '--', $obj_pre, $obj) == 0) {
		unlink $obj_pre;
		return empty_diff($out);
	}
	runval("$libexecdir/ksplice-objmanip", $obj, "$obj.KSPLICE_new_code", "keep-new-code", "$obj.KSPLICE_pre", $ENV{KSPLICE_KID});
	return empty_diff($out) if (!-e "$obj.KSPLICE_new_code");

	open OUT, '>', "$out.tmp";
	print OUT "1\n";
	close OUT;
	rename "$out.tmp", $out;

	runval("$libexecdir/ksplice-objmanip", $obj_pre, "$obj.KSPLICE_old_code", "keep-old-code");
}

此处有三个关键点,第一system系统调用cmp,比较$(obj)和$obj_pre之间的不同,第二通过调用ksplice-objmanip(即objmanip)生成new_code,并且在.o.KSPLICE_pre中写入标志位1,第三步调用ksplice-objmanip(即objmanip)将未打patch之前的代码生成old_code。第二步和第三步进入到C文件objmanip.c的main()函数中,根据传入的参数的不同,调用不同的函数,最后生成new和old。

1.7 模块编译

命令:

1
runstr(qw(find -name *.KSPLCE* !  ( - name *.KSPLICE -empty ) ! -name .*.KSPLICE.cmd -print0))

找出所有.KSPLICE非空的文件,将读入的内容保存到@modules中。对MOD的处理是在KSPLICE_KMODSRC中生成的。

命令:

1
runval(@make_ksplice, "KSPLICE_MODE=modinst", "MODLIB=$tmpdir/modules", "INSTALL_MOD_STRIP=1", "modules=@modulepaths");

在Makefile.ksplice中,对modinst的处理是:

1
2
3
4
5
6
ifeq ($(KSPLICE_MODE),modinst)
ksplice-deps += ksplice_modinst
PHONY += ksplice_modinst
ksplice_modinst:
	$(Q) $(MAKE) –f $(srctree)/scripts/Makefile.modinst
endif

这里的Makefile.modinst和Makefile.modpost都是内核script中的Makefile。

在ksplice-create中分别调用了两次make_kmodsrc, 第一次编译出ksplice.ko模块,第二次传入参数KSPLICE_MODULES=@modules 生成new.ko 和 old.ko文件。在kmodsrc目录中的Makefile中,第一次编译的是KSPLICE_CORE:

1
2
KSPLICE_CORE = ksplice-$(KSPLICE_KID)
obj-m += $(KSPLICE_CORE).o

实际上最终编译生成ksplice-kid.ko 还是依靠的obj-m的方法编译的。

第二次编译的时候传入的modules,同时KSPLICE_SKIP_CORE=1,表示不编译ksplice.ko

在ksplice-create中,执行命令:

1
2
runval(@make_kmodsrc, "KSPLICE_MODULES=@modules", "KSPLICE_SKIP_CORE=1");
runval(@make_kmodsrc_install, "KSPLICE_MODULES=@modules", "KSPLICE_SKIP_CORE=1");

在kmodsrc/Makefile中:

1
2
3
4
5
6
ifneq ($(KSPLICE_MODULES),)
	$(foreach mod,$(KSPLICE_MODULES),$(obj)/new-code-$(target).o): $(obj)/%.o: $ (src)/new_code_loader.c FORCE
	$(call if_changed_rule,cc_o_c)
	$(foreach mod,$(KSPLICE_MODULES),$(obj)/old-code-$(target).o): $(obj)/%.o: $ (src)/old_code_loader.c FORCE
	$(call if_changed_rule,cc_o_c)
endif

以new为例:

1
2
$(KSPLICE)-n-objs = $(ksplice-new-code-objs)
ksplice-new-code-objs = new-code-$(target).o collect-new-code-$(mod).o

new.ko由new-code-mod.o 和 collect-new-code-$(mod).o 组成。

new-code-mod.o的命令:

1
2
3
$(foreach mod,$(KSPLICE_MODULES),$(obj)/new-code-$(target).o): $(obj)/%.o: \
	$ (src)/new_code_loader.c FORCE
$(call if_changed_rule,cc_o_c)

collect-new-code-$(mod).o的命令:

1
2
3
4
5
$(obj)/collect-new-code-%.o: $(obj)/%.o.KSPLICE_new_code $(obj)/ksplice.lds     FORCE
$(call if_changed,ksplice-collect)
cmd_ksplice-collect = \
	$(ksplice-script) finalize $< $<.final $* && \
	$(LD) --script=$(obj)/ksplice.lds -r -o $@ $<.final

collect的命令最后调用do_finalize生成mod.final,再结合ksplice.lds 生成collect-new-code-mod.o

2.ksplice-apply

2.1 校验补丁文件

第一,执行命令:chdir(unpack_update($file))

其中 unpack_update()在文件Ksplice.pm中,首先检测使用的补丁文件是否是目录,如果是则返回到ksplice-apply文件中;如果是压缩文件则将其解压到/tmp/临时目录下,然后将路径返回到ksplice-apply文件中。

第二,检测目标路径中是否存在contents文件,不存在就退出ksplice-apply程序。

第三,检测当前系统/sys/moudle下面是否已经加载了该补丁文件。

在上述操作中,如果有不满足要求的,通过设置apply_errors来输出错误信息。

2.2 加载补丁文件

执行命令load_module($change->{new_code_file})

1
2
3
4
5
6
7
8
9
10
sub load_module {
	my ($module, @params) = @_;
	push @modules_loaded, ($module =~ m/^(.)\.ko$/);
	if (runval_raw("insmod", $module, @params) != 0){
		pop @modules_loaded;
		child_error();
		return 0;
	}
	return 1;
}

在函数load_module()中调用系统函数insmod来加载ko文件。如果在加载过程中出现错误,由insmod返回错误信息。