在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
10 本节新增汇编指令汇总(接上篇)
10.1 arm汇编指令执行条件码
条件码助记符 | 标志位 | 说明 |
---|---|---|
EQ (EQual) | Z=1 | 相等 |
NE (Not Equal) | Z=0 | 不相等 |
HI (HIgh) | C=1,Z=0 | 无符号数大于 |
CS/HS (Carry Set/High or Same) | C=1 | 无符号数大于或等于 |
CC/LO (Carry Clear/LOwer) | C=0 | 无符号数小于 |
LS (Lower or Same) | C=0,Z=1 | 无符号数小于或等于 |
GT (Greater Than) | Z=0,N=V | 有符号数大于 |
GE (Greater or Equal) | N=V | 有符号数大于或等于 |
LT (Less Than) | N!=V | 有符号数小于 |
LE (Less or Equal) | Z=1,N!=V | 有符号数小于或等于 |
MI (MInus) | N=1 | 负数 |
PL (PLus) | N=0 | 正数或零 |
VS (oVerflow Set) | V=1 | 溢出 |
VC (oVerflow Clear) | V=0 | 没有溢出 |
AL (ALl) | 任何 | 无条件执行(默认) |
NV (NeVer) | 任何 | 从不执行 |
10.2 arm通用寄存器PCS
所谓PCS(Procedure Call Standard for Arm architecture),就是在过程调用中,寄存器的特殊用途。
如下表:
通用寄存器名称 | 特殊用途名称 | 说明 |
---|---|---|
r15 | pc (Program Counter) | 程序计数器,用于控制程序流程 |
r14 | lr (Linker Register) | 链接寄存器,用于函数返回 |
r13 | sp (Stack Pointer) | 栈顶指针,用于指示栈顶位置 |
r12 | ip (Intra-Procedure-call scratch register) | 用来暂存sp压栈前的值,以便在压栈过程中压入 |
r11 | fp (Frame Pointer) | 栈帧指针,用于追溯函数调用关系或优化代码 |
11 __switch_data剖析
11.1 代码详细分析
/*
* linux/arch/arm/kernel/head-common.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.
*
*/
.type __switch_data, %object @ 声明__switch_data是一个变量(可以理解为结构体)
__switch_data:
.long __mmap_switched @ 第一个成员是函数指针
@ 这里特别做一个说明,r13中存储的不是__switch_data,
@ 而是mem(__switch_data,4),也就是__mmap_switched。
.long __data_loc @ r4 data_location,数据段存储地址(由链接器定义)
.long __data_start @ r5 .data段起始(链接)地址(由链接器定义)
.long __bss_start @ r6,.bss段起始(链接)地址/.data段结束地址(由链接器定义)
.long _end @ r7,.bss段结束(链接)地址/内核结束地址(由链接器定义)
.long processor_id @ r4, unsigned int processor_id;(定义在/arc/arm/kernel/setup.c中)
.long __machine_arch_type @ r5, unsigned int __machine_arch_type;(定义在/arc/arm/kernel/setup.c中)
.long cr_alignment @ r6,定义在/arch/arm/kernel/entry-armv.S中:
@ .globl cr_alignment
@ .globl cr_no_alignment
@ cr_alignment:
@ .space 4
@ cr_no_alignment:
@ .space 4
.long init_thread_union + THREAD_START_SP @ sp
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses(你看,这里出现了绝对地址);
* this is not position independent.(这里出现了位置有关码)
* 因此,概念搞清楚再看代码才更通透,没看过上一篇文章的建议看一看里面的概念介绍
*
* 当前寄存器占用情况如下:
* r0 = c1 parameters (用来配置控制寄存器的参数)
* r4 = pgtbl (page table 的物理基地址)
* r8 = machine info (struct machine_desc的基地址)
* r9 = cpu id (processor ID,通过cp15获得的cpu id)
* r10 = procinfo (struct proc_info_list的基地址)
*
* 输入参数如下:
* r0 = cp#15 control register
* r1 = machine ID
* r9 = processor ID
*/
.type __mmap_switched, %function
__mmap_switched:
adr r3, __switch_data + 4 @ 将r3指向__data_loc指针变量所在地址
ldmia r3!, {r4, r5, r6, r7} @ r4 = mem[r3,4] = __data_loc;
@ r5 = mem[r3+4*1,4] = __data_start;
@ r6 = mem[r3+4*2,4] = __bss_start;
@ r7 = mem[r3+4*3,4] = _end;
@ r3 = r3 + 4*4。
cmp r4, r5 @ Copy data segment if needed,
@ 查看数据段链接地址和存储地址是否相同
@ 如果不相同则需要将数据段从存储地址处拷贝到链接地址处
1: cmpne r5, r6 @ 该行代码有两个作用,一个是控制第一次是否拷贝;另一个是
@ 控制拷贝结束条件(r5是否最终指向了__bss_start)。
ldrne fp, [r4], #4 @ 这两行就是从__data_loc拷贝到__data_start。还有一个点,
strne fp, [r5], #4 @ 这里为啥要用fp?其实fp在这里就是当普通寄存器使用的,因为其他寄存器都被占用了。
bne 1b
mov fp, #0 @ Clear BSS (and zero fp),同时清除了fp
1: cmp r6, r7 @ 循环清除.bss段
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp} @ 如法炮制,使用r4,r5,r6分别指向processor_id,__machine_arch_type
@ 和cr_alignment。需要特别说明的是,该步也设置了堆栈指针sp,
@ sp = init_thread_union + THREAD_START_SP
str r9, [r4] @ Save processor ID(存入内存变量processor_id)
str r1, [r5] @ Save machine type(存入内存变量__machine_arch_type)
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values,
@ 将r0和r4的值分别存入内存变量cr_alignment和cr_no_alignment中
b start_kernel @ 跳转到C语言入口start_kernel执行,一去不复返。
11.2 栈指针设置说明
下面详细分析下栈指针的设置。
sp = init_thread_union + THREAD_START_SP
其中,init_thread_union的定义如下:
/* /arch/arm/kernel/init_task.c */
/*
* Initial thread structure.
*
* We need to make sure that this is 8192-byte aligned(一定要保证8KB对齐)due to the
* way process stacks are handled. This is done by making sure
* the linker maps this in the .text segment right after head.S,
* and making head.S ensure the proper alignment.
*
* The things we do for performance..
*/
union thread_union init_thread_union
__attribute__((__section__(".data.init_task"))) =
{ INIT_THREAD_INFO(init_task) };
/* /include/linux/sched.h */
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
其中,THREAD_START_SP的定义如下:
/* /include/asm-arm/thread_info.h */
#define THREAD_SIZE 8192
#define THREAD_START_SP (THREAD_SIZE - 8)
我们综合以上信息,可以知道,栈空间其实是 “ 寄居 ”在init_task的进程描述符表中的,而且是寄居在高地址处(因为是满减栈)。有一个细节是sp并没有指向8KB的边界,而是留出了8Byte的空间,我个人认为是出于安全考虑,留出的 安全带 ,大家有什么好的想法可以留言探讨。
我们再来看看链接脚本中是如何保证init_thread_union的首地址是8KB对齐的。至于为什么要8KB对齐,就涉及到内存管理了,我们后续再讨论。
/* /arch/arm/kernel/vmlinux.lds */
...
. = ALIGN(8192); /*这里保证了8KB对齐*/
__data_loc = .;
.data : AT(__data_loc) {
__data_start = .; /* address in memory */
/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
*(.data.init_task)
...
<完>