中断体系建立起来后,虽然后面还有很多行代码,但是都是些比较好理解的初始化函数了,也就是说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函数。这个函数跟上一个函数一样,都是系统安全相关的内容,感兴趣的同学可以好好研究一下。