[汇总] ARM-LINUX小札

 一、镜像结构

以下是一个利用arm-linux-readelf -S vmlinux命令列出的ARM-LINUX镜像结构范例:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .init PROGBITS c0008000 008000 017000 00 WAX 0 0 32
[ 2] .text PROGBITS c001f000 01f000 228fd4 00 AX 0 0 32
[ 3] .text.init PROGBITS c0247fd4 247fd4 00009c 00 AX 0 0 4
[ 4] __ksymtab PROGBITS c0249000 249000 004060 00 A 0 0 4
[ 5] __ksymtab_gpl PROGBITS c024d060 24d060 000ac8 00 A 0 0 4
[ 6] __ksymtab_gpl_fut PROGBITS c024db28 24db28 000018 00 A 0 0 4
[ 7] __ksymtab_strings PROGBITS c024db40 24db40 00aa6c 00 A 0 0 4
[ 8] __param PROGBITS c0259000 259000 0002e4 00 A 0 0 4
[ 9] .data PROGBITS c025a000 25a000 079390 00 WA 0 0 32
[10] .bss NOBITS c02d33a0 2d3390 01ae28 00 WA 0 0 32
[11] .comment PROGBITS 00000000 2d3390 00a3ec 00 0 0 1
[12] .ARM.attributes ARM_ATTRIBUTES 00000000 2dd77c 000010 00 0 0 1
[13] .debug_abbrev PROGBITS 00000000 2dd78c 095fd6 00 0 0 1
[14] .debug_info PROGBITS 00000000 373762 1173371 00 0 0 1
[15] .debug_line PROGBITS 00000000 14e6ad3 118ca4 00 0 0 1
[16] .debug_pubnames PROGBITS 00000000 15ff777 01b70a 00 0 0 1
[17] .debug_str PROGBITS 00000000 161ae81 1bb60f 00 0 0 1
[18] .debug_aranges PROGBITS 00000000 17d6490 0050b8 00 0 0 1
[19] .debug_frame PROGBITS 00000000 17db548 04e2f8 00 0 0 4
[20] .debug_loc PROGBITS 00000000 1829840 1b2514 00 0 0 1
[21] .debug_ranges PROGBITS 00000000 19dbd54 0570a8 00 0 0 1
[22] .shstrtab STRTAB 00000000 1a32dfc 00010d 00 0 0 1
[23] .symtab SYMTAB 00000000 1a332f4 075650 10 24 24797 4
[24] .strtab STRTAB 00000000 1aa8944 051689 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

其中,最重要的几个section为:.text,.data,.bss和.init。前三个section是GCC的缺省section,分别用于存储没有特别指明段属性的函数代码、已初始化的全局数据、未初始化的全局数据,.init则是LINUX专门定义的section,来存储所有用于初始化的kernel数据和代码,为了节省空间,这个section所占据的内存空间将在kernel启动完成后被尽行释放(by free_initmem()@arch/arm/mm/init.c)。

__ksymtab、__ksymtab_strings、__ksymtab_gpl_fut、__ksymtab_strings用于保存提供给module使用的kernel symbol信息,使用EXPORT_SYMBOL宏可以将一个kernel symbol的信息置于__ksymtab section中,从而使动态插入的module可以使用这些symbol。

__param section存储一系列由__module_param_call宏(include/linux/moduleparam.h)定义的kernel_param结构,这些结构负责给出命令行参数中各项目的解析方法,可能要代替现存的宏__setup。
示例:__module_param_call("", nousb, param_set_bool, param_get_bool, &nousb, 0444); in driver/usb/core/usb.c

.text.init section的作用尚没有弄清楚,只有proc-v6.S和tlb-v6.S的几个函数在这一section中,有些怀疑它应该是.init.text,但又不太相信Rusell King的代码中会出现这么明显的疏忽。

.debug* section保存debug信息,symtab和strtab保存symbol信息,这些section对LINUX kernel的运作不构成影响,可以利用arm-linux-strip -s将它们去掉。

