uboot源码分析(基于S5PV210)之uboot如何启动内核

一、uboot和内核到底是什么

1、uboot是一个裸机程序

  uboot的本质就是一个复杂点的裸机程序,和我们编写的用于操作某个硬件模块(如led)的裸机程序并无差别,只不过功能更复杂,包含了许多软件设计思想:分层、分离、耦合度等等。

2、内核本身也是一个"裸机程序"

(1)操作系统内核本身就是一个裸机程序,和uboot、和其他裸机程序并没有本质区别。

(2)区别就是操作系统运行起来后在软件上分为 内核层 和 应用层,分层后两层的权限不同,内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址,为了安全管控)

  直观来看:uboot的镜像是u-boot.bin,linux系统的镜像是zImage(之所以没带.bin,是起名字的问题,本质是和uboot.bin一样的),这两个东西其实都是两个裸机程序镜像。从系统的启动角度来讲,内核其实就是一个大的复杂点裸机程序。

3、部署在SD卡中特定分区内

  使用无盘系统的计算机将不使用本机的硬盘获得启动系统镜像,而是通过网络的指定服务器去获得启动系统的镜像,并下载回本机后用于机器启动。同时也不需要使用传统硬盘读取资料,而是通过局域网内的服务器读取资料。掉电后电脑的数据不保存。例如网吧中的电脑。

(1)一个完整的 软件 + 硬件 的嵌入式系统,静止时(未上电时)bootloader、kernel、rootfs等必须的软件都以镜像的形式存储在启动介质中(X210中是iNand/SD卡);运行时都是在DDR内存中运行的,与存储介质无关。上面2个状态都是稳定状态,第3个状态是动态过程,即从静止态到运行态的过程,也就是启动过程。

(2)动态启动过程就是一个从SD卡逐步搬移到DDR内存,并且运行启动代码进行相关的硬件初始化和软件架构的建立,最终达到运行时稳定状态

(3)静止时u-boot.bin、zImage、rootfs都在SD卡中,他们不可能随意存在SD卡的任意位置,因此需要对SD卡(我的开发板是SD以实际存放这些文件的介质为准)进行一个分区,然后将各种镜像各自存在各自的分区中,这样在启动过程中uboot、内核等就知道到哪里去找谁。(uboot和kernel中的分区表必须一致,同时和SD卡的实际使用的分区要一致,否则无法读取相应的镜像进行启动。

4、运行时必须先加载到DDR中链接地址处

(1)uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是uboot的链接地址。

(2)内核也有类似要求,uboot启动内核时将内核从SD卡读取放到DDR中(其实就是个重定位的过程),不能随意放置,必须放在内核的链接地址处,否则启动不起来。譬如我们使用的内核链接地址是0x30008000。

5、内核启动需要必要的启动参数

(1)uboot是无条件启动的,从零开始启动的。

(2)内核是不能开机自动完全从零开始启动的,内核启动要别人帮忙。uboot要帮助内核实现重定位(从SD卡到DDR),uboot还要给内核提供启动参数

二、启动内核第一步:加载内核到DDR中

  uboot要启动内核,分为2个步骤:第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。(内核代码自己本身根本就没考虑重定位,因为内核知道会有uboot之类的把自己加载到DDR中链接地址处的,所以内核直接就是从链接地址处开始运行的)

1、静态内核镜像在哪里?

(1)SD卡/iNand/Nand/NorFlash等:raw(原始)分区

  常规启动时各种镜像都在SD卡(这里SD卡只是一个代称,具体以自己开发板的启动介质为主,如inand、nand、emmc)中,因此uboot只需要从SD卡的kernel分区去读取内核镜像到DDR中即可。读取要使用uboot的命令来读取(譬如X210的iNand版本是movi命令,X210的Nand版本就是Nand命令),可通过fastboot在uboot命令行查看分区

(2)加载ddr,使用命令:movi read kernel 30008000。其中kernel指的是uboot中的kernel分区(就是uboot中规定的SD卡中的一个区域范围,这个区域范围被设计来存放kernel镜像,就是所谓的kernel分区)

(2)也可通过tftp、nfs等网络下载方式从远端服务器获取镜像

  uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中,而是放在主机的服务器中,然后需要启动时uboot通过网络从服务器中下载镜像到开发板的DDR中。

  分析总结:最终结果要的是内核镜像到DDR中特定地址即可,不管内核镜像是怎么到DDR中的。以上2种方式各有优劣。产品出厂时会设置为从SD卡中启动(避免客户还要搭建tftp服务器才能用···);tftp下载远程启动这种方式一般用来开发,测试kernel镜像,先不用fastboot烧录,通过服务器的方式将其下载到DDR内存中取运行,看是否可用。

2、镜像要放在DDR的什么地址?

  内核一定要放在链接地址处链接地址去内核源代码的链接脚本或者Makefile中去查找。我的开发板中是0x30008000。

三、zImage和uImage的区别联系

1、bootm命令对应do_bootm函数

(1)命令名前加do_即可构成这个uboot命令对应的函数,因此当我们bootm命令执行时,uboot实际执行的函数叫do_bootm函数,在cmd_bootm.c。

(2)do_bootm刚开始定义了一些变量,然后用宏来条件编译执行了secureboot的一些代码(主要进行签名认证,进行校验确保是正确的),先不管它;然后进行了一些一些细节部分操作,也不管它。然后到了CONFIG_ZIMAGE_BOOT,用这个宏来控制进行条件编译一段代码,这段代码是用来支持zImage格式的内核启动的。

2、vmlinuz和zImage和uImage

(1)uboot经过编译直接生成的elf格式的可执行程序是u-boot,这个程序类似于windows下的exe格式,在操作系统下是可以直接执行的。但是这种格式不能用来烧录下载。我们用来烧录下载的是u-boot.bin,这个东西是由u-boot使用arm-linux-objcopy工具进行加工(主要目的是去掉一些无用的东西)得到的。这个u-boot.bin就叫镜像(image),镜像就是用来烧录到iNand中执行的。

(2)linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的原版内核elf文件;嵌入式系统部署时烧录的一般不是这个vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),经过制作加工成烧录镜像的文件就叫Image(在我实践过程中,通过制作加工,把78M大的精简成了7.5M,因此这个制作烧录镜像主要目的就是缩减大小,节省磁盘)。

