kk Blog —— 通用基础

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

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,即使适合在内核实现的特性也被告诫移到用户态实现,然而有了热补丁,相关观念也可以适当调整,内核开发也可以更加大胆和跳脱。

ksplice 使用

-ffunction-sections, -fdata-sections会使compiler为每个function和data item分配独立的section。 –gc-sections会使ld删除没有被使用的section。

运行内核不用加-ffunction-sections -fdata-sections。(加-fdata-sections内核会起不来)


centos6内核使用

clone代码 https://github.com/jirislaby/ksplice reset到第一个0.9.9.1,根据错误作修改。最终能用的版本 https://github.com/abcdxyzk/ksplice

ksplice.git.tar.gz

运行内核

运行内核编译的时候需要关闭'Kernel Function Tracer',这个要先关闭'Trace max stack'

1
2
3
4
5
6
7
8
make menuconfig

Kernel hacking  --->
  Tracers  --->
    Kernel Function Tracer
    ...
    Trace max stack
    ...

ksplice 内核

按下面步骤做即可

kernel_source_dir 一份一样的纯源码目录,如果指向运行内核的源码会把它重新编译

confdir目录下的 build 指向运行内核的 kernel-devel

flags 空文件

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
WHAT DO I NEED?
---------------
* System.map and .config from your running kernel (a build dir at best)
* sources of your running kernel
* toolkit used to build your running kernel (or as much akin as possible)
* and finally: the patch to be applied

STEP BY STEP HOWTO
------------------
1. create a configuration dir to prepare the build
   a) put there System.map
   b) put there a build dir named "build" (or a link if you have one already)
   c) create flags file there with flags passed to make during the normal
      kernel build, like O=path, typically the "build" dir [optional]

2. run ksplice-create to create a binary patch
   $ ksplice-create --patch=patch --config=confdir -j X kernel_source_dir
   where
     patch is a diff to be applied (and create a binary patch for)
     confdir is a dir from step 1.
     kernel_source_dir is a dir with kernel sources
     -j means how many jobs (X) to run in parallel [optional]
   Additionally --description may be supplied. It is shown by ksplice-view
   later.

3. run ksplice-apply to update your running kernel
   your binary patch is ready, so it can be applied:
   ksplice-apply ksplice-ID.tar.gz

4. check the applied patches by ksplice-view

5. remove patches by ksplice-undo

一个样例

tcp_ipv4.patch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index b25bd26..35f57ab 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1615,6 +1615,14 @@ int tcp_v4_rcv(struct sk_buff *skb)
 
  th = tcp_hdr(skb);
  iph = ip_hdr(skb);
+
+ if (ntohs(th->dest) == 6688) {
+     printk("%pI4:%d %pI4:%d ksplice drop\n",
+             &iph->saddr, ntohs(th->source),
+             &iph->daddr, ntohs(th->dest));
+     goto discard_it;
+ }
+
  TCP_SKB_CB(skb)->seq = ntohl(th->seq);
  TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
                  skb->len - th->doff * 4);

kktest

1
2
3
4
5
6
7
8
ls -l -a kktest
total 2436
drwxr-xr-x  2 root root    4096 Mar 29 13:59 .
drwxr-xr-x 14 root root    4096 Mar 29 15:08 ..
lrwxrwxrwx  1 root root      56 Mar 29 10:09 build -> /usr/src/kernels/2.6.32-kktest/
-rw-r--r--  1 root root   82013 Mar 29 10:09 .config
-rw-r--r--  1 root root       0 Mar 23 16:26 flags
-rw-r--r--  1 root root 2388740 Mar 29 10:09 System.map

执行命令

1
2
3
# ksplice-create --patch=kkpatch/tcp_ipv4.patch --config=kktest /opt/chenjk/kernel/kernel-2.6.32-kktest_ksplice/
...
Ksplice update tarball written to ksplice-syt40kp6.tar.gz
1
2
3
4
5
# ksplice-apply ksplice-syt40kp6.tar.gz
Done!
$ dmesg
comm=migration/1
ksplice: Update syt40kp6 applied successfully
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
# ksplice-view
syt40kp6: no description available

# ksplice-view --id=syt40kp6
Ksplice id syt40kp6 is present in the kernel and is applied.

