Exynos4412 内核移植(三)—— 内核启动过程分析

发布者:chuyifei最新更新时间:2021-12-14 来源: eefocus关键字:Exynos4412  内核移植  内核启动 手机看文章 扫描二维码
随时随地手机看文章

内核启动所用函数如下:


与移植U-Boot 的过程相似,在移植Linux 之前,先了解它的启动过程。Linux 的过程可以分为两部分:架构/开发板相关的引导过程、后续的通用启动过程。对于uImage、zImage ,它们首先进行自解压得到vmlinux ,然后执行 vmlinux 开始“正常的”启动流程。

引导阶段通常使用汇编语言编写,它首先检查内核是否支持当前架构的处理器,然后检查是否支持当前开发板。通过检查后,就为调用下一阶段的start_kernel函数作准备了。这主要分如下两个步骤:


1)-- 连接内核时使用的虚拟地址,所以要设置页表、使能MMU;


2)调用C 函数 start_kernel 之前的常规工作,包括复制数据段、清除BSS段、调用start_kernel 函数。


第二阶段的关键代码主要使用C语言编写。它进行内核初始化的全部工作,最后调用 rest_init 函数启动init 过程,创建系统第一个进程:init 进程。在第二阶段,仍有部分架构/开发板相关的代码,比如重新设置页表、设置系统时钟、初始化串口等。


下面是详细解析:


一、第一阶段

与Uboot 一样,我们在连接文件中查看函数入口点,内核编译完成后会在arch/arm/kernel/下生成 vmlinux.lds 文件,打开:

stext 在 linux/arch/arm/kernel/head.S 中被定义,做为函数入口点,linux/arch/arm/kernel/head.S是linux内核映像解压后执行的第一个文件。

代码只是部分,但可以看到这一阶段究竟做了些什么:


a -- 设定为SVC模式,关闭IRQ、FIQ;


b -- 确定CPU的ID号,判定其是否有效;


c -- 确定machine的ID号,检查合法性;


d -- 检查bootloader传入的参数列表atags的合法性


e -- 创建初始页表


下面对上面遇到的程序段展开分析:


a -- 确保处于SVC模式

这没什么好讲的,就是设置CPSR 模式位,并屏蔽中断;


b -- 检查CPU ID 是否匹配

获取ID并放到 r9 寄存器中,调用_lookup_processor_type 函数, 函数主要用来判定内核是否和当前的CPU匹配,如果不匹配,r5寄存器的值应为0,此时会调用 _error_p函数,它用来打印错误信息,即内核和当前的CPU不匹配,此时内核时不能启动的;如果两者匹配,会返回一个描述处理器结构的地址(在r5寄存器中),然后调用下面的函数。


下面看_lookup_processor_type 函数,在arch/arm/kernel/head-common.S 中定义:

上面的代码其实就是一个地址转换过程,因为在判定CPU架构时未开启系统的MMU功能,所以均使用物理地址,而内核代码在连接时是以虚拟地址来实现的,因此要想用proc_info_list 结构体,就要先找到proc_info_list 结构的物理地址,这样必须使用上面的转换代码。


proc_info_list 结构体很重要。在Linux 内核映像中定义了很多个proc_info_list 结构,该结构表示的是内核所支持的CPU架构,这部分下面会讲到,先分析上面的代码:


153 行:r3 存储的是_lookup_processor_type_data 的物理地址 ;

155 行:得到虚拟地址和物理地址之间的offset;


156 - 157 行:利用offset,将 r5 和 r6 中保存的虚拟地址转变为物理地址,主要是获得_proc_info_begin 及_proc_info_end 的物理地址,分别放到r5 和 r6 中;


159 行:r9 中存放的是先前读出的 processor ID,此处屏蔽不需要的位;


160 行:查看代码和CPU硬件是否匹配,如果匹配就返回,此时 r5 存放的是该CPU类型对应的结构体_proc_info_list 的基地址 ;不成功,则查看下一个 proc_info_list 结构体;


163行:如果直到 _proc_info_end ,都没有匹配,则定为未知CPU,向 r5 赋 0,然后返回 ;


下面来看看 proc_info_list 结构体 ,这个结构体在 arch/arm/include/asm/procinfo.h 中定义:

