第3阶段——内核启动分析之创建si工程和分析stext启动内核函数(4)
目标:
(1)创建Source Insight 工程,方便后面分析如何启动内核的
(2)分析uboot传递参数,链接脚本如何进入stext的
(3) 分析stext函数如何启动内核:
(3.1) 关闭irq和fiq,设置svc管理模式
(3.2)判断是或支持这个CPU
(3.3)判断是否支持这个单板(通过uboot传入的机器ID判断)
(3.4)创建页表,为后面的MMU做准备
(3.5) 使能MMU并跳到__switch_data处,复制数据段,清除bss段,设置栈,调用start_kernel第一个C函数
1 创建内核source sight 工程
1.1 点击 “add all” 添加所有文件,后面再慢慢删去Arch目录和Include目录中与2440芯片没用的文件。
1.2 点击Remove Tree 删除Arch文件夹,再添加与2440相关的硬件核心代码以及其它公用的代码
Arch:包含了平台,处理器相关的代码,并包括boot文件夹。
1.2.1 点击Add Tree添加以下子目录:
linux-2.6.22.6/arch/arm/boot (启动配置文件)
linux-2.6.22.6/arch/arm/common (公共文件)
linux-2.6.22.6/arch/arm/configs (配置文件)
linux-2.6.22.6/arch/arm/kernel (内核文件)
linux-2.6.22.6/arch/arm/lib (固件库)
linux-2.6.22.6/arch/arm/mach-s3c2440 (machine 设备,2440设备库)
linux-2.6.22.6/arch/arm/mach-s3c2410 (2440中部分调用了2410设备库)
linux-2.6.22.6/arch/arm/Mm (内存管理文件)
linux-2.6.22.6/arch/arm/nwfpe
linux-2.6.22.6/arch/arm/oprofile (性能分析工具文件)
linux-2.6.22.6/arch/arm/plat-s3c24xx (s3c24系列平台文件)
linux-2.6.22.6/arch/arm/tools (常用工具文件)
linux-2.6.22.6/arch/arm/vfp (浮点运算文件)
1.3 点击Remove Tree 删除Include文件夹,再添加与2440相关的头文件
Include: 包括了核心的大多数include文件,另外对于每种支持的体系结构分别有一个子目录
1.3.1 点击Add All 添加 linux-2.6.22.6/include/asm-arm目录下文件(不包含子目录所有文件),如下图所示:
1.3.2 点击Add Tree添加以下子目录:
linux-2.6.22.6/include/asm-arm/arch-s3c2410 (2410处理器架构)
linux-2.6.22.6/include/asm-arm/hardware (硬件相关头文件)
linux-2.6.22.6/include/asm-arm/mach (具体的设备文件)
linux-2.6.22.6/include/asm-arm/plat-s3c24xx (s3c24系列平台头文件)
1.3.3返回到 linux-2.6.22.6/include目录下,点击Add Tree添加除了asm-xx开头的其它通用文件:
linux-2.6.22.6/include/acpi (高级配置与电源接口文件)
linux-2.6.22.6/include/config
linux-2.6.22.6/include/crypto
linux-2.6.22.6/include/keys
linux-2.6.22.6/include/linux
linux-2.6.22.6/include/math-emu
linux-2.6.22.6/include/mtd
linux-2.6.22.6/include/net
linux-2.6.22.6/include/pcmcia
linux-2.6.22.6/include/rdma
linux-2.6.22.6/include/rxrpc
linux-2.6.22.6/include/scsi
linux-2.6.22.6/include/sound
linux-2.6.22.6/include/video
1.4 最后点击synchronize files 创建source insight工程
2.内核启动之分析uboot传递参数和链接脚本
2.1 内核在uboot启动之前是进入do_boom_linux函数
(do_boom_linux函数启动内核详解:http://www.cnblogs.com/lifexy/p/7310279.html)
do_boom_linux代码如下:
theKernel = (void (*)(int, int, unsigend int))0x30008000; // 设置theKernel地址=0x30008000,用于后面启动内核 /*设置atag参数*/ setup_start_tag (void); //从0X30000100地址处开始保存start_tag数据, setup_memory_tags (void); //保存memory_tag数据,让内核知道内存多大 setup_commandline_tag (“boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0”); /*保存命令行bootargs参数,让内核知道根文件系统位置在/dev/mtdblock3,指定开机运行第一个脚本/linuxrc,指定打印串口0*/ setup_end_tag (void); //初始化tag结构体结束 theKernel(,,0x300000100); //362:机器ID, 0x300000100: params(atag)参数地址 /*传递参数跳转执行到0x30008000启动内核, */ /*相当于: mov r0,#0 */ /*ldr r1,=362 */ /*ldr r2,= 0x300000100 */ /*mov pc,#0x30008000 */
TAG参数内存布局图如下:
2.2然后来分析链接脚本arm/arm/kernel/vmlinux.lds
OUTPUT_ARCH(arm) //设置输出文件的体系架构 ENTRY(stext) //设置stext全局符号为入口地址 jiffies = jiffies_64; SECTIONS
{
. = (0xc0000000) + 0x00008000; /*设置内核虚拟地址=0xc0000000+0x00008000 */
.text.head : {
_stext = .;
_sinittext = .;
*(.text.head) //添加所有.text.head段
}
.init : { /* Init code and data */
*(.init.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init) //存放处理器相关的信息初始化
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init) //存放与架构(arch)相关的信息(info)初始化
__arch_info_end = .;
... ...
从vmlinux.lds中得出linux内核启动第一步是进入stext入口函数。
那么stext入口函数又在哪里定义的呢?
搜索ENTRY(stext)得出,它在arch/arm/kernel/head.S中,
stext函数的在前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 = machine nr, r2 = atags prointer.代码如下:
/* * Kernel startup entry point. //内核 启动 入口 点 * --------------------------- * * This is normally called from the decompressor code. The requirements * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0, /* 前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 =机器ID, r2 =atag参数地址.*/ * r1 = machine nr. * This code is mostly position independent, so if you link the kernel at * 0xc0008000, you call this at __pa(0xc0008000). * See linux/arch/arm/tools/mach-types for the complete list of machine * numbers for r1. * * We're trying to keep crap to a minimum; DO NOT add any machine specific * crap here - that's what the boot loader (or in extreme, well justified * circumstances, zImage) is for. */ section ".text.head", "ax" /* 定义一个.text.head段,段的属性a是允许段,x可 执行 */ .type stext, %function /*定义了由bootloader进入内核的入口stext */ ENTRY(stext) ... ...
它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能(因为内核代码中全是0XCxxxxxxx地址),并跳进第一个C语言函数start_kernel。
所以,内核启动后第一步是 进入arch/arm/kernel/head.S的stext函数中.
3内核启动之stext函数分析(arch/arm/kernel/head.S)
stext函数内容,如下图:
(1) 关闭irq和fiq,设置svc管理模式
(2)判断是或支持这个CPU
(3)判断是否支持这个单板(通过uboot传入的机器ID判断)
(4)创建页表,为后面的MMU做准备
(5) 使能MMU并跳到__switch_data处,复制数据段,清除bss段,设置栈,调用start_kernel第一个C函数
stext函数代码如下:
section ".text.head", "ax" /* 定义一个.text.head段,段的属性a是允许段,x可 执行 */ .type stext, %function /*定义了由bootloader进入内核的入口stext */ ENTRY(stext) //入口地址stext函数 /*msr cpsr_c,0xD3 关闭irq和fiq,设置svc管理模式 */ msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled /*获取cpu ID */ mrc p15, , r9, c0, c0 @ get processor id /*查找内核是否支持r9这个cpuID,若不支持r5=0,支持r5=处理器ID*/ bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=)? /*不支持则跳转到__error_p,死循环*/ beq __error_p @ yes, error 'p' /*查找内核是否支持uboot传入的r1机器ID(362),若不支持r5=0,支持r5=机器ID*/ bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=)? /*不支持则跳转到__error_a,死循环*/ beq __error_a @ yes, error 'a' /*跳转到__create_page_tables 创建页表,为后面的MMU做准备*/ bl __create_page_tables
3.1 分析上面”__lookup_machine_type函数”是如何通过查找r1机器ID(362)是或等于单板机器ID的,代码如下(位于arch/arm/kernel):
: .long .
.long __arch_info_begin
.long __arch_info_end __lookup_machine_type: /*(b:bank)r3=后面的符号3处. 虚拟地址,由于mmu未启动,所以=物理地址*/
adr r3, 3b
ldmia r3, {r4, r5, r6}/* r4=3b处的虚拟地址 ,r5=__arch_info_begin处的虚拟地址,r6=__arch_info_end处的虚拟地址 */
sub r3, r3, r4 @ get offset between virt&phys //得到虚拟地址(virtual)与物理地址(physical)的偏移值
add r5, r5, r3 @ convert virt addresses to //找到arch_info_begin处的物理地址
add r6, r6, r3 @ physical address space //找到__arch_info_end处的物理地址
: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type //r3=r5+偏移地址里内容= 单板机器ID
teq r3, r1 //判断r1(365)和单板机器ID是否相等,相等说明内核支持该单板
beq 2f @ found //相等则直接返回到stext函数继续执行
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6 blo 1b
mov r5, # @ unknown machine //r5=0,不支持该单板
: mov pc, lr //退出
其中__arch_info_begin和__arch_info_end是在链接脚本arm/arm/kernel/vmlinux.lds中定义:
__arch_info_begin = .; //__arch_info_begin=信息开始地址
*(.arch.info.init) //存放架构相关的信息初始化
__arch_info_end = .; //__arch_info_end =信息结束地址
通过grep “.arch.info.init” -nR其中.arch.info.init段在include/asm-ram/mach/arch.h中53行处定义:
代码如下:
#define MACHINE_START(_type,_name) //定义了一个MACHINE_START宏, _type:CPU名字,_name:开发板名字
static const struct machine_desc __mach_desc_##_type \ //##:连词符号
__used \
__attribute__((__section__(".arch.info.init"))) = { \ //强制将 MACHINE_START宏里的成员组成.arch.info.init段
.nr = MACH_TYPE_##_type, \
.name = _name, #define MACHINE_END \ //定义宏MACHINE_END= }; };
搜索MACHINE_START宏发现arch/arm目录下每个文件都使用了这个宏定义,由于我们选用的是S3C2440和SMDKs3c2440
所以得出使用宏#define MACHINE_START(_type,_name)的是:
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> ) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100, .init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
其中上面第1段使用的宏就是之前在arch.h中定义的MACHINE_START(_type,_name),其中_type替换成S3C2440, _name替换成"SMDK2440".
第11段的MACHINE_END在被arch.h中定义为等于“};”
最终
将宏定义代入上面MACHINE_START(S3C2440, "SMDK2440")处的11段代码中,展开如下所示:
static const struct machine_desc __mach_desc_ S3C2440 //定义一个machine_desc型结构体,名字为__mach_desc_ S3C2440
__used \
__attribute__((__section__(".arch.info.init"))) = { //强制将MACHINE_START宏里的成员组成.arch.info.init段
.nr = MACH_TYPE_ S3C2440, // __mach_desc_ S3C2440.nr= MACH_TYPE_ S3C2440 机器ID
.name = "SMDK2440", //__mach_desc_ S3C2440. name = "SMDK2440" 机器ID名字 /* Maintainer: Ben Dooks <ben@fluff.org> */
/*.phys_io =0X50000000,存放物理IO基地址*/
.phys_io = S3C2410_PA_UART, /* .io_pg_offst存放物理IO偏移地址*/
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> ) & 0xfffc, /*其中S3C2410_SDRAM_PA=0X30000000, .boot_params= 0X30000100,所以我们uboot传入的atag参数地址必须是0X30000100*/
.boot_params = S3C2410_SDRAM_PA + 0x100, .init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
}; // MACHINE_END替换成 };
从上面可以看出主要是初始化了machine_desc结构体,然后将其放在.arch.info.init段上,让 内核启动时将uboot传递进来的ID与这个段上的ID进行比较是否吻合,支不支持该单板初始化。
因为不同的单板都有不同MACHINE_START(_type,_name)以及硬件上可能有差别,所以需要初始化的内容也不同
返回stext函数中继续往下看:
ldr r13, __switch_data @ address to jump to after
//MMU使能之后会跳转(jump)到__switch_data@ mmu has been enabled adr lr, __enable_mmu @ return (PIC) address//使能MMU
add pc, r10, #PROCINFO_INITFUNC
为什么使能MMU后会跳转到__switch_data?
在__enable_mmu函数中最后面可以看到使能MMU后,会将r13赋给PC,跳转到了__switch_data,代码如下:
__enable_mmu:
... ....
mov r3, r3
mov r3, r3
mov pc, r13
跳转到了__switch_data中,其中 __switch_data 是__mmap_switched的虚拟地址,所以最终跳转到__mmap_switched中.
__switch_data函数代码如下:
__switch_data:
.long __mmap_switched //进入__mmap_switched函数
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp __mmap_switched:
adr r3, __switch_data + //r3=__data_loc段内容
/*其中
__data_loc 是数据存放的位置
__data_start 是数据开始的位置
__bss_start 是bss开始的位置
_end 是bss结束的位置, 也是内核结束的位置
这几个符号都在arch/arm/kernel/vmlinux.lds中定义的变量
*/ ldmia r3!, {r4, r5, r6, r7} //r4=__data_loc , r5=__data_start , r6=__bss_start ,r7=_end , r3= processor_id
cmp r4, r5 // __data_loc段不等于__data_start段则执行下面1处的内容
: cmpne r5, r6 // 比较r5(__data_start段)和r6(__bss_start段)
ldrne fp, [r4], #
strne fp, [r5], # //str r4,[r5] 将整个段里内容从 __data_loc段 复制到__data_start段
bne 1b //r5不等于r6,则继续复制
mov fp, # @ Clear BSS (and zero fp) //清除bss段
: cmp r6, r7 //比较r6(__bss_start t段)和r7(_end段)
strcc fp, [r6],# // 清除bss段
bcc 1b //(cc:小于)r6<r7,继续清除bss段
ldmia r3, {r4, r5, r6, sp} //r4=r3= processor_id, r5=__machine_arch_type,r6= cr_alignment,
//设置栈sp= init_thread_union + THREAD_START_SP,方便执行C函数start_kernel
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values b start_kernel //然后执行start_kernel函数
最终跳到start_kernel函数,此函数代码用纯C来实现,它会调用各个平台的相关初始化函数
下一节开始分析start_kernel函数
第3阶段——内核启动分析之创建si工程和分析stext启动内核函数(4)的更多相关文章
- linux内核中socket的创建过程源码分析(详细分析)
1三个相关数据结构. 关于socket的创建,首先需要分析socket这个结构体,这是整个的核心. 104 struct socket { 105 socket_state ...
- linux内核中socket的创建过程源码分析(总结性质)
在漫长地分析完socket的创建源码后,发现一片浆糊,所以特此总结,我的博客中同时有另外一篇详细的源码分析,内核版本为3.9,建议在阅读本文后若还有兴趣再去看另外一篇博文.绝对不要单独看另外一篇. 一 ...
- 建立uboot,内核的SI工程(1)
1. 建立Uboot的SI工程1.1首先给uboot打上补丁,然后来生成压缩文件 tar cjf u-boot- 1.2 编译uboot make 100ask24x0_config //使用打好补丁 ...
- Linux内核设计第三周学习总结 跟踪分析Linux内核的启动过程
陈巧然 原创作品 转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验步骤 登陆实验楼虚 ...
- Linux内核分析第六周学习笔记——分析Linux内核创建一个新进程的过程
Linux内核分析第六周学习笔记--分析Linux内核创建一个新进程的过程 zl + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...
- [置顶] linux内核启动1-启动参数(启动参数的获取和处理,分析setup_arch)
最近公司要求调试一个内核,启动时有问题,所以就花了一点时间看看内核启动. 看的过程中总结了一点东西,希望可以帮助大家调试内核. 当我开始看的时候,第一件事是从网上搜集资料,不看不知道,一看吓一跳!牛人 ...
- 《Linux内核分析》 week8作业-Linux加载和启动一个可执行程序
一.ELF文件格式 ELF(Executable and Linking Format)是x86 Linux系统下常用的目标文件格式,有三种主要类型: 适于连接的可重定位文件,可与其他目标文件一起创建 ...
- linux-3.2.36内核启动1-启动参数(arm平台 启动参数的获取和处理,分析setup_arch)【转】
转自:http://blog.csdn.net/tommy_wxie/article/details/17093297 最近公司要求调试一个内核,启动时有问题,所以就花了一点时间看看内核启动. 看的过 ...
- Linux驱动之内核自带的S3C2440的LCD驱动分析
先来看一下应用程序是怎么操作屏幕的:Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户 ...
随机推荐
- VMware workstation虚拟集群实践(1)—— 配置集群多节点互信
一. 简述 节点互信,是集群管理的基本操作之一.节点互信是通过SSH协议的公钥密钥认证来代替密码认证来实现的.对于单点批量管理多个节点,多个节点之间相互通信来说,配置SSH单方向信任,或者互信十分必要 ...
- 移动玩具[HAOI2008]
题目描述 在一个4*4的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动时只能将玩具向上下左右四个方向移动,并且移动的位置不能有玩具,请你用最少的移动次数将初始的玩 ...
- nginx实现请求的负载均衡 + keepalived实现nginx的高可用
前言 使用集群是网站解决高并发.海量数据问题的常用手段.当一台服务器的处理能力.存储空间不足时,不要企图去换更强大的服务器,对大型网站而言,不管多么强大的服务器,都满足不了网站持续增长的业务需求.这种 ...
- java运算符优先级与流程控制
1. Java 的方法Method (函数 Function), 功能, 动作 1) 方法就是函数: y=f(x)=3x+6; 2) 方法的语法 (修饰词)(返回值类型)(方法名)(参数列表){ ...
- 分页工具类 BaseAction
package com.xxxxxxx.bos.web.action.common; import java.io.IOException; import java.lang.reflect.Para ...
- CodeForces 797C Minimal string:贪心+模拟
题目链接:http://codeforces.com/problemset/problem/797/C 题意: 给你一个非空字符串s,空字符串t和u.有两种操作:(1)把s的首字符取出并添加到t的末尾 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(三)用户接口层之RTSP命令
截至版本1.2.3,myRtspClient函数库共支持以下6个RTSP命令: (1)OPTIONS (2)DESCRIBE (3)SETUP (4)PLAY (5)PAUSE (6)TEARDOWN ...
- .net 正则获取url参数
public static string GetParams(string paramName) { var url = "http://fdsfs.com/Home/Index?corp= ...
- textarea 空格的问题
如果textarea开始标签和结束标签中间有空格,则textarea默认有空格. <textarea name=" > </textarea> 效果图 所以,如果想去 ...
- 编译安装 Nginx1.12.1
本文描述Nginx 的源码编译安装过程 ############## 一.安装OpenSSL ###################### 下载地址 https://www.openssl.org/s ...