(3)原则上Image就可以直接被烧录到Flash上进行启动执行(类似于u-boot.bin),但是实际上并不是这么简单。

  实际上linux的作者们觉得Image还是太大了所以对Image进行了压缩,并且在image压缩后的文件的前端附加了一部分解压缩代码。构成了一个压缩格式的镜像就叫zImage。(因为当年Image大小刚好比一张软盘(软盘有2种,1.2M的和1.44MB两种)大,为了节省1张软盘的钱于是乎设计了这种压缩Image成zImage的技术)。

  Ubuntu直接使用vmlinux进行启动,因为其是桌面系统使用硬盘,硬盘空间足够大,不需要考虑空间和成本的问题。而且这样做省时间,不需要解压

(4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage

  注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可(利用mkimage.c 与 mkimage.h生成的mkimage工具)。

  早期Linux内核不是用uboot启动的,而是用别的bootloader。

(4)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了CONFIG_ZIMAGE_BOOT这个宏。所以大家可以看出:有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。

3、编译内核得到uImage去启动

  如果直接在kernel底下去make uImage会显示mkimage command not found。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统目录下。再去make uImage即可。

四、zImage启动细节

  uboot开始并不支持zImage启动,是之后添加上去的

/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    
    
	image_header_t	*hdr;
	ulong		addr;
	ulong		iflag;
	const char	*type_name;
	uint		unc_len = CFG_BOOTM_LEN;
	uint8_t		comp, type, os;

	void		*os_hdr;
	ulong		os_data, os_len;
	ulong		image_start, image_end;
	ulong		load_start, load_end;
	ulong		mem_start;
	phys_size_t	mem_size;

	struct lmb lmb;

#if defined(CONFIG_SECURE_BOOT)
	int rv;
#endif

#if defined(CONFIG_SECURE_BOOT)//一般手机这些设备·不需要这么严格的检查,所以并未定义这个宏进行安全校验
	rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR,
                                (unsigned char*)CONFIG_SECURE_KERNEL_BASE,
                                CONFIG_SECURE_KERNEL_SIZE-128,
                                (unsigned char*)(CONFIG_SECURE_KERNEL_BASE+CONFIG_SECURE_KERNEL_SIZE-128),
                                128 );
        if(rv != SB_OK) {
    
    
                printf("Kernel Integrity check fail\nSystem Halt....");
                while(1);
        }
        printf("Kernel Integirty check success.\n");

	rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR,
                                (unsigned char*)CONFIG_SECURE_ROOTFS_BASE,
                                CONFIG_SECURE_ROOTFS_SIZE-128,
                                (unsigned char*)(CONFIG_SECURE_ROOTFS_BASE+CONFIG_SECURE_ROOTFS_SIZE-128),
                                128 );
	if(rv != SB_OK) {
    
    
                printf("rootfs Integrity check fail\nSystem Halt....");
                while(1);
        }

        printf("rootfs Integirty check success.\n");

