2012/7

KVM 中搭建 VLAN 和 IPv6 环境

普通的 IPv4 环境很简单,如果你使用 virt-manager 的话它自动都给你搭好了。每个 kvm guest 都在同一个子网内,通过 bridge 连接到一起,然后通过 host 上的 NAT 访问外网,如下所示:

# brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.5254002e2392 yes virbr0-nic
vnet0
vnet1

iptables -L -t nat

Chain PREROUTING (policy ACCEPT)
target prot opt source destination

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE tcp — 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
MASQUERADE udp — 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
MASQUERADE all — 192.168.122.0/24 !192.168.122.0/24
稍微有点儿困难的是在此基础上搭建 VLAN 和 IPv6 环境。

先看VLAN,虽然我们现在还没有 openvswitch,我们可以通过 bridge 设置一个简单的 vlan 局域网。因为 virt-manager 默认使用的是 192.168.122.0/24,我为VLAN单独开了一个 192.168.1.0/24,有两台机器 A 和 B,A 上运行此VLAN的 DHCP 服务,B 就可以直接动态获取地址了。

机器 A 上运行:

vconfig add eth0 2
ifconfig eth0.2 192.168.1.1
dnsmasq —listen-address=192.168.1.1 -i eth0.2 —dhcp-range=192.168.1.2,192.168.1.240

机器 B 上运行:

vconfig add eth0 2
dhclient eth0.2

注:vconfig 已经过时了,现在都使用 ip,我之所以还使用它是因为整个命令比较短。;-)

再来看 IPv6,因为我对 IPv6 不熟悉,所以折腾了好长时间最后还是不得不使用 sit tunnel,而且 IP 地址也是静态手工设置的。还是假设 A 和 B 两台机器,先让 IPv4 在上面跑起来,然后在两台机器上分别运行下面的命令:

echo 1 > /proc/sys/net/ipv4/conf/all/forwarding
echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
ip tunnel add sit1 mode sit local ${SELF} remote ${REMOTE} dev eth0
ip -6 addr add fd00:1:2:3::2/64 dev sit1
ip -6 route add fd00:1:2:3::1/64 dev sit1
ifconfig sit1 up

${SELF} 为自己的IPv4地址;${REMOTE}为对方的IPv4地址,即在A上面为B的地址,B上面为A的地址。他们的IPv6地址分别是fd00:1:2:3::1 和 fd00:1:2:3::2。

Update:如果使用 libvirt 的话,可以用 virsh net-edit <NET> 编辑网络配置,添加下面两行:

<ip family=’ipv6’ address=’fd00:1:2:3::1’ prefix=’64’>
</ip>

可参考这里

去尼玛的现实好么!

时间过得真快,离开大理已经四个多月了。早就开始各种怀念在大理的日子了,尤其是在这么炎热的夏天,一想到大理现在那只有20多度的气温就恨不得买张机票马上飞过去。可是很无奈,现实中有各种各样的事儿让我无法回到大理。

仿佛离开大理之后再也回不到那种无忧无虑的日子了。在大理的时候每天都那么悠闲,喝喝茶、晒晒太阳、摆摆地摊,从来不用为现在为将来发愁,因为来大理的人们大多数也都是这个状态。似乎也只有在大理这种地方,你才能活在当下,不为前途顾虑,不为现实烦恼,无所事事地活在当下……

仔细想想人这一生:小时候不太懂事,可以毫无顾虑地去玩,这应该是人生最快乐的时光;长大了上了学就开始顾虑各种考试;好不容易挨到高中毕业,上了大学又开始顾虑找工作,找对象;大学毕业了又开始顾虑工作薪水,买车买房子,结婚生孩子;有了孩子顾虑就更多了,一直到孩子长大成人,可那时候我们也已经老了,然后又不得不顾虑自己的身体健康……

你看看,我们这一辈子能够完完全全只为自己活着的时间能有多少?这其中,能够完完全全活在当下,不去考虑将来的时间更是少之又少!更何况,有些时候为了自己活着和为了别人活着会有很大的冲突,我们不得不在两者之间放弃一个。人这样活着能不累么!

有时候真想竖起中指对现实说:去尼玛的现实好么!去尼玛的前途好么!老子只想活在当下!

Parallelism != Concurrency

Parallelism,中文一般翻译成“并行”;Concurrency,中文一般翻译成“并发”。这两个词往往被混淆使用,即使在英文资料中,中文翻译使得这种情况变得更糟糕。其实这两个词的含义是完全不同的,这里并不是在咬文嚼字,而是想说明它们本质上确实有不同。为了表达方便,下文一律使用英文中的原词。