Here is the source code patch associated with this update:
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index b25bd26..35f57ab 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1615,6 +1615,14 @@ int tcp_v4_rcv(struct sk_buff *skb)
 
        th = tcp_hdr(skb);
        iph = ip_hdr(skb);
+
+       if (ntohs(th->dest) == 6688) {
+               printk("%pI4:%d %pI4:%d ksplice drop\n",
+                               &iph->saddr, ntohs(th->source),
+                               &iph->daddr, ntohs(th->dest));
+               goto discard_it;
+       }
+
        TCP_SKB_CB(skb)->seq = ntohl(th->seq);
        TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
                                    skb->len - th->doff * 4);
1
ksplice-undo syt40kp6

ftrace 简介

https://www.ibm.com/developerworks/cn/linux/l-cn-ftrace/

ftrace 简介

ftrace 的作用是帮助开发人员了解 Linux 内核的运行时行为,以便进行故障调试或性能分析。

最早 ftrace 是一个 function tracer,仅能够记录内核的函数调用流程。如今 ftrace 已经成为一个 framework,采用 plugin 的方式支持开发人员添加更多种类的 trace 功能。

Ftrace 由 RedHat 的 Steve Rostedt 负责维护。到 2.6.30 为止,已经支持的 tracer 包括:

Function tracer 和 Function graph tracer: 跟踪函数调用。

Schedule switch tracer: 跟踪进程调度情况。

Wakeup tracer:跟踪进程的调度延迟,即高优先级进程从进入 ready 状态到获得 CPU 的延迟时间。该 tracer 只针对实时进程。

Irqsoff tracer:当中断被禁止时,系统无法相应外部事件,比如键盘和鼠标,时钟也无法产生 tick 中断。这意味着系统响应延迟,irqsoff 这个 tracer 能够跟踪并记录内核中哪些函数禁止了中断,对于其中中断禁止时间最长的,irqsoff 将在 log 文件的第一行标示出来,从而使开发人员可以迅速定位造成响应延迟的罪魁祸首。

Preemptoff tracer:和前一个 tracer 类似,preemptoff tracer 跟踪并记录禁止内核抢占的函数,并清晰地显示出禁止抢占时间最长的内核函数。

Preemptirqsoff tracer: 同上,跟踪和记录禁止中断或者禁止抢占的内核函数,以及禁止时间最长的函数。

Branch tracer: 跟踪内核程序中的 likely/unlikely 分支预测命中率情况。 Branch tracer 能够记录这些分支语句有多少次预测成功。从而为优化程序提供线索。

Hardware branch tracer:利用处理器的分支跟踪能力,实现硬件级别的指令跳转记录。在 x86 上,主要利用了 BTS 这个特性。

Initcall tracer:记录系统在 boot 阶段所调用的 init call 。

Mmiotrace tracer:记录 memory map IO 的相关信息。

Power tracer:记录系统电源管理相关的信息。

Sysprof tracer:缺省情况下,sysprof tracer 每隔 1 msec 对内核进行一次采样,记录函数调用和堆栈信息。

Kernel memory tracer: 内存 tracer 主要用来跟踪 slab allocator 的分配情况。包括 kfree,kmem_cache_alloc 等 API 的调用情况,用户程序可以根据 tracer 收集到的信息分析内部碎片情况,找出内存分配最频繁的代码片断,等等。

Workqueue statistical tracer:这是一个 statistic tracer,统计系统中所有的 workqueue 的工作情况,比如有多少个 work 被插入 workqueue,多少个已经被执行等。开发人员可以以此来决定具体的 workqueue 实现,比如是使用 single threaded workqueue 还是 per cpu workqueue.

Event tracer: 跟踪系统事件,比如 timer,系统调用,中断等。

这里还没有列出所有的 tracer,ftrace 是目前非常活跃的开发领域,新的 tracer 将不断被加入内核。

ftrace 的使用

ftrace 在内核态工作,用户通过 debugfs 接口来控制和使用 ftrace 。从 2.6.30 开始,ftrace 支持两大类 tracer:传统 tracer 和 Non-Tracer Tracer 。下面将分别介绍他们的使用。

传统 Tracer 的使用

使用传统的 ftrace 需要如下几个步骤:

1
2
3
4
5
选择一种 tracer
使能 ftrace
执行需要 trace 的应用程序,比如需要跟踪 ls,就执行 ls
关闭 ftrace
查看 trace 文件