#endif
	
	memset ((void *)&images, 0, sizeof (images));
	images.verify = getenv_yesno ("verify");
	images.lmb = &lmb;

	lmb_init(&lmb);

	mem_start = getenv_bootm_low();
	mem_size = getenv_bootm_size();

	lmb_add(&lmb, (phys_addr_t)mem_start, mem_size);

	board_lmb_reserve(&lmb);

#ifdef CONFIG_ZIMAGE_BOOT   //支持zImage启动
#define LINUX_ZIMAGE_MAGIC	0x016f2818
	/* find out kernel image address */
	if (argc < 2) {
    
    //函数参数argc的值
		addr = load_addr;
		debug ("*  kernel: default image load address = 0x%08lx\n",
				load_addr);
	} else {
    
    
		addr = simple_strtoul(argv[1], NULL, 16);
		debug ("*  kernel: cmdline image address = 0x%08lx\n", img_addr);
	}


	if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
    
    //zImage头部开始的第37-40字节处存放着zImage标志魔数
		printf("Boot with zImage\n");                  //从这个位置取出然后对比LINUX_ZIMAGE_MAGIC
		addr = virt_to_phys(addr);//虚拟地址转物理地址
		hdr = (image_header_t *)addr;
		hdr->ih_os = IH_OS_LINUX;
		hdr->ih_ep = ntohl(addr);

		memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));

		/* save pointer to image header */
		images.legacy_hdr_os = hdr;

		images.legacy_hdr_valid = 1;

		goto after_header_check;//跳到后面的程序去执行
	}
#endif

	/* get kernel image header, start address and length */
	os_hdr = 



(cmdtp, flag, argc, argv,
			&images, &os_data, &os_len);
	if (os_len == 0) {
    
    
		puts ("ERROR: can't get kernel image!\n");
		return 1;
	}

	/* get image parameters */
	switch (genimg_get_format (os_hdr)) {
    
    
	case IMAGE_FORMAT_LEGACY://uImage方式
		type = image_get_type (os_hdr);
		comp = image_get_comp (os_hdr);
		os = image_get_os (os_hdr);

		image_end = image_get_image_end (os_hdr);
		load_start = image_get_load (os_hdr);
		break;
#if defined(CONFIG_FIT)//设备树方式
	case IMAGE_FORMAT_FIT:
		if (fit_image_get_type (images.fit_hdr_os,
					images.fit_noffset_os, &type)) {
    
    
			puts ("Can't get image type!\n");
			show_boot_progress (-109);
			return 1;
		}

		if (fit_image_get_comp (images.fit_hdr_os,
					images.fit_noffset_os, &comp)) {
    
    
			puts ("Can't get image compression!\n");
			show_boot_progress (-110);
			return 1;
		}

		if (fit_image_get_os (images.fit_hdr_os,
					images.fit_noffset_os, &os)) {
    
    
			puts ("Can't get image OS!\n");
			show_boot_progress (-111);
			return 1;
		}

		image_end = fit_get_end (images.fit_hdr_os);

		if (fit_image_get_load (images.fit_hdr_os, images.fit_noffset_os,
					&load_start)) {
    
    
			puts ("Can't get image load address!\n");
			show_boot_progress (-112);
			return 1;
		}
		break;
#endif
	default:
		puts ("ERROR: unknown image format type!\n");
		return 1;
	}

	image_start = (ulong)os_hdr;
	load_end = 0;
	type_name = genimg_get_type_name (type);

	/*
	 * We have reached the point of no return: we are going to
	 * overwrite all exception vector code, so we cannot easily
	 * recover from any failures any more...
	 */
	iflag = disable_interrupts();

