在20.3.3节编译的内核代码最后出现了链接错误,提示vmlinux.lds文件链接失败。lds文件是GNU ld工具使用的一种脚本文件,该文件描述了如何分配链接后的内存区域和地址等信息,通过lds文件报的错误可以推理分析问题产生的原因。
首先打开arch/arm/kernel/vmlinux.lds文件,找到815行,代码如下:
815 ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
该行代码使用一个ASSERT宏判断__proc_info_end标号的地址与__proc_info_begin标号地址是否相同。__proc_info_end和__proc_info_begin标号之间定义了一个初始化阶段使用的结构,在ARM处理器启动的时候,内核会调用该结构初始化ARM处理器。
在arch/arm目录下搜索__proc_info_begin标号:
$ grep -nR '__proc_info_begin' *
kernel/head.S:516: .long __proc_info_begin
kernel/vmlinux.lds.S:25: __proc_info_begin = .;
kernel/vmlinux.lds.S:166:ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
使用grep命令搜索得到3条结果。从结果看出,__proc_info_begin标号定义在kernel/ head.S文件的第516行,在kernel/vmlinux.lds.S文件使用到了__proc_info_begin标号。打开kernel/vmlinux.lds.S文件查看:
25 __proc_info_begin = .; // .proc.info段的起始地址
26 *(.proc.info) // 段名称
27 __proc_info_end = .; // .proc.info段的结束地址
在vmlinux.lds.S文件中定义了.proc.info代码段,该段代码的起始地址和结束地址分别由__proc_info_begin和__proc_info_end标号标示,这两个标号表示的地址是通过计算得 到的。
在ARM体系代码中,使用machine_desc结构描述与处理器相关的代码,该结构定义在include/asm-arm/mach/arch.h头文件定义如下:
17 struct machine_desc {
18 /*
19 * Note! The first five elements are used
20 * by assembler code in head-armv.S
21 */
22 unsigned int nr; /* architecture number */
// 处理器编号,自动生成
23 unsigned int phys_ram; /* start of physical ram */
// 物理内存起始地址
24 unsigned int phys_io; /* start of physical io */
// 物理I/O端口起始地址
25 unsigned int io_pg_offst; /* byte offset for io
26 * page tabe entry */
27
28 const char *name; /* architecture name */ // 处理器名称
29 unsigned long boot_params; /* tagged list */ // 启动参数列表地址
30
31 unsigned int video_start; /* start of video RAM */
// 视频设备存储器起始地址
32 unsigned int video_end; /* end of video RAM */
// 视频设备存储器结束地址
33
34 unsigned int reserve_lp0 :1; /* never has lp0 */
35 unsigned int reserve_lp1 :1; /* never has lp1 */
36 unsigned int reserve_lp2 :1; /* never has lp2 */
37 unsigned int soft_reboot :1; /* soft reboot */ // 是否软启动
38 void (*fixup)(struct machine_desc *,
39 struct tag *, char **,
40 struct meminfo *);
41 void (*map_io)(void);/* IO mapping function */
// I/O中断处理映射函数
42 void (*init_irq)(void); // 中断响应函数
43 struct sys_timer *timer; /* system tick timer */
// 定时器
44 void (*init_machine)(void); // 初始化函数
45 };
machine_desc结构描述了处理器体系结构编号、物理内存大小、处理器名称、I/O处理函数、定时器处理函数等。每种ARM核的处理器都必须实现一个machine_desc结构,内核代码会使用该结构。
Linux内核提供了MACHINE_START和MACHINE_END宏供建立machine_desc结构使用,建议使用宏建立结构。打开arch/arm/mach-s3c2410/mach-mini2440.c文件,加入下面的代码:
53 MACHINE_START(MINI2440, "MINI2440") // 定义结构名称
54 .phys_ram = S3C2410_SDRAM_PA, // 物理内存起始地址
55 .phys_io = S3C2410_PA_UART, // 物理端口起始地址
56 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
57 .boot_params = S3C2410_SDRAM_PA + 0x100, // 启动参数存放地址
58
59 .init_irq = mini2440_init_irq, // 中断初始化函数
60 .map_io = mini2440_map_io, // I/O端口内存映射函数
61 .init_machine = mini2440_init, // 初始化函数
62 .timer = &s3c24xx_timer, // 定时器
63 MACHINE_END
MACHINE_START宏定义了一个名为MINI2440的结构,并且定义了相关的内存和端口地址、处理函数等。请读者注意定义结构的行号不是从第1行开始的,前面的代码行包含了头文件。头文件可以从其他文件复制一份,如从mach-smdk2440.c文件复制一份头文件定义。
%提示:参考其他类似工程的代码是一个捷径,对于能复用的代码和变量建议使用已经定义好的,这样可以减轻编码和调试的工作量,减少出错机会。
MINI2440结构中使用到了S3C2410_SDRAM_PA和S3C2410_PA_UART宏,这两个宏分别定义了开发板物理内存起始地址和物理端口起始地址。由于2410和2440处理器对内存地址映射关系相同,可以直接使用S3C2410_SDRAM_PA和S3C2410_PA_UART宏。有关S3C2440处理器内存映射请参考处理器手册的内存管理章节。
在mach-mini2440.c文件中加入MINI2440结构指定的几个函数,定义如下:
52 void __init mini2440_init_irq(void) // 中断初始化函数
53 {
54 }
55
56 void __init mini2440_init(void) // 处理器初始化函数
57 {
58 }
59
60 void __init mini2440_map_io(void) // I/O端口映射初始化函数
61 {
62 }
在mach-mini2440.c文件中加入了mini2440_init_irq()、mini2440_init()和mini2440_ map_io()这3个函数。请读者注意这3个函数定义的时候使用了__init关键字,__init关键字告诉ld链接器把函数放在初始化段,初始化段的代码仅在初始化的时候被调用一次。
%提示:在本节函数留空即可,后面的章节会不断增加代码,本节主要是搭建代码框架。
在MINI2440结构定义中,使用了一个名为s3c24xx_timer的sys_timer结构变量,该变量定义在arch/arm/mach-s3c2410/timer.c文件定义如下:
252 struct sys_timer s3c24xx_timer = {
253 .init = s3c2410_timer_init, // 定时器初始化函数
254 .offset = s3c2410_gettimeoffset, // 读取定时器延时
255 .resume = s3c2410_timer_setup // 恢复定时器
256 };
S3C24xx系列处理器定时器的操作相同,因此使用内核代码已经定义好的定时器结构即可,无须从头开发。
回到内核源代码根目录,执行make ARCH=arm CROSS_COMPILE=arm-linux- bzImage开始编译内核。这次编译没有出错信息,会得到正确的编译结果。查看arch/arm/boot目录已经有目标文件Image.gz,表示已经编译生成运行于ARM处理器的内核。
到目前为止,已经可以编译工作在ARM处理器上的代码,但是内核代码还不能启动,因为还没有加入实际的代码,在20.5节中将介绍如何加入目标平台相关的代码。