走进start_kernel尾声

5.11 走进start_kernel尾声

中断体系建立起来后,虽然后面还有很多行代码,但是都是些比较好理解的初始化函数了,也就是说start_kernel进入尾声了。

5.11.1 初始化slab的后续工作

继续分析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函数重新计算他们的batchcountlimitshared字段,并为NUMA体系中那些还没有初始化nodelists[node]的节点进行初始化工作。最后调用register_cpu_notifier函数,将全局变量cpucache_notifier挂到全局cpu_chain链中,具体的代码我就不去分析了。

 

5.11.2 启动console

回到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的驱动,很典型的一个串口终端驱动程序。

 

5.11.3 一些简单的函数

start_kenel的下一个函数632行的panic函数。这个函数是绝对不会被执行的,因为它是当系统发现无法继续运行下去的故障时才调用它,会导致系统中止,然后由系统显示错误号。 这里是当全局变量panic_later被设置是,才会调用。内核的panic 函数位于kernel/panic.c 文件中。

 

接下来,由于我们没有CONFIG_LOCKDEP,所以634汗的lockdep_info宏是个空宏。因为没有配置CONFIG_DEBUG_LOCKING_API_SELFTESTS641locking_selftest()也是个空函数。643652的代码根据全局变量initrd_start的值打印一些信息。

 

继续走,由于我们没有配置CONFIG_CGROUP_MEM_RES_CTLR653行的page_cgroup_init是一个空函数。654行,没有配置CONFIG_DEBUG_PAGEALLOCdebug_pagealloc_enabled也是个空函数。655行的kmemtrace_init是个待开发函数,

 

656kmemleak_init 函数是有效取决于CONFIG_DEBUG_KMEMLEAK编译选项,我在.config文件中没找到,所以忽略。

 

657debug_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字段分配空间,前提是如果那个zonepresent_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中,661662行,来了,执行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

 

5.11.4 校准CPU时钟速度

回到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()的意思,MIPSmillions of instructions per second(百万条指令每秒)的缩写。这样我们就知道了其实这个函数是linux内核中一个cpu性能测试函数。由于内核对这个数值的精度要求不高,所以内核使用了一个十分简单而有效的算法用于得到这个值。

 

这个值虽然不准确,但也足以令我们心动。如果你想了解自己机器的BogoMIPS,你可以察看/proc/cpuinfo文件中的bogomips一行。在你知道了自己cpuBogoMIPS之后,如果你觉得不过瘾,那么让我们一起来看看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”。

 

146157行,是第一次计算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在“新起点”与“新起点加减半范围(新终点)”之间。

 

167173行前面一致,都是等待新的滴答,执行延时。如果延时过短,说明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()函数就跑完了。

 

5.11.5 创建一些slab缓存

回到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,

};

 

这里为它数组字段pidmap0号元素分配一个页面,作为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_vmaslab缓存。这个技术是PFRA(页框回收算法)技术中的组成部分。这个技术为定位而生——快速的定位指向同一页框的所有页表项。

 

此技术中,涉及到几个关键的数据结构:anon_vmavm_area_structmm_structpage。就是通过这几个结构中的部分字段,使得线性区彼此之间形成链表,线性区与页表项之间存在联系。首先以anon_vma为链表头,形成线性区之间的双向循环链表。此循环链表中的元素就是vma_area_struct,内核将匿名线性区插入此中。而此结构中有两个字段是用于此链表的:anon_vmaanon_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_xstateslab缓存:

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_structsignal_structfiles_structfs_structmm_structvm_area_structslab缓存的建立。随后调用mmap_init初始化每CPU计数器,也就是全局percpu_counter结构的变量vm_committed_as

 

回到start_kernel675行,执行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_kernel676key_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_kernel677security_init函数。这个函数跟上一个函数一样,都是系统安全相关的内容,感兴趣的同学可以好好研究一下。

 

你可能感兴趣的:(struct,cache,null,buffer,delay,loops)