1、Uclinux 内核配置与裁减创建时间:08/01/17创建人:叶振风最后修改时间:修改人:叶振风Uclinux 的配置和裁减也是利用的华恒科技提供的源码包(用于 hhbf531 学习板)。我们使用的开发板信息如下:CPU:BF533FLASH:S29AL004D-512KBSDRAM:HY57V281620-16MB这里我不敢说“uclinux 的移植”,而只是以“ 配置与裁减”代之,是因为我觉得自己的工作真的谈不上什么移植。现成的源码包,所有的底层驱动都已经完成,我们所要做的只是选择自己需要的驱动、配置一下内核、做一些裁减工作而已。每每听到其他人提到“最近又完成了平台的 linux 移植”
2、,我都会有点担心:国内有多少工程师能真正从最初始的工作开始,完成一个平台的系统移植应该很少吧。下面,我分以下步骤简单介绍一下我的配置过程。一,配置并在 RAM 中运行内核(不带根文件系统):由于我们的 flash 空间有限,在没有裁减之前,就算不带根文件系统,也无法烧写到 flash内保存;所以先尝试下载到 RAM 中运行。另外,我们目前的开发板上没有网络功能,只能通过串口下载,所以在这里配置内核的过程中,做一些简单裁减,以便节约下载时间。解压源码包后,进入 uclinux 目录:#cd uClinux-dist设定交叉工具链:#PATH=”/usr/local/bin/gcc-bfin-3.
3、4-uclinux/bin/:$PATH”进入配置:#make menuconfig运行后,进入“MainMenu”配置页,可以在此选择 Vender/Product 和Kernel/Library/Defaults 等内容。根据我们使用的平台,我们选择:Vender-AnalogDevices,Product-HHBF533(或者 HHBF531),Libc-uClibc;如果要配置内核和应用程序还要分别选中“Customize Kernel Settings”、“Customize Vender/User Settings”。退出保存后,将依次进入配置内核和配置应用程序页。如果想单独配置内
4、核,可以进入目录 linux-2.6.x/内运行“make menuconfig”。配置应用程序在这个源码包里好像没有单独的 config 选项。这些关于内核源码包结构的基本知识,需要大家提前了解。下面,我们来配置内核。配置一个可以在我们的 SDRAM 中运行的内核很简单,因为底层工作都已经完成。我们只需要配置一下处理器相关内容即可。处理器选项位于内核配置页的“Blackfin Processer Options”。进入该配置页,进行如下配置:CPU - BF533System type - BF533-HHBFBoard Customizations - 根据你的开发板时钟、SDRAM 信息
5、配置,其他不用修改。Clock Settings - 取消“Re-programClocks while Kernel boots”,默认为 u-boot 的时钟配置。其他选项不用修改,各项配置功能介绍见文档附.Linux 2.6.19.x 内核编译配置选项简介。以上配置正确后,下载到你的开发板上,应该就可以运行了。但通过串口下载速度太慢,我们先去掉一些不需要的驱动。由于我们没有网络功能,所以把网络及其驱动全部取消,可以裁减 150KB 左右的空间;我们也不需要音视频功能,所以把音视频驱动也取消,又可以减小很大空间。如此配置后,我们可以尝试下载到 SDRAM 中运行了。现在,我们还不想裁减根文
6、件系统,所以,我们想得到一个不带根文件系统的压缩内核镜像。由于华恒提供的源码包,编译后不能得到压缩的不带根文件系统的镜像,所以我们要通过修改 Makefile 得到我们需要的编译结果。需要修改的 Makefile 位于 uClinux-dist 目录下,打开该 Makefile,在“.PHONY:linux”项的”ln f $(LINUXDIR)/vmlinux $(LINUXDIR)/linux;”语句后,添加以下内容。rm f $(LINUXDIR)/*.gz;bfin-uclinux-objcpy -O binary -S linuxlinux.bin; gzip -f9 linux.b
7、in; bfin-uclinux-mkimage -A blackfin -O linux-T kernel -Cgzip -a 0x1000 -e 0x1000 -n “uClinux Kernel Image“ -dlinux.bin.gz uImage.bin;这样在 uClinux-dist 目录下执行“make linux”就可以生成压缩的不带根文件系统的内核镜像了,该镜像文件为 uImage.bin,位于 linux-2.6.x 目录内。现在,可以将得到的内核下载到SDRAM 中运行了。因为是压缩内核,所以运行时要使用 u-boot 的 bootm 命令。至于 u-boot 命令的
8、使用方法,自行学习。Makefile 也是编译内核的基础知识,需要大家逐步掌握。这样,该步的工作就可以告一段落了。下载到 SDRAM 中,如果解压后无法运行,先检查一下上述配置操作是否有误。如果确定无误,就需要分析内核的执行过程,仔细分析问题了。接下来简单介绍一下内核执行流程。二,内核执行流程:承接上篇u-boot 引导 uclinux 过程分析,介绍内核启动流程。/下载完内核镜像(zImage.bin)后,可以在内存中直接引导运行 uclinux。因为内核是压缩后的镜像,所以需要 u-boot 进行解压引导,具体由 u-boot 的bootm 指令实现。Bootm 指令在 u-boot 内由
9、 do_bootm()函数实现,该函数位于/common/Cmd_bootm.c 文件中。引导过程通过控制终端打印的信息如下:UBOOT bootm 00e00000# Booting image at 00e00000 .Image Name: Bfin uClinux KernelCreated: 2007-12-20 2:05:37 UTCImage Type: Blackfin Linux Kernel Image (gzip compressed)Data Size: 685719 Bytes = 669.6 kBLoad Address: 00001000Entry Point:
10、00001000Verifying Checksum . OKUncompressing Kernel Image . OKStarting Kernel at 0x1000由打印信息可以看出,引导过程可以分为以下几步: 打印内核镜像头信息 内核镜像头校验 内核镜像解压 传递环境变量给内核并切换到内核运行下面分别介绍:1, 打印内核镜像头信息:打印内核镜像头信息由以下函数实现:print_image_hdr (image_header_t *)addr);其中的 addr 可以通过以下语句查看:printf (“# Booting image at %08lx .n“, addr);addr
11、就是内核镜像下载到 RAM 中的起始地址。打印信息的内容见前面的控制终端打印的信息。2, 内核镜像头校验:由以下语句实现:if (verify) puts (“ Verifying Checksum . “);if (crc32 (0, (char *)data, len) != ntohl(hdr-ih_dcrc)printf (“Bad Data CRCn“);SHOW_BOOT_PROGRESS (-3);return 1;puts (“OKn“);其中,(char *)data 指向内核镜像起始地址,len 为内核镜像头的长度,hdr-ih_dcrc 为内核镜像头内保存的校验结果。校验
12、正确,打印:Verifying Checksum . OK3, 内核镜像解压:内核镜像解压根据内核的不同压缩格式调用不同的解压缩函数执行。压缩类型存储于内核镜像头中的 hdr-ih_comp 变量中,主要类型有:IH_COMP_NONE 无压缩的 XIPIH_COMP_GZIP GZIP 压缩CONFIG_BZIP2 BZIP2 压缩我们使用的内核采用 GZIP 压缩格式,所以将调用 GZIP 解压缩函数执行解压,具体由以下函数实现:gunzip (void *)ntohl(hdr-ih_load), unc_len, (uchar *)data, char *cmdline;#ifdef S
13、HARED_RESOURCES swap_to(FLASH);#endifappl = (int (*)(char *)ntohl(header.ih_ep); (1)printf(“Starting Kernel at %#xn“, appl); (2)cmdline = make_command_line(); (3)if (icache_status() flush_instruction_cache(); icache_disable(); if (dcache_status() flush_data_cache(); dcache_disable(); (*appl)(cmdline
14、); (4)以上代码黑色部分是我们重要分析的部分,灰色内容暂时忽略;并对主要的四个语句分别标识了(1)到(4)的标号。通过语句(1)将函数指针 appl 指向内核解压后的起始地址,我们这里的起始地址默认为 0x1000。所以接下来语句(2)的打印信息为:Starting Kernel at 0x1000语句(3)为了获得传递给 linux 内核的环境参数,保存在 cmdline 指针变量中。我们这里的内容是:root=/dev/mtdblock0 ro console=ttyS0,115200n8前半段指示根文件系统在 mtd 块设备中的 block 序号和读写属性,根据根文件系统在 flas
15、h 里实际存储的位置决定;后半段指示控制终端的属性。最后,语句(4)将保存在 cmdline 内的 u-boot 环境参数传递给 linux 内核,并切换到 linux 内核的起始入口点开始运行 linux 内核。我们看看语句(4)的汇编代码:R0 = FP 4;P1 = FP 8;CALL(P1);也就是说,编译器是将要传递的字符串参数指针放在 R0 中的。指向 0x1000 地址的函数指针则放在 P1 中,最后通过“CALL(P1);”跳转到内核入口点。/A,内核 vmlinux 入口u-boot 执行“(*appl)(cmdline);”语句后,控制权就移交给 linux 内核,appl
16、 变量指向的地址就是 linux 内核的首地址。Linux 内核执行的第一个文件是/linux-2.6.x/arch/blackfin/mach-bf533/head.S。经过一系列的初始化,跳转到 start_kernel()函数,即进入 linux 系统初始化阶段。B, Linux 系统初始化Start_kernel()函数位于文件 /linux-2.6.x/init/main.c 中,是 linux 内核通用的初始化函数。无论对于什么体系结构的 linux,都要执行这个函数。asmlinkage void _initstart_kernel(void)char * command_lin
17、e;extern struct kernel_param _start_param, _stop_param;/* Interrupts are still disabled. Do necessarysetups, then* enable them*/#if 0 /* comment by mhfan */*(volatile unsignedshort*)UART_LCR) asmvolatile (“ssync;“);*(volatile unsignedshort*)UART_THR) = C; asm volatile (“ssync;“);#endif /* comment by
18、 mhfan */lock_kernel();page_address_init();printk(KERN_NOTICE);printk(linux_banner);setup_arch(setup_per_cpu_areas();/* Do the rest non-_inited, were now alive*/rest_init();Start_kernel()函数负责初始化内核各子系统,最后调用 rest_init(),启动一个叫作 init 的内核线程,继续初始化。static void noinlinerest_init(void)_releases(kernel_lock)k
19、ernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);numa_default_policy();unlock_kernel();/* The boot idle thread mustexecute schedule()* at least one to getthings moving:*/preempt_enable_no_resched();schedule();preempt_disable();/* Call into cpu_idle with preempt disabled */cpu_idle();起始内核线程 init 的任
20、务依然是初始化,只不过是一种更高层次的初始化。static int init(void *unused)lock_kernel();/* init can run on any cpu.*/set_cpus_allowed(current, CPU_MASK_ALL);/* Tell the world that weregoing to be the grim* reaper of innocentorphaned children.* We dont want people tohave to make incorrect* assumptions about wherein the ta
21、sk array this* can be found.*/child_reaper = current;smp_prepare_cpus(max_cpus);do_pre_smp_initcalls();fixup_cpu_present_map();smp_init();sched_init_smp();cpuset_init_smp();/* Do this before initcalls,because some drivers want to access* firmware files.*/populate_rootfs();do_basic_setup();/* check i
22、f there is anearly userspace init. If yes, let it doall* the work*/if (!ramdisk_execute_command)ramdisk_execute_command = “/init“;if (sys_access(const char _user *) ramdisk_execute_command, 0)!= 0) ramdisk_execute_command = NULL;prepare_namespace();/* Ok, we have completed theinitial bootup, and* we
23、re essentially up andrunning. Get rid of the* initmem segments andstart the user-mode stuff*/free_initmem();unlock_kernel();mark_rodata_ro();system_state = SYSTEM_RUNNING;numa_default_policy();if (sys_open(const char _user *) “/dev/console“,O_RDWR, 0) ram内核通过_initramfs_start 和_initramfs_end 找到根文件系统的
24、 img,这两个变量在文件/linux-2.6.x/init/Initramfs.c 中被引用。介绍完根文件系统的挂载方式,我们来介绍如何配置和裁减应用程序。由于 flash 容量限制,而且我们也并不需要很多应用程序的支持,所以我们可以只保留最简单的 init、sh、ls、cd 等应用程序,其他应用全部裁减掉。注意必须保证要有 init 和sh,否则内核无法运行或没有 shell 界面。另外,为了进一步裁减体积,我们利用busybox 制作根文件系统,busybox 的介绍文档网上非常多,这里不再介绍。按照以上分析,我们来配置应用程序和 busybox。按照“一,配置并在 RAM 中运行内核(
25、不带根文件系统) ”中介绍的方法进入应用程序配置页。只需选中 Busybox 内的 BusyboxSVN,其余选项全部取消,完全用 busybox 代替。然后,我们来配置 busybox。进入 busybox 目录,运行配置命令:#cd user/busybox-svn#make menuconfig除了按照我们上面介绍的,保留最基本的应用程序之外,其他全部取消;还有一点需要特别注意。就是在“Build options”选择中选中编译成静态库,而不要编译成共享库,这样在根文件系统挂载时省去很多麻烦,虽然最后得到的内核体积会稍微增大一下。共享库的应用可以在内核运行成功后,进一步学习。这样配置得到
26、的根文件系统已经裁减了很大体积,但下载到 SDRAM 中运行时会发现根文件系统占有的内存空间仍然很大,始终保持 12.5MB 空间。这是因为,根文件系统的运行空间是在生成镜像时指定的。要裁减占有的内存空间,可以如下修改。#vi vender/HHTech/BF533-HHBF/Makefile修改第 14 行的“BLOCKS = 12800”为较小的值,比如说 4096 等,必须是 256 的整数倍,否则内核运行时根文件系统报错。这样修改后,根文件系统占有的 flash 和 SDRAM 空间都会相应减小。通过以上裁减后,带有根文件系统的内核镜像完全可以控制在 448KB 以内,下载保存到flash 后运行,你就可以看到可爱的“uClinux”欢迎界面了。如果要进一步裁减根文件系统,可以修改和删除 vender/HHTech/BF533-HHBF/目录下的相关文件,具体操作不再详述。以上,就基本完成了我们需要的 uClinux 的配置工作,当然还有很多内容需要完善。接下来,就可以在这个平台上开发你自己的驱动和应用了。后面还有很多工作需要继续努力!