先类比下Windows PC的启动流程,一上电后BIOS会去引导扇区读取系统引导程序引导windows内核的启动,内核启动过程中会去识别C盘,D盘,装载驱动程序,启动应用,对于嵌入式LINUX来说,BIOS称为Bootloader,它主要完成的工作有如下3步
1.装载内核到内存中
2.设置TAG参数
3.启动内核,将参数传递给内核,r0=0,r1=机器ID,r2=TAG参数的地址
内核启动中主要完成的工作有如下3步
1.根据r1判断能该内核能否支持该机器,若支持的话调用相应的单板初始化函数
2.装载驱动程序(比如我们从nandflash上读取根文件系统那么必须有nandflash的驱动程序,网络挂载也是同理)
3.解析bootloader传进来的TAG参数(比如分区信息,内存信息.....以便知道从哪个地址去挂载根文件系统等等有用的信息)
4.挂载根文件系统
5.启动应用程序
解压编译内核
1.解压内核
2.修改内核根目录下的Makefile,修改为arm架构和交叉编译器
3.查看arm架构下的配置文件
4.配置单板,生成.config文件,然后make uImage
我们前面提到,要根据uboot传递给内核的机器ID来调用内核里面对应板子的初始化函数,那么这个机器ID是多少呢?我们进去Bootloader的源代码文件看看
进入cmd_bootm.c,找到对应的bootm命令对应的do_bootm()
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
boot_os_fn *boot_fn; //boot_fn是个数组函数
... ..
boot_fn(0, argc, argv, &images); //调用数组函数
... ...
}
如下所示,boot_os_fn是一个函数类型
由于定义了宏CONFIG_BOOTM_LINUX,最终会跳转到do_bootm ->do_bootm_linux()
代码如下所示:
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
/* No need for those on ARM */
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
return -1;
if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images);
return 0;
}
if (flag & BOOTM_STATE_OS_GO) {
boot_jump_linux(images);
return 0;
}
boot_prep_linux(images); //该函数会将各个tag参数保存在指定位置,比如:内存tag、bootargs环境变量tag、串口tag等
boot_jump_linux(images); //该函数会跳转到内核起始地址
return 0;
}
最终跳转到do_bootm ->do_bootm_linux-> boot_jump_linux()
static void boot_jump_linux(bootm_headers_t *images)
{
unsigned long machid = gd->bd->bi_arch_number; //获取机器ID
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
kernel_entry = (void (*)(int, int, uint))images->ep; //设置kernel_entry()的地址为0x30000000
s = getenv("machid"); //判断环境变量machid是否设置,若设置则使用环境变量里的值
if (s) {
strict_strtoul(s, 16, &machid); //重新获取机器ID
printf("Using machid 0x%lx from environment\n", machid); //使用环境变量的machid
}
... ...
r2 = gd->bd->bi_boot_params; //获取tag参数地址, gd->bd->bi_boot_params在setup_start_tag()函数里被设置
kernel_entry(0, machid, r2); //跳转到0x30000000,r0=0,r1=机器ID,r2=tag参数地址
}
上面的machid默认值为MACH_TYPE_SMDK2410(也就是193),我们也可以在环境变量里设置machid变量,最终,便跳到内核执行代码
由上图可知出现了乱码的情况,那肯定是我们的单板初始化串口的函数不对,所以我们必须找到机器ID相应的单板初始化函数,所以任意设置一个ID,这样再次启动内核时,内核识别不出来,就会打印出所有设备对应的机器ID.下面开始测试机器ID是否正确,进入uboot,输入
set machid 33333
tftp 32000000 uImage
bootm 32000000
如下图所示,由于内核不支持这个机器ID,所以打印出内核能支持的ID表:
再次烧写启动,发现7cf(mini2440)这个ID,有串口输出正常.下面看下16a(smdk2440)为什么串口乱码,进入mach-smdk2440.c( 位arch/arm/mach-s3c24xx)找到问题出在smdk2440_map_io():
static void __init smdk2440_map_io(void)
{
s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
s3c24xx_init_clocks(16934400); //初始化时钟clock,需要改为12000000
s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
}
重新编译内核,启动
make s3c2410_defconfig //将mach-s3c2440.c配置进内核
make uImage
cp uImage /work/nfs_root/ uImage_new
uboot设置nfs下载内核启动
set machid 16a
nfs 32000000 172.16.245.101:/work/nfs_root/uImage_new
bootm 32000000