kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

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返回错误信息。

内核热补丁技术揭秘

https://ruby-china.org/topics/20680

下述为UCloud资深工程师邱模炯在InfoQ架构师峰会上的演讲——《UCloud云平台的内核实践》中非常受关注的内核热补丁技术的一部分。给大家揭开了UCloud云平台内核技术的神秘面纱。

如何零代价修复海量服务器的Linux内核缺陷?

对于一个拥有成千上万台服务器的公司,Linux内核缺陷导致的死机屡见不鲜。让工程师们纠结的是,到底要不要通过给服务器升级内核来修复缺陷?升级意味者服务器重启、业务中断以及繁重的准备工作;不升级则担心服务器死机,同样造成业务中断和繁重的善后工作。

而在今天的云计算时代,一台宿主机往往运行多个云主机,每一次重启不管是主动升级还是被动死机,都意味着中断其上运行的所有云主机。因此,宿主机内核缺陷的修复更加棘手。

而作为一个支撑着上万家企业用户IT基础架构的云服务商,UCloud云平台上的海量宿主机又是如何修复内核缺陷的呢?

邱模炯透露,如果按照传统的重启方式来修复,那么无论是对于UCloud或是用户,都意味着繁重的运维和业务中断。但是,UCloud通过“内核热补丁技术”——即给运行中的内核打上二进制补丁,UCloud已经做到了零代价免重启修复海量服务器的内核缺陷!目前为止,UCloud对所发现的上游内核10+个缺陷全以热补丁方式修复,累计数万台次,无一例失败且无任何副作用;理论上避免了相应次数的宿主机重启及所隐含的云主机业务中断。这项技术在UCloud已经成熟。

UCloud内核热补丁技术揭秘

UCloud的热补丁技术基于多年前的开源ksplice加以定制优化而来,通过加载一个特殊准备的热补丁模块来修复内核。其过程如下图所示:

热补丁模块由ksplice程序编译生成,包含有缺陷的二进制指令和修复后的二进制指令(这些二进制按函数级别组织);模块加载后,自动定位到内核的缺陷处并以修复指令动态替换缺陷指令。

ksplice热补丁模块的创建原理见下图:

首先获取一份运行中内核对应的源码并编译出二进制,称为pre对象;打上源码补丁再次编译,称为post对象。而运行中的内核二进制称为run对象。post和pre逐条指令比较并找出存在差异的函数,之后把这些差异合并为内核模块形式的热补丁。

创建好的热补丁模块在加载到内核时还会做些检验工作:对比pre和run对象。只有通过检验才能成功加载进内核。pre-run比较的目的是为了辨别编译过程差异是否过大以致于不能打入post对象的热补丁;更重要的是,从pre-run差异中提取的关键信息才能把post对象的热补丁顺利打入运行中内核。

热补丁模块加载到内核后,便自动进行内核修复。也就是使用热补丁中的二进制指令替换有缺陷的二进制指令。这里ksplice利用了Linux内核的stop_machine机制:停止所有CPU的执行,只留下主CPU进行二进制指令替换。值得注意的是,stop_machine后如果发现任何一个线程栈的内容与热补丁存在冲突,就需要退出指令替换以避免系统崩溃。所以并非所有热补丁都能打入内核,有些频繁使用的内核函数(如schedule, hrtimer相关)就无法热补丁,重试次数再多也无济于事。

ksplice同时支持对内核和模块进行热补丁,也支持热补丁后叠加热补丁,灵活方便。不过也存在一些缺陷:stop_machine期间整个系统处于中断状态,虽然单次中断小于1ms,但有些时候多次重试的累计中断也不小;另外,有些频繁使用的函数无法打入热补丁。

kpatch和kgraft

kpatch和kgraft均是近期新出现的内核热补丁技术,前者属于Redhat公司,后者SuSE。两者原理和ksplice大致相同,都想合并进Linux内核,内核社区正在争议对比。

kpatch原理和前面讲的ksplice很接近。最大的区别在于二进制指令替换,stop_machine停止所有CPU执行后ksplice直接修改,而kpatch则借助ftrace机制来触发替换。目前的实现上,kpatch有较大局限性,不支持对模块打热补丁,不支持函数静态变量等。

kgraft原理也基本一样。主要的差异有两点:

1)热补丁生成方法不同;

2)热补丁打入内核过程里kgraft用到了RCU渐进方法。得益于RCU方法,kgraft无需像ksplice和kpatch一样调用stop_machine并检查线程栈的冲突。不过它的缺点也缘于RCU,涉及到数据结构改变时,kgraft更难通过编写辅助代码打入热补丁,这限制了kgraft的应用范围。

有关kpatch和kgraft的详细情况请分别参考Redhat和SuSE网站的技术资料。

除了免重启修复,热补丁还用于内核开发过程的性能分析和故障定位。比如,加上性能统计代码生成热补丁,就可以在线分析感兴趣的性能问题;加入额外调试代码捕捉运行中内核的异常。这些非常有用,更是海量服务器里捕捉不可重现内核异常的不二法宝。由于热补丁不需要重启服务器,既可打入也可撤销,所以不会有副作用。

UCloud对开源Ksplice的优化主要在以下三个方面:

支持高版本内核

热补丁技术与内核紧密耦合。不同版本的内核在指令结构体,符合表结构体和一些特性上(比如早期内核没有ftrace)有所不同,直接影响热补丁成败。UCloud研究了各版本内核的区别,使得同一份ksplice支持各个版本的Linux内核。值得一提的是,解决了ftrace与ksplice不兼容的问题。

允许热修复频繁调用的函数

不管什么样的热补丁技术,两种类型的内核函数难以热补丁:频繁使用的内核函数如schedule, hrtimer;经常处于线程栈内核部分顶部的函数,如sys_poll, sys_read。UCloud更改了ksplice相关内核代码和用户态工具,成功解除了这些限制,比如UCloud现网服务器已打入了三个hrtimer热补丁。

减少业务中断时间

ksplice是在stop_machine后替换二进制指令的。虽然单次stop_machine对业务造成的中断在一毫秒左右,但有些频繁使用的内核函数需要大量重试才能碰到合适的热补丁时机,于是会造成最长达上百毫秒的中断。UCloud在此做过一点优化,使得业务中断时间控制在十毫秒级别。

海量服务器环境下热补丁技术可用来零代价且无副作用地修复内核缺陷,而且内核开发也因热补丁能走得更远更好。以前因为缺乏辅助分析手段和惧怕内核BUG,即使适合在内核实现的特性也被告诫移到用户态实现,然而有了热补丁,相关观念也可以适当调整,内核开发也可以更加大胆和跳脱。