中断体系建立起来后,虽然后面还有很多行代码,但是都是些比较好理解的初始化函数了,也就是说start_kernel进入尾声了。
继续分析start_kenel的下一个函数,613行,profile_init函数,用于对系统剖析做相关初始化,系统剖析用于系统调用:
int __ref profile_init(void)
{
int buffer_bytes;
if (!prof_on)
return 0;
/* only text is profiled */
prof_len = (_etext - _stext) >> prof_shift;
buffer_bytes = prof_len*sizeof(atomic_t);
if (!alloc_cpumask_var(&prof_cpu_mask, GFP_KERNEL))
return -ENOMEM;
cpumask_copy(prof_cpu_mask, cpu_possible_mask);
prof_buffer = kzalloc(buffer_bytes, GFP_KERNEL|__GFP_NOWARN);
if (prof_buffer)
return 0;
prof_buffer = alloc_pages_exact(buffer_bytes,
GFP_KERNEL|__GFP_ZERO|__GFP_NOWARN);
if (prof_buffer)
return 0;
prof_buffer = vmalloc(buffer_bytes);
if (prof_buffer) {
memset(prof_buffer, 0, buffer_bytes);
return 0;
}
free_cpumask_var(prof_cpu_mask);
return -ENOMEM;
}
函数无非就是初始化一些简单的全局变量。继续在start_kenel中前进,614~616行,根据irqs_disabled是否返回成功而打印一些信息。617行,由于CONFIG_PROVE_LOCKING没有被配置,所以early_boot_irqs_on是一个空函数。618行,local_irq_enable函数将打开可屏蔽中断,与549行的local_irq_disable遥相呼应。621行设置全局GFP常量gfp_allowed_mask,注释上写得很清楚,中断处理模块已经成型,所以可以进行GFP分配了。继续走,623行,调用kmem_cache_init_late函数。我们在“初始化内存管理”讲解的那个mm_init()中曾经调用过一个kmem_cache_init,用于初始化内核slab分配体系,还记得吧。那么这个加了_late后缀的函数是啥意思呢,它也来自mm/slab.c:
void __init kmem_cache_init_late(void)
{
struct kmem_cache *cachep;
/* 6) resize the head arrays to their final sizes */
mutex_lock(&cache_chain_mutex);
list_for_each_entry(cachep, &cache_chain, next)
if (enable_cpucache(cachep, GFP_NOWAIT))
BUG();
mutex_unlock(&cache_chain_mutex);
/* Done! */
g_cpucache_up = FULL;
/* Annotate slab for lockdep -- annotate the malloc caches */
init_lock_keys();
/*
* Register a cpu startup notifier callback that initializes
* cpu_cache_get for all new cpus
*/
register_cpu_notifier(&cpucache_notifier);
/*
* The reap timers are started later, with a module init call: That part
* of the kernel is not yet operational.
*/
}
很简单,就是做几个slab分配器初始化的后续工作,其实就只做一件事,遍历cache_chain链表,把里面的所有作为slab缓存头的kmem_cache结构通过enable_cpucache函数重新计算他们的batchcount、limit和shared字段,并为NUMA体系中那些还没有初始化nodelists[node]的节点进行初始化工作。最后调用register_cpu_notifier函数,将全局变量cpucache_notifier挂到全局cpu_chain链中,具体的代码我就不去分析了。
回到start_kenel函数中,下一个函数630行的console_init(),用于初始化系统控制台结构。该函数执行后可调用printk()函数将log_buf中符合打印级别要求的系统信息打印到控制台上。还记得我们在讲printk函数的时候说过,这个函数是把字符串写到log_buf中,而启动了系统控制台后,这个log_buf中的信息就会显示在屏幕上方了。
void __init console_init(void)
{
initcall_t *call;
/* Setup the default TTY line discipline. */
tty_ldisc_begin();
/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
call = __con_initcall_start;
while (call < __con_initcall_end) {
(*call)();
call++;
}
}
函数虽然简单,但涉及到的东西很多。首先看到tty_ldisc_begin函数:
void tty_ldisc_begin(void)
{
/* Setup the default TTY line discipline. */
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
}
tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY)这段代码主要的用途就是注册tty线路规程的,大家研究tty的驱动就会发现,在用户和硬件之间的tty驱动是分了三层的,其中最底层是tty驱动程序了,主要负责从硬件接受数据,和格式化上层发下来的数据后给硬件。
在驱动程序之上就是线路规程,他负责把从tty核心层或者tty驱动层接受的数据进行特殊的按着某个协议的格式化,就像是ppp或者蓝牙协议,然后在分发出去的。
在tty线路规程之上就是tty核心层。大家可参考ldd3学习一下。那么,如何初始化终端呢?这又要从编译说起了,看看我的vmlinux.lds.S中连接脚本汇编中有这段代码:
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
原来的call = __con_initcall_start就是把__con_initcall_start的虚拟地址给call,去执行在 __con_initcall_start = .;和__con_initcall_end = .;之间的con_initcall.init。这就应该是linux惯用做法,把某个实际初始化函数的指针数据是放到了con_initcall.init段中,那么是怎么放的呢?
在linux/init.h里面有这么一句宏定义:
#define console_initcall(fn) /
static initcall_t __initcall_##fn /
__used __section(.con_initcall.init) = fn
在我的Linux2.6.34.1的所有驱动程序里面,大约有48个文件调用了这个console_initcall宏,那么到底用的是哪个驱动程序呢?这得看看配置文件了,找到了CONFIG_SERIAL_8250,那么肯定是调用的drivers/serial/8250.c中的文件代码:
console_initcall(serial8250_console_init);
这回就看出来了,通过#define console_initcall(fn) /宏,调用的是serial8250_console_init函数。如果对tty的驱动程序感兴趣的同学可以好好分析一下这个代码,涉及到与显示器相连接的串口终端8250的驱动,很典型的一个串口终端驱动程序。
start_kenel的下一个函数632行的panic函数。这个函数是绝对不会被执行的,因为它是当系统发现无法继续运行下去的故障时才调用它,会导致系统中止,然后由系统显示错误号。 这里是当全局变量panic_later被设置是,才会调用。内核的panic 函数位于kernel/panic.c 文件中。
接下来,由于我们没有CONFIG_LOCKDEP,所以634汗的lockdep_info宏是个空宏。因为没有配置CONFIG_DEBUG_LOCKING_API_SELFTESTS,641行locking_selftest()也是个空函数。643~652的代码根据全局变量initrd_start的值打印一些信息。
继续走,由于我们没有配置CONFIG_CGROUP_MEM_RES_CTLR,653行的page_cgroup_init是一个空函数。654行,没有配置CONFIG_DEBUG_PAGEALLOC,debug_pagealloc_enabled也是个空函数。655行的kmemtrace_init是个待开发函数,
656行kmemleak_init 函数是有效取决于CONFIG_DEBUG_KMEMLEAK编译选项,我在.config文件中没找到,所以忽略。
657行debug_objects_mem_init函数是否有用取决于CONFIG_DEBUG_OBJECTS编译选项,我们看到,仍然是“is not set”,所以不去管它。
658行,idr_init_cache函数,很简单,来自lib/idr.c:
void __init idr_init_cache(void)
{
idr_layer_cache = kmem_cache_create("idr_layer_cache",
sizeof(struct idr_layer), 0, SLAB_PANIC, NULL);
}
创建一个idr_layer结构的slab缓存,其头部由全局变量idr_layer_cache指向。
659行,setup_per_cpu_pageset:
void __init setup_per_cpu_pageset(void)
{
struct zone *zone;
int cpu;
for_each_populated_zone(zone) {
zone->pageset = alloc_percpu(struct per_cpu_pageset);
for_each_possible_cpu(cpu) {
struct per_cpu_pageset *pcp = per_cpu_ptr(zone->pageset, cpu);
setup_pageset(pcp, zone_batchsize(zone));
if (percpu_pagelist_fraction)
setup_pagelist_highmark(pcp,
(zone->present_pages /
percpu_pagelist_fraction));
}
}
}
函数很简单,为每个NUMA节点的zone结构的per_cpu_pageset字段分配空间,前提是如果那个zone的present_pages不为0。
继续走,660行,numa_policy_init函数,初始化NUMA策略:
/* assumes fs == KERNEL_DS */
void __init numa_policy_init(void)
{
nodemask_t interleave_nodes;
unsigned long largest = 0;
int nid, prefer = 0;
policy_cache = kmem_cache_create("numa_policy",
sizeof(struct mempolicy),
0, SLAB_PANIC, NULL);
sn_cache = kmem_cache_create("shared_policy_node",
sizeof(struct sp_node),
0, SLAB_PANIC, NULL);
/*
* Set interleaving policy for system init. Interleaving is only
* enabled across suitably sized nodes (default is >= 16MB), or
* fall back to the largest node if they're all smaller.
*/
nodes_clear(interleave_nodes);
for_each_node_state(nid, N_HIGH_MEMORY) {
unsigned long total_pages = node_present_pages(nid);
/* Preserve the largest node */
if (largest < total_pages) {
largest = total_pages;
prefer = nid;
}
/* Interleave this node? */
if ((total_pages << PAGE_SHIFT) >= (16 << 20))
node_set(nid, interleave_nodes);
}
/* All too small, use the largest */
if (unlikely(nodes_empty(interleave_nodes)))
node_set(prefer, interleave_nodes);
if (do_set_mempolicy(MPOL_INTERLEAVE, 0, &interleave_nodes))
printk("numa_policy_init: interleaving failed/n");
}
函数很简单,通过node_set宏为每个节点的nodemask_t->bits进行设置,以满足相应的策略。回到start_kenel中,661、662行,来了,执行late_time_init函数。这个函数就是我们刚才在“初始化定时器中断”中分析过的x86_late_time_init。内核为啥要设计成这幅颜色,还得请高人们多多指点。
663行,sched_clock_init,来自kernel/sched_clock.c:
void sched_clock_init(void)
{
u64 ktime_now = ktime_to_ns(ktime_get());
int cpu;
for_each_possible_cpu(cpu) {
struct sched_clock_data *scd = cpu_sdc(cpu);
scd->tick_raw = 0;
scd->tick_gtod = ktime_now;
scd->clock = ktime_now;
}
sched_clock_running = 1;
}
这段代码牵涉到一个每CPU变量sched_clock_data,该函数很简单,就是把每个CPU的每CPU变量sched_clock_data初始化成ktime_now。
回到start_kernel中,在start_kernel的尾声有两个重要的函数,664行的calibrate_delay就是其中一个,来自init/calibrate.c:
122void __cpuinit calibrate_delay(void)
123{
124 unsigned long ticks, loopbit;
125 int lps_precision = LPS_PREC;
126 static bool printed;
127
128 if (preset_lpj) {
129 loops_per_jiffy = preset_lpj;
130 if (!printed)
131 pr_info("Calibrating delay loop (skipped) "
132 "preset value.. ");
133 } else if ((!printed) && lpj_fine) {
134 loops_per_jiffy = lpj_fine;
135 pr_info("Calibrating delay loop (skipped), "
136 "value calculated using timer frequency.. ");
137 } else if ((loops_per_jiffy = calibrate_delay_direct()) != 0) {
138 if (!printed)
139 pr_info("Calibrating delay using timer "
140 "specific routine.. ");
141 } else {
142 loops_per_jiffy = (1<<12);
143
144 if (!printed)
145 pr_info("Calibrating delay loop... ");
146 while ((loops_per_jiffy <<= 1) != 0) {
147 /* wait for "start of" clock tick */
148 ticks = jiffies;
149 while (ticks == jiffies)
150 /* nothing */;
151 /* Go .. */
152 ticks = jiffies;
153 __delay(loops_per_jiffy);
154 ticks = jiffies - ticks;
155 if (ticks)
156 break;
157 }
158
159 /*
160 * Do a binary approximation to get loops_per_jiffy set to
161 * equal one clock (up to lps_precision bits)
162 */
163 loops_per_jiffy >>= 1;
164 loopbit = loops_per_jiffy;
165 while (lps_precision-- && (loopbit >>= 1)) {
166 loops_per_jiffy |= loopbit;
167 ticks = jiffies;
168 while (ticks == jiffies)
169 /* nothing */;
170 ticks = jiffies;
171 __delay(loops_per_jiffy);
172 if (jiffies != ticks) /* longer than 1 tick */
173 loops_per_jiffy &= ~loopbit;
174 }
175 }
176 if (!printed)
177 pr_cont("%lu.%02lu BogoMIPS (lpj=%lu)/n",
178 loops_per_jiffy/(500000/HZ),
179 (loops_per_jiffy/(5000/HZ)) % 100, loops_per_jiffy);
180
181 printed = true;
182}
要看懂上面的代码,需要了解一些背景知识。首先,BogoMIPS值是什么意思?Bogo就是Bogus(伪)的意思,MIPS是millions of instructions per second(百万条指令每秒)的缩写。这样我们就知道了其实这个函数是linux内核中一个cpu性能测试函数。由于内核对这个数值的精度要求不高,所以内核使用了一个十分简单而有效的算法用于得到这个值。
这个值虽然不准确,但也足以令我们心动。如果你想了解自己机器的BogoMIPS,你可以察看/proc/cpuinfo文件中的bogomips一行。在你知道了自己cpu的BogoMIPS之后,如果你觉得不过瘾,那么让我们一起来看看calibrate_delay函数是怎么完成工作的。
首先,125行定义计算BogoMIPS的精度,这个值越大,则计算出的BogoMIPS越精确。我们看到LPS_PREC宏被定义成了8:
#define LPS_PREC 8
看到129行,loops_per_jiffy为每个时钟滴答执行一个极短的循环的次数。这个全局变量的值在我们配置了ARCH_HAS_READ_CURRENT_TIMER编译选项的情况下,由函数calibrate_delay_direct得出。很遗憾,我们并没有配置它,所以142行,loops_per_jiffy就等于1<<12,即1后面12个“0”。
第146至157行,是第一次计算loops_per_jiffy的值,这次计算只是一个粗略的计算,为下面的计算打好基础。
可以想象我们要计算loops_per_jiffy的值,可以在一个滴答的开始时,立即重复执行一个极短的循环,当一个滴答结束时,这个循环执行了多少次就是我们要求的初步的值。系统用jiffies全局变量记录了从系统开始工作到现在为止,所经过的滴答数。它会被内核自动更新——每次定时器中断发生时(每个节拍)它便加1。这行语句用于记录当前滴答数到tick变量中。
注意149行这是一个没有循环体得空循环,150行仅有一个“;”号。这条循环语句是通过判断tick的值与jiffies的值是否不同,来判断jiffies是否变化,即是否一个新的滴答开始了。
152~156行用于判断执行的延时是否超过一个滴答。一般loops_per_jiffy的初始值并不大,所以循环会逐步加大loops_per_jiffy的值,直到延时超过一个滴答。这里面的延时方法主要是153行那个__delay(loops_per_jiffy)计算:
void __delay(unsigned long loops)
{
delay_fn(loops);
}
static void (*delay_fn)(unsigned long) = delay_loop;
static void delay_loop(unsigned long loops)
{
asm volatile(
" test %0,%0 /n"
" jz 3f /n"
" jmp 1f /n"
".align 16 /n"
"1: jmp 2f /n"
".align 16 /n"
"2: dec %0 /n"
" jnz 2b /n"
"3: dec %0 /n"
: /* we don't need output */
:"a" (loops)
);
}
我们可以看出,前一次loops_per_jiffy的值还因太小不合适时,经过一次增大,它提高了两倍,满足了循环条件,跳出循环,而这个值实在是误差太大,所以我们还要经过第二次计算。这里还要注意的是通过上面的分析,我们可以知道更加精确loops_per_jiffy的值应该在现在的值与它的一半之间。
所以163行,对loops_per_jiffy进行折半,这里开始就是第二次计算了。它用折半查找法在我们上面所说的范围内计算出了更精确的loops_per_jiffy的值。
164行定义查找范围的最小值,我把它称为起点。165行进入循环,将查找范围减半。我们看到loopbit >>= 1是在重新定义起点,起点在“原起点加loopbit减半范围”处,即新起点在原先起点与终点的中间。这时我们可以看出loops_per_jiffy在“新起点”与“新起点加减半范围(新终点)”之间。
第167至173行前面一致,都是等待新的滴答,执行延时。如果延时过短,说明loops_per_jiffy的值小了,将会跳过这部分,再次进入循环。它将是通过不断的折半方式来增大。如果延时过长,说明loops_per_jiffy的值大了,将起点重新返回原起点,当再次进入循环,由于范围减半,故可以达到减小的效果。
从这里我们可以看出它好像是个死循环,所以加入了lps_precision变量,来控制循环,即LPS_PREC越大,循环次数越多,越精确。可能这些不太好懂,总的说来,它首先将loops_per_jiffy的值定为原估算值的1/2,作为起点值(我这样称呼它),以估算值为终点值,然后找出起点值到终点值的中间值。用上面相同的方法执行一段时间的延时循环,如果延时超过了一个tick,说明loops_per_jiffy值偏大,则仍以原起点值为起点值,以原中间值为终点值,以起点值和终点值的中间为中间值继续进行查找。如果没有超过一个tick,说明loops_per_jiffy偏小,则以原中间值为起点值,以原终点值为终点值继续查找。
最后176~179行计算出BogoMIPS,并打印,至此,我们的calibrate_delay()函数就跑完了。
回到start_kernel中,下一个函数是pidmap_init,来自kernel/pid.c:
void __init pidmap_init(void)
{
init_pid_ns.pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);
/* Reserve PID 0. We never call free_pidmap(0) */
set_bit(0, init_pid_ns.pidmap[0].page);
atomic_dec(&init_pid_ns.pidmap[0].nr_free);
init_pid_ns.pid_cachep = KMEM_CACHE(pid,
SLAB_HWCACHE_ALIGN | SLAB_PANIC);
}
该函数用于初始化pid_namespace结构的全局变量init_pid_ns,该全局变量在内核编译的时候被初始化为:
struct pid_namespace init_pid_ns = {
.kref = {
.refcount = ATOMIC_INIT(2),
},
.pidmap = {
[ 0 ... PIDMAP_ENTRIES-1] = { ATOMIC_INIT(BITS_PER_PAGE), NULL }
},
.last_pid = 0,
.level = 0,
.child_reaper = &init_task,
};
这里为它数组字段pidmap的0号元素分配一个页面,作为0号进程的pid。这里简单地讲讲为什么要在全局建立一个pid的位图。在操作系统中,每一个进程或线程都有一个唯一的编号——pid,这个号码是一个int整形字段,在缺省情况下,最大的pid号是32767。
那么,系统在分配pid号的时候,就根据这个pidmap中的值来判断,取当前系统最大pid的值从pidmap中寻找下一个空的pid。如果找不到了,就从pidmap表中的0号项开始寻找那些被释放了的进程或线程留下来的pid。只要不是系统同时有32767个进程在运行,就总能找到。
当然,在一些超级计算机系统上,3万多个进程限制显得有些过分,所以系统管理员可以通过往/proc/sys/kernel/pid_max 这个文件中写入一个更小的值来减小PID的上限值,使PID的上限小于32767。在64位体系结构中,系统管理员可以把PID的上限扩大到4194304。
接着走,666行,调用anon_vma_init,来自mm/rmap.c:
void __init anon_vma_init(void)
{
anon_vma_cachep = kmem_cache_create("anon_vma", sizeof(struct anon_vma),
0, SLAB_DESTROY_BY_RCU|SLAB_PANIC, anon_vma_ctor);
anon_vma_chain_cachep = KMEM_CACHE(anon_vma_chain, SLAB_PANIC);
}
很简单,分配一个anon_vma_cachep作为anon_vma的slab缓存。这个技术是PFRA(页框回收算法)技术中的组成部分。这个技术为定位而生——快速的定位指向同一页框的所有页表项。
此技术中,涉及到几个关键的数据结构:anon_vma、vm_area_struct、mm_struct和page。就是通过这几个结构中的部分字段,使得线性区彼此之间形成链表,线性区与页表项之间存在联系。首先以anon_vma为链表头,形成线性区之间的双向循环链表。此循环链表中的元素就是vma_area_struct,内核将匿名线性区插入此中。而此结构中有两个字段是用于此链表的:anon_vma、anon_vma_node,前一个指向anon_vma,后一个指向链表的前一个和后一个元素。
而同时内核将anon_vma字段存放在page中的mapping字段中。而具体运作过程就是当进程引用的页框插入另一个进程的页表项时,内核将第二个进程的匿名线性区插入到anon_vma的双向循环链表中。而在vma_area_sturct中,还有一个字段:vm_mm指向对应的内存描述符,而内存描述符中有一个字段:pgd,存放的是页全局目录。通过这样的结构,页表项就可以从匿名页的起始线性地址得到,而该线性地址可以由线性区描述符以及页描述符的index字段得到。
继续走,667~670行,efi_enter_virtual_mode函数,由于我们没有配置CONFIG_EFI,所以全局变量为0,不会去执行它。671行,thread_info_cache_init函数,待开发。672行,执行一个cred_init函数,创建一个cred结构的slab缓存:
void __init cred_init(void)
{
/* allocate a slab in which we can store credentials */
cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred),
0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
}
673行,fork_init函数,用来根据物理内存大小计算允许创建进程/线程的数量,来自fork.c:
void __init fork_init(unsigned long mempages)
{
#ifndef __HAVE_ARCH_TASK_STRUCT_ALLOCATOR
#ifndef ARCH_MIN_TASKALIGN
#define ARCH_MIN_TASKALIGN L1_CACHE_BYTES
#endif
/* create a slab on which task_structs can be allocated */
task_struct_cachep =
kmem_cache_create("task_struct", sizeof(struct task_struct),
ARCH_MIN_TASKALIGN, SLAB_PANIC | SLAB_NOTRACK, NULL);
#endif
/* do the arch specific task caches init */
arch_task_cache_init();
/*
* The default maximum number of threads is set to a safe
* value: the thread structures can take up at most half
* of memory.
*/
max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);
/*
* we need to allow at least 20 threads to boot a system
*/
if(max_threads < 20)
max_threads = 20;
init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
init_task.signal->rlim[RLIMIT_SIGPENDING] =
init_task.signal->rlim[RLIMIT_NPROC];
}
注意这里,内核中最重要的task_struct结构的slab缓存,就是在这个函数中建立的。然后调用arch_task_cache_init函数,建立thread_xstate的slab缓存:
void arch_task_cache_init(void)
{
task_xstate_cachep =
kmem_cache_create("task_xstate", xstate_size,
__alignof__(union thread_xstate),
SLAB_PANIC | SLAB_NOTRACK, NULL);
}
最后根据全局变量totalram_pages,即可用的页面数来计算出允许创建进程/线程的数量,赋给init_task的相关字段。
回到start_kernel中,下一个函数是674行的proc_caches_init,来自fork.c:
void __init proc_caches_init(void)
{
sighand_cachep = kmem_cache_create("sighand_cache",
sizeof(struct sighand_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_DESTROY_BY_RCU|
SLAB_NOTRACK, sighand_ctor);
signal_cachep = kmem_cache_create("signal_cache",
sizeof(struct signal_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_NOTRACK, NULL);
files_cachep = kmem_cache_create("files_cache",
sizeof(struct files_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_NOTRACK, NULL);
fs_cachep = kmem_cache_create("fs_cache",
sizeof(struct fs_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_NOTRACK, NULL);
mm_cachep = kmem_cache_create("mm_struct",
sizeof(struct mm_struct), ARCH_MIN_MMSTRUCT_ALIGN,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_NOTRACK, NULL);
vm_area_cachep = KMEM_CACHE(vm_area_struct, SLAB_PANIC);
mmap_init();
}
几个主要的数据结构sighand_struct、signal_struct、files_struct、fs_struct、mm_struct和vm_area_struct的slab缓存的建立。随后调用mmap_init初始化每CPU计数器,也就是全局percpu_counter结构的变量vm_committed_as。
回到start_kernel中,675行,执行buffer_init,初始化页高速缓存:
void __init buffer_init(void)
{
int nrpages;
bh_cachep = kmem_cache_create("buffer_head",
sizeof(struct buffer_head), 0,
(SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|
SLAB_MEM_SPREAD),
NULL);
/*
* Limit the bh occupancy to 10% of ZONE_NORMAL
*/
nrpages = (nr_free_buffer_pages() * 10) / 100;
max_buffer_heads = nrpages * (PAGE_SIZE / sizeof(struct buffer_head));
hotcpu_notifier(buffer_cpu_notify, 0);
}
著名的页高速缓存头buffer_head就是在这里被初始化的,系统建立了它的slab缓存,然后设置全局变量max_buffer_heads作为系统能最大限度使用的高速缓存页面的数量。最后把buffer_cpu_notify加入到通知链中。有关页高速缓存的相关内容,请访问博客“磁盘高速缓存”http://blog.csdn.net/yunsongice/archive/2010/08/23/5833154.aspx。
start_kernel的676行,key_init函数:
void __init key_init(void)
{
/* allocate a slab in which we can store keys */
key_jar = kmem_cache_create("key_jar", sizeof(struct key),
0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
/* add the special key types */
list_add_tail(&key_type_keyring.link, &key_types_list);
list_add_tail(&key_type_dead.link, &key_types_list);
list_add_tail(&key_type_user.link, &key_types_list);
/* record the root user tracking */
rb_link_node(&root_key_user.node,
NULL,
&key_user_tree.rb_node);
rb_insert_color(&root_key_user.node,
&key_user_tree);
}
很简单,建立一个key结构的slab缓存,并初始化一些全局变量,然后建立一个key_user_tree为根节点的红黑树。
start_kernel的677行,security_init函数。这个函数跟上一个函数一样,都是系统安全相关的内容,感兴趣的同学可以好好研究一下。