kk Blog —— 通用基础


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

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