#if defined(CONFIG_CMD_USB)
	/*
	 * turn off USB to prevent the host controller from writing to the
	 * SDRAM while Linux is booting. This could happen (at least for OHCI
	 * controller), because the HCCA (Host Controller Communication Area)
	 * lies within the SDRAM and the host controller writes continously to
	 * this area (as busmaster!). The HccaFrameNumber is for example
	 * updated every 1 ms within the HCCA structure in SDRAM! For more
	 * details see the OpenHCI specification.
	 */
	usb_stop();
#endif


#ifdef CONFIG_AMIGAONEG3SE
	/*
	 * We've possible left the caches enabled during
	 * bios emulation, so turn them off again
	 */
	icache_disable();
	invalidate_l1_instruction_cache();
	flush_data_cache();
	dcache_disable();
#endif

	switch (comp) {
    
    
	case IH_COMP_NONE:
		if (load_start == (ulong)os_hdr) {
    
    
			printf ("   XIP %s ... ", type_name);
		} else {
    
    
			printf ("   Loading %s ... ", type_name);

			memmove_wd ((void *)load_start,
				   (void *)os_data, os_len, CHUNKSZ);
		}
		load_end = load_start + os_len;
		puts("OK\n");
		break;
	case IH_COMP_GZIP://GZIP压缩格式,解压缩代码,但kernel本身有解压缩代码,所以用不到
		printf ("   Uncompressing %s ... ", type_name);
		if (gunzip ((void *)load_start, unc_len,
					(uchar *)os_data, &os_len) != 0) {
    
    
			puts ("GUNZIP: uncompress or overwrite error "
				"- must RESET board to recover\n");
			show_boot_progress (-6);
			do_reset (cmdtp, flag, argc, argv);
		}

		load_end = load_start + os_len;
		break;
#ifdef CONFIG_BZIP2
	case IH_COMP_BZIP2://BZIP2压缩格式
		printf ("   Uncompressing %s ... ", type_name);
		/*
		 * If we've got less than 4 MB of malloc() space,
		 * use slower decompression algorithm which requires
		 * at most 2300 KB of memory.
		 */
		int i = BZ2_bzBuffToBuffDecompress ((char*)load_start,
					&unc_len, (char *)os_data, os_len,
					CFG_MALLOC_LEN < (4096 * 1024), 0);
		if (i != BZ_OK) {
    
    
			printf ("BUNZIP2: uncompress or overwrite error %d "
				"- must RESET board to recover\n", i);
			show_boot_progress (-6);
			do_reset (cmdtp, flag, argc, argv);
		}

		load_end = load_start + unc_len;
		break;
#endif /* CONFIG_BZIP2 */
	default:
		if (iflag)
			enable_interrupts();
		printf ("Unimplemented compression type %d\n", comp);
		show_boot_progress (-7);
		return 1;
	}
	puts ("OK\n");
	debug ("   kernel loaded at 0x%08lx, end = 0x%08lx\n", load_start, load_end);
	show_boot_progress (7);

	if ((load_start < image_end) && (load_end > image_start)) {
    
    
		debug ("image_start = 0x%lX, image_end = 0x%lx\n", image_start, image_end);
		debug ("load_start = 0x%lx, load_end = 0x%lx\n", load_start, load_end);

		if (images.legacy_hdr_valid) {
    
    
			if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
				puts ("WARNING: legacy format multi component "
					"image overwritten\n");
		} else {
    
    
			puts ("ERROR: new format image overwritten - "
				"must RESET the board to recover\n");
			show_boot_progress (-113);
			do_reset (cmdtp, flag, argc, argv);
		}
	}

	show_boot_progress (8);

	lmb_reserve(&lmb, load_start, (load_end - load_start));

#if defined(CONFIG_ZIMAGE_BOOT)
after_header_check:
	os = hdr->ih_os;