Wikipedia 上对 Concurrency 的定义如下:

Concurrent computing is a form of computing in which programs are designed as collections of interacting computational processes that may be executed in parallel. Concurrent programs (processes or threads) can be executed on a single processor by interleaving the execution steps of each in a time-slicing way, or can be executed in parallel by assigning each computational process to one of a set of processors that may be close or distributed across a network.
可见,concurrency 并不意味着一定是 parallelism,它是把多个互相作用的计算过程(注:上文第一句中的 processes 不是进程的意思!)组合起来的一种计算形式。为了完成一个大的任务,我们可以把它切割成几个独立的几个步骤,然后把每个步骤交给单独的线程去完成,那么在 UP 上 concurrency 看起来就像是这样:

Parallelism 的定义如下:

Parallel computing is a form of computation in which many calculations are carried out simultaneously, operating on the principle that large problems can often be divided into smaller ones, which are then solved concurrently (“in parallel”).
也就是多个计算同时执行,在 SMP 上如下图所示:

这里需要注意的是:广义上 parallelism 并不总是指 SMP,广义的 parallelism 也可以发生在指令上,比如支持 SIMD 的 CPU,像 Intel 的 SSE 指令;也可以发生在 CPU 内部的指令 pipeline 上。请结合上下文去理解。

考虑到现在流行的操作系统都是多任务的,那么 UP 也可以达到 SMP 的效果,对于用户空间的程序来说,多任务操作系统即使运行在 UP 上也是和 SMP 上一样的。因此下文中我们将不再区分 UP 和 SMP,并且假设操作系统就是多任务的,即所有多线程的程序都是并行运行的。

这时候,另一个问题随之而来:有 parallelism 的程序一定是 concurrent 的么?未必!根据上面 concurrency 的定义,不同的处理步骤之间需要是互相作用的(interacting),我们完全可以写一个多线程程序,线程之间没有任何互相的作用:比如我们要处理 [1 ~ 40000] 这个数组,我们可以启动4个线程,让它们分别处理 [1 ~ 10000]、[10000 ~ 20000]、[20000 ~ 30000]、[30000 ~ 40000],最后主线程把 4 个结果汇总成一个。这个多线程程序显然可以做到 parallelism,但它没有 concurrency!

有人这么认为,说 concurrency 是程序的属性;而 parallelism 程序执行时机器的属性。现在我们可以知道,这是不严格的。只有当 parallel 的程序的线程之间有了互相作用之后才会有 concurrency!concurrency 比 parallelism 要难得多。

Rob Pike 大牛是这么概括它们的区别的:

Concurrency is a way to structure a program by breaking it into pieces that can be executed independently. Communication is the means to coordinate the independent executions.
所以,concurrency 和 parallelism 的本质区别是,多个线程/进程(或程序的多个部分)之间有没有互相作用和交流。这种交流分两种:一种是共享内存,一种是消息传递。前者我们很熟悉,我们最常用的锁(Lock))或者信号量(Semaphore)就属于共享内存的一种。

另外,需要说明的是,并不是所有的 paraellel 的程序都使用多线程/进程,它们也可以使用 coroutine,使用 events。一言以蔽之,parallelism 是建立在同步(synchronous )代码的基础上,同步代码同时运行;而把一个程序分成几个同步的部分,它们之间允许进行 communication 就有 concurrency 了!

借用 Rob Pike 使用的图来说明一下就是:

Original Program

Parallel Program

Concurrent Program

从图中我们可以一目了然地看出两者之间并没有直接的关系。最后,在《Parallelism is not concurrency》一文中对此有更精辟的概括:

Concurrency is concerned with nondeterministic composition of programs (or their components). Parallelism is concerned with asymptotic efficiency of programs with deterministic behavior. Concurrency is all about managing the unmanageable[… ] Parallelism, on the other hand, is all about dependencies among the subcomputations of a deterministic computation.
参考资料:

1. http://stackoverflow.com/questions/1050222/concurrency-vs-parallelism-what-is-the-difference
2. http://existentialtype.wordpress.com/2011/03/17/parallelism-is-not-concurrency/
3. http://ghcmutterings.wordpress.com/2009/10/06/parallelism-concurrency/
4. http://stackoverflow.com/questions/1897993/difference-between-concurrent-programming-and-parallel-programming
5. http://concur.rspace.googlecode.com/hg/talk/concur.html#title-slide

