内核移植
bootloader的启动流程:
1,把内核读入内存
2,设置TAG参数
3,调用theKernel函数(0, 单板的机器ID, TAG参数)
内核启动流程:
1,根据 theKernel的第2个参数:单板的机器ID 判断能否支持该机器,调用机器相关的初始化函数。
注意 : 内核根据参数传递进来的具体的机器ID,和自己设置的机器ID比较,
来确定具体是哪块开发板,并调用相关的初始化函数。
也是根据 单板的机器ID 来配置内核的。
1.1, 修改makefile
1.2, 使用默认的配置
1.3, make zImage
2,解析TAG参数
3,装载驱动程序(网卡+flash)
4,挂接根文件系统
5,启动应用程序
linux内核移植步骤:
1.准备
//可以从这里下载最新的内核源码
HTTP pub/ //从这里进去可以下载指定版本的内核
老版本的内核不支持s3c2440,需要我们自己增加修改一些东西去支持,可以学到更多的东西。
新版本的很完善了,可能就不需要我们修改了。
我们以4.0.4 稳定版为例,下载内核 linux-4.0.4.tar.xz
# tar -xjvf linux-4.0.4.tar.xz
2.目录结构介绍
arch:
硬件体系结构相关的代码,支持的每种体系结构在arch目录下都有对应的子目录。
crypto:
内核本身所用的加密API,实现了常用的加密和散列算法,还有一些压缩和CRC校验算法。
fs:
虚拟文件系统(VFS,Virtual FileSystem)的代码,包含所有的文件系统代码和各种类型的文件操作代码,
它的每一个子目录支持一个文件系统。
sound:
声卡驱动以及其他声音相关的代码。
block:
block层的实现。最初block层的代码一部分位于drivers目录,一部分位于fs目录,从2.6.15 开始,
block层的核心代码被提取出来放在了顶层的block目录。
Documentation:
存放了与内核相关的文档。
include:
目录包含了内核中大部分的头文件,与平台无关的头文件在include/linux子目录下,
include/scsi目录则是有关scsi 设备的头文件目录
kernel:
主要的核心代码,此目录下的文件是内核的最核心部分,包括进程调度、定时器等,
实现了大多数Linux 系统的内核函数。同样,和体系结构相关的代码在 arch/*/kernel 中。
mm:
包含了体系结构无关部分的内存管理代码,体系相关的部分位于 arch/*/mm 目录下。
samples:
tools:
drivers:
linux 支持的外围设备或总线的驱动程序,每个不同的驱动占用一个子目录
init:
内核的初始化代码。包括main.c、创建早期用户空间的代码以及其他初始化代码。
lib:
放置核心库代码,实现了一个标准 C 库的通用子集,与arch/lib 下的代码不同,这里的库代码都是使用C
编写的,在内核新的移植版本中可以直接使用。
net:
网络相关代码,实现了各种常见的网络协议
scripts:
该目录下没有内核代码,只包含了用来配置内核的脚本文件。当运行 make menuconfig 或者
make xconfig 之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。
usr:
实现了用于打包和压缩的的cpio 等。
firmware:
ipc:
IPC,即进程间通信(interprocess communication)。核心的进程间通讯的代码,它包含了共享内存、信号量以及其他形式IPC的代码。
security:
这个目录包括了不同的Linux 安全模型的代码,比如NSA Security-Enhanced Linux。
virt:
3.编译
新内核配置的3中方法 :
1),make menuconfig(太多,太麻烦)
2),使用默认配置,再修改(合适)
3),使用厂家提供的配置文件(已经做好了,没东西可学)
# cd /home/wangxc/linux/kernel/linux-4.0.4/arch/arm/configs //这里面有很多arm架构的配置文件。
# ls //里面可以看见 s3c2410_defconfig 是支持s3c2410芯片的,我们使用的芯片是2440,比较接近了。可以在这个文件的基础上修改。
# cd /home/wangxc/linux/kernel/linux-4.0.4 //回到内核主目录
# make s3c2410_defconfig //生成配置文件 生成对应平台的的配置信息,如s3c2410则为s3c2410_defconfig
首先确定平台和编译器,如果没有确定平台,就执行make s3c2410_defconfig时会默认平台为
当前系统的平台(一般开发用的PC为x86),这有可能与自己的目标平台不一致,就会报错,
我的开发平台报错如下:
/*
***
*** Can not find default configuration "arch/x86/configs/s3c2410_defconfig"!
***
make[1]: *** [s3c2410_defconfig] Error 1
make: *** [s3c2410_defconfig] Error 2
*/
所以首先确定平台和编译器,方法是修改内核顶层目录下的Makefile文件(ARM平台):
修改前:
251 ARCH ?= $(SUBARCH)
252 CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
修改后:
251 # ARCH ?= $(SUBARCH)
252 # CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
253 ARCH = arm
254 CROSS_COMPILE = /home/wangxc/linux/toolchain/crosstools_4.4.3_softfloat/bin/arm-linux-
然后执行
# make s3c2410_defconfig //这样就生成了.config配置文件。
/*
#
# configuration written to .config
#
*/
# cp config_厂家提供的配置文件 .config //也可以使用厂家提供的配置文件来生成 .config配置文件。
# cd /home/wangxc/linux/kernel/linux-4.0.4 //回到内核主目录
# make menuconfig //读取这个刚生成的 .config文件来生成菜单,就可以开始选择修改配置项了。
//运行make menuconfig,根据实际需要制定内核。
/*可能报错:
/usr/bin/ld: cannot find -lncurses
lncurs 找不到
*/
# apt-get install libncurses-dev //下载安装图形界面的库
# cd /home/wangxc/linux/kernel/linux-4.0.4 //回到内核主目录
# make clean
# make zImage //可以编译出来内核映像文件 arch/arm/boot/zImage
执行上面的make命令,还会先自动创建文件 :
.config ==> include/config/auto.conf //这个配置文件,是给顶层的Makefile文件来包含的。但是是子目录下的Makefile来使用的。
605行 include/config/auto.conf: ; //顶层的Makefile文件来包含 auto.conf
.config ==> include/generated/autoconf.h //头文件,给c源代码使用
步骤1:
在UBOOT里:
set machid 16a // smdk2440 mach-smdk2440.c
或
set machid 7CF // mini2440 mach-mini2440.c
set machid A8 // TQ2440 168
步骤2:
arch\arm\mach-s3c24xx\mach-smdk2440.c
s3c24xx_init_clocks(16934400);
改为
s3c24xx_init_clocks(12000000);
步骤3:
配置/编译: make s3c2410_defconfig 或 make mini2440_defconfig
make uImage
步骤4:
在uboot里:set bootargs console=ttySAC0,115200 .....
4, 分析配置文件 .config
# cd /home/wangxc/linux/kernel/linux-4.0.4 //回到内核主目录
# vi .config
里面有很多项 : //俗称配置项
CONFIG_DM9000=y //表示对DM9000网卡的支持,会直接编译进内核。
CONFIG_DM9000=m //表示把DM9000编译成模块,模块以后可以动态加载进内核。
# CONFIG_DM9000 is not set //表示对DM9000网卡不支持,不会编译进内核,也不编译成模块。
CONFIG_RCU_KTHREAD_PRIO=0 //
CONFIG_LOG_BUF_SHIFT=16 //
CONFIG_INITRAMFS_SOURCE="" //
CONFIG_VECTORS_BASE=0xffff0000 //
CONFIG_DEFAULT_HOSTNAME="(none)" //
CONFIG_DEFAULT_IOSCHED="cfq" //
CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" //
CONFIG_CMDLINE="root=/dev/hda1 ro init=/bin/bash console=ttySAC0" //
CONFIG_DM9000 这个配置项,谁来使用??
# grep CONFIG_DM9000 * -R
# grep CONFIG_DM9000 * -rwR
使用CONFIG_DM9000 的文件有:
1),c源代码文件中
arch/arm/mach-pxa/cm-x300.c:#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
arch/arm/mach-pxa/em-x270.c:#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
arch/arm/mach-pxa/colibri-pxa270.c:#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
arch/arm/mach-pxa/cm-x2xx.c:#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
arch/arm/mach-pxa/vpac270.c:#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
解析 : CONFIG_DM9000 对c文件来说,是个宏定义。肯定不是在Makefile中定义,也不是 在auto.conf文件中定义。
宏,对c文件来说,只能在头文件,或者c文件中定义。
来源于 文件:include/generated/autoconf.h
2),子目录Makefile文件中
drivers/net/ethernet/Makefile:obj-$(CONFIG_DM9000) += davicom/
drivers/net/ethernet/davicom/Makefile:obj-$(CONFIG_DM9000) += dm9000.o
# cat drivers/net/ethernet/Makefile
obj-$(CONFIG_DM9000) += davicom/ //配置项CONFIG_DM9000,如果被定义成y:编译进内核,定义成m:编译成模块
解析 : 子目录Makefile文件中CONFIG_DM9000,由谁来定义??
来源于 文件:include/config/auto.conf 这个配置文件由顶层Makefile来包含。
解析内核子目录的Makefile : 格式很简单
obj-m += oled_drv.o //表示对oled_drv的支持,会直接编译进内核。
obj-y += oled_drv.o //表示把oled_drv编译成模块oled_drv.ko,模块以后可以动态加载进内核
3),Makefile识别的,特殊的配置文件
//可以大致猜测出来:这个特殊的配置文件auto.conf是make时,自动生成的,里面的内容来源于 文件.config
//auto.conf是用来被别人包含的。其实就是被顶层的Makefile包含的。子目录下的Makefile来使用的。
include/config/auto.conf:CONFIG_DM9000=y //和.config中的内容很像。
include/config/tristate.conf:CONFIG_DM9000=Y
# cat Makefile //顶层的Makefile
PHONY += include/config/auto.conf //包含了auto.conf配置文件
4),c使用的头文件中
//可以大致猜测出来:这个头文件是make时,自动生成的,里面的内容来源于 文件.config
include/generated/autoconf.h:#define CONFIG_DM9000 1
# cat include/generated/autoconf.h
#define CONFIG_DM9000 1 //CONFIG_DM9000被定义成宏 值是1 注意:只有在配置文件中=y =m的才会被定义为1。
5),其他配置文件中,不用理会
arch/arm/configs/zeus_defconfig:CONFIG_DM9000=y
5, 分析Makefile文件
学习文档 : \linux-4.0.4\Documentation\kbuild\makefiles.txt
每个子目录下,都有一个makefile文件:
5.1), 各级子目录makefile的规则:
obj-y += mem.o random.o //-y : mem.o random.c文件会编译成.o, 最后连接到内核中。表示编译进内核。
obj-m += mem.o random.o //-m : mem.o random.c文件会编译成.ko。表示编译成模块。
obj-$(CONFIG_TTY_PRINTK) += ttyprintk.o //-m:
//变量 CONFIG_TTY_PRINTK 如果在配置文件 \linux-4.0.4\include\config\auto.conf 中被定义成
//-y : 则编译进内核, -m : 则编译成模块。 配置文件中没有定义,则不编译。
5.2), 各级子目录makefile中2个文件编译成一个模块:
obj-m += startstop.o
startstop-objs := start.o stop.o
//start.c文件会被编译成start.o, stop.c文件会被编译成stop.o
//start.o stop.o会被链接成startstop.ko模块。
5.3), 分析顶层目录下的Makefile
# make uImage
//uImage这个目标,不在顶层Makefile中,是在体系结构相关的文件\linux-4.0.4\arch\arm\Makefile中
//所以,可以猜测,这个 体系结构相关的Makefile文件,被包含在顶层Makefile中。
538 include $(srctree)/arch/$(SRCARCH)/Makefile //这个就是包含 体系结构相关的Makefile文件。
/*在顶层Makefile中,查看变量 srctree SRCARCH 定义成什么了??
//srctree := . 这里定义成当前目录
ARCH = arm
CROSS_COMPILE = /home/wangxc/linux/toolchain/crosstools_4.4.3_softfloat/bin/arm-linux-
UTS_MACHINE := $(ARCH)
SRCARCH := $(ARCH)
*/
体系结构相关的Makefile文件中 :
302 BOOT_TARGETS = zImage Image xipImage bootpImage uImage //有我们要编译的目标 uImage
307 $(BOOT_TARGETS): vmlinux //uImage 又依赖于 vmlinux, vmlinux的依赖关系又在顶层的Makefile中定义。
顶层目录下的Makefile
# make //会自动找到编译目标 all。
612 all: vmlinux //顶层的Makefile中
922 vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
分析上面的依赖关系 :
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
分析 KBUILD_VMLINUX_INIT :
/*
head-y := arch/arm/kernel/head$(MMUEXT).o //在 体系结构相关的Makefile文件 中定义这个。
MMUEXT := -nommu //MMUEXT最终是没有定义的,所以上面就是 head.o
init-y := init/ //在 顶层目录下的Makefile文件 中定义这个。
init-y := $(patsubst %/, %/built-in.o, $(init-y)) //init-y := init/built-in.o 意思是:init-y 依赖于init/built-in.o init/目录下面的所有文件会被编译成 built-in.o。
//patsubst 是Makefile里面的一个函数, %/代表 init/
*/
分析 KBUILD_VMLINUX_MAIN :
/*
core-y := usr/
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y := $(patsubst %/, %/built-in.o, $(core-y))
//相关目录下的所有文件,都会被编译成 built-in.o core-y := usr/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o
libs-y := lib/
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y := $(libs-y1) $(libs-y2) // libs-y := lib/lib.a lib/built-in.o
drivers-y := drivers/ sound/ firmware/
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) // drivers-y := drivers/built-in.o sound/built-in.o firmware/built-in.o
net-y := net/
net-y := $(patsubst %/, %/built-in.o, $(net-y)) // net-y := net/
*/
顶层目录下的Makefile下,执行make的时候,原材料这么多,究竟是如何组织的呢?
# make uImage V=1 //V=1 : 是详细的显示编译命令
/home/wangxc/linux/toolchain/crosstools_4.4.3_softfloat/bin/arm-linux-ld -EL -p --no-undefined -X --build-id -o vmlinux -T ./arch/arm/kernel/vmlinux.lds
arch/arm/kernel/head.o
init/built-in.o --start-group
usr/built-in.o arch/arm/nwfpe/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/probes/built-in.o arch/arm/net/built-in.o arch/arm/crypto/built-in.o arch/arm/firmware/built-in.o arch/arm/mach-s3c24xx/built-in.o arch/arm/plat-samsung/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o
arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o
net/built-in.o --end-group .tmp_kallsyms2.o
编译命令详细分析如下 :
arm-linux-ld : 链接命令
-o vmlinux : 想生成 vmlinux文件
-T ./arch/arm/kernel/vmlinux.lds : 指定的链接脚本,决定了所有.o文件在 vmlinux文件 中是如何排布的。
不同的段出现的顺序,有链接脚本来决定。
相同段中文件出现的顺序,按照上面.o文件出现的顺序来决定。
分析makefile要确定2点:
1),第一个文件 : arch/arm/kernel/head.o //应该是汇编文件
2),链接脚本 : /arch/arm/kernel/vmlinux.lds
//.lds文件是由 /arch/arm/kernel/vmlinux.S文件来生成的。 具体是怎么生成的,我们就不用管了,只要关注 .lds文件就行了。
6,内核启动过程分析 : 2.6.13内核
内核启动过程第一阶段分析 : 先看 arch/arm/kernel/head.S 文件
1),判断是否支持这个cpu。
bl __lookup_processor_type @ //r5=procinfo r9=cpuid 判断是否支持相关的CPU处理器。
2),判断是否支持这个单板机器ID。
bl __lookup_machine_type @ //r5=machinfo 判断是否支持相关的单板 这里就是判断theKernel的第2个参数(机器ID=168),是否支持
分析__lookup_machine_type:函数定义如下 :
adr r3, 3b @ //r3=3b的地址,在518行。是实际存在的物理地址。还没有启动mmu
ldmia r3, {r4, r5, r6} @ //r4= .代表标号3的虚拟地址 r5=__arch_info_begin r6=__arch_info_end
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
//s3c2410 ARCH_S3C24X0 S3C2410 168 :定义在\kernel-2.6.13\arch\arm\tools\mach-types
//#define MACH_TYPE_TQ2440 168 :定义在\kernel-2.6.13\include\asm-arm\mach-types.h
teq r3, r1 @ matches loader number? //比较参数传递过来的 machine ID=r1=168
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b //循环,逐个比较
mov r5, #0 @ unknown machine
2: mov pc, lr
分析3b 定义如下 :
3: .long .
.long __arch_info_begin
.long __arch_info_end
分析上面定义的几个地址,定义在链接脚本中
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info) //段 .arch.info
__arch_info_end = .;
分析段 .arch.info, 谁被定义成 .arch.info段??
#define MACHINE_START(_type,_name) \ //\kernel-2.6.13\include\asm-arm\mach\arch.h
const struct machine_desc __mach_desc_##_type \
__attribute__((__section__(".arch.info"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
分析,上面宏的调用,在c文件中,一般如下 :
MACHINE_START(S3C2440, "TQ2440") //kernel-2.6.13\arch\arm\mach-s3c2410\tq2440.c
/* Maintainer: Ben Dooks
*/
.phys_ram = S3C2410_SDRAM_PA,
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = tq2440_init_irq,
.map_io = tq2440_map_io,
.init_machine = tq2440_init,
.timer = &s3c24xx_timer,
MACHINE_END
分析,上面MACHINE_START调用 宏定义展开如下 :
const struct machine_desc __mach_desc_##_type \ //其实就是一个结构体,定义了一个属性,他的段被强制设为".arch.info"
__attribute__((__section__(".arch.info"))) = { \
.nr = MACH_TYPE_TQ2440, \ //MACH_TYPE_TQ2440 = 机器ID168
.name = _name,
/* Maintainer: Ben Dooks */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100, //uboot传递进来的参数地址
.init_irq = s3c24xx_init_irq,
.map_io = tq2440_map_io,
.init_machine = tq2440_machine_init,
.timer = &s3c24xx_timer,
/*对机器ID判断的总的分析 :
MACHINE_START : 内核支持多少个单板,就有多少个这样的结构体。
内核把这个结构体编译进内核,就表示内核支持了这个单板。
所有这样的结构体,都被放在 ".arch.info"段,内核启动的时候,会逐个比较这个段里面的ID内容。
如果ID吻合,就表示内核支持这个单板。
若支持,则调用该机器相关的初始化函数。
*/
3),创建页表
bl __create_page_tables
/*
SECTIONS //链接脚本中
{
. = 0xC0008000; //这个是虚拟地址,所以要创建页表,让物理地址和虚拟地址对应
.init : { //Init code and data
*/
__enable_mmu
4),使能mmu
adr lr, __enable_mmu @ return (PIC) address
5),调用内核的第一个c函数
b start_kernel
下面进入内核启动过程的第二阶段 : //\kernel-2.6.13\init\main.c
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
setup_arch(&command_line); //这里解析,uboot传递给内核的启动参数
...
rest_init(); //会创建内核线程 static void noinline rest_init(void)__releases(kernel_lock)
init(); //static int init(void * unused)
prepare_namespace(); //void __init prepare_namespace(void) 在文件 \kernel-2.6.13\init\do_mounts.c
mount_root(); //挂载根文件系统。
run_init_process(execute_command); //执行各种应用程序。
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
}
6),分区修改
文件 : \linux-4.0.4\arch\arm\mach-s3c24xx\common-smdk.c
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "bootloader",
.size = SZ_256K,
.offset = 0,
},
[1] = {
.name = "params",
.offset = MTDPART_OFS_APPEND, /*这个宏表示:偏移值紧跟着上面一个分区的大小*/
.size = SZ_128K, /* 2*64K */
},
[2] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00300000, /* 3*1M */
},
[3] = {
.name = "rootfs", /*根文件系统*/
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL, /*这个宏表示:nandflash剩下的所有的空间都是*/
}
};
分区信息是在内核代码中写死的,uboot中怎么设置,不会影响内核中的设置。
7), 制作新的文件系统