Linux内核启动流程分析(一)
- 格式:docx
- 大小:179.99 KB
- 文档页数:9
嵌入式linux系统的启动流程
嵌入式Linux系统的启动流程一般包括以下几个步骤:
1.硬件初始化:首先会对硬件进行初始化,例如设置时钟、中
断控制等。
这一步骤通常是由硬件自身进行初始化,也受到系统的BIOS或Bootloader的控制。
2.Bootloader引导:接下来,系统会从存储介质(如闪存、SD
卡等)的Bootloader区域读取引导程序。
Bootloader是一段程序,可以从存储介质中加载内核镜像和根文件系统,它负责进行硬件初始化、进行引导选项的选择,以及加载内核到内存中。
3.Linux内核加载:Bootloader会将内核镜像从存储介质中加载到系统内存中。
内核镜像是包含操作系统核心的一个二进制文件,它由开发者编译并与设备硬件特定的驱动程序进行连接。
4.内核初始化:一旦内核被加载到内存中,系统会进入内核初
始化阶段。
在这个阶段,内核会初始化设备驱动程序、文件系统、网络协议栈等系统核心。
5.启动用户空间:在内核初始化完毕后,系统将启动第一个用
户空间进程(init进程)。
init进程会读取并解析配置文件(如
/etc/inittab)来决定如何启动其他系统服务和应用程序。
6.启动其他系统服务和应用程序:在用户空间启动后,init进
程会根据配置文件启动其他系统服务和应用程序。
这些服务和应用程序通常运行在用户空间,提供各种功能和服务。
以上是嵌入式Linux系统的基本启动流程,不同的嵌入式系统可能会有一些差异。
同时,一些特定的系统也可以添加其他的启动流程步骤,如初始化设备树、加载设备固件文件等。
启动流程启动时要加载核心,让核心来驱动整个硬件。
整个启动过程:1.加载BIOS的硬件信息,并获得第一个启动设备的代号(CMOS中设定的启动项)。
2.读取第一个启动设备的MBR的引导加载程序(lilo、grub、spfdisk)3.加载核心操作系统的核心信息,核心开始解压缩,并且尝试驱动所有硬件设备。
4.核心执行init程序并获取运行信息。
5.Init执行/etc/rc.d/rc.sysinit文件6.启动核心的外挂模块(/etc/modprobe.conf)7.Init 执行各个批处理文件(根据运行级别)。
8.Init 执行/etc/rc.d/rc.local文件9.执行/bin/login程序,等待用户登录。
10.登录之后开始以shell控制主机。
引导加载程序与核心的载入主机读取BIOS,并且了解主要的主机硬件信息后,主机便开始尝试加载操作系统。
主机首先读取的就是硬盘的主引导记录(MBR),在MBR中装有引导加载程序(比如说grub)。
主机刚启动时是不认识磁盘文件系统的,这就需要引导加载程序帮忙。
但我们知道MBR是整个硬盘的第一个扇区,整个大小为一个扇区的大小(512KB),而我们的加载程序却远远大于这个容量。
怎么办?引导加载程序分成两个阶段来执行:1,执行引导加载程序的主程序,这个主程序放在MBR或超级块中。
2,载入引导加载程序的所有设置文件与相关的环境参数文件。
一般来说,设置文件都放在/boot目录下。
引导加载程序必须能做到:●引导加载程序可以直接指定并取用核心文件,并加载到主存储器中。
●也可以将加载程序的控制权移交给下一个加载程序(超级块中的引导加载程序)。
grub是如何被载入的呢?grub有几个重要文档,stage1,stage2,以及stage1.5,这些文档都在/boot/grub下,grub被载入时有以下几个步骤。
Stage1阶段装载基本的引导程式(stage1),这也是安装在MBR中的内容,大小为512字节,但这并不意味着占用的空间为512字节,这还要看块的大小以及inode控制的块数。
Linux 内核启动分析1. 内核启动地址1.1. 名词解释ZTEXTADDR解压代码运行的开始地址。
没有物理地址和虚拟地址之分,因为此时MMU处于关闭状态。
这个地址不一定时RAM的地址,可以是支持读写寻址的flash等存储中介。
Start address of decompressor. here's no point in talking about virtual or physical addresses here, since the MMU will be off at the time when you call the decompressor code. Y ou normally call the kernel at this address to start it booting. This doesn't have to be located in RAM, it can be in flash or other read-only or read-write addressable medium.ZRELADDR内核启动在RAM中的地址。
压缩的内核映像被解压到这个地址,然后执行。
This is the address where the decompressed kernel will be written, and eventually executed. The following constraint must be valid:__virt_to_phys(TEXTADDR) == ZRELADDRThe initial part of the kernel is carefully coded to be position independent.TEXTADDR内核启动的虚拟地址,与ZRELADDR相对应。
一般内核启动的虚拟地址为RAM的第一个bank地址加上0x8000。
arm版本linux系统的启动流程ARM架构是一种常见的处理器架构,被广泛应用于嵌入式设备和移动设备中。
在ARM版本的Linux系统中,启动流程是非常重要的,它决定了系统如何从开机到正常运行。
本文将详细介绍ARM版本Linux系统的启动流程。
一、引导加载程序(Bootloader)引导加载程序是系统启动的第一阶段,它位于系统的固化存储器中,比如ROM或Flash。
在ARM版本的Linux系统中,常用的引导加载程序有U-Boot和GRUB等。
引导加载程序的主要功能是加载内核镜像到内存中,并将控制权转交给内核。
二、内核初始化引导加载程序将内核镜像加载到内存后,控制权被转交给内核。
内核初始化是系统启动的第二阶段,它主要完成以下几个步骤:1. 设置异常向量表:ARM架构中,异常是指硬件产生的中断或故障,比如系统调用、中断请求等。
内核需要设置异常向量表,以便正确处理异常。
2. 初始化处理器:内核对处理器进行初始化,包括设置页表、启用缓存、初始化中断控制器等。
3. 启动第一个进程:内核创建第一个用户进程(一般是init进程),并将控制权转交给它。
init进程是系统中所有其他进程的父进程,负责系统的初始化工作。
三、设备树(Device Tree)设备树是ARM版本Linux系统中的一种机制,用于描述硬件设备的相关信息。
在内核初始化过程中,内核会解析设备树,并建立设备树对象,以便后续的设备驱动程序使用。
设备树描述了硬件设备的类型、地址、中断等信息,以及设备之间的连接关系。
它使得内核能够在运行时自动识别和配置硬件设备,大大提高了系统的可移植性和灵活性。
四、启动初始化(Init)启动初始化是系统启动的第三阶段,它是用户空间的第一个进程(init进程)接管系统控制权后的操作。
启动初始化主要完成以下几个任务:1. 挂载根文件系统:启动初始化会挂载根文件系统,使得用户可以访问文件系统中的文件和目录。
2. 加载系统服务:启动初始化会加载并启动系统服务,比如网络服务、日志服务、时间同步服务等。
MBR和GRUB概述Linux 的启动流程目前比较流行的方式主要是以下步骤:1、引导器(例如 GRUB)启动;2、内核启动;3、系统进程启动与配置。
本文以 GRUB 为研究对象,对 GRUB 启动与内核启动两个部分进行描述,关于系统进程的进一步启动与配置将用另一篇文章来说明。
常见的目录结构(以 CentOS 5.3 为例):/boot|-- System.map-2.6.18-128.el5|-- System.map-2.6.18-128.el5xen|-- config-2.6.18-128.el5|-- config-2.6.18-128.el5xen|-- initrd-2.6.18-128.el5.img|-- initrd-2.6.18-128.el5xen.img|-- lost+found|-- memtest86+-1.65|-- message|-- symvers-2.6.18-128.el5.gz|-- symvers-2.6.18-128.el5xen.gz|-- vmlinuz-2.6.18-128.el5|-- vmlinuz-2.6.18-128.el5xen|-- xen-syms-2.6.18-128.el5|-- xen.gz-2.6.18-128.el5`-- grub|-- device.map|-- e2fs_stage1_5|-- fat_stage1_5|-- ffs_stage1_5|-- grub.conf|-- iso9660_stage1_5|-- jfs_stage1_5|-- menu.lst -> ./grub.conf|-- minix_stage1_5|-- reiserfs_stage1_5|-- splash.xpm.gz|-- stage1|-- stage2|-- ufs2_stage1_5|-- vstafs_stage1_5`-- xfs_stage1_5图一: CentOS 5.3 的 /boot 目录目录分作两大部分,一个是 /boot 目录下除 grub 目录以外的所有文件,这些是 Linux 的内核以及内核启动相关的一些文件;另一个就是 grub 下的所有文件, GRUB 引导器启动所需要的所有文件都在 grub 目录下。
Linux-mips启动流程-存储相关linux内核启动的第一个阶段是从/arch/mips/kernel/head.s文件开始的。
而此处正是内核入口函数kernel_entry(),该函数定义在/arch/mips/kernel/head.s文件里。
kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备,接着用一段循环将内核映像的未初始化数据段(bss段在_edata和_end之间)清零,最后跳转到/arch/mips/kernel/setup.c 中的start_kernel()初始化硬件平台相关的代码。
下面讲述start_kernel() 函数。
在这个函数中跟内存初始化的函数是setup_arch()。
第一部分:以函数调用关系为线索下面是函数之间调用关系的框图:第一章:start_kenel()->setup_arch()setup_arch(&command_line);每种体系结构都有自己的setup_arch() 函数,这些是体系结构相关的。
【如何确定编译那个体系结构的setup_arch() 函数呢?主要由linux 源码树顶层Makefile 中ARCH 变量来决定的。
例如:MIPS 体系结构的。
SUBARCH := mipsARCH ?= $(SUBARCH)】。
void __init setup_arch(char **cmdline_p){cpu_probe();调用函数cpu_probe(),该函数通过MIPS CPU的PRID寄存器来确定CPU类型,从而确定使用的指令集和其他一些CPU参数,如TLB等prom_init();prom_init() 函数是和硬件相关的,做一些低层的初始化,接受引导装载程序传给内核的参数,确定mips_machgroup,mips_machtype 这两个变量,这两个变量分别对应着相应的芯片组合开发板;cpu_report();打印cpu_probe() 函数检测到的CPU 的Processor ID。
Linux内核分析_课程设计计算机科学与⼯程学院课程设计报告题⽬全称:Linux内核初起代码分析学⽣学号:姓名:指导⽼师:职称:指导⽼师评语:签字:课程设计成绩:⽬录摘要 (2)第⼀章引⾔ (1)1.1 问题的提出 (1)1.2任务与分析 (1)第⼆章代码分析 (2)2.1系统初始化过程流程 (2)2.2数据结构 (2)2.3常量和出错信息的意义 (4)2.4调⽤关系图 (4)2.5各模块/函数的功能及详细框图 (5)2.5.1 static void time_init(void)分析 (6)2.5.2 void main(void)分析 (6)2.5.3 pause()分析 (8)2.5.4 static int printf(const char *fmt, ...)分析 (8)2.5.5 void init(void)分析 (9)第三章内核调试 (12)3.1运⾏环境 (12)3.2编译内核过程 (12)第四章总结与体会 (15)致谢 (16)参考⽂献 (17)摘要随着计算机的普及,计算机发挥着越来越重要的作⽤,计算机的使⽤也越来越普遍,所以让更多的⼈能够更好的使⽤和掌握⼀些计算机⽅法显得⼗分重要。
充分发挥计算机的作⽤也显得⼗分重要。
操作系统应运⽽⽣。
操作系统是⼀种软件,⽤来帮助其他的程序控制计算机并和⽤户进⾏交互。
因⽽,对操作系统的研究是很有必要的。
操作系统包含了多个部分或者组件,最核⼼的部分是内核。
其他的部分⽤来帮助内核完成计算机资源的管理和应⽤程序的控制。
Linux操作系统是使⽤很⼴泛的,⾼质量的⼀个操作系统。
此次起始代码分析,我分析了init/main.c⽂件中的main()、init()以及编译内核代码。
main()中主要是关于起始的调⽤和设备和系统信息初始化,以及创建进程。
此时中断仍被禁⽌着,做完必要的设置后就将其开启init()是创建进程,并检测是否出错,出错则再次创建执⾏并打印出出错信息。
arm-linux 启动代码分析——stage1 (1)本文针对arm linux, 从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数.我们当前以linux-2.6.19内核版本作为范例来分析,本文中所有的代码,前面都会加上行号以便于和源码进行对照.例:在文件init/main.c中:00478: asmlinkage void __init start_kernel(void)前面的"00478:" 表示478行,冒号后面的内容就是源码了.在分析代码的过程中,我们使用缩进来表示各个代码的调用层次.由于启动部分有一些代码是平台特定的,虽然大部分的平台所实现的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择at91(ARM926EJS)平台进行分析.另外,本文是以uncompressed kernel开始讲解的.对于内核解压缩部分的code,在arch/arm/boot/compressed中,本文不做讨论.一. 启动条件通常从系统上电到执行到linux kenel这部分的任务是由bootloader来完成.关于boot loader的内容,本文就不做过多介绍.这里只讨论进入到linux kernel的时候的一些限制条件,这一般是bootloader在最后跳转到kernel之前要完成的:1.CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;2. MMU(内存管理单元)必须是关闭的, 此时虚拟地址对物理地址;3. 数据cache(Data cache)必须是关闭的4. 指令cache(Instructioncache)可以是打开的,也可以是关闭的,这个没有强制要求;5. CPU 通用寄存器0 (r0)必须是0;6. CPU 通用寄存器1 (r1)必须是ARM Linux machinetype (关于machine type, 我们后面会有讲解)7. CPU 通用寄存器2 (r2) 必须是kernel parameterlist 的物理地址(parameter list 是由bootloader传递给kernel,用来描述设备信息属性的列表,详细内容可参考"Booting ARM Linux"文档).二. starting kernel首先,我们先对几个重要的宏进行说明(我们针对有MMU的情况): 宏 位置 默认值 说明KERNEL_RAM_ADDR arch/arm/kernel/head.S+26 0xc0008000 kernel在RAM中的的虚拟地址PAGE_OFFSET include/asm-arm/memeory.h+50 0xc0000000 内核空间的起始虚拟地址TEXT_OFFSET arch/arm/Makefile+137 0x00008000 内核相对于存储空间的偏移TEXTADDR arch/arm/kernel/head.S+49 0xc0008000 kernel的起始虚拟地址PHYS_OFFSET include/asm-arm/arch-xxx/memory.h 平台相关 RAM的起始物理地址内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:00011: ENTRY(stext)对于vmlinux.lds.S,这是ldscript文件,此文件的格式和汇编及C程序都不同,本文不对ldscript作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考这里的ENTRY(stext) 表示程序的入口是在符号stext.而符号stext是在arch/arm/kernel/head.S中定义的:下面我们将arm linuxboot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.在arch/arm/kernel/head.S中72 - 94 行,是armlinux boot的主代码:00072:ENTRY(stext) 00073: msr cpsr_c,#PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode00074: @and irqsdisabled 00075: mrc p15, 0,r9, c0, c0 @ getprocessorid 00076:bl __lookup_processor_type @r5=procinfor9=cpuid 00077: movs r10,r5 @invalid processor (r5=0)?00078:beq __error_p @yes, error‘p’ 00079:bl __lookup_machine_type @r5=machinfo 00080: movs r8,r5 @invalid machine (r5=0)?00081:beq __error_a @yes, error‘a’ 00082:bl __create_page_tables 00083: 00084: 00091: ldr r13,__switch_data @address to jump to after00092: @mmu has beenenabled 00093: adr lr,__enable_mmu @return (PIC)address 00094: add pc,r10,#PROCINFO_INITFUNC 其中,73行是确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭,这样做是很谨慎的.arm linux boot的主线可以概括为以下几个步骤:1. 确定processortype (75 - 78行)2. 确定machinetype (79 - 81行)3.创建页表 (82行) 4.调用平台特定的__cpu_flush函数 (在struct proc_info_list中) (94行) 5.开启mmu (93行)6. 切换数据 (91行)最终跳转到start_kernel (在__switch_data的结束的时候,调用了b start_kernel)下面,我们按照这个主线,逐步的分析Code.1. 确定processor type arch/arm/kernel/head.S中:00075: mrc p15, 0,r9, c0, c0 @ getprocessorid 00076:bl __lookup_processor_type @r5=procinfor9=cpuid 00077: movs r10,r5 @invalid processor (r5=0)?00078:beq __error_p @yes, error‘p’ 75行: 通过cp15协处理器的c0寄存器来获得processor id的指令.关于cp15的详细内容可参考相关的arm手册76行:跳转到__lookup_processor_type.在__lookup_processor_type中,会把processortype 存储在r5中77,78行: 判断r5中的processor type是否是0,如果是0,说明是无效的processortype,跳转到__error_p(出错)__lookup_processor_type 函数主要是根据从cpu中获得的processorid和系统中的proc_info进行匹配,将匹配到的proc_info_list的基地址存到r5中,0表示没有找到对应的processor type.下面我们分析__lookup_processor_type函数arch/arm/kernel/head-common.S中:00145:.type __lookup_processor_type,%function00146: __lookup_processor_type:00147: adr r3,3f00148: ldmda r3,{r5 - r7}00149: sub r3, r3,r7 @get offset between virt&phys00150: add r5, r5,r3 @convert virt addresses to00151: add r6, r6,r3 @physical address space00152: 1: ldmia r5,{r3,r4} @value, mask00153: and r4, r4,r9 @mask wanted bits00154: teq r3,r400155:beq 2f00156: add r5, r5,#PROC_INFO_SZ @sizeof(proc_info_list)00157: cmp r5,r600158:blo 1b00159: mov r5,#0 @unknown processor00160: 2: mov pc,lr00161:00162:00165: ENTRY(lookup_processor_type)00166: stmfd sp!,{r4 - r7, r9, lr}00167: mov r9,r000168:bl __lookup_processor_type00169: mov r0,r500170: ldmfd sp!,{r4 - r7, r9, pc}00171:00172:00176:.long __proc_info_begin00177:.long __proc_info_end00178:3: .long .00179:.long __arch_info_begin00180:.long __arch_info_end 145, 146行是函数定义147行: 取地址指令,这里的3f是向前symbol名称是3的位置,即第178行,将该地址存入r3. 这里需要注意的是,adr指令取址,获得的是基于pc的一个地址,要格外注意,这个地址是3f 处的"运行时地址",由于此时MMU还没有打开,也可以理解成物理地址(实地址).(详细内容可参考arm指令手册) 148行: 因为r3中的地址是178行的位置的地址,因而执行完后: r5存的是176行符号__proc_info_begin的地址; r6存的是177行符号__proc_info_end的地址; r7存的是3f处的地址. 这里需要注意链接地址和运行时地址的区别. r3存储的是运行时地址(物理地址),而r7中存储的是链接地址(虚拟地址). __proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中: 00031: __proc_info_begin= .; 00032: *(.init) 00033: __proc_info_end= .; 这里是声明了两个变量:__proc_info_begin 和__proc_info_end,其中等号后面的"."是locationcounter(详细内容请参考) 这三行的意思是: __proc_info_begin 的位置上,放置所有文件中的".init"段的内容,然后紧接着是__proc_info_end 的位置. kernel 使用struct proc_info_list来描述processor type. 在include/asm-arm/procinfo.h 中: 00029: struct proc_info_list { 00030: unsignedint cpu_val; 00031: unsignedint cpu_mask; 00032: unsignedlong __cpu_mm_mmu_flags; 00033: unsignedlong __cpu_io_mmu_flags; 00034: unsignedlong __cpu_flush; 00035: constchar *arch_name; 00036: constchar *elf_name; 00037: unsignedint elf_hwcap; 00038: constchar *cpu_name; 00039: structprocessor *proc; 00040: structcpu_tlb_fns *tlb; 00041: structcpu_user_fns *user; 00042: structcpu_cache_fns *cache; 00043: }; 我们当前以at91为例,其processor是926的. 在arch/arm/mm/proc-arm926.S 中: 00464: .section ".init", #alloc,#execinstr 00465: 00466:.type __arm926_proc_info,#object 00467: __arm926_proc_info: 00468:.long 0x41069260 @ARM926EJ-S (v5TEJ) 00469:.long 0xff0ffff0 00470:.long PMD_TYPE_SECT | \ 00471: PMD_SECT_BUFFERABLE| \ 00472: PMD_SECT_CACHEABLE| \ 00473: PMD_BIT4 |\ 00474: PMD_SECT_AP_WRITE| \ 00475: PMD_SECT_AP_READ 00476:.long PMD_TYPE_SECT | \ 00477: PMD_BIT4 |\ 00478: PMD_SECT_AP_WRITE| \ 00479: PMD_SECT_AP_READ 00480:b __arm926_setup 00481:.long cpu_arch_name 00482:.long cpu_elf_name 00483:.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HW CAP_EDSP|HWCAP_JA V A 00484:.long cpu_arm926_name 00485:.long arm926_processor_functions 00486:.long v4wbi_tlb_fns 00487:.long v4wb_user_fns 00488:.long arm926_cache_fns 00489:.size __arm926_proc_info,. - __arm926_proc_info 从464行,我们可以看到__arm926_proc_info 被放到了".init"段中. 对照struct proc_info_list,我们可以看到__cpu_flush的定义是在480行,即__arm926_setup.(我们将在"4.调用平台特定的__cpu_flush函数"一节中详细分析这部分的内容.) 从以上的内容我们可以看出: r5中的__proc_info_begin是proc_info_list的起始地址,r6中的__proc_info_end是proc_info_list的结束地址.149行:从上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中.150行: 将r5存储的虚拟地址(__proc_info_begin)转换成物理地址151行: 将r6存储的虚拟地址(__proc_info_end)转换成物理地址152行: 对照structproc_info_list,可以得知,这句是将当前proc_info的cpu_val和cpu_mask分别存r3,r4中153行: r9中存储了processorid(arch/arm/kernel/head.S中的75行),与r4的cpu_mask进行逻辑与操作,得到我们需要的值154行: 将153行中得到的值与r3中的cpu_val进行比较155行: 如果相等,说明我们找到了对应的processor type,跳到160行,返回156行: (如果不相等) , 将r5指向下一个proc_info,157行: 和r6比较,检查是否到了__proc_info_end.158行: 如果没有到__proc_info_end,表明还有proc_info配置,返回152行继续查找159行: 执行到这里,说明所有的proc_info都匹配过了,但是没有找到匹配的,将r5设置成0(unknownprocessor)160行: 返回2. 确定machine type arch/arm/kernel/head.S中:00079:bl __lookup_machine_type @r5=machinfo 00080: movs r8,r5 @invalid machine (r5=0)?00081:beq __error_a @yes, error ‘a’79行: 跳转到__lookup_machine_type函数,在__lookup_machine_type中,会把structmachine_desc的基地址(machine type)存储在r5中80,81行: 将r5中的machine_desc的基地址存储到r8中,并判断r5是否是0,如果是0,说明是无效的machinetype,跳转到__error_a(出错)__lookup_machine_type 函数下面我们分析__lookup_machine_type 函数: arch/arm/kernel/head-common.S中: 00176:.long __proc_info_begin00177:.long __proc_info_end00178:3: .long .00179:.long __arch_info_begin00180:.long __arch_info_end00181:00182: 00193:.type __lookup_machine_type,%function00194: __lookup_machine_type:00195: adr r3,3b00196: ldmia r3,{r4, r5, r6}00197: sub r3, r3,r4 @get offset between virt&phys00198: add r5, r5,r3 @convert virt addresses to00199: add r6, r6,r3 @physical address space00200: 1: ldr r3,[r5, #MACHINFO_TYPE] @ get machinetype00201: teq r3,r1 @matches loader number?00202:beq 2f @found00203: add r5, r5,#SIZEOF_MACHINE_DESC @ nextmachine_desc00204: cmp r5,r600205:blo 1b00206: mov r5,#0 @unknown machine00207: 2: mov pc,lr193, 194行: 函数声明195行: 取地址指令,这里的3b是向后symbol名称是3的位置,即第178行,将该地址存入r3. 和上面我们对__lookup_processor_type 函数的分析相同,r3中存放的是3b处物理地址.196行: r3是3b处的地址,因而执行完后: r4存的是3b处的地址 r5存的是__arch_info_begin 的地址 r6存的是__arch_info_end 的地址 __arch_info_begin 和__arch_info_end是在arch/arm/kernel/vmlinux.lds.S中: 00034: __arch_info_begin= .; 00035: *(.init) 00036: __arch_info_end= .; 这里是声明了两个变量:__arch_info_begin 和__arch_info_end,其中等号后面的"."是locationcounter(详细内容请参考) 这三行的意思是: __arch_info_begin 的位置上,放置所有文件中的".init"段的内容,然后紧接着是__arch_info_end 的位置. kernel 使用struct machine_desc 来描述machine type. 在include/asm-arm/mach/arch.h 中: 00017: struct machine_desc { 00018: 00022: unsignedint nr; 00023: unsignedint phys_io; 00024: unsignedint io_pg_offst; 00026: 00027: constchar *name; 00028: unsignedlong boot_params; 00029: 00030: unsignedint video_start; 00031: unsignedint video_end; 00032: 00033: unsignedint reserve_lp0:1; 00034: unsignedint reserve_lp1:1; 00035: unsignedint reserve_lp2:1; 00036: unsignedint soft_reboot:1; 00037:void (*fixup)(structmachine_desc *, 00038: struct tag *, char **, 00039: struct meminfo *); 00040:void (*map_io)(void); 00041:void (*init_irq)(void); 00042: structsys_timer *timer; 00043:void (*init_machine)(void); 00044: }; 00045: 00046: 00050: #defineMACHINE_START(_type,_name) \ 00051: static const struct machine_desc__mach_desc_##_type \ 00052:__attribute_used__ \ 00053:__attribute__((__section__(".init"))) ={ \ 00054:.nr =MACH_TYPE_##_type, \ 00055:.name =_name, 00056: 00057: #defineMACHINE_END \ 00058:}; 内核中,一般使用宏MACHINE_START来定义machine type. 对于at91, 在arch/arm/mach-at91rm9200/board-ek.c 中: 00137: MACHINE_START(AT91RM9200EK, "Atmel AT91RM9200-EK") 00138: 00139: .phys_io =AT91_BASE_SYS, 00140:.io_pg_offst =(AT91_VA_BASE_SYS >> 18)& 0xfffc, 00141:.boot_params =AT91_SDRAM_BASE + 0x100, 00142:.timer =&at91rm9200_timer, 00143:.map_io =ek_map_io, 00144: .init_irq =ek_init_irq, 00145:.init_machine =ek_board_init, 00146: MACHINE_END197行:r3中存储的是3b处的物理地址,而r4中存储的是3b处的虚拟地址,这里计算处物理地址和虚拟地址的差值,保存到r3中198行: 将r5存储的虚拟地址(__arch_info_begin)转换成物理地址 199行:将r6存储的虚拟地址(__arch_info_end)转换成物理地址 200行: MACHINFO_TYPE 在arch/arm/kernel/asm-offset.c 101行定义, 这里是取struct machine_desc中的nr(architecture number) 到r3中201行: 将r3中取到的machine type 和r1中的machine type(见前面的"启动条件")进行比较202行: 如果相同,说明找到了对应的machine type,跳转到207行的2f处,此时r5中存储了对应的structmachine_desc的基地址203行: (不相同), 取下一个machine_desc的地址204行: 和r6进行比较,检查是否到了__arch_info_end.205行: 如果不相同,说明还有machine_desc,返回200行继续查找.206行: 执行到这里,说明所有的machind_desc都查找完了,并且没有找到匹配的, 将r5设置成0(unknownmachine).207行: 返回3. 创建页表通过前面的两步,我们已经确定了processor type 和machine type.此时,一些特定寄存器的值如下所示:r8 = machineinfo (struct machine_desc的基地址)r9 = cpuid (通过cp15协处理器获得的cpu id)r10 =procinfo (struct proc_info_list的基地址)创建页表是通过函数__create_page_tables 来实现的.这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(section page table)L1 主页表将4 GB 的地址空间分成若干个1 MB的段(section),因此L1页表包含4096个页表项(sectionentry). 每个页表项是32 bits(4 bytes)因而L1主页表占用4096 *4 = 16k的内存空间. 对于ARM926,其L1 section entry的格式为:(可参考arm926EJS TRM): 。
linux启动顺序讲解⼀、简单介绍RHEL开机时的先后顺序BIOS —> MBR —> Kernel —> init1、当电脑⼀打开电源时电脑就会进⼊BIOS(BIOS的⼯作主要是检测⼀些硬件设备);2、检测完后会进⼊MBR也就是boot loader(MBR位于硬盘的第⼀个扇区总共512bytes,其中前446bytes⾥⾯的编码是在选择引导分区也就是决定要由哪个分区来引导);3、载⼊系统的Kernel(核⼼),在Kernel⾥主要是载⼊电脑设备的驱动程序,以便可以控制电脑上的设备,并且以只读⽅式来挂载根⽬录,也就是⼀开始只能读取到根⽬录所对应的那个分区,所以/etc、/bin、/sbin、/dev、/lib这五个⽬录必须同根⽬录在⼀个分区中;4、最后启动init这个程序,所以init这个程序的进程编号为1,是Linux中第⼀个执⾏的程序;init这个程序会根据Run level来执⾏以下这些程序:·/etc/rc.d/rc.sysinit;·/etc/rc.d/rc 和etc/rc.d/rc?.d/·/etc/rc.d/rc.local·如果有适当的图形界⾯管理程序⼆、BIOS初始化时主要的三个任务BIOS(B asic I nput/O utput S ystem)1、电脑周边设备的检测,加电⾃检POST (Power on self test);2、BIOS会选择要由哪⼀个设备来开机,例如:软盘启动、光盘启动、⽹络启动、最常见的从硬盘启动;3、选择好由哪个设备开机后,就开始读取这个设备的MBR 引导扇区;三、介绍Boot Loader中的主要⼯作1、Boot Loader可以安装在两个地⽅:·安装在硬盘的MBR中;·当有时候MBR中被其他开机管理程序占⽤就可以将Boot Loader 安装在硬盘中的其中⼀个分区的引导扇区上,;2、Boot Loader的程序码分为两个阶段:(1)Boot Loader第⼀阶段的程序码⾮常⼩,只有446bytes,可以存⼊在MBR或是某⼀个分区的引导扇区⾥,(2)Boot Loader第⼀阶段的程序码是从boot 分区来载⼊的,就是说Boot Loader 第⼆阶段程序码存放在/boot 这个分区中;3、下⾯来看三个Boot Loader 的开机流程范例,如在⼀块硬盘中安装了两个系统分别为:windows 2003 和Red hat linux当电脑开机后,会先载⼊MBR通过第⼀阶段程序码来载⼊第⼆阶段程序码,进⼊GRUB开机菜单这⾥选择哪个系统就会载⼊相应的核⼼;四、介绍GRUB和grub.conf 这个配置⽂件的内容其实从MBR载⼊Boot Loader开始,载⼊Kernel,载⼊init这些程序之间都是由GRUB这个多重开机管理程序所负责的。
很久以前分析的,一直在电脑的一个角落,今天发现贴出来和大家分享下。
由于是word直接粘过来的有点乱,敬请谅解!S3C2410 Linux 2.6.35.7启动分析(第一阶段) arm linux 内核生成过程1. 依据arch/arm/kernel/vmlinux.lds 生成linux内核源码根目录下的vmlinux,这个vmlinux属于未压缩,带调试信息、符号表的最初的内核,大小约23MB;命令:arm-linux-gnu-ld -o vmlinux -T arch/arm/kernel/vmlinux.ldsarch/arm/kernel/head.oinit/built-in.o--start-grouparch/arm/mach-s3c2410/built-in.okernel/built-in.omm/built-in.ofs/built-in.oipc/built-in.odrivers/built-in.onet/built-in.o--end-group .tmp_kallsyms2.o2. 将上面的vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/Image,这是不带多余信息的linux内核,Image的大小约3.2MB;命令:arm-linux-gnu-objcopy -O binary -S vmlinux arch/arm/boot/Image3.将 arch/arm/boot/Image 用gzip -9 压缩生成arch/arm/boot/compressed/piggy.gz大小约1.5MB;命令:gzip -f -9 < arch/arm/boot/compressed/../Image > arch/arm/boot/compressed/piggy.gz 4. 编译arch/arm/boot/compressed/piggy.S 生成arch/arm/boot/compressed/piggy.o大小约1.5MB,这里实际上是将piggy.gz通过piggy.S编译进piggy.o文件中。
而piggy.S文件仅有6行,只是包含了文件piggy.gz;命令:arm-linux-gnu-gcc -o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/piggy.S5. 依据arch/arm/boot/compressed/vmlinux.lds 将arch/arm/boot/compressed/目录下的文件head.o 、piggy.o 、misc.o链接生成arch/arm/boot/compressed/vmlinux,这个vmlinux是经过压缩且含有自解压代码的内核,大小约1.5MB;命令:arm-linux-gnu-ld zreladdr=0x30008000 params_phys=0x30000100 -T arch/arm/boot/compressed/vmlinux.lds a rch/arm/boot/compressed/head.o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/misc.o -o arch/arm/boot/compressed/vmlinux6.将arch/arm/boot/compressed/vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/zImage 大小约1.5MB;这已经是一个可以使用的linux内核映像文件了;命令:arm-linux-gnu-objcopy -O binary -S arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage7. 将arch/arm/boot/zImage添加64Bytes的相关信息打包为arch/arm/boot/uImage大小约1.5MB;命令: ./mkimage -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n 'Linux-2.6.35.7' -d arch/arm/ boot/zImage arch/arm/boot/uImage内核启动分析:本文着重分析S3C2410 linux-2.6.35.7内核启动的详细过程,主要包括: zImage 解压缩阶段、 vmlinux 启动汇编阶段、 startkernel 到创建第一个进程阶段三个部分,一般将其称为 linux 内核启动一、二、三阶段,本文也将采用这种表达方式。
对于 zImage 之前的启动过程,本文不做表述,可参考前面正亮讲得“ u-boot的启动过程分析”。
本文中涉及到的术语约定如下:基本内核映像:即内核编译过程中最终在内核源代码根目录下生成的 vmlinux 映像文件,并不包含任何内核解压缩和重定位代码;zImage 内核映像:包含了内核piggy.o及解压缩和重定位代码,通常是目标板 bootloader 加载的对象;zImage 下载地址:即 bootloader 将 zImage 下载到目标板内存的某个地址或者 nand read 将 zImage 读到内存的某个地址;zImage 加载地址:由 Linux 的 bootloader 完成的将 zImage 搬移到目标板内存的某个位置所对应的地址值,默认值 0x30008000 。
1、Linux 内核启动第一阶段:内核解压缩和重定位该阶段是从u-boot引导进入内核执行的第一阶段,我们知道u-boot引导内核启动的最后一步是:通过一个函数指针thekernel()带三个参数跳转到内核(zImage)入口点开始执行,此时,u-boot的任务已经完成,控制权完全交给内核(zImage)。
稍作解释,在u-boot的文件arch\arm\lib\bootm.c(uboot-2010.9)中定义了thekernel,并在do_bootm_linux的最后执行thekernel.定义如下:void (*theKernel)(int zero, int arch, uint params);theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);//hdr->ih_ep----Entry Point Address uImage 中指定的内核入口点,这里是0x30008000。
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);其中第二个参数为机器ID,第三参数为u-boot传递给内核参数存放在内存中的首地址,此处是0x30000100。
由上述zImage的生成过程我们可以知道,第一阶段运行的内核映像实际就是arch/arm/boot/compressed/vmlinux,而这一阶段所涉及的文件也只有三个:(1)arch/arm/boot/compressed/vmlinux.lds(2)arch/arm/boot/compressed/head.S(3)arch/arm/boot/compressed/misc.c下面的图是使用64MRAM时,通常的内存分布图:下面我们的分析集中在 arch/arm/boot/compressed/head.S, 适当参考 vmlinux.lds 。
从linux/arch/arm/boot/compressed/vmlinux.lds文件可以看出head.S的入口地址为ENTRY(_start),也就是head.S汇编文件的_start标号开始的第一条指令。
下面从head.S中得_start 标号开始分析。
(有些指令不影响初始化,暂时略去不分析)代码位置在/arch/arm/boot/compressed/head.S中:start:.type start,#function /*uboot跳转到内核后执行的第一条代码*/.rept 8 /*重复定义8次下面的指令,也就是空出中断向量表的位置*/mov r0, r0 /*就是nop指令*/.endrb 1f @ 跳转到后面的标号1处.word 0x016f2818 @ 辅助引导程序的幻数,用来判断镜像是否是zImage.word start @ 加载运行zImage的绝对地址,start表示赋的初值.word _edata @ zImage结尾地址,_edata是在vmlinux.lds.S中定义的,表示init,text,data三个段的结束位置1: mov r7, r1 @ save architecture ID 保存体系结构ID 用r1保存mov r8, r2 @ save atags pointer 保存r2寄存器参数列表,r0始终为0mrs r2, cpsr @ get current mode 得到当前模式tst r2, #3 @ not user?,tst实际上是相与,判断是否处于用户模式bne not_angel @ 如果不是处于用户模式,就跳转到not_angel标号处/*如果是普通用户模式,则通过软中断进入超级用户权限模式*/mov r0, #0x17 @ angel_SWIreason_EnterSVC,向SWI中传递参数swi 0x123456 @ angel_SWI_ARM这个是让用户空间进入SVC空间not_angel: /*表示非用户模式,可以直接关闭中断*/mrs r2, cpsr @ turn off interrupts to 读出cpsr寄存器的值放到r2中orr r2, r2, #0xc0 @ prevent angel from running关闭中断msr cpsr_c, r2 @ 把r2的值从新写回到cpsr中/*读入地址表。
因为我们的代码可以在任何地址执行,也就是位置无关代码(PIC),所以我们需要加上一个偏移量。
下面有每一个列表项的具体意义。
LC0是表的首项,它本身就是在此head.s中定义的.type LC0, #objectLC0: .word LC0 @ r1 LC0表的起始位置.word __bss_start @ r2 bss段的起始地址在vmlinux.lds.S中定义.word _end @ r3 zImage(bss)连接的结束地址在vmlinux.lds.S中定义.word zreladdr @ r4 zImage的连接地址,我们在arch/arm/mach-s3c2410/makefile.boot中定义的.word _start @ r5 zImage的基地址,bootp/init.S中的_start函数,主要起传递参数作用.word _got_start @ r6 GOT(全局偏移表)起始地址,_got_start是在compressed/vmlinux.lds.in中定义的.word _got_end @ ip GOT结束地址.word user_stack+4096 @ sp 用户栈底 user_stack是紧跟在bss段的后面的,在compressed/vmlinux.lds.in中定义的@ 在本head.S的末尾定义了zImag的临时栈空间,在这里分配了4K的空间用来做堆栈。