Linux kernel memory model

稍微了解 Linux 内核的人都知道,在 x86 上内核中所有的 struct page 是放在一个数组中管理的,它就是 mem_map,通过它我们就可以用 pfn 作为 index 来找到对应的 struct page 了:

[c]

define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))

define __page_to_pfn(page) ((unsigned long)((page) - mem_map) +

                             ARCH_PFN_OFFSET)

[/c]

这是以前旧的 memory model(这个词很流行,你应该在很多地方见过,比如 C/C++标准),现在情况变了。

因为对 NUMA 和内存热插拔(memory hot-plug)的支持,Linux 内核中现在又引入两个新的 memory model,之前那个旧的被称为 Flat memory,新的两个被称为 Discontiguous memory 和 Sparse memory。它们对应的选项是:CONFIG_FLATMEM,CONFIG_DISCONTIGMEM,CONFIG_SPARSEMEM。让我们来看看这两个新的 memory model。

顺便多说两句,NUMA 和内存热插拔并没有直接的联系,NUMA 系统不一定支持内存的热插拔,而可以进行内存热插拔的系统也不一定是 NUMA 的!NUMA 支持对应的选项是 CONFIG_NUMA,而内存热插拔对应的选项是 CONFIG_MEMORY_HOTPLUG 和 CONFIG_MEMORY_HOTREMOVE。由此也可以看出 Linux 内核配置是多么灵活,你可以任意搭配你需要的选项。

CONFIG_DISCONTIGMEM

mm/Kconfig 中对它的介绍是:

This option provides enhanced support for discontiguous memory systems, over FLATMEM. These systems have holes in their physical address spaces, and this option provides more efficient handling of these holes. However, the vast majority of hardware has quite flat address spaces, and can have degraded performance from the extra overhead that this option imposes.

Many NUMA configurations will have this as the only option.
Discontiguous memory 其实很简单,它上从 flat memory 的基础上对 NUMA 进行扩展得出来的。每一个 node 都有一个 struct pglist_data,对于 discontiguous memory 其中记录了每个 node 的 node_start_pfn、node_spanned_pages、node_mem_map,分别表示该 node 的起始页的 PFN、物理页的数量、这些页面的 mem_map。我们可以看 alloc_node_mem_map() 是如何初始化这几个值的:

[c]
static void __init_refok alloc_node_mem_map(struct pglist_data pgdat)
{
/
Skip empty nodes */
if (!pgdat->node_spanned_pages)
return;

ifdef CONFIG_FLAT_NODE_MEM_MAP

    /* ia64 gets its own node_mem_map, before this, without bootmem */
    if (!pgdat-&gt;node_mem_map) {
            unsigned long size, start, end;
            struct page *map;

            /*
             * The zone's endpoints aren't required to be MAX_ORDER
             * aligned but the node_mem_map endpoints must be in order
             * for the buddy allocator to function correctly.
             */
            start = pgdat-&gt;node_start_pfn &amp; ~(MAX_ORDER_NR_PAGES - 1);
            end = pgdat-&gt;node_start_pfn + pgdat-&gt;node_spanned_pages;
            end = ALIGN(end, MAX_ORDER_NR_PAGES);
            size =  (end - start) * sizeof(struct page);
            map = alloc_remap(pgdat-&gt;node_id, size);
            if (!map)
                    map = alloc_bootmem_node_nopanic(pgdat, size);
            pgdat-&gt;node_mem_map = map + (pgdat-&gt;node_start_pfn - start);
    }
    //....

}
[/c]

所以,它对应的 pfn_to_page() 和 page_to_pfn() 定义如下:

[c]

define arch_local_page_offset(pfn, nid)

    ((pfn) - NODE_DATA(nid)-&gt;node_start_pfn)

define __pfn_to_page(pfn)

({ unsigned long pfn = (pfn);
unsigned long
nid = arch_pfn_to_nid(pfn);
NODE_DATA(
nid)->node_mem_map + arch_local_page_offset(pfn, nid);
})

define __page_to_pfn(pg)

({ const struct page __pg = (pg);
struct pglist_data
pgdat = NODE_DATA(page_to_nid(pg));
(unsigned long)(pg - pgdat->node_mem_map) +
__pgdat->node_start_pfn;
})
[/c]

两个 node 之间的内存未必是连续的,中间可能有内存空洞,空洞的单位是 64M,但整个内存依然是 flat 的:

