从start_kernel开始Linux内核启动

**作者:黄志恒
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”**

本篇博客从内核启动一个简易系统来理解Linux内核的启动机制。

首先,简单讲一下这个简易系统MenuOS。源码在https://github.com/mengning/menu。这个系统(去年我们写这个东西的时候说它是一个程序,今年就升级成系统了 - -)的功能就是能够在控制台界面接受一些指令,并根据接受的指令做出相应的反馈。如果感兴趣可以去阅读以下源码,对学习C语言和软件工程还是挺有帮助的。(没错,是C语言和软件工程)

接下来,讲一下环境的搭建。我们的内核用的是x86内核。最简单的方法是在实验楼的虚拟机中做实验。那里已经配置好了环境,可以很方便的操作。如果想自己动手在自己的本地搭建一个环境的话,参考这个地址:http://mooc.study.163.com/learn/USTC-1000029000#/learn/content?type=detail&id=1000116009。当然“度娘”也可以给你很多好的答案。不过自己手动搭建会遇到各种问题,要做好心理准备,祝好运~

总结一下,我们的实验就是要让linux内核来启动我们的MenuOS。好了,下面上图。

首先是运行一下MenuOS的效果图:
从start_kernel开始Linux内核启动_第1张图片
(那几个“MENUOS”字样是不是很炫?老师做的……)
之后是让这个MenuOS重新执行并在开始端暂停,然后通过gdb单步调试运行,看一看start_kernel函数具体的运行过程。
从start_kernel开始Linux内核启动_第2张图片
开启gdb并连上kernel
从start_kernel开始Linux内核启动_第3张图片
然后设置断点到start_kernel函数,并使执行到start_kernle
从start_kernel开始Linux内核启动_第4张图片
OK,现在内核运行停止在start_kernel处。我们分析下这个函数的代码。网上大神的说法,这个函数里内容句句关键。这个函数中的内容大部分是在调用函数。而每一个调用的函数都是在初始化运行环境。具体的从网上的一篇博客http://laiyuanyuan7.blog.163.com/blog/static/1527432120124119431631/
转载过来,里面对start_kernel函数中调用的函数的用处做了很好的说明。
513 asmlinkage void __init start_kernel(void)
514 {
515 char * command_line;
516 extern struct kernel_param __start___param[], __stop___param[];
517
/*
* 当只有一个CPU的时候这个函数就什么都不做,
但是如果有多个CPU的时候那么它就
* 返回在启动的时候的那个CPU的号
*/
518 smp_setup_processor_id();
519
520 /*
521 * Need to run as early as possible, to initialize the
522 * lockdep hash:
523 */
524 unwind_init();
525 lockdep_init();
526
/* 关闭当前CPU的中断 */
527 local_irq_disable();
528 early_boot_irqs_off();
/*
* 每一个中断都有一个中断描述符(struct irq_desc)
来进行描述,这个函数的
* 作用就是设置所有中断描述符的锁
*/
529 early_init_irq_lock_class();
530
531 /*
532 * Interrupts are still disabled. Do necessary setups, then
533 * enable them
534 */
/* 获取大内核锁,锁定整个内核。 */
535 lock_kernel();
/* 如果定义了CONFIG_GENERIC_CLOCKEVENTS,则注册clockevents框架 */
536 tick_init();
537 boot_cpu_init();
/* 初始化页地址,使用链表将其链接起来 */
538 page_address_init();
539 printk(KERN_NOTICE);
/* 显示内核的版本信息 */
540 printk(linux_banner);
/*
* 每种体系结构都有自己的setup_arch()函数,是
体系结构相关的,具体编译哪个
* 体系结构的setup_arch()函数,由源码树顶层目录
下的Makefile中的ARCH变量
* 决定
*/
541 setup_arch(&command_line);
542 setup_command_line(command_line);
543 unwind_setup();
/* 每个CPU分配pre-cpu结构内存, 并复制.data.percpu段的数据 */
544 setup_per_cpu_areas();
545 smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
546
547 /*
548 * Set up the scheduler prior starting any
interrupts (such as the
549 * timer interrupt). Full topology setup
happens at smp_init()
550 * time - but meanwhile we still have a
functioning scheduler.
551 */
/* 进程调度器初始化 */
552 sched_init();
553 /*
554 * Disable preemption - early bootup scheduling is extremely
555 * fragile until we cpu_idle() for the first time.
556 */
/* 禁止内核抢占 */
557 preempt_disable();
558 build_all_zonelists();
559 page_alloc_init();
/* 打印Linux启动命令行参数 */
560 printk(KERN_NOTICE “Kernel command line: %s\n”, boot_command_line);
/* 对内核选项的两次解析 */
561 parse_early_param();
562 parse_args(“Booting kernel”, static_command_line, __start___param,
563 __stop___param - __start___param,
564 &unknown_bootoption);
/* 检查中断是否已经打开,如果已经打开,则关闭中断 */
565 if (!irqs_disabled()) {
566 printk(KERN_WARNING “start_kernel(): bug: interrupts were ”
567 “enabled very early, fixing it\n”);
568 local_irq_disable();
569 }
570 sort_main_extable();
/*
* trap_init函数完成对系统保留中断向量(异常、
非屏蔽中断以及系统调用) * 的初始化,init_IRQ函
数则完成其余中断向量的初始化
*/
571 trap_init();
/* 初始化RCU(Read-Copy Update)机制 */
572 rcu_init();
573 init_IRQ();
/* 初始化hash表,便于从进程的PID获得对应的进程描述符指针 */
574 pidhash_init();
/* 初始化定时器相关的数据结构 */
575 init_timers();
/* 对高精度时钟进行初始化 */
576 hrtimers_init();
/* 初始化tasklet_softirq和hi_softirq */
577 softirq_init();
578 timekeeping_init();
/* 初始化系统时钟源 */
579 time_init();
/* 对内核的profile(一个内核性能调式工具)功能进行初始化 */
580 profile_init();
581 if (!irqs_disabled())
582 printk(“start_kernel(): bug: interrupts
were enabled early\n”);
583 early_boot_irqs_on();
584 local_irq_enable();
585
586 /*
587 * HACK ALERT! This is early. We’re enabling
the console before
588 * we’ve done PCI setups etc, and console_init()
must be aware of
589 * this. But we do want output early, in
case something goes wrong.
590 */
/*
* 初始化控制台以显示printk的内容,在此之前调用的printk
* 只是把数据存到缓冲区里
*/
591 console_init();
592 if (panic_later)
593 panic(panic_later, panic_param);
594
/* 如果定义了CONFIG_LOCKDEP宏,则打印锁依赖信息,否则什么也不做 */
595 lockdep_info();
596
597 /*
598 * Need to run this when irqs are enabled, because it wants
599 * to self-test [hard/soft]-irqs on/off lock inversion bugs
600 * too:
601 */
602 locking_selftest();
603
604 #ifdef CONFIG_BLK_DEV_INITRD
605 if (initrd_start && !initrd_below_start_ok &&
606 initrd_start < min_low_pfn << PAGE_SHIFT) {
607 printk(KERN_CRIT “initrd overwritten
(0x%08lx < 0x%08lx) - ”
608 “disabling it.\n”,initrd_start,
min_low_pfn << PAGE_SHIFT);
609 initrd_start = 0;
610 }
611 #endif
/* 虚拟文件系统的初始化 */
612 vfs_caches_init_early();
613 cpuset_init_early();
614 mem_init();
/* slab初始化 */
615 kmem_cache_init();
616 setup_per_cpu_pageset();
617 numa_policy_init();
618 if (late_time_init)
619 late_time_init();
/*
* 一个非常有趣的CPU性能测试函数,可以计
算出CPU在1s内执行了多少次一个
* 极短的循环,计算出来的值经过处理后得
到BogoMIPS值(Bogo是Bogus的意思),
*/
620 calibrate_delay();
621 pidmap_init();
/* 接下来的函数中,大多数都是为有关的管理机制建立专用的slab缓存 */
622 pgtable_cache_init();
/* 初始化优先级树index_bits_to_maxindex数组 */
623 prio_tree_init();
624 anon_vma_init();
625 #ifdef CONFIG_X86
626 if (efi_enabled)
627 efi_enter_virtual_mode();
628 #endif
/* 根据物理内存大小计算允许创建进程的数量 */
629 fork_init(num_physpages);
/*
* proc_caches_init(),buffer_init(),
unnamed_dev_init(), key_init()
*
*/
630 proc_caches_init();
631 buffer_init();
632 unnamed_dev_init();
633 key_init();
634 security_init();
635 vfs_caches_init(num_physpages);
636 radix_tree_init();
637 signals_init();
638 /* rootfs populating might need page-writeback */
639 page_writeback_init();
640 #ifdef CONFIG_PROC_FS
641 proc_root_init();
642 #endif
643 cpuset_init();
644 taskstats_init_early();
645 delayacct_init();
646
/*
* 测试该CPU的各种缺陷,记录检测到的缺陷,以
便于内核的其他部分以后可以
* 使用它们的工作。
*/
647 check_bugs();
648
649 acpi_early_init(); /* before LAPIC and SMP init */
650
651 /* Do the rest non-__init’ed, we’re now alive */
/* 创建init进程 */
652 rest_init();
从start_kernel开始Linux内核启动_第5张图片
这张图片是执行到console_init()代码处的情景。之前的menu控制台都是没有文字提示的。在start_kernel到console_init()之间的代码都是将要输出的内容存入缓存,没有在控制台中显示。(控制台都没有初始化,怎么可能在控制台上显示……)

从start_kernel开始Linux内核启动_第6张图片
这句话的意思就是启动1号进程的,从图中也可以看出来,在执行了rest_init()之后,MenuOS出现了。

总结:
start_kernel可以看做kernel的“main函数”,内核从这里执行。而这个函数也被称为0号进程。这个函数主要做的就是对硬件,内存,中断的初始化,进程上下文切换初始化。刚开始可以看到,中断是被关闭的。因为内核在进行初始化,这个优先级是最高的。不允许其他事项来中断初始化。在start_kernel函数的执行的后期会对中断做初始化。在初始化做完之后,调用rest_init(),这就是1号进程。rest_init()函数中也做了很多工作,比如继续初始化,调用系统程序等等。
强烈建议阅读start_kernel()和rest_init()函数的源码,会让你知道原来一个系统的执行不是在变魔术,而是一个一个C函数在辛苦的运行。
linux-3.18.6内核源码地址http://codelab.shiyanlou.com/xref/linux-3.18.6/
相关博客转载:
http://laiyuanyuan7.blog.163.com/blog/static/1527432120124119431631/
http://blog.sina.com.cn/s/blog_7a83e7960100uq6p.html
http://blog.chinaunix.net/uid-23769728-id-3127671.html
http://blog.csdn.net/armeasy/article/details/6027466
http://blog.itpub.net/10617542/viewspace-955306/

你可能感兴趣的:(linux内核,操作系统,c)