走进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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值