#endif
//以上为启动镜像的格式及校验过程
	switch (os) {
    
    //支持多种内核,我们选用的是linux
	default:			/* handled by (original) Linux case */
	case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
	    fixup_silent_linux();
#endif
	    do_bootm_linux (cmdtp, flag, argc, argv, &images);//存储了镜像的有效信息
	    break;

	case IH_OS_NETBSD:
	    do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
	    break;

#ifdef CONFIG_LYNXKDI
	case IH_OS_LYNXOS:
	    do_bootm_lynxkdi (cmdtp, flag, argc, argv, &images);
	    break;
#endif

	case IH_OS_RTEMS:
	    do_bootm_rtems (cmdtp, flag, argc, argv, &images);
	    break;

#if defined(CONFIG_CMD_ELF)
	case IH_OS_VXWORKS:
	    do_bootm_vxworks (cmdtp, flag, argc, argv, &images);
	    break;

	case IH_OS_QNX:
	    do_bootm_qnxelf (cmdtp, flag, argc, argv, &images);
	    break;
#endif

#ifdef CONFIG_ARTOS
	case IH_OS_ARTOS:
	    do_bootm_artos (cmdtp, flag, argc, argv, &images);
	    break;
#endif
	}

	show_boot_progress (-9);
#ifdef DEBUG
	puts ("\n## Control returned to monitor - resetting...\n");
	do_reset (cmdtp, flag, argc, argv);
#endif
	if (iflag)
		enable_interrupts();

	return 1;
}

注意:以下分析的内容均在上边这个程序中,整个uboot源码文件链接在《uboot源码分析(基于S5PV210)之启动第二阶段》提供。

1、LINUX_ZIMAGE_MAGIC(cmd_bootm.c的197行)

(1)这个是一个定义的魔数,这个数等于0x016f2818,表示这个镜像是一个zImage。也就是说zImage格式的镜像中在头部的一个 固定位置 存放了这个数作为格式标记。如果我们拿到了一个image,去他的那个位置去取4字节判断它是否等于LINUX_ZIMvAGE_MAGIC,则可以知道这个镜像是不是一个zImage。

(2)int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

命令 bootm 0x30008000,所以do_boom的argc=2,argv[0]=bootm  argv[1]=0x30008000。
但是实际bootm命令还可以不带参数执行。如果不带参数直接bootm,则会从CFG_LOAD_ADDR
地址去执行(定义在x210_sd.h中)。

(3)(cmd_bootm.c的209行)zImage头部开始的第37-40字节处 特定位置 存放着zImage标志魔数,从这个位置取出然后对比LINUX_ZIMAGE_MAGIC。可以用二进制阅读软件来打开zImage查看,就可以证明。很多软件都可以打开二进制文件,如winhex、UltraEditor、Notepad++安装一个插件也可以。

2、image_header_t

(1)这个数据结构是我们uboot启动内核使用的一个标准启动数据结构,zImage头信息也是一个image_header_t,但是在实际启动之前需要进行一些改造。

hdr->ih_os = IH_OS_LINUX;
hdr->ih_ep = ntohl(addr);这两句就是在进行改造。

(2)images全局变量是do_bootm函数中使用,用来完成启动过程的。zImage的校验过程其实就是先确认是不是zImage,确认后再修改zImage的头信息到合适,修改后用头信息去初始化images这个全局变量(cmd_bootm.c的216-221行),然后就完成了校验。

cmd_bootm.c的123行:
static bootm_headers_t images;		/* pointers to os/initrd/fdt images */

五、uImage启动

1、uImage启动

(1)LEGACY(遗留的),在do_bootm函数中,这种方式指的就是uImage的方式。

(2)uImage方式是uboot本身发明的支持linux启动的镜像格式,但是后来这种方式被一种新的方式替代,这个新的方式就是设备树方式(在do_bootm方式中叫FIT)

(3)uImage的启动校验主要在boot_get_kernel函数中,主要任务就是校验uImage的头信息,并且得到真正的kernel的起始位置去启动。

2、设备树方式内核启动

