Linux驱动开发 内核的启动过程

1.head.S文件分析

(1)内核运行的物理地址与虚拟地址

KERNEL_RAM_VADDR(VADDR就是virtual address),这个宏定义了内核运行时的虚拟地址。值为0xC0008000
KERNEL_RAM_PADDR(PADDR就是physical address),这个宏定义内核运行时的物理地址。值为0x30008000
总结:内核运行的物理地址是0x30008000,对应的虚拟地址是0xC0008000。

(2)内核的真正入口

内核的真正入口就是ENTRY(stext)处
前面的__HEAD定义了后面的代码属于段名为.head.text的段

2.内核启动的汇编阶段

__lookup_processor_type:读取出硬件的CPU ID号,进行合法性检验,合法则继续启动,不合法则停止启动,转向__error_p启动失败。

__lookup_machine_type:本函数校验的是机器码

__vet_atags:用来校验uboot通过tag给内核传的参数(上篇uboot如何启动内核有介绍tag传参)

__create_page_tables:建立页表,linux内核本身被连接在虚拟地址处,因此kernel希望尽快建立页表并且启动MMU进入虚拟地址工作状态。

__switch_data

__switch_data部分是个函数指针数组,下一步执行__mmap_switched函数,复制数据段、清除bss段,保存起来cpu id号、机器码、tag传参的首地址,b start_kernel跳转到C语言运行阶段。

3.内核启动C语言阶段

内核启动文件main.c

(1)一些初始化代码

smp_setup_processor_id:smp就是对称多处理器,是建立smp。
lockdep_init:锁定依赖,是一个内核调试模块,处理内核自旋锁死锁问题相关的。
boot_init_stack_canary:用来防止栈溢出。
cgroup_init_early:control group,内核提供的一种来处理进程组的技术。
local_irq_disable:屏蔽当前CPU上的所有中断。
early_boot_irqs_off:通过该标记可以让我们知道是否在early bootup code。
early_init_irq_lock_class:设置所有IRQ描述符的锁是统一的锁还是各有各的小锁。
lock_kernel:获得大内核锁,该锁可以用来锁定整个内核。
tick_init:初始化tick控制功能,注册clockevents的框架。
boot_cpu_init:设置第一个CPU核为活跃CPU核。若系统为单CPU核系统,则设置仅有的CPU为活跃CPU核。
page_address_init:函数初始化高端内存页表池的链表。
 

(2)打印内核版本信息

kernel/init/main.c中的572行,printk函数是内核中用来从console打印信息的,类似于应用层编程中的printf。

为了解决打印信息过多,无效信息会淹没有效信息这个问题,linux内核的解决方案是给每一个printk添加一个打印级别,级别定义0-7。

(3)setup_arch函数

实际上这个函数是用来确定我们当前内核的机器(arch、machine)的,之前说过的机器码就是给这个硬件平台一个固定的编码,以表征这个平台,当前使用的平台:CPU:S5PV210开发板:X210。

setup_arch函数->setup_processor函数:用来查找CPU信息,通过read_cpuid_id函数,对硬件进行查找,从而确定CPI的ID。

setup_arch函数->setup_machine函数:传参是机器码编号

该函数的工作原理:内核在建立的时候就把各种CPU架构的信息组织成一个一个的machine_desc结构体实例,然后都给一个段属性.arch.info.init,链接的时候会保证这些描述符会被连接在一起。__lookup_machine_type就去那个那些描述符所在处依次挨个遍历各个描述符,比对看机器码哪个相同。

setup_arch函数进行了基本的cmdline处理:cmdline就是指的uboot给kernel传参时传递的命令行启动参数,也就是uboot的bootargs。

内核中维护了一个默认的cmdline,uboot还可以通过tag给kernel再传递一个cmdline。如果uboot给内核传cmdline成功则内核会优先使用uboot传递的这一个;如果uboot没有给内核传cmdline或者传参失败,则内核会使用自己默认的这个cmdline

4.rest_init

(1)rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd
(2)调用schedule函数开启了内核的调度系统,从此linux系统开始转起来了。
(3)rest_init最终调用cpu_idle函数结束了整个内核的启动。也就是说linux内核最终结束了一个函数cpu_idle。这个函数里面肯定是死循环。
(4)简单来说,linux内核最终的状态是:有事干的时候去执行有意义的工作(执行各个进程任务),实在没活干的时候就去死循环(实际上死循环也可以看成是一个任务)。
(5)之前已经启动了内核调度系统,调度系统会负责考评系统中所有的进程,这些进程里面只有有哪个需要被运行,调度系统就会终止cpu_idle死循环进程(空闲进程)转而去执行有意义的干活的进程。这样操作系统就转起来了。

init进程原理:

(1)init进程刚开始运行的时候是内核态,然后他自己运行了一个用户态下面的程序后把自己强行转成了用户态。

(2)内核态下重点就是挂载根文件系统并试图找到用户态下的那个init程序。init进程要把自己转成用户态就必须运行一个用户态的应用程序(这个应用程序名字一般也叫init),挂载根文件系统,因为所有的应用程序都在文件系统中,然后才可以找到这个应用程序。

(3)用户态下init进程大部分有意义的工作都是在用户态下进行的,进程都直接或者间接派生自init进程。

init进程作用:

(1)打开控制台,访问一个设备,就要去打开这个设备对应的文件描述符。譬如/dev/fb0这个设备文件就代表LCD显示器设备,/dev/buzzer代表蜂鸣器设备,/dev/console代表控制台设备。

(2)挂载根文件系统,prepare_namespace函数中挂载根文件系统。

    如果内核挂载根文件系统成功,则会打印出:VFS: Mounted root (ext3 filesystem) on device 179:2.
    如果挂载根文件系统失败,则会打印:No filesystem could mount root, tried: yaffs2
(3)执行用户态下的进程1程序,上面一旦挂载rootfs成功,则进入rootfs中寻找应用程序的init程序,这个程序就是用户空间的进程1。


 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式_笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值