zoukankan      html  css  js  c++  java
  • 20169211《Linux内核原理与分析》第三周作业

    假期中抽时间学习了一下linux内核的启动过程,在此做一下学习总结。

    Linux启动过程描述:

    1、启动BootLoader

    2、Linux系统的初始化

    3、Linux的应用程序的初始化

    通用寄存器的作用

    • r0 :在函数开始时使用

    • r1 :存放堆栈指针,相当于ia32架构中的esp寄存器

    • r2 :存放当前进程的描述符的地址

    • r3 :存放第一个参数和返回地址

    • r4-r10 :存放函数的参数

    • r11 :用在指针的调用和当前一些语言的环境指针

    • r12 :用于存放异常处理

    • r13 :保留做为系统线程ID

    • r14-r31 :作为本地变量,具有非易失性

    Linux启动过程描述:

    第一步:使用BootLoader(一般是U-boot)加载Linux内核映像到内存,并负责目标系统的基本初始化过程,并搜集这个系统的基本信息,比如内存大小、处理器主频、外设的使用情况等一系列信息。然后把这些信息传递给linux内核。然后Boot loader把linux内核复制到从0x0000 0000 开始的物理内存处(虚拟地址一般为0xc000 0000处)开始执行。

    从文件archpowerpcootzImage.lds中可以看出,bootstraploader的入口为_zimage_start。在代码archpowerpcootcrt0.S中
    D:virtual_machineshare_folderlinux-2.6.23archpowerpcootzImage.lds中定义的入口地址为4MB,见下面:

    SECTIONS{. = (410241024);_start = .;.text:

    进入linux内核:从vmlinux.lds看到,内核入口为_stext,通过段.text.head 将代码定位到0xc0000000处。

    在代码arch/powerpc/kernel/head_32.S中_stext之后紧接着是_start,他们之间没有代码,他们表示相同的地址。在vmlinux.lds中将.text.head规划为.text的第一个字段(保证了地址定位到0xc0000000)。

    第二步:Linux系统的初始化。

    1、 bootstraploader 过程

    注意:需要知道从uboot跳到此处时,r3寄存器的内容,以及其他register的内容。如果运行地址和链接地址不同,则修正got表中各个函数的指针,清零BSS段调用platform_init(),保存bd到__res,初始化ppc_md(ppc module)中的各个函数。调用archpowerpcootmain.c中的start()。

    在start()中:

    • 1.1将命令行拷贝到cmdline中
    • 1.2调用open函数打开串口
    • 1.3解压缩kernel代码
    • 1.4解压缩ramdisk image
    • 1.5最终初始化设备树
    • 1.6跳到内核代码中执行

    有两个调用语句,应该是运行了语句:kentry(ft_addr, 0, NULL); need confirm

    2、 进入linux内核入口:arch/powerpc/kernel/head_32.S中的_start。

    • 2.1 early_init() ,arch/powerpc/kernel/setup_32.c中

           计算运行地址和链接地址的差值。根据cpu型号调用do_feature_fixups函数来对__ftr_fixup段进行修复处理。例如若HIDO寄存器的HIGH_BAT_EN位置位,另外的4组寄存器 IBATs (4–7) 和 4组 DBATs (4-7) 将会被激活,ftr_fixup段中对这8组寄存器进行初始化的代码就会生效;否则__ftr_fixup中的这段代码就会被nop指令所替!early_init()函数调用identify_cpu()函数,通过cpu中的pvr寄存器存放的CPU核的版本号在全局数组cpu_specs中寻找到当前cpu的详细信息,identify_cpu()函数在找到之后,会调用setup_cpu_spec()函数把上述cpu的信息所在的链接地址赋值给cur_cpu_spec变量。

    • 2.2 mmu_off() 关闭mmu
    • 2.3 flush_tlbs() 从TLB中移除页表
    • 2.4 call_setup_cpu():call_setup_cpu()位于misc_32.S文件中
    • 2.5 relocate_kernel():把内核代码拷贝到链接地址指向的位置
    • 2.6 turn_on_mmu():映射了256MB内存,就可以避免调用reloc_offset()函数来显式得把虚拟地址映射到物理地址!
    • 2.7跳到start_here()函数中运行,可以认为是真正内核开始运行。。。

    3、start_kernel

    本阶段也是有0号线程init_task中调用的,将完成Linux内核核心数据结构的初始化,最终创建1号线程kernel_init,最后由1号内核线程启动1号用户进程。需要后续确认。

    4、 rest_init()函数。

    • 4.1 调用kernel_thread()创建1号内核线程。

    • 4.2 调用kernel_thread()创建kthreadd内核线程。尚不明作用。

    • 4.3 init_idle_bootup_task():当前0号进程init_task最终会退化成idle进程,所以这里调用init_idle_bootup_task()函数,让init_task进程隶属到idle调度类中。即选择idle的调度相关函数。

    • 4.4 调用schedule()函数切换当前进程,在调用该函数之前,Linux系统中只有两个进程,即0号进程init_task和1号进程kernel_init,其中kernel_init进程也是刚刚被创建的。调用该函数后,1号进程kernel_init将会运行!

    • 4.5 调用cpu_idle(),0号线程进入idle函数的循环,在该循环中会周期性地检查。

    5、kernel_init 1号线程初始化

    主要包括三方面:

    第一:引导SMP系统中的其它CPU(即AP(Aplication Processor))

    第二:调用do_basic_setup()函数,完成Linux系统其它模块的初始化;

    第三:更换核心进程kernel_init为普通进程之后,完成对Linux系统的二次引导,即对Linux系统应用程序的引导!

    • 5.1设置当前1号线程所允许的BSP(即core 0)在cpu_all_mask中的对应bit位

    • 5.2 init_pid_ns.child_reaper = current; 设置1号线程回收orphan 线程。1号进程在Linux系统中相当于一个收容所,专门用于处理那些孤儿进程。

    • 5.3 smp_prepare_cpus(setup_max_cpus)该函数的作用是首先探测我们的目标系统中有多少个CPU,该函数为每个CPU创建一个idle进程。

    • 5.4 smp_init()是我们的linux-smp映像中启动另外一个核的最重要的代码,该函数主要是引导SMP系统中的AP,该函数会依次调用:smp_init()-> cpu_up()-> _cpu_up()-> __cpu_up()-> smp_ops-> kick_cpu(kick_cpu是一个函数指针,指向smp_86xx_kick_cpu,故会执行smp_86xx_kick_cpu()函数)smp_86xx_kick_cpu()-> smp_86xx_release_core()-> __secondary_start_mpc86xx()-> __secondary_start()来启动core 1,并调用set_cpu_online()函数将次CPU加入到cpu_online_map变量中,用以向主CPU通知该次CPU已经被激活。

    • 5.5 sched_init_smp()该函数的作用,有待遇进一步的分析!

    • 5.6 do_basic_setup():到目前为止,内核已经初始化了,memory管理和process scheduler 已经开始运行。但尚未注册设备。在本函数中将初始化workqueue,初始化device drivers,初始化中断处理,最后调用do_initcalls()进行静态安装所有模块,其中包括驱动人员最关心的用device_initcall声明的设备模块的安装。

    • 5.7 调用init_post()创建用户模式1号进程。

    第三步:Linux的应用程序的初始化。

           1号kernel_init进程完成linux的各项配置(包括启动AP)后,就会在/sbin,/etc,/bin寻找init程序来运行。该init程序会替换kernel_init进程(注意:并不是创建一个新的进程来运行init程序,而是一次变身,使用sys_execve函数改变核心进程的正文段,将核心进程kernel_init转换成用户进程init),此时处于内核态的1号kernel_init进程将会转换为用户空间内的1号进程init。户进程init将根据/etc/inittab中提供的信息完成应用程序的初始化调用。然后init进程会执行/bin/sh产生shell界面提供给用户来与Linux系统进行交互。
    调用init_post()创建用户模式1号进程。

    在init_post()中最终调用下面的任何一个入口(按顺序,第一个执行成功后将不返回)

       if (execute_command) {
              run_init_process(execute_command);
              printk(KERN_WARNING "Failed to execute %s.  Attempting "
                                   "defaults...
    ", execute_command);
       }
       run_init_process("/sbin/init");
       run_init_process("/etc/init");
       run_init_process("/bin/init");
       run_init_process("/bin/sh");
  • 相关阅读:
    年轻人的第一个 Spring Boot 应用,太爽了!
    面试问我 Java 逃逸分析,瞬间被秒杀了。。
    Spring Boot 配置文件 bootstrap vs application 到底有什么区别?
    坑爹的 Java 可变参数,把我整得够惨。。
    6月来了,Java还是第一!
    Eclipse 最常用的 10 组快捷键,个个牛逼!
    Spring Cloud Eureka 自我保护机制实战分析
    今天是 Java 诞生日,Java 24 岁了!
    厉害了,Dubbo 正式毕业!
    Spring Boot 2.1.5 正式发布,1.5.x 即将结束使命!
  • 原文地址:https://www.cnblogs.com/sharemi/p/5944514.html
Copyright © 2011-2022 走看看