(1)设备树方式启动暂时不讲,我的开发板并没有使用设备树进行启动,要学习设备树相关的知识,请阅读我的《设备树学习》专栏。

  uboot本身设计时只支持uImage启动,原来uboot的代码也是这样写的。后来有了fdt方式之后,就把uImage方式命名为LEGACY方式,fdt方式命令为FIT方式,于是乎多了写#if #endif添加的代码。后来移植的人又为了省事添加了zImage启动的方式,又为了省事把zImage启动方式直接写在了uImage和fdt启动方式之前,于是乎又有了一对#if #endif。于是乎整天的代码看起来很恶心。

  第二阶段校验头信息结束,下面进入第三阶段,第三阶段主要任务是启动linux内核,调用do_bootm_linux函数来完成。

六、do_bootm_linux函数

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
		     bootm_headers_t *images)
{
    
    
	ulong	initrd_start, initrd_end;
	ulong	ep = 0;
	bd_t	*bd = gd->bd;
	char	*s;
	int	machid = bd->bi_arch_number;//机器码,写死的一个,优先级低于环境变量
	void	(*theKernel)(int zero, int arch, uint params);//函数指针
	int	ret;

#ifdef CONFIG_CMDLINE_TAG
	char *commandline = getenv ("bootargs");//读取环境变量的值
#endif

	/* find kernel entry point */
	if (images->legacy_hdr_valid) {
    
    //在之前的函数设置过值,zimage与uimage都走这条路
		ep = image_get_ep (&images->legacy_hdr_os_copy);
#if defined(CONFIG_FIT)//设备树的路
	} else if (images->fit_uname_os) {
    
    
		ret = fit_image_get_entry (images->fit_hdr_os,
					images->fit_noffset_os, &ep);
		if (ret) {
    
    
			puts ("Can't get entry point property!\n");
			goto error;
		}
#endif
	} else {
    
    
		puts ("Could not find kernel entry point!\n");
		goto error;
	}
	theKernel = (void (*)(int, int, uint))ep;

	s = getenv ("machid");//得到环境变量的机器码,若为空,则使用之前写死的那个机器码
	if (s) {
    
    
		machid = simple_strtoul (s, NULL, 16);
		printf ("Using machid 0x%x from environment\n", machid);
	}

	ret = boot_get_ramdisk (argc, argv, images, IH_ARCH_ARM,
			&initrd_start, &initrd_end);
	if (ret)
		goto error;

	show_boot_progress (15);

	debug ("## Transferring control to Linux (at address %08lx) ...\n",
	       (ulong) theKernel);

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \  //这几个定义的宏来控制传参
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG) || \
    defined (CONFIG_LCD) || \
    defined (CONFIG_VFD) || \
    defined (CONFIG_MTDPARTITION)
	setup_start_tag (bd);//传递一个起始tag
#ifdef CONFIG_SERIAL_TAG
	setup_serial_tag (&params);//static struct tag *params;一个全局变量
#endif
#ifdef CONFIG_REVISION_TAG
	setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
	setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
	setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
	if (initrd_start && initrd_end)
		setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
	setup_videolfb_tag ((gd_t *) gd);
#endif

#ifdef CONFIG_MTDPARTITION
	setup_mtdpartition_tag();
#endif

	setup_end_tag (bd);
#endif

	/* we assume that the kernel is in place */
	printf ("\nStarting kernel ...\n\n");

#ifdef CONFIG_USB_DEVICE
	{
    
    
		extern void udc_disconnect (void);
		udc_disconnect ();
	}
#endif

	cleanup_before_linux ();

	theKernel (0, machid, bd->bi_boot_params);//uboot的最后一句。启动内核,uboot就死掉了
	/* does not return */
	return;

error:
	do_reset (cmdtp, flag, argc, argv);
	return;
}

1、找到do_bootm_linux函数

(1)函数在uboot/lib_arm/bootm.c中。

(2)SI(SourceInsight)工具找不到(是黑色的)不代表就没有,要搜索一下才能确定;搜索不到也不能代表就没有,因为我们在向SI工程中添加文件时,SI只会添加它能识别的文件格式的文件,有一些像Makefile、xx.conf等不识别的文件是没有被添加的。所以如果要搜索的关键字在makefile中或者脚本中,可能就是搜索不到的。(譬如TEXT_BASE)

2、镜像的entrypoint

(1)ep就是entrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像开头有n个字节的头信息),真正的镜像文件执行时第一句代码在镜像的中部某个字节处,相对于头是有一定的偏移量的。这个偏移量记录在头信息中。

