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");
原文地址:https://www.cnblogs.com/sharemi/p/5944514.html