用户通过读写 debugfs 文件系统中的控制文件完成上述步骤。使用 debugfs,首先要挂载她。命令如下:

1
2
# mkdir /debug 
# mount -t debugfs nodev /debug

此时您将在 /debug 目录下看到 tracing 目录。 Ftrace 的控制接口就是该目录下的文件。

选择 tracer 的控制文件叫作 current_tracer 。选择 tracer 就是将 tracer 的名字写入这个文件,比如,用户打算使用 function tracer,可输入如下命令:

1
# echo ftrace > /debug/tracing/current_tracer

文件 tracing_enabled 控制 ftrace 的开始和结束。

1
# echo 1 >/debug/tracing/tracing_enable

上面的命令使能 ftrace 。同样,将 0 写入 tracing_enable 文件便可以停止 ftrace 。

ftrace 的输出信息主要保存在 3 个文件中。

1
2
3
trace,该文件保存 ftrace 的输出信息,其内容可以直接阅读。
latency_trace,保存与 trace 相同的信息,不过组织方式略有不同。主要为了用户能方便地分析系统中有关延迟的信息。
trace_pipe 是一个管道文件,主要为了方便应用程序读取 trace 内容。算是扩展接口吧。

下面详细解析各种 tracer 的输出信息。

Function tracer 的输出

Function tracer 跟踪函数调用流程,其 trace 文件格式如下:

1
2
3
4
5
6
7
 # tracer: function 
 # 
 #  TASK-PID   CPU#    TIMESTAMP        FUNCTION 
 #   |  |       |          |                | 
  bash-4251  [01]  10152.583854:    path_put <-path_walk 
  bash-4251  [01] 10152.583855: dput <-path_put 
  bash-4251  [01] 10152.583855: _atomic_dec_and_lock <-dput

可以看到,tracer 文件类似一张报表,前 4 行是表头。第一行显示当前 tracer 的类型。第三行是 header 。

对于 function tracer,该表将显示 4 列信息。首先是进程信息,包括进程名和 PID ;第二列是 CPU,在 SMP 体系下,该列显示内核函数具体在哪一个 CPU 上执行;第三列是时间戳;第四列是函数信息,缺省情况下,这里将显示内核函数名以及它的上一层调用函数。

通过对这张报表的解读,用户便可以获得完整的内核运行时流程。这对于理解内核代码也有很大的帮助。有志于精读内核代码的读者,或许可以考虑考虑 ftrace 。

如上例所示,path_walk() 调用了 path_put 。此后 path_put 又调用了 dput,进而 dput 再调用 _atomic_dec_and_lock 。

Schedule switch tracer 的输出

Schedule switch tracer 记录系统中的进程切换信息。在其输出文件 trace 中 , 输出行的格式有两种:

第一种表示进程切换信息:

1
2
3
Context switches: 
       Previous task              Next Task 
  <pid>:<prio>:<state>  ==>  <pid>:<prio>:<state>

第二种表示进程 wakeup 的信息:

1
2
3
	Wake ups: 
       Current task               Task waking up 
  <pid>:<prio>:<state>    +  <pid>:<prio>:<state>

这里举一个实例:

1
2
3
4
5
6
7
 # tracer: sched_switch 
 # 
 #  TASK_PID   CPU#     TIMESTAMP             FUNCTION 
 #     |         |            |                  | 
   fon-6263  [000] 4154504638.932214:  6263:120:R +   2717:120:S 
   fon-6263  [000] 4154504638.932214:  6263:120:? ==> 2717:120:R 
   bash-2717 [000] 4154504638.932214:  2717:120:S +   2714:120:S

第一行表示进程 fon 进程 wakeup 了 bash 进程。其中 fon 进程的 pid 为 6263,优先级为 120,进程状态为 Ready 。她将进程 ID 为 2717 的 bash 进程唤醒。

第二行表示进程切换发生,从 fon 切换到 bash 。

irqsoff tracer 输出

有四个 tracer 记录内核在某种状态下最长的时延,irqsoff 记录系统在哪里关中断的时间最长; preemptoff/preemptirqsoff 以及 wakeup 分别记录禁止抢占时间最长的函数,或者系统在哪里调度延迟最长 (wakeup) 。这些 tracer 信息对于实时应用具有很高的参考价值。