[c]
/*

  • generic node memory support, the following assumptions apply:
    *
  • 1) memory comes in 64Mb contiguous chunks which are either present or not
  • 2) we will not have more than 64Gb in total
    *
  • for now assume that 64Gb is max amount of RAM for whole system
  • 64Gb / 4096bytes/page = 16777216 pages
    */

    define MAX_NR_PAGES 16777216

    define MAX_SECTIONS 1024

    define PAGES_PER_SECTION (MAX_NR_PAGES/MAX_SECTIONS)

extern s8 physnode_map[];

static inline int pfn_to_nid(unsigned long pfn)
{

ifdef CONFIG_NUMA

    return((int) physnode_map[(pfn) / PAGES_PER_SECTION]);

else

    return 0;

endif

}
[/c]

CONFIG_SPARSEMEM

Sparse memory 是一个相对比较复杂的模型。mm/Kconfig 中对它的介绍是:

This will be the only option for some systems, including memory hotplug systems. This is normal. For many other systems, this will be an alternative to “Discontiguous Memory”. This option provides some potential performance benefits, along with decreased code complexity, but it is newer, and more experimental.

它主要是因为支持内存热插拔引入的。对于支持内存热插拔的系统,系统中的某一个部分内存可以随时被移除和添加,这就是得原本连续的内存空间变得稀疏。这个内存模型是把所有的内存空间划分成一个个 section,每个 section 都是同样大小的,可以进行热插拔的内存大小就是以 section 为单位的。在 x86_64 上面,每个 section 是 128M:

[c]

ifdef CONFIG_X86_32

ifdef CONFIG_X86_PAE

define SECTION_SIZE_BITS 29

define MAX_PHYSADDR_BITS 36

define MAX_PHYSMEM_BITS 36

else

define SECTION_SIZE_BITS 26

define MAX_PHYSADDR_BITS 32

define MAX_PHYSMEM_BITS 32

endif

else / CONFIG_X86_32 /

define SECTION_SIZE_BITS 27 / matt - 128 is convenient right now /

define MAX_PHYSADDR_BITS 44

define MAX_PHYSMEM_BITS 46

endif

[/c]

每个 section 有自己的 mem_map,所以其 __pfn_to_page 的定义如下:

[c]
/*

  • Note: section’s mem_map is encorded to reflect its start_pfn.
  • section[i].section_mem_map == mem_map’s address - start_pfn;
    */

    define __page_to_pfn(pg)

    ({ const struct page *__pg = (pg);
     int __sec = page_to_section(__pg);                      
     (unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec))); 
    
    })

define __pfn_to_page(pfn)

({ unsigned long pfn = (pfn);
struct mem_section *
sec = pfn_to_section(pfn);
section_mem_map_addr(sec) + __pfn;
})
[/c]

Sparse memory 还有两个变异:SPARSEMEM_EXTREME 和 SPARSEMEM_VMEMMAP,前者是对更稀疏的内存做了优化,采用了两层 memory section,即二维数组:

[c]

ifdef CONFIG_SPARSEMEM_EXTREME

define SECTIONS_PER_ROOT (PAGE_SIZE / sizeof (struct mem_section))

else

define SECTIONS_PER_ROOT 1

endif

define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)

define NR_SECTION_ROOTS DIV_ROUND_UP(NR_MEM_SECTIONS, SECTIONS_PER_ROOT)

define SECTION_ROOT_MASK (SECTIONS_PER_ROOT - 1)

ifdef CONFIG_SPARSEMEM_EXTREME

extern struct mem_section *mem_section[NR_SECTION_ROOTS];

else

extern struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];

endif

static inline struct mem_section *__nr_to_section(unsigned long nr)
{
if (!mem_section[SECTION_NR_TO_ROOT(nr)])
return NULL;
return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}
[/c]

后者是针对 x86_64 这种虚拟地址空间比较大的平台做了速度的优化,把 mem_map 里所有 struct page 的地址一一映射进 vmemmap 地址中去,这样它们的虚拟地址就是连续的了:

[c]

define VMEMMAP_START _AC(0xffffea0000000000, UL)

define vmemmap ((struct page *)VMEMMAP_START)

/ memmap is virtually contiguous. /

define __pfn_to_page(pfn) (vmemmap + (pfn))

define __page_to_pfn(page) (unsigned long)((page) - vmemmap)

[/c]

不过额外的代价就是初始化的时候要对每一个 struct page 做 populate,参见 sparse_mem_map_populate() 函数。另外可参考内存热插拔代码中函数 add_section() 和 remove_section() 的实现。

treap

