内核源码——汇编阶段的head.S文件

以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除。

前言

1、内容总结

汇编阶段,或者说内核引导阶段,主要是arch/arm/kernel/head.S文件,主要完成以下内容:

(1)校验启动合法性(CPU ID,机器码,uboot给内核的传参格式等)。

(2)建立段式映射的页表,并开启MMU以方便使用内存。

(3)构建C运行环境,跳入C阶段。

2、head.S文件代码

/**  linux/arch/arm/kernel/head.S**  Copyright (C) 1994-2002 Russell King*  Copyright (c) 2003 ARM Limited*  All Rights Reserved** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License version 2 as* published by the Free Software Foundation.**  Kernel startup code for all 32-bit CPUs*/
#include <linux/linkage.h>
#include <linux/init.h>#include <asm/assembler.h>
#include <asm/domain.h>
#include <asm/ptrace.h>
#include <asm/asm-offsets.h>
#include <asm/memory.h>
#include <asm/thread_info.h>
#include <asm/system.h>#if (PHYS_OFFSET & 0x001fffff)
#error "PHYS_OFFSET must be at an even 2MiB boundary!"
#endif#define KERNEL_RAM_VADDR	(PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR	(PHYS_OFFSET + TEXT_OFFSET)/** swapper_pg_dir is the virtual address of the initial page table.* We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must* make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect* the least significant 16 bits to be 0x8000, but we could probably* relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.*/
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif.globl	swapper_pg_dir.equ	swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000.macro	pgtbl, rdldr	\rd, =(KERNEL_RAM_PADDR - 0x4000).endm#ifdef CONFIG_XIP_KERNEL
#define KERNEL_START	XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
#define KERNEL_END	_edata_loc
#else
#define KERNEL_START	KERNEL_RAM_VADDR
#define KERNEL_END	_end
#endif/** 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,* r1 = machine nr, r2 = atags pointer.** 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.*/__HEAD
ENTRY(stext)setmode	PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode@ and irqs disabledmrc	p15, 0, r9, c0, c0		@ get processor idbl	__lookup_processor_type		@ r5=procinfo r9=cpuidmovs	r10, r5				@ invalid processor (r5=0)?beq	__error_p			@ yes, error 'p'bl	__lookup_machine_type		@ r5=machinfomovs	r8, r5				@ invalid machine (r5=0)?beq	__error_a			@ yes, error 'a'bl	__vet_atagsbl	__create_page_tables/** The following calls CPU specific code in a position independent* manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of* xxx_proc_info structure selected by __lookup_machine_type* above.  On return, the CPU will be ready for the MMU to be* turned on, and r0 will hold the CPU control register value.*/ldr	r13, __switch_data		@ address to jump to after@ mmu has been enabledadr	lr, BSYM(__enable_mmu)		@ return (PIC) addressARM(	add	pc, r10, #PROCINFO_INITFUNC	)THUMB(	add	r12, r10, #PROCINFO_INITFUNC	)THUMB(	mov	pc, r12				)
ENDPROC(stext)#if defined(CONFIG_SMP)
ENTRY(secondary_startup)/** Common entry point for secondary CPUs.** Ensure that we're in SVC mode, and IRQs are disabled.  Lookup* the processor type - there is no need to check the machine type* as it has already been validated by the primary processor.*/setmode	PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9mrc	p15, 0, r9, c0, c0		@ get processor idbl	__lookup_processor_typemovs	r10, r5				@ invalid processor?moveq	r0, #'p'			@ yes, error 'p'beq	__error/** Use the page tables supplied from  __cpu_up.*/adr	r4, __secondary_dataldmia	r4, {r5, r7, r12}		@ address to jump to aftersub	r4, r4, r5			@ mmu has been enabledldr	r4, [r7, r4]			@ get secondary_data.pgdiradr	lr, BSYM(__enable_mmu)		@ return addressmov	r13, r12			@ __secondary_switched addressARM(	add	pc, r10, #PROCINFO_INITFUNC	) @ initialise processor@ (return control reg)THUMB(	add	r12, r10, #PROCINFO_INITFUNC	)THUMB(	mov	pc, r12				)
ENDPROC(secondary_startup)/** r6  = &secondary_data*/
ENTRY(__secondary_switched)ldr	sp, [r7, #4]			@ get secondary_data.stackmov	fp, #0b	secondary_start_kernel
ENDPROC(__secondary_switched).type	__secondary_data, %object
__secondary_data:.long	..long	secondary_data.long	__secondary_switched
#endif /* defined(CONFIG_SMP) *//** Setup common bits before finally enabling the MMU.  Essentially* this is just loading the page table pointer and domain access* registers.*/
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAPorr	r0, r0, #CR_A
#elsebic	r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLEbic	r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLEbic	r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLEbic	r0, r0, #CR_I
#endifmov	r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \domain_val(DOMAIN_IO, DOMAIN_CLIENT))mcr	p15, 0, r5, c3, c0, 0		@ load domain access registermcr	p15, 0, r4, c2, c0, 0		@ load page table pointerb	__turn_mmu_on
ENDPROC(__enable_mmu)/** Enable the MMU.  This completely changes the structure of the visible* memory space.  You will not be able to trace execution through this.* If you have an enquiry about this, *please* check the linux-arm-kernel* mailing list archives BEFORE sending another post to the list.**  r0  = cp#15 control register*  r13 = *virtual* address to jump to upon completion** other registers depend on the function called upon completion*/.align	5
__turn_mmu_on:mov	r0, r0mcr	p15, 0, r0, c1, c0, 0		@ write control regmrc	p15, 0, r3, c0, c0, 0		@ read id regmov	r3, r3mov	r3, r13mov	pc, r3
ENDPROC(__turn_mmu_on)/** 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  = machinfo* r9  = cpuid* r10 = procinfo** Returns:*  r0, r3, r6, r7 corrupted*  r4 = physical page table address*/
__create_page_tables:pgtbl	r4				@ page table address/** Clear the 16K level 1 swapper page table*/mov	r0, r4mov	r3, #0add	r6, r0, #0x4000
1:	str	r3, [r0], #4str	r3, [r0], #4str	r3, [r0], #4str	r3, [r0], #4teq	r0, r6bne	1bldr	r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags/** Create identity mapping for first MB of kernel to* cater for the MMU enable.  This identity mapping* will be removed by paging_init().  We use our current program* counter to determine corresponding section base address.*/mov	r6, pcmov	r6, r6, lsr #20			@ start of kernel sectionorr	r3, r7, r6, lsl #20		@ flags + kernel basestr	r3, [r4, r6, lsl #2]		@ identity mapping/** Now setup the pagetables for our kernel direct* mapped region.*/add	r0, r4,  #(KERNEL_START & 0xff000000) >> 18str	r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!ldr	r6, =(KERNEL_END - 1)add	r0, r0, #4add	r6, r4, r6, lsr #18
1:	cmp	r0, r6add	r3, r3, #1 << 20strls	r3, [r0], #4bls	1b#ifdef CONFIG_XIP_KERNEL/** Map some ram to cover our .data and .bss areas.*/orr	r3, r7, #(KERNEL_RAM_PADDR & 0xff000000).if	(KERNEL_RAM_PADDR & 0x00f00000)orr	r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000).endifadd	r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> 18str	r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!ldr	r6, =(_end - 1)add	r0, r0, #4add	r6, r4, r6, lsr #18
1:	cmp	r0, r6add	r3, r3, #1 << 20strls	r3, [r0], #4bls	1b
#endif/** Then map first 1MB of ram in case it contains our boot params.*/add	r0, r4, #PAGE_OFFSET >> 18orr	r6, r7, #(PHYS_OFFSET & 0xff000000).if	(PHYS_OFFSET & 0x00f00000)orr	r6, r6, #(PHYS_OFFSET & 0x00f00000).endifstr	r6, [r0]#ifdef CONFIG_DEBUG_LLldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags/** Map in IO space for serial debugging.* This allows debug messages to be output* via a serial console before paging_init.*/ldr	r3, [r8, #MACHINFO_PGOFFIO]add	r0, r4, r3rsb	r3, r3, #0x4000			@ PTRS_PER_PGD*sizeof(long)cmp	r3, #0x0800			@ limit to 512MBmovhi	r3, #0x0800add	r6, r0, r3ldr	r3, [r8, #MACHINFO_PHYSIO]orr	r3, r3, r7
1:	str	r3, [r0], #4add	r3, r3, #1 << 20teq	r0, r6bne	1b
#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 >> 18orr	r3, r7, #0x7c000000str	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 >> 18orr	r3, r7, #0x02000000str	r3, [r0]add	r0, r4, #0xd8000000 >> 18str	r3, [r0]
#endif
#endifmov	pc, lr
ENDPROC(__create_page_tables).ltorg#include "head-common.S"

一、分析kernel的链接脚本

由内核配置与编译——内核的链接脚本可知,kernel的入口地址在arch/arm/kernel/head.S文件的ENTRY(stext)处。

二、分析head.S文件

1、内核运行的物理地址与虚拟地址

(1)KERNEL_RAM_VADDR(VADDR就是virtual address),这个宏定义了内核运行时的虚拟地址,值为0xC0008000。

(2)KERNEL_RAM_PADDR(PADDR就是physical address),这个宏定义内核运行时的物理地址,值为0x30008000。

(3)因此,内核运行的物理地址是0x30008000,对应的虚拟地址是0xC0008000。

2、内核的真正入口

(1)__HEAD定义了段名为.head.text的段。在/include/linux/init.h文件中,有如下代码:

/* For assembly routines */
#define __HEAD		.section	".head.text","ax" //定义了段名为.head.text的段
#define __INIT		.section	".init.text","ax"
#define __FINIT		.previous

(2)“ENTRY(stext)”表明内核的真正入口。

(3)uboot启动内核后,实际调用zImage前面的那段未经压缩的解压代码,解压代码运行时先将zImage后面的部分解压开,然后再去调用运行真正的内核入口(即这里)。

(4)内核启动需要一定先决条件,这个条件由启动内核的bootloader(比如uboot)来构建保证。

(5)ARM体系中,函数调用时实际是通过寄存器传参的。

  • 函数调用时传参有两种设计:一种是寄存器传参,另一种是栈内存传参。
  • uboot中最后theKernel (0, machid, bd->bi_boot_params);执行内核时,实际把0放入r0中,machid放入到了r1中,bd->bi_boot_params放入到了r2中。
  • ARM的这种处理技巧刚好满足了kernel启动的条件和要求。

(6)此时MMU是关闭的,因此硬件上需要的是物理地址。但是内核是一个整体(zImage)只能被链接到一个地址(不能分散加载),这个链接地址肯定是虚拟地址。因此head.S文件中尚未开启MMU之前的代码必须是位置无关码,而且其中涉及到操作硬件寄存器等时必须使用物理地址。

3、检验CPU_ID与机器码的合法性

分别通过__lookup_processor_type与__lookup_machine_type,校验CPU_ID与机器码的合法性。这两个函数都在arch/arm/kernel/head-common.S文件中。

 __lookup_processor_type函数内容如下:

 __lookup_machine_type函数内容如下:

(1)cp15协处理器的c0寄存器中读取出硬件的CPU ID号,然后调用__lookup_processor_type来进行合法性检验。如果合法则继续启动,如果不合法则停止启动,转向__error_p启动失败。

(2)__lookup_processor_type检验cpu id合法性的方法。内核会维护一个本内核支持的CPU ID号码的数组,然后该函数将从硬件中读取到的cpu id号码和数组中存储的各个id号码依次对比,如果没有一个相等则不合法,如果有一个相等的则合法。

(3)内核启动时设计这个校验,也是为了内核启动的安全性着想。

(4)__lookup_machine_type函数的设计理念和思路和上面校验cpu id的函数一样的,不同之处是本函数校验的是机器码。

4、校验uboot给内核传参的格式

利用__vet_atags函数,对uboot通过tag给内核传参的格式进行校验。

这函数在arch/arm/kernel/head-common.S文件中。

(1)该函数的设计思路和上面2个一样,用来对uboot通过tag给内核传参的格式进行校验。参数包括板子的内存分布memtag、uboot的bootargs等等。

(2)如果uboot给内核传参的格式不对,内核将启动不起来。比如uboot的bootargs设置不正确,则内核可能就会不启动。

5、建立段式页表

利用__create_page_tables函数建立段式页表。

这函数在​arch/arm/kernel/head.S文件​中。

(1)linux内核本身被链接在虚拟地址处,因此kernel希望尽快建立页表并且启动MMU进入虚拟地址工作状态。

(2)kernel建立页表分为2步。

  • 第一步,先建立一个段式页表(1MB为单位的段页表)。段式页表建立过程简单(段式页表1MB一个映射,4GB空间需要4096个页表项,每个页表项4字节,因此一共需要16KB内存来做页表),但不能精细管理内存。上面的函数就是用来建立段式页表的。
  • 第二步,然后建立一个细页表(4kb为单位的细页表),然后启用新的细页表,并废除第一步建立的段式映射页表。

(3)内核启动的早期建立段式页表,并在内核启动早期使用;内核启动的后期再次建立细页表并启用。等内核工作起来后,就只有细页表了。

6、构建C语言运行环境

(1)建立段式页表后进入__switch_data部分,它是一个函数指针数组。

 
 

(2)分析得知下一步要执行__mmap_switched函数。

  • 复制数据段、清除bss段(目的是构建C语言运行环境)。
  • 保存起来cpu id号、机器码、tag传参的首地址。
  • b start_kernel跳转到C语言运行阶段。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461775.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

phpcms V9利用num++实现多样形式列表标签调用

在首页或者频道页调用文章列表的时候&#xff0c;经常会使用到左右对称或者每五行出现一条横线的调用形式。 其实代码很简单&#xff0c;利用num的循环方式&#xff0c;以及{if}{/if}进行样式判断即可。代码如下&#xff1a; {pc:content action"lists" catid"6…

Python第十七课(面向对象基础)

Python第17课(面向对象基础) >>>思维导图>>>中二青年 面向对象编程思想OOP 什么是面向对象 面向对象是一种编程思想&#xff0c;是前辈们总结出的经验&#xff0c;指导程序员如何编写出更好的程序&#xff0c;核心是对象&#xff0c;程序就是一系列对象的…

USACO 6.1.3 Cow XOR

题目大意&#xff1a; 给出一个数列&#xff0c;求最大区间异或和。异或和相同时取终点最靠前的&#xff0c;仍相同取最短的。简单题解&#xff1a;先求出前缀和。对每个数&#xff0c;将其前一项的前缀和插入0-1树中。然后在该树中&#xff0c;从高位到低位&#xff08;贪心思…

内核源码——C语言阶段的start_kernel函数

以下内容源于朱有鹏嵌入式课程的学习&#xff0c;如有侵权请告知删除。 前言 1、内容总结 C语言阶段的start_kernel函数位于内核源码/init/main.c文件&#xff0c;主要完成以下内容&#xff1a; &#xff08;1&#xff09;打印一些输出信息。 &#xff08;2&#xff09;初始化…

Excel2007数据透视表学习(四)改变数据透视表布局

通过在【数据透视表字段列表】对话框内将【列标签】、【行标签】和【报表筛选】区域的字段转换即可创建不同分析角度的数据透视表。 如果还是习惯Excel2003版本字段拖拽方式创建数据透视表&#xff0c;可以在数据透视表中单击鼠标右键→【数据透视表选项】→【显示】→勾选【…

经典基础算法之面试题(系列一)(转)

1. 打靶问题的递归解法 Technorati 标记: 递归算法,字典法,二分法,打靶问题,日期问题,求幂,多项式求值伦敦奥运会火热进行中&#xff0c;让我们来看个打靶的问题&#xff1a;一个射击运动员打靶&#xff0c;靶一共有10环&#xff0c;求连开10枪打中90环的可能行有多少种&#x…

2013年11月份我国网络不良与垃圾短信息分析报告

IDC评述网&#xff08;idcps.com&#xff09;12月17日报道&#xff1a;近日&#xff0c;12321网络不良与垃圾信息举报受理中心最新播报了11月份我国不良与垃圾短信息发送情况。根据报告显示&#xff0c;11月份垃圾邮件发送IP地址与上月大体相同&#xff0c;主要集中在湖北、北京…

字节对齐的写法.

What I write, what I lose. 之前看到Nginx的code的时候. 看到一个关于字节对齐的实现. 突然想起之前去面试的时候, 别人让我实现一个4字节对齐. 当然奇怪别人怎么问这种问题. 这时候开始有一点点体会. 以下将个人总结的几种写法写上. #define common_align1(x) ((x/4((x%4)?…

SCOM Rule 介绍 [SCOM中文系列之六]

SCOM的Rule主要用来收集数据&#xff0c;我们看到的EventView&#xff0c;PerformanceData&#xff0c;Report所需数据大部分都是通过Rule来收集的。 SCOM Rule的基础结构是触发指定的条件&#xff0c;就执行指定的动作。这个我们通过Rule的属性界面可以看的清楚点&#xff1a;…

init进程的详解

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 参考博客 内核源码——C语言阶段的start_kernel函数_天糊土的博客-CSDN博客 分析根文件系统中的/linuxrc文件_天糊土的博客-CSDN博客 linux内核sys_mount()分析_kai_ding的博客-CSDN博客_sys_mount…

跟着石头哥哥学cocos2d-x(四)--cocos2dx中的动画以及TexturePacker使用

2019独角兽企业重金招聘Python工程师标准>>> 之前向Andreas Loew申请了一枚TexturePacker注册码&#xff0c;很快都下来了&#xff0c;作为回报我打算还是写一篇关于TexturePacker的使用博客吧&#xff0c;有兴趣的可以在这里申请密钥,http://www.codeandweb.com&am…

【C/C++学习】之七、指向函数的指针

什么是指向函数的指针 函数指针是指向函数的指针变量&#xff0c;不是指向对象的指针&#xff01;函数指针本身应该是“指针变量”&#xff1b; “在C语言中&#xff0c;函数本身不是变量&#xff0c;但可以定义指向函数的指针&#xff0c;这种指针可以被赋值、存放于数组之中&…

头像和Karma汽车

Powered by Zoundry RavenTechnorati : Karma, 头像, 汽车 Del.icio.us : Karma, 头像, 汽车 转载于:https://www.cnblogs.com/bsmagic/archive/2008/08/06/1261858.html

内核中架构相关代码简介

以下内容源于朱友鹏《物联网大讲堂》课程的学习&#xff0c;如有侵权&#xff0c;请告知删除。 1、内核代码基本分为3块 &#xff08;1&#xff09;arch&#xff1a;本目录下全是cpu架构有关的代码 &#xff08;2&#xff09;drivers&#xff1a;本目录下全是硬件的驱动 &am…

All-In-One Code Framework [一站式示例代码库] 【转】

All-In-One Code Framework [一站式示例代码库]2010 对一站式示例代码库&#xff0c;对奋战在一站式示例代码库上的每一位工程师来说都是不同寻常的一年。 在我们共同努力和开发社区的支持下&#xff0c;该项目从一年前的草根雏形&#xff0c;成长为CodePlex排名前三&#xff0…

Repeater的嵌套

今天做了一个小项目学习一下&#xff0c;是关于两个Repeater的嵌套使用的&#xff1a; 关于Repeater的嵌套&#xff0c;关键在于数据项的绑定。 首先&#xff0c;外Repeater需要设置OnItemDataBound事件&#xff0c;在事件中添加子Repeater的数据项绑定&#xff1b; <asp:Re…

Ioc容器Autofac介绍

Autofac是轻量级的开源Ioc容器&#xff0c;在这里可以下载http://code.google.com/p/autofac/。如果你用过其他的Ioc容器&#xff0c;那么学习Autofac使用也会比较容易&#xff0c;下面将通过一些例子来讲解其用法。 先看一个例子&#xff1a; 首先新建一个工程&#xff0c;添加…

汇编指令的学习2——常用的ARM指令

一、常用ARM指令1&#xff1a;数据处理指令 &#xff08;1&#xff09;数据传输指令 mov mvn&#xff08;源目标按位取反后赋给目标&#xff09; &#xff08;2&#xff09;算术指令 add sub rsb adc sbc rsc &#xff08;3&#xff09;逻辑指令 and orr eor …

cocos2dx 3.4 截图代码

Size size Director::sharedDirector()->getWinSize(); //定义一个屏幕大小的渲染纹理 RenderTexture* pScreen RenderTexture::create(size.width,size.height, kCCTexture2DPixelFormat_RGBA8888); log("-------log1---");//获得当前的场景指针 Scene* pC…

NSURLRequest详解IOS最基础的api

http://blog.csdn.net/bl1988530/article/details/6590099转载于:https://www.cnblogs.com/qiqibo/archive/2012/08/22/2650996.html