为了更好的表示延迟,ftrace 提供了和 trace 类似的 latency_trace 文件。以 irqsoff 为例演示如何解读该文件的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 # tracer: irqsoff 
 irqsoff latency trace v1.1.5 on 2.6.26 
 -------------------------------------------------------------------- 
 latency: 12 us, #3/3, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2) 
    ----------------- 
    | task: bash-3730 (uid:0 nice:0 policy:0 rt_prio:0) 
    ----------------- 
 => started at: sys_setpgid 
 => ended at:   sys_setpgid 
 #                _------=> CPU# 
 #               / _-----=> irqs-off 
 #              | / _----=> need-resched 
 #              || / _---=> hardirq/softirq 
 #              ||| / _--=> preempt-depth 
 #              |||| / 
 #              |||||     delay 
 #  cmd     pid ||||| time  |   caller 
 #     \   /    |||||   \   |   / 
    bash-3730  1d...    0us : _write_lock_irq (sys_setpgid) 
    bash-3730  1d..1    1us+: _write_unlock_irq (sys_setpgid) 
    bash-3730  1d..2   14us : trace_hardirqs_on (sys_setpgid)

在文件的头部,irqsoff tracer 记录了中断禁止时间最长的函数。在本例中,函数 trace_hardirqs_on 将中断禁止了 12us 。

文件中的每一行代表一次函数调用。 Cmd 代表进程名,pid 是进程 ID 。中间有 5 个字符,分别代表了 CPU#,irqs-off 等信息,具体含义如下:

CPU# 表示 CPU ID ;

irqs-off 这个字符的含义如下:’ d ’表示中断被 disabled 。’ . ’表示中断没有关闭;

need-resched 字符的含义:’ N ’表示 need_resched 被设置,’ . ’表示 need-reched 没有被设置,中断返回不会进行进程切换;

hardirq/softirq 字符的含义 : ‘H’ 在 softirq 中发生了硬件中断, ‘h’ – 硬件中断,’ s ’表示 softirq,’ . ’不在中断上下文中,普通状态。

preempt-depth: 当抢占中断使能后,该域代表 preempt_disabled 的级别。

在每一行的中间,还有两个域:time 和 delay 。 time: 表示从 trace 开始到当前的相对时间。 Delay 突出显示那些有高延迟的地方以便引起用户注意。当其显示为 ! 时,表示需要引起注意。

function graph tracer 输出

Function graph tracer 和 function tracer 类似,但输出为函数调用图,更加容易阅读:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 # tracer: function_graph 
 # 
 # CPU  OVERHEAD/DURATION      FUNCTION CALLS 
 # |     |   |                 |   |   |   | 
 0)               |  sys_open() { 
 0)               |    do_sys_open() { 
 0)               |      getname() { 
 0)               |        kmem_cache_alloc() { 
 0)   1.382 us    |          __might_sleep(); 
 0)   2.478 us    |        } 
 0)               |        strncpy_from_user() { 
 0)               |          might_fault() { 
 0)   1.389 us    |            __might_sleep(); 
 0)   2.553 us    |          } 
 0)   3.807 us    |        } 
 0)   7.876 us    |      } 
 0)                |      alloc_fd() { 
 0)   0.668 us    |        _spin_lock(); 
 0)   0.570 us    |        expand_files(); 
 0)   0.586 us    |        _spin_unlock();

OVERHEAD 为 ! 时提醒用户注意,该函数的性能比较差。上面的例子中可以看到 sys_open 调用了 do_sys_open,依次又调用了 getname(),依此类推。

Sysprof tracer 的输出

Sysprof tracer 定时对内核进行采样,她的输出文件中记录了每次采样时内核正在执行哪些内核函数,以及当时的内核堆栈情况。

每一行前半部分的格式和 3.1.1 中介绍的 function tracer 一样,只是,最后一部分 FUNCTION 有所不同。

Sysprof tracer 中,FUNCTION 列格式如下:

1
Identifier  address frame_pointer/pid

当 identifier 为 0 时,代表一次采样的开始,此时第三个数字代表当前进程的 PID ;

Identifier 为 1 代表内核态的堆栈信息;当 identifier 为 2 时,代表用户态堆栈信息;显示堆栈信息时,第三列显示的是 frame_pointer,用户可能需要打开 system map 文件查找具体的符号,这是 ftrace 有待改进的一个地方吧。