(2)一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定镜像种类;第二步对镜像进行校验;第三步再次读取头信息,由特定地址知道这个镜像的各种信息(镜像长度、镜像种类、入口地址);第四步就去entrypoint处开始执行镜像。

(3)theKernel = (void (*)(int, int, uint))ep;将ep赋值给theKernel,则这个函数指针就指向了内存中加载的OS镜像的真正入口地址(就是操作系统的第一句执行的代码)。

3、机器码的再次确定

(1)uboot在启动内核时,机器码要传给内核。uboot传给内核的机器码是怎么确定的?

  第一顺序备选是环境变量machid,第二顺序备选是gd->bd->bi_arch_num(x210_sd.h中硬编码配置的)若无第一个,则使用第二个备选。环境变量是可以覆盖后者的,若想修改机器码,定义machid赋值即可。

4、传参并启动概述

(1)bootm.c从110行到144行就是uboot 对 准备给linux内核传递的参数 进行处理。

(2)Starting kernel … 这个是uboot中最后一句打印出来的东西。这句如果能出现,说明uboot整个是成功的,也成功的加载了内核镜像,也校验通过了,也找到入口地址了,也试图去执行了。如果这句后串口就没输出了,说明内核并没有被成功执行。原因一般是:传参(80%的原因)、内核在DDR中的加载地址·······

七、Uboot向内核传参详解

1、tag方式传参

(1)struct tag(包含了一个struct tag_header和一个共用体),tag是一个数据结构,在uboot和linux kernel中都有定义tag数据结构,而且定义是一样的。

struct tag_header {
    
    
	u32 size;//tag的大小
	u32 tag;//什么样的tag,宏定义了一些魔数,如#define ATAG_CORE	0x54410001等
};

struct tag {
    
    
        struct tag_header hdr;//其中记录了tag的类型,从而确定下面的共用体的选择
        union {
    
     //不同种类的tag
                struct tag_core         core;
                struct tag_mem32        mem;
                struct tag_videotext    videotext;
                struct tag_ramdisk      ramdisk;
                struct tag_initrd       initrd;
                struct tag_serialnr     serialnr;
                struct tag_revision     revision;
                struct tag_videolfb     videolfb;
                struct tag_cmdline      cmdline;
                
                /*
                * Acorn specific
                */
                struct tag_acorn        acorn;
                
                /*
                 * DC21285 specific
                 */
                struct tag_memclk       memclk;
                
                struct tag_mtdpart      mtdpart_info;
        } u;
};

(2)tag_header和tag_xxx(结构体)。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。

(3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。

(4)tag传参的方式是由linux kernel发明的,kernel定义了这种向我传参的方式,uboot只是实现了这种传参方式从而可以支持给kernel传参。
在这里插入图片描述

2、x210_sd.h中配置传参宏

(1)CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。

(2)CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.

(3)CONFIG_INITRD_TAG

(4)CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。

(5)起始tag是ATAG_CORE、结束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。

  思考:内核如何拿到这些tag?

  uboot最终是调用theKernel函数来执行linux内核的,uboot调用这个函数(其实就是linux内核)时传递了3个参数。这3个参数就是uboot直接传递给linux内核的3个参数,通过寄存器来实现传参的。(第1个参数就放在r0中,第二个参数放在r1中,第3个参数放在r2中)第1个参数固定为0,第2个参数是机器码,第3个参数传递的就是大片传参tag的首地址。

3、移植时注意事项

(1)uboot移植时一般只需要配置相应的宏即可

(2)kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。

八、uboot启动内核的总结

1、启动4步骤
	第一步:将内核搬移到DDR中(链接地址处 )
	第二步:校验内核格式、CRC(一种校验方法)等
	第三步:准备传参(以tag的形式准备好传的参数)
	第四步:跳转执行内核(用函数指针进行远跳转)

2、涉及到的主要函数是:do_boom和do_bootm_linux

3、uboot能启动的内核格式:zImage uImage fdt方式(设备树)

4、跳转与函数指针的方式运行内核

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来、引用了百度百科、部分他人博客的内容并结合自己实际开发经历,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。

猜你喜欢

转载自blog.csdn.net/weixin_45842280/article/details/126825189