strip后的ARM-LINUX镜像如下:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .init PROGBITS c0008000 008000 017000 00 WAX 0 0 32
[ 2] .text PROGBITS c001f000 01f000 228fd4 00 AX 0 0 32
[ 3] .text.init PROGBITS c0247fd4 247fd4 00009c 00 AX 0 0 4
[ 4] __ksymtab PROGBITS c0249000 249000 004060 00 A 0 0 4
[ 5] __ksymtab_gpl PROGBITS c024d060 24d060 000ac8 00 A 0 0 4
[ 6] __ksymtab_gpl_fut PROGBITS c024db28 24db28 000018 00 A 0 0 4
[ 7] __ksymtab_strings PROGBITS c024db40 24db40 00aa6c 00 A 0 0 4
[ 8] __param PROGBITS c0259000 259000 0002e4 00 A 0 0 4
[ 9] .data PROGBITS c025a000 25a000 079390 00 WA 0 0 32
[10] .bss NOBITS c02d33a0 2d3390 01ae28 00 WA 0 0 32
[11] .comment PROGBITS 00000000 2d3390 00a3ec 00 0 0 1
[12] .ARM.attributes ARM_ATTRIBUTES 00000000 2dd77c 000010 00 0 0 1
[13] .shstrtab STRTAB 00000000 2dd78c 000087 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

