第3阶段——内核启动分析之创建si工程和启动内核分析(3)

目标:

(1)创建Source Insight 工程,方便后面分析如何启动内核的

(2)分析uboot传递参数,链接脚本如何进入stext的

(3) 分析stext函数如何启动内核

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(0,362,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 = .;

...  ...

得出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, 0, 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=0)?

/*不支持则跳转到__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=0)?

/*不支持则跳转到__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):

3:       .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处的物理地址

1:       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, #0                             @ unknown machine  //r5=0,不支持该单板

2:       mov   pc, lr                       //退出

其中__arch_info_begin和__arch_info_end是在链接脚本arm/arm/kernel/vmlinux.lds中定义:

305  __arch_info_begin = .;           //__arch_info_begin=信息开始地址

306     *(.arch.info.init)              //存放架构相关的信息初始化

307  __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)的是:

1 MACHINE_START(S3C2440, "SMDK2440")

2     /* Maintainer: Ben Dooks <[email protected]> */

3     .phys_io  = S3C2410_PA_UART,

4     .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

5     .boot_params  = S3C2410_SDRAM_PA + 0x100,

6

7     .init_irq   = s3c24xx_init_irq,

8     .map_io          = smdk2440_map_io,

9     .init_machine  = smdk2440_machine_init,

10   .timer             = &s3c24xx_timer,

11  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 <[email protected]> */

/*.phys_io  =0X50000000,存放物理IO基地址*/

.phys_io  = S3C2410_PA_UART,

/* .io_pg_offst存放物理IO偏移地址*/

.io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 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:

...  ....

mov    r3, r3

mov    r3, r3

mov    pc, r13

跳转到了__switch_data中,代码如下, __switch_data 是__mmap_switched的虚拟地址,然后跳转到__mmap_switched中:

__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 + 4           //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处的内容

1:       cmpne  r5, r6                  // 比较r5(__data_start段)和r6(__bss_start段)

ldrne   fp, [r4], #4

strne   fp, [r5], #4                //str r4,[r5] 将整个段里内容从 __data_loc段 复制到__data_start段

bne     1b                      //r5不等于r6,则继续复制

mov   fp, #0                             @ Clear BSS (and zero fp)  //清除bss段

1:       cmp    r6, r7                   //比较r6(__bss_start t段)和r7(_end段)

strcc   fp, [r6],#4               // 清除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函数

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函数,此函数代码用纯C来实现,它会调用各个平台的相关初始化函数,接下来分析start_kernel函数

时间: 2024-10-12 14:46:00

第3阶段——内核启动分析之创建si工程和启动内核分析(3)的相关文章

linux-3.2.36内核启动1-启动参数(arm平台 启动参数的获取和处理,分析setup_arch)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/17093297 最近公司要求调试一个内核,启动时有问题,所以就花了一点时间看看内核启动. 看的过程中总结了一点东西,希望可以帮助大家调试内核. 当我开始看的时候,第一件事是从网上搜集资料,不看不知道,一看吓一跳!牛人太多了,像这种内核启动的上古代码早就被人分析的彻彻底底.这注定我写的只能是烂微博了. 为了此微博有存在的必要,我会显示内核启动打印的代码位置(用绿色表示)及出现错误打印的原因(用红

linux内核中socket的创建过程源码分析(详细分析)

http://www.cnblogs.com/hyd-desert-camel/p/3536341.html 1三个相关数据结构. 关于socket的创建,首先需要分析socket这个结构体,这是整个的核心. 104 struct socket { 105         socket_state            state; 106 107         kmemcheck_bitfield_begin(type); 108         short                 

Linux进程的创建函数fork()及其fork内核实现解析

进程的创建之fork() Linux系统下,进程可以调用fork函数来创建新的进程.调用进程为父进程,被创建的进程为子进程. fork函数的接口定义如下: #include <unistd.h> pid_t fork(void); 与普通函数不同,fork函数会返回两次.一般说来,创建两个完全相同的进程并没有太多的价值.大部分情况下,父子进程会执行不同的代码分支.fork函数的返回值就成了区分父子进程的关键.fork函数向子进程返回0,并将子进程的进程ID返给父进程.当然了,如果fork失败,

Linux内核分析(一)---linux体系简介|内核源码简介|内核配置编译安装

原文:Linux内核分析(一)---linux体系简介|内核源码简介|内核配置编译安装 Linux内核分析(一) 从本篇博文开始我将对linux内核进行学习和分析,整个过程必将十分艰辛,但我会坚持到底,同时在博文中如果那些地方有问题还请各位大神为我讲解. 今天我们会分析到以下内容: 1.      Linux体系结构简介 2.      Linux内核源码简介 3.      Linux内核配置.编译.安装 l  Linux体系结构简介 1.       Linux体系结构(linux系统构成)

Activity启动模式 及 Intent Flags 与 栈 的关联分析

   在学习Android的过程中,Intent是我们最常用Android用于进程内或进程间通信的机制,其底层的通信是以Binder机制实现的,在物理层则是通过共享内存的方式实现的.     Intent主要用于2种情景下:(1)发起意图  (2)广播     它的属性有:ComponentName,action,data,category,extras,flags等,通常情况下,进行Intent的匹配涉及到3个属性:Action,Data,Category.这些东西都需要了解才能对它有个深入的

Android系统在新进程中启动自定义服务过程(startService)的原理分析

在编写Android应用程序时,我们一般将一些计算型的逻辑放在一个独立的进程来处理,这样主进程仍然可以流畅地响应界面事件,提高用户体验.Android系统为我们提供了一个Service类,我们可以实现一个以Service为基类的服务子类,在里面实现自己的计算型逻辑,然后在主进程通过startService函数来启动这个服务.在本文中,将详细分析主进程是如何通过startService函数来在新进程中启动自定义服务的. 在主进程调用startService函数时,会通过Binder进程间通信机制来

3. 文件系统——创建、删除分区和内核同步分区信息

一.创建分区 如果使用fdisk命令来创建.删除.修改分区,可以直接使用fdisk [DEVICE]来操作,如: # fdisk /dev/sda 但需要注意的是,用命令来操作硬件,很容易造成数据丢失,故务必谨慎使用. fdisk命令是个交互命令,使用fdisk [DEVICE] 时,系统会提示各种可以使用的选项: [[email protected] ~]# fdisk /dev/sda # 不要加上 -l选项 --------------运行结果----------------- WARNI

【转】Activity启动模式 及 Intent Flags 与 栈 的关联分析

http://blog.csdn.net/vipzjyno1/article/details/25463457    在学习Android的过程中,Intent是我们最常用Android用于进程内或进程间通信的机制,其底层的通信是以Binder机制实现的,在物理层则是通过共享内存的方式实现的.     Intent主要用于2种情景下:(1)发起意图  (2)广播     它的属性有:ComponentName,action,data,category,extras,flags等,通常情况下,进行

spring启动component-scan类扫描加载过程---源码分析

有朋友最近问到了 spring 加载类的过程,尤其是基于 annotation 注解的加载过程,有些时候如果由于某些系统部署的问题,加载不到,很是不解!就针对这个问题,我这篇博客说说spring启动过程,用源码来说明,这部分内容也会在书中出现,只是表达方式会稍微有些区别,我将使用spring 3.0的版本来说明(虽然版本有所区别,但是变化并不是特别大),另外,这里会从WEB中使用spring开始,中途会穿插自己通过newClassPathXmlApplicationContext 的区别和联系.