对于 Cortex-A9 来说,其结构体在文件 arch/arm/mm/proc-v7.S 中初始化:

.section ".proc.info.init"表明了该结构在编译后存放的位置。在链接文件arch/arm/kernel/vmlinux.lds中:

__proc_info_begin = .;


*(.proc.info.init)


__proc_info_end = .;



      上面两个变量 _proc_info_begin  与 _proc_info_end 用于计算 proc_info_list 结构的物理地址。


      如果CPU ID匹配,在编译内核文件时,会编译 proc-v7.S 这个文件,可以在arch/arm/mm/Makefile 中看到这个文件

c -- 检测 机器ID是否匹配


      主要用到_lookup_machine_type 函数,其与_lookup_processor_type 函数实现代码很相似,这里不予阐述;

d -- 检查bootloader传入的参数列表atags的合法性

        _vet_atags 函数用于检测参数列表atags的合法性


        内核参数链表的格式和说明可以从内核源代码目录树中的 中找到,参数链表必须以ATAG_CORE 开始,以ATAG_NONE结束。这里的 ATAG_CORE,ATAG_NONE是各个参数的标记,本身是一个32位值,例如:ATAG_CORE=0x54410001。其它的参数标记还包括: ATAG_MEM32 , ATAG_INITRD , ATAG_RAMDISK ,ATAG_COMDLINE 等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参数链表。参数结构体的定义如下:

struct tag {

      struct  tag_header  hdr;

      union {

             struct tag_core  core;

       struct tag_mem32   mem;

          struct tag_videotext videotext;

struct tag_ramdisk  ramdisk;

struct tag_initrd     initrd;

          struct tag_serialnr     serialnr;

struct tag_revision  revision;

          struct tag_videolfb  videolfb;

          struct tag_cmdline  cmdline;

 struct tag_acorn       acorn;

struct tag_memclk    memclk;

        } u;

};


参数结构体包括两个部分,一个是 tag_header结构体,一个是u联合体。

tag_header结构体的定义如下: 

   struct tag_header { 

                 u32 size;   

                 u32 tag; 

}; 


其中 size:表示整个 tag 结构体的大小(用字的个数来表示,而不是字节的个数),等于tag_header的大小加上 u联合体的大小,例如,参数结构体 ATAG_CORE 的 size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通过函数 tag_size(struct * tag_xxx)来获得每个参数结构体的 size。其中 tag:表示整个 tag 结构体的标记,如:ATAG_CORE等。 


__vet_atags:

tst r2, #0x3 //r2指向该参数链表的起始位置,此处判断它是否字对齐

bne 1f

 