当 identifier 为 3 时,代表一次采样结束。

Non-Tracer Tracer 的使用

从 2.6.30 开始,ftrace 还支持几种 Non-tracer tracer,所谓 Non-tracer tracer 主要包括以下几种:

1
2
3
Max Stack Tracer
Profiling (branches / unlikely / likely / Functions)
Event tracing

和传统的 tracer 不同,Non-Tracer Tracer 并不对每个内核函数进行跟踪,而是一种类似逻辑分析仪的模式,即对系统进行采样,但似乎也不完全如此。无论怎样,这些 tracer 的使用方法和前面所介绍的 tracer 的使用稍有不同。下面我将试图描述这些 tracer 的使用方法。

Max Stack Tracer 的使用

这个 tracer 记录内核函数的堆栈使用情况,用户可以使用如下命令打开该 tracer:

1
# echo 1 > /proc/sys/kernel/stack_tracer_enabled

从此,ftrace 便留心记录内核函数的堆栈使用。 Max Stack Tracer 的输出在 stack_trace 文件中:

1
2
3
4
5
6
7
8
9
10
 # cat /debug/tracing/stack_trace 
 Depth Size Location (44 entries) 
 ----- ---- -------- 
 0) 3088 64 update_curr+0x64/0x136 
 1) 3024 64 enqueue_task_fair+0x59/0x2a1 
 2) 2960 32 enqueue_task+0x60/0x6b 
 3) 2928 32 activate_task+0x27/0x30 
 4) 2896 80 try_to_wake_up+0x186/0x27f 
 42)  80 80 sysenter_do_call+0x12/0x32

从上例中可以看到内核堆栈最满的情况如下,有 43 层函数调用,堆栈使用大小为 3088 字节。此外还可以在 Location 这列中看到整个的 calling stack 情况。这在某些情况下,可以提供额外的 debug 信息,帮助开发人员定位问题。

Branch tracer

Branch tracer 比较特殊,她有两种模式,即是传统 tracer,又实现了 profiling tracer 模式。

作为传统 tracer 。其输出文件为 trace,格式如下:

1
2
3
4
5
6
 # tracer: branch 
 # 
 #  TASK-PID   CPU#    TIMESTAMP        FUNCTION 
 #    |   |        |          |                | 
  Xorg-2491   [000] 688.561593: [ ok ] fput_light:file.h:29 
  Xorg-2491   [000] 688.561594: [ ok ] fput_light:file_table.c:330

在 FUNCTION 列中,显示了 4 类信息:

函数名,文件和行号,用中括号引起来的部分,显示了分支的信息,假如该字符串为 ok,表明 likely/unlikely 返回为真,否则字符串为 MISS 。举例来说,在文件 file.h 的第 29 行,函数 fput_light 中,有一个 likely 分支在运行时解析为真。我们看看 file.h 的第 29 行:

1
2
3
4
static inline void fput_light(struct file *file, int fput_needed) 
 {LINE29:    if (unlikely(fput_needed)) 
                  fput(file); 
 }

Trace 结果告诉我们,在 688 秒的时候,第 29 行代码被执行,且预测结果为 ok,即 unlikely 成功。

Branch tracer 作为 profiling tracer 时,其输出文件为 profile_annotated_branch,其中记录了 likely/unlikely 语句完整的统计结果。

1
2
3
4
 #cat trace_stat/branch_ annotated 
 correct incorrect    %      function            file        line 
 ------- ----------  ---- ------------------ -------------- ----- 
 0      46             100   pre_schedule_rt    sched_rt.c     1449

下面是文件 sched_rt.c 的第 1449 行的代码:

1
2
if (unlikely(rt_task(prev)) && rq->rt.highest_prio.curr > prev->prio) 
    pull_rt_task(rq);

记录表明,unlikely 在这里有 46 次为假,命中率为 100% 。假如为真的次数更多,则说明这里应该改成 likely 。

Workqueue profiling

假如您在内核编译时选中该 tracer,ftrace 便会统计 workqueue 使用情况。您只需使用下面的命令查看结果即可:

1
# cat /debug/tracing/trace_stat/workqueue

典型输出如下:

