Linux内核源码分析(二)--启动汇编上篇
来源:互联网 发布:linux磁盘分区挂载 编辑:程序博客网 时间:2024/06/05 05:49
在跳入c函数start_kernel之前有一段汇编代码,从内核链接脚本可以看出内核的入口在stext代码段,这里正是汇编代码的入口。在arch/arm/kernel/目录下有两个相关的汇编源文件分别是head.S和head-common.S,stext代码包含在head.S中:
ENTRY(stext)
mrc p15, 0, r9, c0, c0
bl __lookup_processor_type
movs r10, r5
beq __error_p
bl __lookup_machine_type
movs r8, r5
beq __error_a
bl __create_page_tables
adr lr, __enable_mmu
add pc, r10, #PROCINFO_INITFUNC
先说一下大概流程,再说详细的分析过程。
(1) 设置cpu到管理模式并禁用中断;
(2) 根据cp15中的主标识查找处理器相关信息,将对应proc_info_list结构地址保存在r10寄存器中;
(3) 根据bootloader传过来的machine编号,查找machine相关信息,将对应machine_desc结构地址保存在r8寄存器中;
(4) 根据前面查找到的信息初始化页表;
(5) 开启mmu进入虚拟世界;
(6) 调用对应的proc_info_list上的__cpu_flush钩子;
(7) 从__switch_data函数转入start_kernel开启万里长征。
下面进行详细分析。
(1) msr指令可以将立即数或寄存器值设置到状态寄存器中,指令的编码中有4位的掩码,每1位对应着状态寄存器中的8个位,也就是说32位的状态寄存器被平均分成4个域,cpsr_c正对应着控制域,不了解的话,可能会误以为是伪指令。
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE
上面指令的立即数定义可以参看include/asm-arm/ptrace.h头文件。
#define SVC_MODE 0x00000013
#define PSR_F_BIT 0x00000040
#define PSR_I_BIT 0x00000080
(2) arm的协处理器cp15中的c0寄存器是个只读寄存器,可以获取arm相关的标识码。mrc指令可以将cp15中的寄存器值搬到arm通用寄存器中,通过指令mrc指令的第二个操作数可以指定获取的标识码类型,第二个操作数缺省值为0,0代表的是获取主标识。
mrc p15, 0, r9, c0, c0
对于arm7之后的处理器,主标识格式如下。
30
23
19
15
3
由生产商确定
产品子编号
ARM
产品主编号
处理器版本号
位
位
生产商定义的处理器版本号
位
生产商定义的产品主编号
其中最高
因为:
0x0表示
0x7
位
ARM
0x1
0x2
0x3
0x4
0x5
其他
位
生产商定义的产品子编号。当产品主编号相同时,使用子编号来区分不同的产品子类,如产品中不 同的高速缓存的大小等
位
生产厂商的编号,现在已经定义的有以下值:
0x41
0x44
0x69
如此r9中的信息就如上面的表格所示。
(3) __lookup_processor_type函数定义在head-common.S文件中。
__lookup_processor_type:
adr r3, 3f
ldmda r3, {r5 - r7}
sub r3, r3, r7
add r5, r5, r3
add r6, r6, r3
1: ldmia r5, {r3, r4}
and r4, r4, r9
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ
cmp r5, r6
blo 1b
mov r5, #0
2: mov pc, lr
.long __proc_info_begin
.long __proc_info_end
3: .long .
adr r3, 3f 执行后r3中保存的是标号3的物理地址;
ldmda r3, {r5-r7} 执行后r7中是标号3的编译链接地址,r6中存的是.proc.info.init段的链接结束地址,r5中存的是.proc.info.init段的链接起始地址;
sub r3, r3, r7 执行后r3中存放的是物理地址与链接地址的差值;
add r5, r5, r3 执行后r5中存放的是.proc.info.init段的物理起始地址;
add r6, r6, r3执行后r6中存放的是.proc.info.init段的物理结束地址;
因为内核链接的地址不一定是内核的加载地址,我们又是与位置无关的代码,所以才有了上面的操作。
从标号1出开始遍历.proc.info.init段,这个段里放的是什么样的数据,可以在arch/arm/kernel目录下grep一下段名。因为我们用s3c2440的cpu来分析,所以我们只关注arch/arm/mm/proc-arm920.S文件,其中有如下的数据结构。
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
.long arm920_cache_fns
#else
.long v4wt_cache_fns
#endif
.size __arm920_proc_info, . - __arm920_proc_info
通过分析标号1处的代码可以看到结构的第一个成员是处理器标识,第二个成员是处理器标识掩码。在前面我们分析过r9寄存器中存放的是处理器主标识,这里将掩码与主标识做位与运算再和结构中的标识对比,若相等则找到对应处理器的数据结构,则r5中存放的便是此数据结构的地址。
在include/asm-arm/procinfo.h中可以看到此结构的c语言描述。
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags; /* used by head.S */
unsigned long __cpu_io_mmu_flags; /* used by head.S */
unsigned long __cpu_flush; /* used by head.S */
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};
而PROC_INFO_SZ这个宏也在arch/arm/kernel/asm-offsets.c中如下定义,并转成汇编中间文件供外部引用。
DEFINE(PROC_INFO_SZ, sizeof(struct proc_info_list));
这个结构会在后面被用到,这里该从__lookup_processor_type函数返回了,r5内容会被拷到r10中,如果r5是0表示没有找到则跳到__error_p函数打印错误信息,最后进入死循环。
(4) 一路向下该分析__lookup_machine_type函数了,这个函数也定义在head-common.S文件中。
3: .long .
.long __arch_info_begin
.long __arch_info_end
__lookup_machine_type:
adr r3, 3b
ldmia r3, {r4, r5, r6}
sub r3, r3, r4
add r5, r5, r3
add r6, r6, r3
1: ldr r3, [r5, #MACHINFO_TYPE]
teq r3, r1
beq 2f
add r5, r5, #SIZEOF_MACHINE_DESC
cmp r5, r6
blo 1b
mov r5, #0
2: mov pc, lr
与上面的函数很像,不管3721先在include目录下grep一把.arch.info.init,发现在include/asm-arm/mach/arch.h头文件中有这样的一个定义。
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
};
看来用MACHINE_START包起来的数据结构都会放在这个段啊,想必看过板级代码的对这个宏都不陌生吧?原来被包的就是struct machine_desc结构啊。
/*
* Note! The first four elements are used
* by assembler code in head-armv.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
unsigned long boot_params; /* tagged list */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
好了,真相大白不用懵逼了。现在可以从__lookup_machine_type函数返回了。
前面忙了一通,就拿到两个信息。一个是proc_info_list结构的地址,保存在r10中;另一个是machine_desc结构的地址,保存在r8中。
一路向下便来到了__create_page_tables函数,写的有点累了,放在下篇中分析。
- Linux内核源码分析(二)--启动汇编上篇
- linux源码分析--内核启动之(2)Image内核启动(汇编部分)
- Linux内核源码分析--内核启动之(2)Image内核启动(汇编部分)(Linux-3.0 ARMv7)
- Linux内核源码分析--内核启动之(2)Image内核启动(汇编部分)(Linux-3.0 ARMv7)
- Linux内核源码分析--内核启动之(2)Image内核启动(汇编部分)(Linux-3.0 ARMv7)
- Linux内核启动分析二
- Linux内核源码分析--内核启动
- Linux内核源码分析—Linux内核中的嵌入式汇编
- 内核启动汇编阶段分析
- linux内核启动过程分析(二)
- Linux内核启动流程分析(二)
- Linux内核启动应用程序分析(二)
- Linux内核VPN实现源码分析(二)
- linux 3.6 启动源码分析(二) start_kernel
- linux 3.6 启动源码分析(二) start_kernel
- xv6源码分析(二):内核初始化和多核启动
- Linux内核修炼之字符设备分析二(源码分析)
- <Linux>Linux内核启动分析(二)——start_kernel
- 系统出错。 发生系统错误 1067。 进程意外终止。
- 求多个数的最小公倍数
- Python中带else子句的for循环执行过程
- ffmpeg动态库在android上的测试
- 文件的简单操作
- Linux内核源码分析(二)--启动汇编上篇
- Spring MVC Ajax Json
- SecurCRT界面乱码的问题解决
- MIT开发出高效「查询方法」,以寻找黑盒机器学习分类器的对抗样本
- ImportError: numpy.core.multiarray failed to import
- 商务图表案例——仿经济学人分组漏斗图~
- PAT训练(乙级)—— 1001. 害死人不偿命的(3n+1)猜想 (15)
- EditText中输入金额保留两位小数
- 微信圣诞帽:OpenCV 库Linux下c++实现