ldr r5, [r2, #0] //获取第一个tag结构的size

//#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2) 判断该tag的长度是否合法

subs r5, r5, #ATAG_CORE_SIZE

bne 1f

ldr r5, [r2, #4]  //获取第一个tag结构体的标记,

ldr r6, =ATAG_CORE 

cmp r5, r6 //判断第一个tag结构体的标记是不是ATAG_CORE

bne 1f  

 

mov pc, lr //正常退出

 

1: mov r2, #0

mov pc, lr  //参数连表不正确

ENDPROC(__vet_atags)


e -- 创建初始页表

其在下面被执行:

下面是详细分析:


/*

 * Setup the initial page tables.  We only setup the barest

 * amount which are required to get the kernel running, which

 * generally means mapping in the kernel code.

 *

 * r8 = phys_offset, r9 = cpuid, r10 = procinfo

 *

 * Returns:

 *  r0, r3, r5-r7 corrupted

 *  r4 = page table (see ARCH_PGD_SHIFT in asm/memory.h)

 */

__create_page_tables:

pgtbl r4, r8 @ page table address

 

/*

* Clear the swapper page table

*/

mov r0, r4

mov r3, #0

add r6, r0, #PG_DIR_SIZE

1: str r3, [r0], #4

str r3, [r0], #4

str r3, [r0], #4

str r3, [r0], #4

teq r0, r6

bne 1b

 

#ifdef CONFIG_ARM_LPAE

/*

* Build the PGD table (first level) to point to the PMD table. A PGD

* entry is 64-bit wide.

*/

mov r0, r4

add r3, r4, #0x1000 @ first PMD table address

orr r3, r3, #3 @ PGD block type

mov r6, #4 @ PTRS_PER_PGD

mov r7, #1 << (55 - 32) @ L_PGD_SWAPPER

1:

#ifdef CONFIG_CPU_ENDIAN_BE8

str r7, [r0], #4 @ set top PGD entry bits

str r3, [r0], #4 @ set bottom PGD entry bits

#else

str r3, [r0], #4 @ set bottom PGD entry bits

str r7, [r0], #4 @ set top PGD entry bits

#endif

add r3, r3, #0x1000 @ next PMD table

subs r6, r6, #1

bne 1b

 

add r4, r4, #0x1000 @ point to the PMD tables

#ifdef CONFIG_CPU_ENDIAN_BE8

add r4, r4, #4 @ we only write the bottom word

#endif

#endif

 

ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

 

/*

* Create identity mapping to cater for __enable_mmu.

* This identity mapping will be removed by paging_init().

*/

adr r0, __turn_mmu_on_loc

ldmia r0, {r3, r5, r6}

sub r0, r0, r3 @ virt->phys offset

add r5, r5, r0 @ phys __turn_mmu_on

add r6, r6, r0 @ phys __turn_mmu_on_end

mov r5, r5, lsr #SECTION_SHIFT

mov r6, r6, lsr #SECTION_SHIFT

 

1: orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base

str r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping

cmp r5, r6

addlo r5, r5, #1 @ next section

blo 1b

 

/*

* Map our RAM from the start to the end of the kernel .bss section.

*/

add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)

ldr r6, =(_end - 1)

orr r3, r8, r7

add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)

1: str r3, [r0], #1 << PMD_ORDER

add r3, r3, #1 << SECTION_SHIFT

cmp r0, r6

bls 1b

 

#ifdef CONFIG_XIP_KERNEL

/*

* Map the kernel image separately as it is not located in RAM.

*/

#define XIP_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)

mov r3, pc

mov r3, r3, lsr #SECTION_SHIFT

orr r3, r7, r3, lsl #SECTION_SHIFT

add r0, r4,  #(XIP_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)

str r3, [r0, #((XIP_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!

ldr r6, =(_edata_loc - 1)

add r0, r0, #1 << PMD_ORDER

add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)

1: cmp r0, r6

add r3, r3, #1 << SECTION_SHIFT

strls r3, [r0], #1 << PMD_ORDER

bls 1b

#endif

 

/*

* Then map boot params address in r2 if specified.

* We map 2 sections in case the ATAGs/DTB crosses a section boundary.

*/

mov r0, r2, lsr #SECTION_SHIFT

movs r0, r0, lsl #SECTION_SHIFT

subne r3, r0, r8

addne r3, r3, #PAGE_OFFSET

addne r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)

orrne r6, r7, r0

strne r6, [r3], #1 << PMD_ORDER

addne r6, r6, #1 << SECTION_SHIFT

strne r6, [r3]

 

#if defined(CONFIG_ARM_LPAE) && defined(CONFIG_CPU_ENDIAN_BE8)

sub r4, r4, #4 @ Fixup page table pointer

@ for 64-bit descriptors

#endif

 

#ifdef CONFIG_DEBUG_LL

#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)

/*

* Map in IO space for serial debugging.

* This allows debug messages to be output

* via a serial console before paging_init.

*/

addruart r7, r3, r0

 

mov r3, r3, lsr #SECTION_SHIFT

mov r3, r3, lsl #PMD_ORDER

 

add r0, r4, r3

mov r3, r7, lsr #SECTION_SHIFT

ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags

orr r3, r7, r3, lsl #SECTION_SHIFT

#ifdef CONFIG_ARM_LPAE

mov r7, #1 << (54 - 32) @ XN

#ifdef CONFIG_CPU_ENDIAN_BE8

str r7, [r0], #4

str r3, [r0], #4

#else

str r3, [r0], #4

str r7, [r0], #4

#endif

#else

orr r3, r3, #PMD_SECT_XN

str r3, [r0], #4

#endif

 

#else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */

/* we don't need any serial debugging mappings */

ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags

#endif

 

#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)

/*

* If we're using the NetWinder or CATS, we also need to map

* in the 16550-type serial port for the debug messages

*/

add r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)

orr r3, r7, #0x7c000000

str r3, [r0]

#endif

#ifdef CONFIG_ARCH_RPC

/*

* Map in screen at 0x02000000 & SCREEN2_BASE

* Similar reasons here - for debug.  This is

* only for Acorn RiscPC architectures.

*/

add r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)

orr r3, r7, #0x02000000

str r3, [r0]

add r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)

str r3, [r0]

#endif

#endif

#ifdef CONFIG_ARM_LPAE

sub r4, r4, #0x1000 @ point to the PGD table

mov r4, r4, lsr #ARCH_PGD_SHIFT

#endif

mov pc, lr

ENDPROC(__create_page_tables)


f -- 使能MMU,跳转到start_kernel

文件linux/arch/arm/kernel/head.S中

文件linux/arch/arm/kernel/head.S中

在前面有过这样的指令操作ldr r13, __switch_data ,


mov pc, r13 就是将跳转到__switch_data处。


在文件linux/arch/arm/kernel/head-common.S中:



.type __switch_data, %object  //定义一个对象

__switch_data:

.long __mmap_switched  //由此可知上面程序将跳转到该程序段处。

.long __data_loc @ r4

.long _data @ r5

.long __bss_start @ r6

.long _end @ r7

.long processor_id @ r4

.long __machine_arch_type @ r5

.long __atags_pointer @ r6

.long cr_alignment @ r7

.long init_thread_union + THREAD_START_SP @ sp

 

 

 . = PAGE_OFFSET + TEXT_OFFSET;

[1] [2]
关键字:Exynos4412  内核移植  内核启动 引用地址:Exynos4412 内核移植(三)—— 内核启动过程分析

上一篇:Exynos4412 内核移植(二)—— 内核编译过程分析
下一篇:Exynos4412 文件系统制作(一)—— 文件系统的启动过程分析

推荐阅读最新更新时间:2024-11-12 23:06