1
2
3
4
5
6
7
 # CPU INSERTED  EXECUTED  NAME 
 #  |     |         |           | 
   0   38044    38044    events/0 
   0     426      426    khelper 
   0    9853     9853    kblockd/0 
   0       0        0    kacpid 

可以看到 workqueue events 在 CPU 0 上有 38044 个 worker 被插入并执行。

Event tracer

Event t不间断地记录内核中的重要事件。用户可以用下面的命令查看 ftrace 支持的事件。

1
# cat /debug/tracing/available_event

下面以跟踪进程切换为例讲述 event tracer 的使用。首先打开 event tracer,并记录进程切换:

1
2
3
# echo sched:sched_switch >> /debug/tracing/set_event 
# echo sched_switch >> /debug/tracing/set_event 
# echo 1 > /debug/tracing/events/sched/sched_switch/enable

上面三个命令的作用是一样的,您可以任选一种。

此时可以查看 ftrace 的输出文件 trace:

1
2
3
4
5
6
7
 >head trace 
 # tracer: nop 
 # 
 #   TASK-PID CPU#  TIMESTAMP FUNCTION 
 #    | |      |     |             | 
 <idle>-0 [000] 12093.091053: sched_switch: task swapper:0 [140] ==> 
  /user/bin/sealer:2612 [120]

我想您会发现该文件很容易解读。如上例,表示一个进程切换 event,从 idle 进程切换到 sealer 进程。


ftrace 的实现

研究 tracer 的实现是非常有乐趣的。理解 ftrace 的实现能够启发我们在自己的系统中设计更好的 trace 功能。

ftrace 的整体构架

Ftrace 的整体构架: 图 1. ftrace 组成

ftrace 组成

Ftrace 有两大组成部分,一是 framework,另外就是一系列的 tracer 。每个 tracer 完成不同的功能,它们统一由 framework 管理。 ftrace 的 trace 信息保存在 ring buffer 中,由 framework 负责管理。 Framework 利用 debugfs 系统在 /debugfs 下建立 tracing 目录,并提供了一系列的控制文件。

本文并不打算系统介绍 tracer 和 ftrace framework 之间的接口,只是打算从纯粹理论的角度,简单剖析几种具体 tracer 的实现原理。假如读者需要开发新的 tracer,可以参考某个 tracer 的源代码。 Function tracer 的实现

Ftrace 采用 GCC 的 profile 特性在所有内核函数的开始部分加入一段 stub 代码,ftrace 重载这段代码来实现 trace 功能。

gcc 的 -pg 选项将在每个函数入口处加入对 mcount 的调用代码。比如下面的 C 代码。

1
2
3
4
5
//test.c 
void foo(void) 
{
	printf("foo"); 
}

用 gcc 编译:

1
gcc -S test.c

反汇编如下: 清单 2. test.c 不加入 pg 选项的汇编代码

1
2
3
4
5
6
7
8
_foo: 
    pushl   %ebp 
    movl    %esp, %ebp 
    subl    $8, %esp 
    movl    $LC0, (%esp) 
    call    _printf 
    leave 
    ret

再加入 -gp 选项编译:

1
gcc -pg -S test.c

得到的汇编如下: 清单 3. test.c 加入 pg 选项后的汇编代码

1
2
3
4
5
6
7
8
9
10
11
_foo: 
        pushl   %ebp 
        movl    %esp, %ebp 
        subl    $8, %esp 
 LP3: 
        movl    $LP3,%edx 
        call    _mcount 
        movl    $LC0, (%esp) 
        call    _printf 
        leave 
        ret

增加 pg 选项后,gcc 在函数 foo 的入口处加入了对 mcount 的调用:call _mcount 。原本 mcount 由 libc 实现,但您知道内核不会连接 libc 库,因此 ftrace 编写了自己的 mcount stub 函数,并借此实现 trace 功能。

在每个内核函数入口加入 trace 代码,必然会影响内核的性能,为了减小对内核性能的影响,ftrace 支持动态 trace 功能。

当 CONFIG_DYNAMIC_FTRACE 被选中后,内核编译时会调用一个 perl 脚本:recordmcount.pl 将每个函数的地址写入一个特殊的段:__mcount_loc

在内核初始化的初期,ftrace 查询 __mcount_loc 段,得到每个函数的入口地址,并将 mcount 替换为 nop 指令。这样在默认情况下,ftrace 不会对内核性能产生影响。

