从命令行获得用户命令,如果输入错误就会打印help信息。
ksplice更换版本有3种途径:
1)patch文件
2)diffext指定新文件的后缀
3)使用git指定新的标记
如果没有指定orig_config_dir(指定config的目录),则默认路径为
"$linuxtree/ksplice"
ksplice还要求config目录下还要有System.map文件。
最好在config目录下再创建一个build目录,用于分离二进制。
如果config目录下存在flags文件,则读入此文件的内容作为kbuild_flags。
ksplice每次制作补丁包都会产生一个随机数kid.
有一些内核System.map的符号地址与运行中的符号地址有一个偏移量。
所以现在通过查找printk的地址,来与运行中内核的printk地址进行比较。
来找出此偏移量。
根据配置变量,组织make命令:
make -rR
如果定义了jobs
-jn
如果定义了verbose level
V=1 否则 -s
make_ksplice变量:
@make -f $datadir/Makefile.ksplice @kbuild_flags
如果定义了build_modules
KSPLICE_BUILD_MODULES=1
如果linuxtree目录下不存在.config,则把config_dir中的.config copy到linuxtree下。
进行revert:
如果一进行过制作补丁,现在要先恢复原始代码。
my @revert_flags = ("KSPLICE_MODE=revert");
revert_orig()
find出.KSPLICE_presrc的文件,回复原始文件。
执行
@make_ksplice, @revert_flags
执行上执行的命令是:
make -rR -f Makefile.ksplice KSPLICE_MODE=revert
我们在看看Makefile.ksplice文件:
Makefile.ksplice默认目标是__ksplice
__ksplice: $(ksplice-deps) $(ksplice-dirs)
@:
此目标只是依赖两个dirs,没有具体的执行命令。说明所有的动作都是在依赖中执行的。
如何调用到内核的Makefile的呢?
- include $(srctree)/scripts/Makefile.build
-
ksplice-deps += $(vmlinux-dirs)
-
ksplice-deps += $(subdir-ym) $(always)
-
ksplice-deps += __build
-
ksplice-deps += $(ksplice-revert-deps)
-
-
ksplice-revert-dirs = $(vmlinux-alldirs:%=_ksplice_%)
-
ksplice-dirs += $(ksplice-revert-dirs)
- ksplice-dirs += $(subdir-ymn:%=_ksplice_%)
对于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
对revert的cmd
- cmd_ksplice-revert = touch -r ksplice-revert-stamp $(@:_ksplice-revert_%=%); \
- mv $(@:_ksplice-revert_%=%) $(@:_ksplice-revert_%.KSPLICE_pre=%)
如果MODE 等于revert
include $(srctree)/scripts/Makefile.clean
这是会根据
ksplice-clean-files
把kslipse生成的文件clean掉。
看看实际例子revert都做了些什么:
- REVERT net/ipv6/ip6_tunnel.o
-
REVERT net/ipv6/sit.o
-
SNAP net/ipv6/ip6_tunnel.o
-
SNAP net/ipv6/sit.o
-
CLEAN net/ipv6/ip6_tunnel.o.KSPLICE_new_code
-
CLEAN net/ipv6/sit.o.KSPLICE_new_code
-
CLEAN net/ipv6/ip6_tunnel.o.KSPLICE_old_code
- CLEAN net/ipv6/sit.o.KSPLICE_old_code
SNAP
revert完毕后就开始进入snap阶段。
也很简单,snap执行的命令和revert类似,只是KSPLICE_MODE不同。
make_ksplice, KSPLICE_MODE=snap
- 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
首先.o.KSPLICE依赖.o,生成完.o后,调用ksplice-obj snap来生成.o.KSPLICE
/usr/local/share/ksplice/ksplice-obj.pl snap init/main.o.KSPLICE
snap的工作就是生成一个.o.KSPLICE空文件。empty_diff生成空文件。
所以之前理解错了,.o.KSPLICE并不是把.o做一个备份用的。
应该只是把.o.KSPLICE作为一个标志位,只是为了后续diff阶段,
如果有不同的.o就会把.o.KSPLICE中写入1,最后遍历所有.o.KSPLICE,哪些为1就知道哪些有差异了。
如果是我这样理解,实际上就没有必要snap阶段了。直接diff就可以了,
毕竟diff的时候如果两个文件相同则只会生成empty_diff.
除了标志位的作用,还有就是作为目标驱动。
$(obj)/%.o.KSPLICE: $(obj)/%.o
$(call cmd,ksplice-diff)
无论snap还是diff都是要创建目标.o.KSPLICE但是动作不一样。
并且snap是FORCE,但diff不是强制的。
所以关键点就在这里,
因为打了patch后,就会重新生成patch对应的.o,此时依赖条件更新了,就会执行diff命令。
那么如果没有备份,那么diff的时候是如何比较的呢?
其实diff比较的是.o.KSPLICE_pre 和 新编译的.o
.o.KSPLICE_pre又是哪来的呢?
从do_diff的实现来看,在diff之前,KSPLICE_pre应该已经生成了。
生成KSPLICE_pre的命令只有
cmd_ksplice-cow = cp -a $@ $@.KSPLICE_pre
revert和snap就差一个mode,为什么一个会编译一个不会编译呢?
就在依赖条件上,snap要依赖.o
接下来编译ksplice 的内核代码部分
cp kmodsrc到一个临时目录。
执行
- @make_kmodsrc = (@make, "-C", $kernel_headers_dir, "M=$kmodsrc", "KSPLICE_KID=$kid", "KSPLICE_VERSION=0.9.9", "map_printk=$map_printk")
编译内核模块。
然后再执行make module_install
patch
runval_infile($patchfile, "patch", @patch_opt, "-bz", ".KSPLICE_presrc");
-bz的意思:
-b备份原始文件
-z是用.KSPLICE_presrc为后缀备份原始文件。
打上补丁后,执行
make_ksplice KSPLICE_MODE=diff
看看实际例子中diff的动作:
COW net/ipv6/sit.o
CC [M] net/ipv6/sit.o
DIFF net/ipv6/sit.o
如何执行到COW的呢?
虽然我不知道如何执行的,但是猜测它的作用是在.o执行动作之前,
检测是否需要执行命令,如果需要更新,则执行cow,把.o cp为.o.KSPLICE_pre。
所以ksplice-cow-check的作用就是检测目标是否需要更新。
snap和diff又非常像
cmd_ksplice-snap = $(ksplice-script) snap $@
cmd_ksplice-diff = $(ksplice-script) diff $@
查看do_diff代码
cmp .o.KSPLICE_pre 和 新编译的.o,如果相同,那不做什么。
如果要有不同,则调用
- runval("$libexecdir/ksplice-objmanip", $obj, "$obj.KSPLICE_new_code", "keep-new-code", "$obj.KSPLICE_pre", $ENV{KSPLICE_KID});
- runval("$libexecdir/ksplice-objmanip", $obj_pre, "$obj.KSPLICE_old_code", "keep-old-code");
接下来开始处理模块编译
runstr(qw(find -name *.KSPLICE* ! ( -name *.KSPLICE -empty ) ! -name .*.KSPLICE.cmd -print0))
找出所有*.KSPLICE*非空的文件。
读入文件的内容保存到@modules中。
例子:
在sit.mod.KSPLICE中内容是:
net/ipv6/sit
- cmd_ksplice-mod = echo $(<:.o.KSPLICE=) > $@; cp -a $< $(<:.KSPLICE=.KSPLICE_new_code) $(<:.KSPLICE=.KSPLICE_old_code) $(KSPLICE_KMODSRC)/
-
rule_ksplice-mod = if [ -s $< ]; then $(echo-cmd) $(cmd_$(1)); fi
-
ksplice-deps += $(ksplice-modnames:%=$(KSPLICE_KMODSRC)/%.mod.KSPLICE)
-
$(KSPLICE_KMODSRC)/%.mod.KSPLICE: $(obj)/%.o.KSPLICE
- $(Q)$(call rule_ksplice-mod,ksplice-mod)
从对mod的处理可以看出,.mod.KSPLICE直接在KSPLICE_KMODSRC中生成。
这是在diff后执行MOD,首先判断-s $<,看.KSPLICE为非空,表示有不同。然后把$< 写入 $@中。
如net/ipv6/sit.o.KSPLICE 取net/ipv6/sit > sit.mod.KSPLICE
并且把sit.o.KSPLICE,sit.o.KSPLICE_new_code,sit.o.KSPLICE_old_code cp 到KMODSRC目录中。
也就是说在diff阶段,new和old.o就应该生成了。
do_diff中调用objmanip来生成的new和old。
- runval(@make_ksplice, "KSPLICE_MODE=modinst", "MODLIB=$tmpdir/modules", "INSTALL_MOD_STRIP=1", "modules=@modulepaths");
在makefile.ksplice中,对modinst的处理
ksplice_modinst:
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst
这里的Makefile.modinst和Makefile.modpost都是内核script中的Makefile
standalone 的作用是是否把ksplice编译成模块,因为可能kernel中已经自带了ksplice内核模块。
在create中分别调用了两次make_kmodsrc,
第一次编译出ksplice.ko
第二次传入参数KSPLICE_MODULES=@modules 生成new.ko和old.ko
我们来看看kmodsrc中的Makefile.
第一次编译的是KSPLICE_CORE
KSPLICE_CORE = ksplice-$(KSPLICE_KID)
obj-m += $(KSPLICE_CORE).o
实际上最终编译成ksplice-kid.ko,还是依靠obj-m的方法编译的。
第二次编译的时候传入modules,同时KSPLICE_SKIP_CORE=1,表示不编译ksplice.ko
- 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
但是一上来先要定义一些变量
- $(foreach mod,$(KSPLICE_MODULES),$(eval $(ksplice-mod-vars)))
-
-
define ksplice-mod-vars
-
obj-m += $(KSPLICE)-new.o $(KSPLICE)-old.o
-
$(KSPLICE)-new-objs = $(ksplice-new-code-objs)
-
$(KSPLICE)-old-objs = $(ksplice-old-code-objs)
-
其中KSPLICE定义为
-
comma ?= ,
-
name-fix ?= $(subst $(comma),_,$(subst -,_,$(1)))
-
target = $(call name-fix,$(mod))
-
KSPLICE_MID = $(KSPLICE_KID)_$(target)
- KSPLICE = ksplice-$(KSPLICE_MID)
target = sit
KSPLICE = ksplice-mbel2vr2_sit
所以我们从ojb-m的值能看出最终需要生成new.ko和old.ko。
以new为例:
- $(KSPLICE)-new-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 的命令:
- $(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的命令:
- $(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
实例看看两次模块编译时的动作:
- CC [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/new-code-ip6_tunnel.o
- LDS /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice.lds
- COLLECT /tmp/ksplice-tmp-NdqKBs/kmodsrc/collect-new-code-ip6_tunnel.o
- CC [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/old-code-ip6_tunnel.o
- COLLECT /tmp/ksplice-tmp-NdqKBs/kmodsrc/collect-old-code-ip6_tunnel.o
- CC [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/new-code-sit.o
- COLLECT /tmp/ksplice-tmp-NdqKBs/kmodsrc/collect-new-code-sit.o
- CC [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/old-code-sit.o
- COLLECT /tmp/ksplice-tmp-NdqKBs/kmodsrc/collect-old-code-sit.o
- LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-new.o
- LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-old.o
- LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-new.o
- LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-old.o
- CC /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-new.mod.o
- LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-new.ko
- CC /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-old.mod.o
- LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-old.ko
- CC /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-new.mod.o
- LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-new.ko
- CC /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-old.mod.o
- LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-old.ko
现在代码大体逻辑还算清楚了,但如何做到不用编译内核只做内核模块的编译呢?
ksplice制作内核补丁使用的是内核kbuild的makefile,但是ncore使用的是自己的makefile。
所以我必须找找制作内核模块补丁执行的命令,并且不能使用kbuild的方法。
我大体猜测,流程是:
先编译一遍原始代码
在编译patch后的代码
比较两次的结果,直接做出patch。
现在不明白的是,比较的是.o文件,难道最后生成的就是根据这些.o的差异生成的补丁吗?
没有必要生成对应的.ko吗?
如果是这样那应该就与是不是模块没有关系了,可能多个模块的patch都会生成一个hotupdate。
这个过程还需要进一步研究。
在lfs的基础上,最好手动执行命令,来完成hotupdate的制作。
目前来说System.map还是非常重要的,不光是用于找map_printk,直接定位的时候都需要用到System.map
而且如果多个内核发生变化,就会分别生成对应的.ko。
我还有疑问了,如果模块B中调用了模块A的函数, 那么如何定位地址呢?
通过实践,此种升级可以成功。
apply的过程:
先insmod ksplice.ko
然后insmod module_new.ko
然后insmod module_old.ko
copy patch 到/var/runn下
升级成功
然后rmmod module_old.ko
好像只要有ld的地方就会调用combine
再来看看do_combine函数,有两个参数输入,
第一个是链接结果的文件名,
第二个参数是组成链接结果的所有的.o文件列表。
ksplice-objs的作用
把.o后边添加.KSPLICE
用到的perl知识:
1) perl中=~的意思是模式匹配,所以
my ($obj) = $out =~ /^(.*)\.KSPLICE$/ or die; 的意思是
注意()中的内容是捕获的内容,所以就是匹配*.KSPLICE,并且把文件的名字保存给obj。
2)unlink 删除文件
3)glob 返回通配符匹配的文件名列表。
4)next 相当于C语言中的continue
5)unless 相当于not if,就是当条件为false时执行block。
6)open fd '>' "filename"
打开一个文件用于写入,文件句柄是fd
7)文件test
-e 文件或目录存在
-z 文件为0
-s 文件或目录存在并且size不为0
-d 是目录
-f 是文件
-l 是链接