treap 是一个很有意思的数据结构,从名字也能看得出来,它是 tree 和 heap 的混合产物。为什么会有这么一个数据结构还得从二叉树(BST)说起。

我们都知道,普通的二叉树是不平衡的,在某些情况下进行插入删除操作可以导致时间复杂度从 O(logN) 下降到 O(N)。为了解决平衡的问题,有很多新的二叉树被引入,比如大家熟知的一些:Linux 内核中 CFS 使用到的红黑树(Red-black tree),数据结构课上都会讲到的 AVL 树。这些树都因为要进行复杂的旋转操作而不容易实现。

那么有没有一个实现容易的平衡二叉树呢?Treap 就是一个。普通的二叉树节点只有 key,而 treap 又多了一个 priority,这里的 priority 就是 heap (优先队列)中的 priority。这样, treap 既可以利用二叉树的特性,又可以利用 heap 的特性了。

看它是怎么利用的,以 Perl 的 Tree::Treap 模块为例。

1) 对于搜索,使用二叉树的 key 即可,和普通二叉树没有区别:

[perl]
sub _get_node {
my $self = shift;
my $key = shift;
while(!$self->_is_empty() and $self->ne($key)){
$self = $self->{$self->lt($key)?”left”:”right”}
}
return $self->_is_empty() ? 0 : $self;
}
[/perl]

2) 插入一个新的节点 key=x 时,随机一个整数值 y 作为 priority,利用二叉树搜索 x,在它应该出现的位置创建一个新的节点,只要 x 不是根节点而且优先级高于它的父节点,那么旋转这个节点使其和其父节点交换位置。

[perl]
sub insert {
my $self = shift;
my $key = shift;
my $data = shift;
$data = defined($data)? $data : $key;
my $priority = shift() || rand();

if($self-&gt;_is_empty()) {
    $self-&gt;{priority} = $priority,
    $self-&gt;{key}      = $key;
    $self-&gt;{data}     = $data;
    $self-&gt;{left}     = $self-&gt;new($self-&gt;{cmp});
    $self-&gt;{right}    = $self-&gt;new($self-&gt;{cmp});
    return $self;
}

if($self-&gt;gt($key)){
    $self-&gt;{right}-&gt;insert($key,$data,$priority);
    if($self-&gt;{right}-&gt;{priority} &gt; $self-&gt;{priority}){
        $self-&gt;_rotate_left();
    }
}elsif($self-&gt;lt($key)){
    $self-&gt;{left}-&gt;insert($key,$data,$priority);
    if($self-&gt;{left}-&gt;{priority} &gt; $self-&gt;{priority}){
        $self-&gt;_rotate_right();
    }

}else{
    $self-&gt;_delete_node();
    $self-&gt;insert($key,$data,$priority);
}
return $self;

}
[/perl]

从代码可以看出,旋转就是为了保持 heap 的属性,优先级高的节点在上层。

3) 删除一个节点相对比较麻烦:如果要删除的节点 x 是一个叶子,直接删掉即可;如果 x 有一个孩子节点 z,把 x 删掉,然后把 z 作为 x 父亲的孩子;如果 x 有两个孩子节点,则把 x 和它的下一个节点(successor)交换,然后进行相应的旋转。实现是递归实现的,很容易理解。注意:这里实现删除时并没有进行实质的删除操作,而只是把优先级设为了最低的 -100,这也使得代码变得比上面的理论更简单。

[perl]
sub delete {
my $self = shift;
my $key = shift;
return 0 unless $self = $self->_get_node($key);
$self->_delete_node();
}

sub _delete_node {
my $self = shift;
if($self->_is_leaf()) {
%$self = (priority => -100, cmp => $self->{cmp});
} elsif ($self->{left}->{priority} > $self->{right}->{priority}) {
$self->_rotate_right();
$self->{right}->_delete_node();
} else {
$self->_rotate_left();
$self->{left}->_delete_node();
}
}
[/perl]

这里的两个旋转操作很容易实现:

[perl]
sub _clone_node {
my $self = shift;
my $other = shift;
%$self = %$other;
}

sub _rotate_left {
my $self = shift;
my $tmp = $self->new($self->{cmp});
$tmp->_clone_node($self);
$self->_clone_node($self->{right});
$tmp->{right} = $self->{left};
$self->{left} = $tmp;

}

sub _rotate_right {
my $self = shift;
my $tmp = $self->new($self->{cmp});
$tmp->_clone_node($self);
$self->_clone_node($self->{left});
$tmp->{left} = $self->{right};
$self->{right} = $tmp;
}
[/perl]