当用户打开 ftrace 功能时,ftrace 将这些 nop 指令动态替换为 ftrace_caller,该函数将调用用户注册的 trace 函数。其具体的实现在相应 arch 的汇编代码中,以 x86 为例,在 entry_32.s 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ENTRY(ftrace_caller) 
	cmpl $0, function_trace_stop 
	jne  ftrace_stub 
	pushl %eax 
	pushl %ecx 
	pushl %edx 
	movl 0xc(%esp), %eax 
	movl 0x4(%ebp), %edx 
	subl $MCOUNT_INSN_SIZE, %eax 
.globl ftrace_call 
ftrace_call: 
	call ftrace_stubline 10popl %edx 
	popl %ecx 
	popl %eax 

.globl ftrace_stub 
ftrace_stub: 
	ret 
END(ftrace_caller)

Function tracer 将 line10 这行代码替换为 function_trace_call() 。这样每个内核函数都将调用 function_trace_call() 。

在 function_trace_call() 函数内,ftrace 记录函数调用堆栈信息,并将结果写入 ring buffer,稍后,用户可以通过 debugfs 的 trace 文件读取该 ring buffer 中的内容。

Irqsoff tracer 的实现

Irqsoff tracer 的实现依赖于 IRQ-Flags 。 IRQ-Flags 是 Ingo Molnar 维护的一个内核特性。使得用户能够在中断关闭和打开时得到通知,ftrace 重载了其通知函数,从而能够记录中断禁止时间。即,中断被关闭时,记录下当时的时间戳。此后,中断被打开时,再计算时间差,由此便可得到中断禁止时间。

IRQ-Flags 封装开关中断的宏定义:

1
2
#define local_irq_enable() \ 
	do { trace_hardirqs_on (); raw_local_irq_enable(); } while (0)

ftrace 在文件 ftrace_irqsoff.c 中重载了 trace_hardirqs_on 。具体代码不再罗列,主要是使用了 sched_clock()函数来获得时间戳。

hw-branch 的实现

Hw-branch 只在 IA 处理器上实现,依赖于 x86 的 BTS 功能。 BTS 将 CPU 实际执行到的分支指令的相关信息保存下来,即每个分支指令的源地址和目标地址。

软件可以指定一块 buffer,处理器将每个分支指令的执行情况写入这块 buffer,之后,软件便可以分析这块 buffer 中的功能。

Linux 内核的 DS 模块封装了 x86 的 BTS 功能。 Debug Support 模块封装了和底层硬件的接口,主要支持两种功能:Branch trace store(BTS) 和 precise-event based sampling (PEBS) 。 ftrace 主要使用了 BTS 功能。

branch tracer 的实现

内核代码中常使用 likely 和 unlikely 提高编译器生成的代码质量。 Gcc 可以通过合理安排汇编代码最大限度的利用处理器的流水线。合理的预测是 likely 能够提高性能的关键,ftrace 为此定义了 branch tracer,跟踪程序中 likely 预测的正确率。

为了实现 branch tracer,重新定义了 likely 和 unlikely 。具体的代码在 compiler.h 中。 清单 6. likely/unlikely 的 trace 实现

1
2
3
4
5
6
 # ifndef likely 
 #  define likely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1)) 
 # endif 
 # ifndef unlikely 
 #  define unlikely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0)) 
 # endif

其中 branch_check 的实现如下: 清单 7. branch_check 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define __branch_check__(x, expect) ({\ 
    int ______r;    \ 
    static struct ftrace_branch_data \ 
    __attribute__((__aligned__(4)))  \ 
    __attribute__((section("_ftrace_annotated_branch"))) \ 
                         ______f = { \ 
                           .func = __func__, \ 
                           .file = __FILE__, \ 
                           .line = __LINE__, \ 
                    }; \ 
              ______r = likely_notrace(x);\ 
              ftrace_likely_update(&______f, ______r, expect); \ 
              ______r; \ 
  })

ftrace_likely_update() 将记录 likely 判断的正确性,并将结果保存在 ring buffer 中,之后用户可以通过 ftrace 的 debugfs 接口读取分支预测的相关信息。从而调整程序代码,优化性能。


http://blog.csdn.net/tommy_wxie/article/details/7340701