.init是一个重要的section,kernel启动需要的数据和代码都置于该section中,
arch/arm/kernel/vmlinx.lds.S给出了这一section的布局:
.init : { /* Init code and data */
_stext = .;
_sinittext = .;
*(.init.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
. = ALIGN(16);
__setup_start = .;
*(.init.setup)
__setup_end = .;
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
__security_initcall_start = .;
*(.security_initcall.init)
__security_initcall_end = .;
. = ALIGN(32);
__initramfs_start = .;
usr/built-in.o(.init.ramfs)
__initramfs_end = .;
. = ALIGN(64);
__per_cpu_start = .;
*(.data.percpu)
__per_cpu_end = .;
#ifndef CONFIG_XIP_KERNEL
__init_begin = _stext;
*(.init.data)
. = ALIGN(4096);
__init_end = .;
#endif
}

第一部分自_sinittext开始,至_einittext结束,在链接过程中,所有.init.text section的内容被置于这个部分,包含着采用__init定义的C函数和__INIT宏提示的ASM代码。其中最起首是head-y定义的目标代码(对于ARM-LINUX来说为head.o),它包含了整个镜像的入口stext。
示例:void __init setup_arch(char **cmdline_p) {...} in arch/arm/setup.c

链接器将所有.proc.info.init section的内容置于自__proc_info_begin开始至__proc_info_end结束的部分,通常包含了一个定义在arch/arm/mm/proc-*.S中的proc_info_list结构(in include/asm/procinfo.h),内中记录着特定处理器的专门信息,如arch名,mmu标识位,处理器专用的设置函数等。

链接器将所有.arch.info.init section的内容置于自__arch_info_begin开始至__arch_info_end结束的部分,包含了一个或多个(?)使用MACHINE_START/MACHINE_END宏(include/asm/mach/arch.h)定义的machine_desc结构,内中记录着特定SOC相关的专门信息,如mach编号,io起始地址,boot参数地址,io映射函数,irq初始化函数,mach初始化函数,timer等。这个结构通常定义在arch/arm/mach-xxx/目录内的文件中。

链接器将所有.taglist.init section的内容置于自__tagtable_begin开始至__tagtable_end结束的部分,包含着一系列利用__tagtable(include/asm/setup.h)宏定义的tagtable_t结构,负责给出boot参数中各种TAG的解析方法,如ATAG_CORE,ATAG_MEM等。
示例:__tagtable(ATAG_CMDLINE, parse_tag_cmdline); in arch/arm/setup.c

链接器将所有.setup.init section的内容置于自__setup_start开始至__setup_end结束的部分,包含着一系列由__setup宏(include/linux/init.h)定义的obs_kernel_param结构(貌似这个结构要被废弃?),负责给出各种命令行参数的解析方法,如root=, console=等。
示例:__setup("root=", root_dev_setup); in init/do_mounts.c

链接器将所有.early_param.init的内容置于自__early_begin开始至__early_end结束的部分,包含着一系列由__early_param宏定义的early_params结构,负责给出各种命令行参数中需要被提前处理的项目的解析方法,如initrd=, mem=等。
示例:__early_param("initrd=", early_initrd); in arch/arm/setup.c

自__initcall_start开始至__initcall_end结束的这一部分存储着一个初始化函数指针列表,包含了各种级别的初始化函数,这些初始化函数会在do_initcalls(init/main.c)中被依次调用。初始化函数共包含7个级别,依次利用宏
core_initcall,
postcore_initcall,
arch_initcall,
subsys_initcall,
fs_initcall,
device_initcall,
late_initcall来定义,这个顺序也是这些初始化函数的调用顺序。
初始化函数定义示例:core_initcall(consistent_init); in arch/arm/mm/consistent.c
此外,还有一个自__con_initcall_start至__con_initcall_end的部分存储控制台初始化函数指针列表和一个自__security_initcall_start至__security_initcall_end的部分存储安全初始化函数列表,分别在console_init(drivers/char/tty_io.c)中和do_security_initcalls(security/security.c)中被调用。
示例:
security_initcall (capability_init); in security/capability.c

自__initramfs_start开始至__initramfs_end的部分则包含了一个ramfs镜像,其二进制文件为位于usr目录中的initramfs_data.cpio.gz

LINUX利用多种自定义的section,提供了一种对集中使用的静态数据列表分散定义的机制,极大提高了软件裁减、移植的灵活性。

二、Porting Guide

1. 参考站点

http://www.arm.linux.org.uk
http://www.linux-arm.com
http://www.kernel.org
http://www.mvista.com

2. kernel version:

注意根路径下Makefile的定义
>>>
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 18
EXTRAVERSION =
...
KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
...
___localver = $(objtree)/localversion* $(srctree)/localversion*
kernelrelease = $(KERNELVERSION)$(localver-full)
<<<
KERNELVERSION标识内核的官方发行版本号,___localver指定的文件中规定了版本的补充信息,如MONTAVISTA的发行版本号。
官方发行版本号和版本补充信息构成了一个UTS_RELEASE标签,它包含着内核版本的完整信息,定义于config自动生成的文件utsrelease.h中,属于VERMAGIC_STRING的一部分。
在insert module的时候,运行中的内核会检查待插入模块文件的.modinfo段,以确定该模块的VERMAGIC_STRING与内核的VERMAGIC_STRING是一致的。

以下列出了三个config过程中自动生成的头文件,它们非常重要:
include/linux/compile.h : 由mkcompile_h生成,包含了LINUX镜像的编译信息。
include/linux/autoconf.h : 根据.config生成的配置文件
include/linux/version.h : 版本信息,定义了一个有官方发行版本号生成的整数。
include/linux/utsrelease.h : UTS_RELEASE信息。

3. kernel tree

KERNEL_ROOT -
- include -
//- asm - (symbol link) <- working folder
- arch (symbol link) <- working folder
//- linux
//- ...
- arch -
///- arm <- working folder
///- ...
-init
- kernel
- mm
- fs
- ipc
- net
- drivers - <- working folder
//- serial
//- net
//- ...
- security
- crypto
- lib
- block
- sound
- scripts

4. Porting Guide

- Modify KBuild System
arch/arm/Kconfig
>>>
menu "System Type"

choice
prompt "ARM system type"
default ARCH_SOC

config ARCH_SOC
bool "#Vendor# #SOC#"
help
This enables support for vendor SOC.

endchoice
<<<
- Add SOC mach definition to arm/tools/mach-types
- Add header files to include/asm-arm/arch-xxx
- Add folder mach-xxx (xxx is the SOC name)
- define arch descriptor of the SOC
phys_io IO空间物理起始地址
io_pg_offst IO空间虚拟页偏移
boot_params 启动参数地址(物理地址)
map_io 函数指针:完成IO设备地址空间的映射
init_irq 函数指针:初始化中断处理寄存器及中断控制相关的LINUX数据结构
timer 定时器结构:定义定时器初始化函数、定时器中断函数等
init_machine   函数指针,初始化SOC相关的特定设备,由setup_arch()将其复制至.initcall3.init(arch_initcall)

三、中断与内存映射

1. 中断

通过设置CP15,可以将异常处理的向量位置由0转到0xFFFF0000,称之为High Vectors。ARM-LINUX的异常向量即为High Vectors,所有异常处理函数的入口都定义在entry-amrv.S中。
这个文件的代码布局如下:
----------------------
svc handlers
----------------------
user handlers
---------------------- <- _stubs_start
vector stubs
---------------------- <- _stubs_end / _vectors_start
vectors
---------------------- <- _vectors_end

负责完成异常向量安装的trap_init()会将vector stub和vectors搬移到指定的位置,最终的布局为:
----------------- <- 0xFFFF0000
vectors
-----------------

----------------- <- 0xFFFF0200
vector stubs
-----------------
因此,在这种情况下vectors中的所有跳转指令必须加上一个stubs_offset=__vectors_start + 0x200 - __stubs_start

最终:
pabt由do_PrefetchAbort(arm/mm/fault.c)处理;dabt由do_DataAbort(arm/mm/fault.c)处理;und由do_undefinstr(arm/kernel/traps.c)处理;irq由asm_doIRQ(arm/kernel/irq.c)处理。
而swi指令引起的系统调用由vector_swi(arm/kernel/entry-common.S)处理。

关于IRQ的处理:
irq的控制变量定义在kernel/irq/handle.c中,是一个通用的LINUX定义:
>>>
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned = {
[0 ... NR_IRQS-1] = {
.status = IRQ_DISABLED,
.chip = &no_irq_chip,
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = RAW_SPIN_LOCK_UNLOCKED(irq_desc),
#ifdef CONFIG_SMP
.affinity = CPU_MASK_ALL
#endif
}
};
EXPORT_SYMBOL(irq_desc);
<<<
在启动内核的start_kernel函数里,定义在arm/kernel/irq.c的IRQ初始化函数init_IRQ()调用SOC专用IRQ初始化函数xxx_init_irq设置上述结构的status位、handle_irq指针,并同时初始化硬件中断控制器。之后,内核便具备了正常处理中断的能力。

关于IRQ的请求:
设备驱动通常需要安装自己的中断处理函数,这个过程是通过定义在kernel/irq/manange.c中的一个通用LINUX函数request_irq完成的。该函数将设备驱动定义的中断处理函数安装到一个irqaction的结构中,并把该结构加入到irq_desc[n]的action列表里,其中n是中断号。由于内核的IRQ处理函数会检查特定中断的irqaction列表并激活装在上面的中断处理函数,所以当某个中断发生时,设备驱动安装的中断处理函数能够被及时调用。

2. 内存映射

setup_arch调用定义在setup.c中的paging_init()函数,根据BOOT TAG中的memory参数或CMD LINE中的memory参数为内核建立内存映射表项,原则如下:
- PAGE_OFFSET必须在TASK_SIZE之外,0-TASK_SIZE之间的虚拟地址空间供用户空间的各个进程使用。
- 对于要映射的虚拟地址空间,1MB对齐的部分采用一级的section映射,不对齐的部分(起首/末尾)采用二级的4KB small page映射。
- 一级的映射由alloc_init_section实现,在这种情况下,虚拟地址体现为:
31-----------20 19--------------------------------0
|___pgd_idx____|____________page offset___________|

对应于LINUX的二级映射方式,可以描述为:
pgdp = swapper_pg_dir + pgd idx
pmdp = pgdp

- 二级的映射由alloc_init_page实现,在这种情况下,虚拟地址体现为:
31-----------20 19------------12 11-----------0
|___pgd_idx____|____pte_idx_____|_page_offset_|

对应于LINUX的三级映射方式,可以描述为:
pgdp = pgd + pgd idx
pmdp = pgdp
ptep = pmdp + pte idx

四、启动

1. 启动参数

在运行内核之前,bootloader需要将启动参数以tag的形式存放在指定的内存地址。
include/asm-arm/setup.h中定义了一系列tag:
ATAG_CORE
ATAG_MEM
ATAG_RAMDISK
ATAG_INITRD
ATAG_SERIAL
ATAG_REVISION
ATAG_CMDLINE
__tagtable宏负责安装各种tag的解析函数到.init段的taglist部分,而所有tag的解析在setup_arch()的一开始就需要完成。


2. 命令行参数

内核的命令行参数是由bootloader以ATAG_CMDLINE拷贝到内存的特定位置,然后在解析tag的时候拷贝到静态数组
default_command_line中。如果bootloader没有提供命令行参数,那么,default_command_line数组保存着一个config过程中设定的缺省值CONFIG_CMDLINE。
根据解析过程的先后,命令行参数分为两种,一种是early parameters,另一种是setup参数。
early parameters的解析函数由__early_param宏安装在.init段的early_param部分,解析过程由setup_arch()调用parse_cmdline()完成,其作用优先级要高于tag中的参数,譬如:"mem="会覆盖ATAG_MEM参数,"initrd="会覆盖ATAG_INITRD参数。
setup参数的解析函数由__setup宏安装在.init段的setup部分,解析过程由start_kernel()调用
parse_args()完成。
根据需要,嵌入式开发者可以自定义setup参数来对内核的行为进行控制。

3. 启动

内核镜像在由bootloader装载至内存之前,首先需要由objcpy将其由ELF格式转换为二进制代码,并附加上装载、启动需要的装载地址和启动地址。因为此时MMU还没有启动,所以这两个地址都是物理地址,而内核启动代码最开始的一部分也必须在MMU没有使能的物理地址空间中运行。
启动过程如下:
stext@head.S ->
...............__lookup_processor_type@head-common.S
...............__lookup_machine_type@head-common.S
...............__create_page_tables@head.S <- 为内核代码创建页表
...............__enable_mmu@head.S <- 启动MMU,开始在虚拟空间中运行代码。
...............__mmap_switched@head-common.S
...............start_kernel@main.c ->
......................................setup_arch@setup.c

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值