内核启动流程分析
1. 内核打补丁、配置、编译、烧写、实验
a. 解压缩、打补丁
tar xjf linux-2.6.22.6.tar.bz2
cd linux-2.6.22.6
patch -p1 <../linux-2.6.22.6_jz2440.patch
b. 配置
. make menuconfig // 自己根据菜单配置
配置项很多,比较麻烦。
. 使用默认配置,并在上面修改
find -name "*defconfig*" // 查找所有默认配置文件
cd ./arch/arm/configs // ARM 架构
找到相似的配置文件 s3c2410_defconfig
cd linux-2.6.22.6/
make s3c2410_defconfig //所有的配置项被写入 .config 文件中去
make menuconfig // 读取 .config 以菜单形式显示
教程中用的Ubuntu9.10
实际用的ubuntu 16.04 make s3c2410_defconfig 报错
Makefile:416: *** mixed implicit and normal rules: deprecated syntax
Makefile:1449: *** mixed implicit and normal rules: deprecated syntax
make: *** No rule to make target ‘s3c2410_defconfig’。 停止。
https://blog.csdn.net/u013944565/article/details/77686569
. 使用厂家提供的配置文件
将厂家提供的配置文件 命名为 .config
make menuconfig
对应开发板 cp config_ok .config 即可
make menuconfig 报错
`Symbol ‘acs_map’ has different size in shared object, consider re-linking
解决方法:
sudo apt-get install libncurses5-dev libncursesw5-dev
https://blog.csdn.net/czg13548930186/article/details/79851149
make menuconfig 菜单操作
上下左右移动光标。
回车进入子菜单
Y 选中,编译进内核
N 不选中
M 编译为模块
加粗字体为相应的快捷键
? 为帮助
/ 搜索
esc 两次退出
c. 编译
make uImage
1> 根据.config 生成 autoconfig.h 文件,供原代码使用
2> 根据.config 生成 auto.config 文件,供子目录 Makefile 使用,在顶层Makefile中包含。
make uImage时出现错误:
UIMAGE arch/arm/boot/uImage
“mkimage” command not found - U-Boot images will not be built
Image arch/arm/boot/uImage is ready
解决方法:
安装u-boot-tools软件包:
sudo apt-get install u-boot-tools
d. 下载内核
在uboot菜单选择k
在电脑上用dnw.exe -> USB Poart -> Transmit 选择编译好的内核文件 uImage
生成的内核文件在 /work/kernel/linux-2.6.22.6/arch/arm/boot 文件下
根文件系统的删除:
在uboot 下执行 nand erase root
没有根文件时,内核初始化后就卡死了。
2. 内核功能、结构,结合Makefile、Kconfig 进行分析
因为配置完后会生成一个.config 文件,所以我们分析 .config 文件中去
.config 里面都是配置项,我们以CONFIG_DM9000 为例分析。
grep "CONFIG_DM9000" * -nwR
1> arch/arm/plat-s3c24xx/common-smdk.c 源码用到 来自头文件 autoconfig.h
2> drivers/net/Makefile 子目录 Makefile 用到 // y 或 m 在 Makefile 体现 obj-$(CONFIG_DM9000) += dm9dev9000c.o Makefile中的CONFIG_DM9000 来自于 auto.config
如果是 y 则被编译进内核
如果是 m 则被编译成 .ko 模块
3> include/config/auto.conf // 来自.config自动生成 CONFIG_DM9000=y auto.conf 中的内容要被别的Makefile使用,所以它被包含在顶层的Makefile中
在顶层Makefile中查找 /auto.conf
-include include/config/auto.conf Makefile:443
4> include/linux/autoconfig.h // 来自.config自动生成 CONFIG_DM9000 = 1 不管是y 或 m 在头文件里都是1
分析Makefile和链接脚本
Makefile的介绍看韦老师书本 16.2.2 表 16.3
关于Makefile的文档请看: 源码下的Documentation/kbuid/Makefile.txt
子目录下的Makefile很简单
obj-y += a.o
obj-m += b.o
a.c b.c
怎么将两个源文件编译进内核
obj-y += a.o b.o
怎么将两个文件编译成模块 Makefile.txt->Line:190
obj-m += ab.o
ab-obj := a.o b.o
我们编译内核是使用 make uImage
所以我们查找 uImage
grep "uImage" * -nwR
uImage 在架构相关的文件中定义
arch/arm/Makefile:227:zImage Image xipImage bootpImage uImage: vmlinux
uImage 并没有在顶层Makefile中定义,所以 顶层文件应该包含 arch/arm/Makefile 文件
在顶层文件中搜索 include
/include n 下一个
Makefile:413
include $(srctree)/arch/$(ARCH)/Makefile
arch/arm/Makefile:227:zImage Image xipImage bootpImage uImage: vmlinux
继续分析 uImage 依赖于 vmlinux uImage = 头 + 真正的内核(vmlinux)
继续搜索 vmlinux 不在架构 Makefile 中,在顶层 Makefile 中有定义
grep "vmlinux" Makefile -nwR
745:vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
展开:
$(vmlinux-lds) 是 链接脚本 arch/arm/kernel/vmlinux.lds
608:vmlinux-init := $(head-y) $(init-y)
/* 第一个文件 */
grep "head-y" arch/arm/Makefile -nwR // 架构 Makefile 中
94:head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o // 最初始的代码 第一个链接的应该是 arch/arm/kernel/head$(MMUEXT).o
/* 初始化 */
grep "init-y" Makefile -nwR // 顶层 Makefile 中
434:init-y := init/
573:init-y := $(patsubst %/, %/built-in.o, $(init-y)) // init-y = init/built-in.o 最终init 目录下的文件会被编译成一个built-in.o文件 (patsubst 是一个Makefile 函数)
609:vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
/* 核心 */
grep "core-y" Makefile -nwR
438:core-y := usr/
562:core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
574:core-y := $(patsubst %/, %/built-in.o, $(core-y))
最终 core-y := 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
/* 库 */
grep "libs-y" Makefile -nwR
437:libs-y := lib/
577:libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
578:libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
579:libs-y := $(libs-y1) $(libs-y2)
最终 libs-y := lib/lib.a lib/built-in.o
/* 驱动 */
grep "drivers-y" Makefile -nwR
435:drivers-y := drivers/ sound/
575:drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
最终 drivers-y := drivers/built-in.o sound/built-in.o
/* 网络 */
grep "net-y" Makefile -nwR
436:net-y := net/
576:net-y := $(patsubst %/, %/built-in.o, $(net-y))
最终 net-y := net/built-in.o
610:vmlinux-all := $(vmlinux-init) $(vmlinux-main)
611:vmlinux-lds := arch/$(ARCH)/kernel/vmlinux.lds
手工展开太过复杂,所以我查看编译的最后一条信息:
rm vmlinux // 删除 vmlinux 重新编译
make uImage V=1 // V=1 显示详细的编译信息
arm-linux-ld -EL -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/mach-s3c2410/built-in.o arch/arm/mach-s3c2400/built-in.o arch/arm/mach-s3c2412/built-in.o arch/arm/mach-s3c2440/built-in.o arch/arm/mach-s3c2442/built-in.o arch/arm/mach-s3c2443/built-in.o arch/arm/nwfpe/built-in.o arch/arm/plat-s3c24xx/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 net/built-in.o --end-group .tmp_kallsyms2.o
通过Makefile的分析我们大概能知道内核的结构,看韦老师书本 16.2.2 表 16.2
确定链接脚本:arch/arm/kernel/vmlinux.lds // 根据 vmlinux.lds.S 生成的 链接脚本中的链接地址是虚拟地址
确定第一个文件:arch/arm/kernel/head.o
然后就可以根据第一个文件,一路跟踪下去了。
3. 内核的启动流程分析 详细请看 韦老师书本 16.3.1 图 16.7
a. __lookup_processor_type 确定内核是否支持该架构
b. __lookup_machine_type 确定内核时候支持该单板 u-boot 传入的第二个参数
c. __create_page_tables 建立一级页表
d. _arm920_setup 禁止ICache、DCache
e. __enable_mmu 使能MMU
f. __mmap_switched 复制数据段,清除BSS段,设置栈指针,保存CPU ID 到processor_id 变量、保存机器类型ID到_machine_arch_type变量,调用start_kernel
分析 arch/arm/kernel/head.S 文件
bl __lookup_processor_type @ r5=procinfo r9=cpuid // 协处理器获取处理器类型
bl __lookup_machine_type @ r5=machinfo // 判断传入的机器ID 在head-common.S 文件中去
__lookup_machine_type: @ 查找机器类型 u-boot 传入的di 2 个参数
adr r3, 3b @ r3 = address of 3b ,real address, phy address 3: .long . qian mian
ldmia r3, {r4, r5, r6} @ r4 ="." virtual address of 3b, r5 = __arch_info_begin , r6 = __arch_info_end r5 r6 在链接脚本中定义,中间是架构信息初始化段 .arch.info.init
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space 转换虚拟地址到物理地址(当前的虚拟地址只是链接时候用的虚拟地址,因为MMU还没有启动)
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
teq r3, r1 @ matches loader number? r1 di 2 个参数
beq 2f @ found 2f hou mian 2: mov pc, lr
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
在链接脚本中:
arch/arm/kernel/vmlinux.lds
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
搜索 .arch.info.init
grep ".arch.info.init" * -nR
Binary file arch/arm/mach-s3c2412/mach-smdk2413.o matches
Binary file arch/arm/mach-s3c2412/built-in.o matches
Binary file arch/arm/mach-s3c2412/mach-vstms.o matches
Binary file arch/arm/mach-s3c2410/mach-qt2410.o matches
Binary file arch/arm/mach-s3c2410/mach-smdk2410.o matches
Binary file arch/arm/mach-s3c2410/built-in.o matches
Binary file arch/arm/mach-s3c2440/mach-smdk2440.o matches
Binary file arch/arm/mach-s3c2440/built-in.o matches
Binary file arch/arm/mach-s3c2443/mach-smdk2443.o matches
Binary file arch/arm/mach-s3c2443/built-in.o matches // 内核也支持上面这些单板
arch/arm/kernel/vmlinux.lds.S:39: *(.arch.info.init) // 生成的链接脚本
arch/arm/kernel/vmlinux.lds:306: *(.arch.info.init) // 真实的链接脚本
include/asm-arm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = { \ // 文件夹链接
include/asm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = {\ // 真实的文件
arch.h 找到定义
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
搜索这个宏 MACHINE_START 谁在使用 ,选择其中一个单板 Mach-smdk2440.c (arch\arm\mach-s3c2440):MACHINE_START(S3C2440, "SMDK2440")
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
展开:
static const struct machine_desc __mach_desc_S3C2440 \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_S3C2440, \
.name "SMDK2440",
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
};
相当于定义了一个结构体 machine_desc 这个结构体被放在 .arch.info.init 段,查看结构体 machine_desc
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 */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
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);
};
继续分析 :arch/arm/kernel/head.S
bl __create_page_tables // 创建一级页表
ldr r13, __switch_data @ address to jump to after mmu has been enabled 当MMU使能之后 跳转到 __mmap_switched
adr lr, __enable_mmu @ return (PIC) address 使能MMU
ldr r13, __switch_data 的意思:
r13 = __switch_data的地址 查看 __switch_data:head-common.S (arch\arm\kernel):__switch_data:
__switch_data:
.long __mmap_switched // 相当于 r13 中保存了 __mmap_switched 函数的入口地址,这个地址是运行时地址 (虚拟地址,因为链接脚本中用的是虚拟地址)
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
adr lr, __enable_mmu 的意思:
相对地址adr 当前地址 与链接位置无关 。查看 __enable_mmu
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
orr r0, r0, #CR_A
#else
bic r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic r0, r0, #CR_I
#endif
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on // MMU结尾调用了 __turn_mmu_on
分析 __turn_mmu_on 代码:
__turn_mmu_on:
mov r0, r0
mcr p15, 0, r0, c1, c0, 0 @ write control reg
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13 // 将PC 指针指向 R13 的运行地址 即 __mmap_switched 函数的入口地址
再分析__mmap_switched 代码:
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel // 最终调用 start_kernel C 函数 到这个位置,第一阶段代码就算完毕了,更复杂的功能在C语言中实现。
第一阶段并没有使用到u-boot 传递进来的参数,所以应该是在C函数中实现的,我们继续分析 start_kernel
一些初始化函数
printk(linux_banner); // 打印内核信息
setup_arch(&command_line);// u-boot 传入进来的命令行
setup_command_line(command_line);
分析 setup_arch(&command_line) 函数:
struct tag *tags = (struct tag *)&init_tags; // 标签
struct machine_desc *mdesc; // 架构信息的结构 machine_desc
char *from = default_command_line; // 默认命令行参数 , 如果u-boot 没有传入参数会执行默认命令行参数。
// 物理地址转虚拟地址
tags = phys_to_virt(mdesc->boot_params); // mdesc->boot_params = S3C2410_SDRAM_PA + 0x100 = 物理地址+0x100 刚好等于 u-boot 参数的存放地址 0x30000100 theKernel (0, bd->bi_arch_number, bd->bi_boot_params)
parse_tags(tags); // 解析 tags
parse_cmdline(cmdline_p, from); // 单独解析命令行
....
返回 start_kernel 函数继续分析:
start_kernel()
...
setup_arch(&command_line); // 解析 u-boot 传入进来的参数
setup_command_line(command_line); // 解析 u-boot 传入进来的参数
...
parse_early_param();
do_early_param
从 __setup_start 扫描到 __setup_end 调用 early 函数
unknown_bootoption
obsolete_checksetup
从 __setup_start 扫描到 __setup_end 调用 非 early 函数
...
rest_init();
创建 kernel_init 线程 :kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
prepare_namespace();
mount_root();// 我们的最终目的是挂接根文件系统
init_post();
sys_open((const char __user *) "/dev/console", O_RDWR, 0)// 打开控制台
// 执行应用程序
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
挂载根文件系统是哪个盘/分区?
传递进来的参数是 char *commandline = getenv ("bootargs");// 来自于环境变量 bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
root=/dev/mtdblock3 根文件放在第四个分区上
prepare_namespace(); 函数中 根文件挂载 mount_root(); 之前肯定要确定挂接的是哪个文件系统
进入 prepare_namespace
if (saved_root_name[0]) {
root_device_name = saved_root_name; // 设备名字在 saved_root_name[0] 数组中
if (!strncmp(root_device_name, "mtd", 3)) {
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name); // ROOT_DEV 根文件设备 root_device_name 设备名字
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
搜索 saved_root_name
Do_mounts.c (init):static char __initdata saved_root_name[64];
Do_mounts.c (init): strlcpy(saved_root_name, line, sizeof(saved_root_name));
Do_mounts.c 中 下面这两段代码与 u-boot 中的命令行相似 一个结构对应一个函数,我们可以猜测,先检测到 "root=" 这个字符串,然后执行对应的函数 root_dev_setup
static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));// root_dev_setup 把/dev/mtdblock3 保存到 saved_root_name 中去
return 1;
}
__setup("root=", root_dev_setup); // 这里有 "root="
分析 __setup 宏,这个宏是一个结构体变量,它被强制定义在某一个段内,然后会有其他程序从这个段的起始地址轮训到结束地址去执行这些 __setup 命令。
搜索 __setup 搜索结果中搜索 define
Init.h (include\linux):#define __setup(str, fn) \
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
查找 __setup_param 的定义
#define __setup_param(str, unique_id, fn, early) \
static char __setup_str_##unique_id[] __initdata = str; \
static struct obs_kernel_param __setup_##unique_id \
__attribute_used__ \
__attribute__((__section__(".init.setup"))) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }
根据上面的信息对 __setup("root=", root_dev_setup) 进行展开
#define __setup_param(str, unique_id, fn, early) \
static char __setup_str_root_dev_setup[] __initdata = "root="; \ // 定义一个字符串 __setup_str_root_dev_setup
static struct obs_kernel_param __setup_root_dev_setup \
__attribute_used__ \
__attribute__((__section__(".init.setup"))) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_root_dev_setup, root_dev_setup, 0 } // __setup_str_root_dev_setup 对应的函数 root_dev_setup
.init.setup 在链接脚本的定义 :
__setup_start = .;
*(.init.setup)
__setup_end = .;
搜索 __setup_start __setup_end 看谁调用扫描了这些段,以及执行了这些对应函数
Main.c (init):extern struct obs_kernel_param __setup_start[], __setup_end[]; // 链接脚本地址声明
Main.c (init): p = __setup_start; // static int __init obsolete_checksetup(char *line) 函数
Main.c (init): for (p = __setup_start; p < __setup_end; p++) { // static int __init do_early_param(char *param, char *val) 函数
分析这两个函数
--------------------------------------------------
static int __init obsolete_checksetup(char *line)
{
struct obs_kernel_param *p;
int had_early_param = 0;
p = __setup_start;
do {
int n = strlen(p->str);
if (!strncmp(line, p->str, n)) {
if (p->early) { // p->early = 1
/* Already done in parse_early_param?
* (Needs exact match on param part).
* Keep iterating, as we can have early
* params and __setups of same names 8( */
if (line[n] == '\0' || line[n] == '=')
had_early_param = 1;
} else if (!p->setup_func) { // 对应的函数为空
printk(KERN_WARNING "Parameter %s is obsolete,"
" ignored\n", p->str);
return 1;
} else if (p->setup_func(line + n)) // 执行 __setup 中字符串对应的函数 p->early = 0 且 对应的函数不为空 上面 __setup("root=", root_dev_setup) 展开中 p->early = 0 所以 __setup("root=", root_dev_setup) 在这里面调用
return 1;
}
p++;
} while (p < __setup_end);
return had_early_param;
}
obsolete_checksetup 谁来调用的
static int __init unknown_bootoption(char *param, char *val)
{
/* Change NUL term back to "=", to make "param" the whole string. */
if (val) {
/* param=val or param="val"? */
if (val == param+strlen(param)+1)
val[-1] = '=';
else if (val == param+strlen(param)+2) {
val[-2] = '=';
memmove(val-1, val, strlen(val)+1);
val--;
} else
BUG();
}
/* Handle obsolete-style parameters */
if (obsolete_checksetup(param)) // 传入参数,调用 obsolete_checksetup
return 0;
...... // 略
......
}
unknown_bootoption 由 C 入口函数 asmlinkage void __init start_kernel(void) 调用
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption); // 调用 unknown_bootoption parse_args 不在细分 应该是给 unknown_bootoption 传递参数 并调用
--------------------------------------------------
/* Check for early params. */
static int __init do_early_param(char *param, char *val)
{
struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) {
if (p->early && strcmp(param, p->str) == 0) { // 判断传入的参数 param 是否 等于 p->str 等于是在.init.setup 段里面扫描匹配字符串 且 p->early = 1 执行 上面 __setup("root=", root_dev_setup) 展开中 p->early = 0 所以 __setup("root=", root_dev_setup) 并不在这里面调用
if (p->setup_func(val) != 0) // p->setup_func(val) 执行对应的函数
printk(KERN_WARNING
"Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
do_early_param 谁来调用的
void __init parse_early_param(void)
{
static __initdata int done = 0;
static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
if (done)
return;
/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_args("early options", tmp_cmdline, NULL, 0, do_early_param); // 这里调用 do_early_param parse_args 有时间自己分析 应该是给 do_early_param 传递参数并调用
done = 1;
}
parse_early_param 由 C 入口函数 asmlinkage void __init start_kernel(void) 调用
parse_early_param();
-------------------------------------------------------------------
分区的概念:
root=/dev/mtdblock3 根文件放在第四个分区上,我们的NAND Flash 没有分区表,所以怎么确认第四个分区的?
由于没有分区表,所以只能在代码里面写死了
以u-boot 为例
bootloader||环境变量(参数)||kernel||文件系统(root)
可以输入 u-boot 命令 mtd 查看
#: name size offset mask_flags
0: boot_loader 0x00040000 0x00000000 0
1: params 0x00020000 0x00040000 0
2: kernel 0x00200000 0x00060000 0
3: root 0x0fda0000 0x00260000 0
怎么修改分区,参考韦老师书本 16.3.3 MTD 分区
如果换一个单板,或内核,不知道这些分区信息怎么办?
可以查看内核启动时的分区打印信息
然后在代码中搜索对应的字符串如:
grep "\"bootloader\"" * -nR
arch/arm/plat-s3c24xx/common-smdk.c:120: .name = "bootloader",
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "bootloader",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "params",
.offset = MTDPART_OFS_APPEND, // 表示接着上一个分区
.size = 0x00020000,
},
[2] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00200000,
},
[3] = {
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
更多请看 韦老师文本第 16 章
离线
xiaoci 真是一个努力的朋友,挖坑网见证小白到大神的过程!
谢谢晕哥!最近的事情有点忙,耽误太多时间,前同事离职,几个烂尾项目都压在我手上,老板一直也在催进度!大女儿刚幼儿园报名要适应节奏,小孩要适应,家长也要适应。二孩还有一个月左右出生,老婆的脾气也是被现实生活折磨的非常敏感易怒,我也非常能理解她的心情,感觉自己在感情上成熟了不少(怕老婆了!!)。总之一言难尽,生活本就如此,有苦有甜吧。然后就是自己时间也安排的不是太合理,学习被一些乱七八糟的事情打乱,有点浮躁。但我不会放弃,最近可能会优先清理手头的烂尾工作,晚上抽时间出来看书学习,论坛进度更新。死磕到底,不会放弃。
离线