或者借助于这个图来理解右旋:

      B            A
     /           / 
    A   2   -->  0   B
   /               / 
  0   1            1   2

参考:
1. http://acs.lbl.gov/~aragon/treaps.html
2. http://www.perlmonks.org/?node_id=289584
3. C 语言实现:http://www.canonware.com/trp/
4. Python 实现:http://stromberg.dnsalias.org/~strombrg/python-tree-and-heap-comparison/

我的内核配置文件

我们知道,在KVM里测试内核会碰到一个很严重的问题:那就是在 host 编译的内核不能直接在 guest 里使用。有两个原因:一是 host 和 guest 的硬件可能不一样,所以需要的 config 不一样;二是内核模块即便是安装进了 initramfs 也仍有很多需要安装到 /lib/modules/uname -r

有三种解决办法:

1. host 和 guest 都使用发行版自带的 config,这个 config 是可以在很多机器上运行,所以基本上一定可以在你的guest 里使用,然后用 guestfish 把编译的内核模块拷贝进 guest;这么做的好处是 host 和 guest 都使用同一个 config;坏处也显然,需要进行太多host => guest 拷贝操作。

2. 在 1) 的基础上进行改进,把 host 作为 build server,为 guest 提供 PXE,NFS 服务,这样 guest 里就可以直接使用编译好的内核以及内核模块。这个方法的缺点是,需要一些系统管理技巧,我暂时还没有时间折腾;优点是,如果你的 build server 搭建得比较好,你甚至可以给其它机器(非虚拟)提供服务。

3. guest 使用和 host 不同的 config,guest 的 config 是经过专门定制的,只编译本机器所需要的内核特性和模块,并且把所有的模块都编译进内核(builtin),这样我们甚至不需要 initramfs!优点是,config 很少,即使重新编译花费时间也很少;缺点也很显然,config 很难用于别的机器,尤其是非虚拟化的机器。需要特别注意:如果你的系统用了 LVM 那么它将无法使用!因为内核无法自己检测 LVM!另外,编译进内核的模块和不像单独加载的模块那样灵活,比如 netconsole 模块,我们通常是通过模块参数来指定其功能,而一旦变成 builtin 我们就不得不通过 configfs 来进行操作了,也就是说测试脚本就得重写了。

为了定制上述 3) 中专门的 config,我花了不少时间把我的 config 精简到最少,但同时又包含了 KVM 里需要功能以及我调试用到的选项,我把最终的 config 放到了 github 上进行管理:

https://github.com/congwang/kernelconfig/blob/master/kvm-mini-config

我的目标是让它可以在尽可能多的 KVM guest 上可以运行(显然不可能保证所有),同时尽量不破坏用户空间的程序比如 systemd(取决于发行版),当然还要保持编译出的内核可能的小。欢迎帮忙测试。:-)

下一步是尝试实现第2种方法,我们需要搭建一个集编译内核、PXE服务器、NFS服务器于一身的服务器,准备弄一个新的 github 项目去搞一下。

当然,如果你有其它更好的主意,希望不吝赐教!

附我常用的一些内核调试选项:

一定要有的:
CONFIG_DEBUG_KERNEL=y

内存相关:
CONFIG_SLUB_DEBUG=y
CONFIG_DEBUG_VM=y
CONFIG_DEBUG_LIST=y

内核加锁相关:
CONFIG_DEBUG_ATOMIC_SLEEP=y
CONFIG_LOCKDEP=y
CONFIG_DEBUG_SPINLOCK=y
CONFIG_DEBUG_MUTEXES=y
CONFIG_DEBUG_LOCK_ALLOC=y
CONFIG_PROVE_LOCKING=y
CONFIG_PROVE_RCU=y

ftrace 相关:
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y

锁死检测:
CONFIG_LOCKUP_DETECTOR=y
CONFIG_HARDLOCKUP_DETECTOR=y
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC_VALUE=1
CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y
CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=1
CONFIG_DETECT_HUNG_TASK=y
CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=300

kdump 相关:
CONFIG_KEXEC=y
CONFIG_CRASH_DUMP=y
CONFIG_PHYSICAL_START=0x1000000
CONFIG_RELOCATABLE=y

netconsole 相关:
CONFIG_NETCONSOLE=y
CONFIG_NETCONSOLE_DYNAMIC=y
CONFIG_NETPOLL=y

kprobe,jump label 相关:
CONFIG_KPROBES=y
CONFIG_JUMP_LABEL=y