内核启动分析
来源:互联网 发布:淘宝网店转让合法吗 编辑:程序博客网 时间:2024/06/04 19:03
内核启动分析
顶层Makefile分析:和uboot的Makefile写法、规则等是一样的。 在编译内核时,也可以通过命令行给内核Makefile传参。(make o=XXX,指定不在源代码目录下编译,而是到另一个单独文件夹下编译)
两个重要参数:ARCH和CROSS_COMPILE。ARCH决定当前配置编译的路径,如ARCH = arm的时候,将来在源码目录下去操作的arch/arm目录。CROSS_COMPILE用来指定交叉编译工具链的路径和前缀。
CROSS_COMPILE = xxx和ARCH = xxx和O=xxx都可以在make编译时通过命令行传参的方式传给顶层Makefile。如:
make O=/tmp/mykernel ARCH=arm CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
(编译生成的zImage在arch/arm/boot/目录下)
链接脚本分析
为何找链接脚本?就是为了找到程序的entry。内核没有像uboot那样直接提供.lds脚本文件,而是提供一个汇编文件vmlinux.lds.S,然后编译的时候再去编译这个汇编文件得到真正的链接脚本vmlinux.lds。在arch/arm/kernel目录下。从vmlinux.lds中ENTRY(stext)可以知道入口符号是stext,就在head.S文件中。
head.S文件分析
内核运行的物理地址与虚拟地址:
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
CONFIG_PAGE_OFFSET 在.config中可以查到。
CONFIG_PAGE_OFFSET = 0xC0000000
TEXT_OFFSET = 0x00008000
#define PHYS_OFFSET UL(0x30000000)
(1) KERNEL_RAM_VADDR:这个宏定义了内核运行时的虚拟地址。值为0xC0008000
(2) KERNEL_RAM_PADDR,这个宏定义内核运行时的物理地址。值为0x30008000
(3)总结:内核运行的物理地址是0x30008000,对应的虚拟地址是0xC0008000。
内核的真正入口是ENTRY(stext),内核的起始部分代码是被解压代码调用的,其启动的条件是由bootloader(uboot)来构建保证的。ARM体系中,函数调用时实际是通过寄存器传参的(函数调用时传参有两种设计:一种是寄存器传参,另一种是栈内存传参)。所以uboot中最后调用函数
theKernel (0, machid, bd->bi_boot_params) 执行内核时,运行时实际把0放入r0中,machid(机器码)放入到了r1中,bd->bi_boot_params(boot_argc)放入到了r2中。
kernel启动时MMU是关闭的,因此硬件上需要的是物理地址。但是内核是一个整体(zImage)只能被连接到一个地址(不能分散加载),这个连接地址肯定是虚拟地址。因此内核运行时前段head.S中尚未开启MMU之前的这段代码必须是位置无关码,而且其中涉及到操作硬件寄存器等时必须使用物理地址。
内核启动的汇编阶段
1、__lookup_processor_type
从cp15协处理器的c0寄存器中读取出硬件的CPU ID号,然后调用这个函数来进行合法性检验。如果失败就会转向__error_p函数。
2、__lookup_machine_type和__lookup_processor_type一样对机器码进行校 验。
3、__vet_atags 检验bootargs传的参数是否正确。
4、__create_page_tables 建立页表。linux内核本身被连接在虚拟地址处,因 此kernel希望尽快建立页表并且启动MMU进入虚拟地址工作状态。
kernel建立页表其实分为2步。第一步,kernel先建立了一个段式页表(和uboot中之前建立的页表一样,页表以1MB为单位来区分的),这里的函数就是建立段式页表的。段式页表本身比较好建立(段式页表1MB一个映射,4GB空间需要4096个页表项,每个页表项4字节,因此一共需要16KB内存来做页表),坏处是比较粗不能精细管理内存;第二步,再去建立一个细页表(4kb为单位的细页表),然后启用新的细页表废除第一步建立的段式映射页表。内核启动的早期建立段式页表,并在内核启动前期使用;内核启动后期就会再次建立细页表并启用。等内核工作起来之后就只有细页表了。
5、__switch_data
执行的功能:复制数据段、清除bss段(目的是构建C语言运行环境);保存起来cpu id号、机器码、tag传参的首地址。b start_kernel跳转到C语言运行阶段。
C语言启动阶段
lockdep_init 锁定依赖,是一个内核调试模块,相关的处理内核自旋锁死锁问题。
cgroup_init_early 内核提供的一种来处理进程组的技术。
打印内核版本信息
printk(KERN_NOTICE "%s", linux_banner);
setup_arch 确定当前平台
setup_processor 查找CPU信息,可通过串口打印分析。
setup_machine 传参是机器码编号,machine_arch_type符号在include/generated/mach-types.h的32039-32050行定义了。X210传参值就是2456。函数的作用是通过传入的机器码编号,找到对应这个机器码的machine_desc描述符,并且返回这个描述符的指针。
__lookup_machine_type函数的工作原理:内核在建立的时候就把各种CPU架构的信息组织成一个一个的machine_desc结构体实例,然后都给一个段属性.arch.info.init,链接的时候会保证这些描述符会被连接在一起。__lookup_machine_type就去那个那些描述符所在处依次挨个遍历各个描述符,比对看机器码哪个相同。
setup_arch函数进行了基本的cmdline处理。内核对cmdline的处理思路是:内核中自己维护了一个默认的cmdline(就是.config中配置的这一个),然后uboot还可以通过tag给kernel再传递一个cmdline。如果uboot给内核传cmdline成功则内核会优先使用uboot传递的这一个;如果uboot没有给内核传cmdline或者传参失败,则内核会使用自己默认的这个cmdline。以上说的这个处理思路就是在setup_arch函数中实现的。
setup_command_line
parse_early_param & parse_args 解析cmdline传参和其他传参
trap_init 设置异常向量表
mm_init 内存管理模块初始化
sched_init 内核调度系统初始化
early_irq_init&init_IRQ 中断初始化
console_init 控制台初始化
rest_init
rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd,调用schedule函数开启了内核的调度系统。到了cpu_idle()函数,系统就起来了。
三个进程
进程0:进程0就是idle进程,叫空闲进程。
进程1:kernel_init函数就是进程1,这个进程被称为init进程。
进程2:kthreadd函数就是进程2,这个进程是linux内核的守护进程。这个进程是用来保证linux内核自己本身能正常工作的。
如何从内核态跳跃到用户态?
init进程在内核态下面时,通过一个函数kernel_execve来执行一个用户空间编译连接的应用程序就跳跃到用户态了,这个跳跃过程是单向的。
prepare_namespace函数中挂载根文件系统
uboot通过传参来告诉内核这些信息。
uboot传参中的root=/dev/mmcblk0p2 rw 这一句就是告诉内核根文件系统在哪里
uboot传参中的rootfstype=ext3这一句就是告诉内核rootfs的类型。
如果内核挂载根文件系统成功,则会打印出:VFS: Mounted root (ext3 filesystem) on device 179:2.
如果挂载根文件系统失败,则会打印:No filesystem could mount root, tried: yaffs2
如果内核启动时挂载rootfs失败,则后面肯定没法执行了。
如何确定init程序是谁?方法是:
先从uboot传参cmdline中看有没有指定,如果有指定先执行cmdline中指定的程序。cmdline中的init=/linuxrc这个就是指定rootfs中哪个程序是init程序。这里的指定方式就表示我们rootfs的根目录下面有个名字叫linuxrc的程序,这个程序就是init程序。
如果uboot传参cmdline中没有init=xx或者cmdline中指定的这个xx执行失败,还有备用方案。第一备用:/sbin/init,第二备用:/etc/init,第三备用:/bin/init,第四备用:/bin/sh。
cmdline常用参数
root=
(1)这个是用来指定根文件系统在哪里的。
(2)一般格式是root=/dev/xxx(一般如果是nandflash上则/dev/mtdblock2, 如果是inand/sd的话则/dev/mmcblk0p2)
(3)如果是nfs的rootfs,则root=/dev/nfs。
rootfstype=
根文件系统的文件系统类型,一般是jffs2、yaffs2、ext3、ubi
console=
控制台信息声明,譬如console=/dev/ttySAC0,115200表示控制台使用串口0,波特率是115200.
mem=
用来告诉内核当前系统的内存有多少
init=
用来指定进程1的程序pathname,一般都是init=/linuxrc
常见cmdline介绍
(1)console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3
第一种这种方式对应rootfs在SD/iNand/Nand/Nor等物理存储器上。这种对应产品正式出货工作时的情况。
(2)root=/dev/nfs nfsroot=192.168.1.141:/root/s3c2440/build_rootfs/aston_rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200
第二种这种方式对应rootfs在nfs上
内核代码基本分为3块
(1)arch。 本目录下全是cpu架构有关的代码
(2)drivers 本目录下全是硬件的驱动
(3)其他 相同点是这些代码都和硬件无关,因此系统移植和驱动开发的时候这些代码几乎都是不用关注的。
架构相关的常用目录名及含义
(1)mach。(mach就是machine architecture)。arch/arm目录下的一个mach-xx目录就表示一类machine的定义,这类machine的共同点是都用xx这个cpu来做主芯片。(譬如mach-s5pv210这个文件夹里面都是s5pv210这个主芯片的开发板machine);mach-xx目录里面的一个mach-yy.c文件中定义了一个开发板(一个开发板对应一个机器码),这个是可以被扩展的。
(2)plat(plat是platform的缩写,含义是平台)plat在这里可以理解为SoC,也就是说这个plat目录下都是SoC里面的一些硬件(内部外设)相关的一些代码。在内核中把SoC内部外设相关的硬件操作代码就叫做平台设备驱动。
(3)include。这个include目录中的所有代码都是架构相关的头文件。(linux内核通用的头文件在内核源码树根目录下的include目录里)
内核中的文件结构很庞大、很凌乱(不同版本的内核可能一个文件存放的位置是不同的)
(2)头文件目录include有好几个,譬如:
kernel/include 内核通用头文件
kernel/arch/arm/include 架构相关的头文件
Kernel/arch/arm/include/asm
Kernel/arch/arm/include/asm/mach
Kernel/arch/arm/arch-s5pv210/include/mach
Kernel/arch/arm/plat-s5p/include/plat
(3)内核中包含头文件时有一些格式
#include <linux/kernel.h> kernel/include/linux/kernel.h
#include <asm/mach/arch.h> kernel/arch/arm/include/asm/mach/arch.h
#include <asm/setup.h> kernel\arch\arm\include\asm/setup.h
#include <plat/s5pv210.h> kernel\arch\arm\plat-s5p\include\plat/s5pv210.h
(4)有些同名的头文件是有包含关系的,有时候我们需要包含某个头文件时可能并不是直接包含他,而是包含一个包含了他的头文件。
- Linux 内核启动分析
- Linux内核启动分析
- 内核启动过程分析
- Linux 内核启动分析
- tms320dm6446内核启动分析
- linux内核启动分析
- Linux 内核启动分析
- Linux内核启动分析
- tms320dm6446内核启动分析
- 内核启动流程分析
- 内核启动makefile分析
- 分析uboot启动内核
- 内核启动流程分析
- Linux内核启动分析
- Linux内核启动分析
- Linux内核启动分析
- 内核启动分析
- 内核启动流程分析
- 主题六 函数(C语言核心)----37.函数递归详解
- 读王垠博文有感
- java语言基础(66)——集合框架(arrayList ConcurrentModificationException 并发修改异常的解决方案)
- MVC下拉控件绑定数据
- 子进程的异步等待方式
- 内核启动分析
- Hbase之表的设计
- iOS学习笔记-078.核心动画04——CATransition(转场动画)
- mongoDB-pymongo-入门
- C++primer-1
- 值得收藏的50个学习C语言的源代码网站
- html学习
- Dockerfile中的Shell风格与JSON风格对比及SHELL指令详解
- java字符串操作:匹配、替换、萃取、分割拆分