基于S3C2440的Linux内核移植和yaffs2文件系统制作--根文件系统
第二章 制作根文件系统 2.1 根文件系统预备知识 嵌入式Linux中都需要构建根文件系统,构建根文件系统的规则在FHS(Filesystem Hierarchy Standard)文档中,下面是根文件系统顶层目录。 目录 内容 bin 存放所有用户都可以使用的、基本的命令。 sbin 存放的是基本的系统命令,它们用于启动系统、修复系统等。 usr 里面存放的是共享、只读的程序和数据。 proc 这是个空目录,常作为proc文件系统的挂载点。 dev 该目录存放设备文件和其它特殊文件。 etc 存放系统配置文件,包括启动文件。 lib 存放共享库和可加载块(即驱动程序),共享库用于启动系统、运行根文件系统中的可执行程序。 boo
[单片机]
Exynos4412 Uboot 移植(四)—— Uboot引导内核过程分析
bootloader 要想启动内核,可以直接跳到内核的第一个指令处,即内核的起始地址,这样便可以完成内核的启动工作了。但是要想启动内核还需要满足一些条件,如下所示: 1、cpu 寄存器设置 * R0 = 0 * R1 = 机器类型 id * R2 = 启动参数在内存中的起始地址 2、cpu 模式 * 禁止所有中断 * 必须为SVC(超级用户)模式 3、Cache、MMU * 关闭 MMU * 指令Cache可以开启或者关闭 * 数据Cache必须关闭 4、设备 * DMA 设备应当停止工作 5、PC为内核的起始地址 这些需求都由 boot loader 实现
[单片机]
<font color='red'>Exynos4412</font> Uboot <font color='red'>移植</font>(四)—— Uboot引导<font color='red'>内核</font>过程分析
05-S3C2440学习之内核移植)linux3.4.2移植(3)之支持DM9000C网卡及修改支持串口2
之前我们裁剪并移植好了linux3.4.2内核 http://blog.csdn.net/fengyuwuzu0519/article/details/70162666 也学习过 移植DM9000C网卡驱动程序到linux2.2.26内核上http://blog.csdn.net/fengyuwuzu0519/article/details/72821567 接下来我们在此基础上,在linux3.4.2中移植DM9000c网卡驱动,使内核可以支持网卡芯片,这样方便使用NFS网络文件系统。 一、移植思路 (1)我们现在移植好的内核中,支持smdk2440单板和mini2440单板。且使用mini2440机器id的时候
[单片机]
05-S3C2440学习之<font color='red'>内核</font>(<font color='red'>移植</font>)linux3.4.2<font color='red'>移植</font>(3)之支持DM9000C网卡及修改支持串口2
Exynos4412从SD卡启动的简单网络文件系统制作
1. 简介 嵌入式系统能够在开发板上正常运行,需要先进行系统配置,一个完整的嵌入式系统应该包含的几个部分::uboot,kernel,rootfs,appfs。这几部分在ARM板Flash上的位置关系如下图所示: 嵌入式系统分区结构 注:图片来源于韦东山老师的《嵌入式Linux应用开发完全手册》 rootfs可以不用添加到开发板,而是部署到PC机上,开发板在运行完kernel,要挂载rootfs时,可以通过NFS网络挂载到设定好的PC机上的文件系统上。 操作系统: 内核 + 文件(应用, 配置, 设备, ...); bootloader: u-boot (交互界面) # lsX help
[单片机]
<font color='red'>Exynos4412</font>从SD卡<font color='red'>启动</font>的简单网络文件系统制作
LINUX 内核移植
1. 下载内核源码linux-2.6.34,解压到工作目录下。 2. 首先在内核中增加一个 SOC ,到 /arch/arm/mach-s3c64xx 下将mach-smdk6410.c 复制成 mach-ok6410.c 修改mach-ok6410.c 将里面的 smdk6410 替换为 ok6410 ,将SMDK6410 替换为 OK6410   :1,$s/smdk6410/ok6410/g 修改该目录下的 Makefile obj-$(CONFIG_MACH_OK6410) += mach-ok6410.o 修改该目录下的 Kconfig 增加 config MACH_OK6410 bool OK6410
[单片机]
LINUX <font color='red'>内核</font><font color='red'>移植</font>
linux 2.6.24.4在S3C2410上的移植(内核配置)(基于GEC2410)
移植完u-boot后,接下来就是linux内核了.以下记录我移植的步骤,如有问题,欢迎指正. 1.下载linux kernel源代码 到http://www.kernel.org/下载linux内核源代码,这里我们使用2.6.24.4的内核. 解压linux-2.6.24.4.tar.bz2 $ tar -xvjf linux-2.6.24.4.tar.bz2 $ cd linux-2.6.24.4 2.修改顶层Makefile,设置交叉编译器 ARCH ?= arm CROSS_COMPILE ?= /home/GEC2410/toolchain/arm-softfloat-linux-gnu/bin/arm-softf
[单片机]
tiny4412学习(一)之从零搭建linux系统(烧写uboot、内核进emmc+uboot启动内核
硬件平台:tiny4412 系统:linux-3.5-20151029 文件系统:busybox-1.22.1.tar.bz2 编译器: arm-linux-gcc-4.5.1 目的: 使用uboot引导linux系统,并挂接根文件系统,搭建起linux开发环境。 由于友善支臂提供的minitools不是开源,使用起来很不舒服。本文将记录从零使用uboot在tiny4412上搭建linux系统的。由于之前只是学了2440,完成这个流程也遇到各种,现在总结如下。其中参考了多篇博客才得以实现,具体涉及的时候会给出链接。 一、准备系统文件 1、安装交叉编译工具链 (1)解压编译器源码 tar arm-linux-gcc-
[单片机]
tiny4412学习(一)之从零搭建linux系统(烧写uboot、<font color='red'>内核</font>进emmc+uboot<font color='red'>启动</font><font color='red'>内核</font>)
ARM Linux内核启动2
上一篇ARM Linux内核启动(1)的衔接。 接着上一篇说,看下面源码: /* * Setup the initial page tables. We only setup the barest * amount which are required to get the kernel running, which * generally means mapping in the kernel code.只创建内核代码的映射 * * r5 = physical address of start of RAM * r6 = physical IO address * r7 = byte offset into pag
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved