前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >嵌入式 linux 根文件系统原理和制作方法

嵌入式 linux 根文件系统原理和制作方法

作者头像
杨源鑫
发布2021-07-30 16:03:06
发布2021-07-30 16:03:06
4.2K00
代码可运行
举报
文章被收录于专栏:嵌入式开发圈嵌入式开发圈
运行总次数:0
代码可运行

1. 根文件系统原理

1.1 为什么需要根文件系统

  • init进程的应用程序在根文件系统上
  • 根文件系统提供了根目录 /
  • 内核启动后的应用层配置( etc 目录)在根文件系统上。几乎可以认为:发行版=内核+rootfs
  • shell命令程序在根文件系统上,比如 ls、cd 等命令

一套linux体系,只有内核本身是不能工作的,必须要 rootfs 上的 etc 目录下的配置文件、/bin /sbin 等目录下的 shell 命令,还有 /lib 目录下的库文件等···)相配合才能工作 。

1.2 根文件系统的实质

  • 根文件系统是特殊用途的文件系统。
  • 根文件系统也必须属于某种文件系统格式。rootfstype=

存储设备(块设备,像硬盘、flash等)是分块(扇区)的,物理上底层去访问存储设备时是按照块号(扇区号)来访问的。

文件系统是一些代码,是一套软件,这套软件的功能就是对存储设备的扇区进行管理,将这些扇区的访问变成了对目录和文件名的访问。我们在上层按照特定的目录和文件名去访问一个文件时,文件系统会将这个目录+文件名转换成对扇区号的访问。不同的文件系统的差异就在于对这些扇区的管理策略和方法不同,譬如坏块管理、碎片管理。

1.3 根文件系统的形式

  • 使用专用工具软件制作的可供烧录的镜像文件
  • 镜像中包含了根文件系统中的所有文件
  • 烧录此镜像类似于对相应分区格式化。
  • 镜像文件系统具有一定的格式,格式是内化的,跟文件名后缀是无关的。

以文件夹形式构成的文件系统:

  • 根文件系统其实就是一个包含特定内容的文件夹
  • 根文件系统可由任何一个空文件夹添加必要文件构成而成
  • 根文件系统的雏形就是在开发主机中构造的文件夹形式的

镜像文件形式的根文件系统主要目的是用来烧录到块设备上,设备上的内核启动后去挂载它。镜像文件形式的根文件系统是由文件夹形式的根文件系统使用专用的镜像制作工具制作而成的。

最初在开发主机中随便 mkdir 创建了一个空文件夹,然后向其中添加一些必要的文件(包括etc目录下的运行时配置文件、/bin 等目录下的可执行程序、/lib目录下的库文件等···)后就形成了一个文件夹形式的 rootfs。然后这个文件夹形式的 rootfs 可以被 kernel 通过 nfs 方式来远程挂载使用,但是不能用来烧录块设备。我们为了将这个 rootfs 烧录到块设备中于是用一些专用的软件工具将其制作成可供烧录的一定格式的根文件系统镜像。

文件夹形式的 rootfs 是没有格式的,制作成镜像后就有了一定的 rootfs 格式了,格式是由我们的镜像制作过程和制作工具来决定的。每一种格式的镜像制作工具的用法都不同。

1.4 自己制作简单的根文件系统

1.4.1 动手制作ext3格式的根文件系统
  • mke2fs介绍

mke2fs 是一个应用程序,在 ubuntu 中默认是安装了的。这个应用程序就是用来制作 ext2、ext3、ext4 等格式的根文件系统的。一般用来制作各种不同格式的 rootfs 的应用程序的名字都很相似,类似于 mkfs.xxx(譬如用来制作 ext2 格式的 rootfs 的工具叫 mkfs.ext2、用来制作 jffs2 格式的 rootfs 的工具就叫 mkfs.jffs2)。ubuntu14.04中的 mkfs.ext2 等都是 mke2fs 的符号链接而已。

  1. 创建rootfs.ext2文件并且将之挂载到一个目录下方便访问它 《参考资料:http://blog.csdn.net/zhengmeifu/article/details/24174513》
代码语言:javascript
代码运行次数:0
复制
dd if=/dev/zero of=rootfs.ext2 bs=1024 count=2048
losetup  /dev/loop1 rootfs.ext2
mke2fs -m 0 /dev/loop1 2048
mount -t ext2 /dev/loop1 ./rootfs/
  1. 我们向镜像中写入一个普通文件linuxrc。这个文件就会成为我们制作的镜像中的/linuxrc。内核挂载了这个镜像后就会尝试去执行/linuxrc。然后执行时必然会失败。我们将来实验看到的现象就应该是:挂载成功,执行/linuxrc失败。
  2. 将来真正去做有用的rootfs时,就要在这一步添加真正可以执行的 linuxrc 程序,然后还要添加别的 /lib 目录下的库文件,/etc目录下的配置文件等。
  3. 卸载掉,然后镜像就做好了。
代码语言:javascript
代码运行次数:0
复制
umount /dev/loop1
losetup -d /dev/loop1
1.4.2 nfs 方式启动 rootfs
  • 什么是 nfs?

nfs 是一种网络通讯协议,由服务器和客户端构成。利用 nfs 协议可以做出很多直接性应用,我们这里使用 nfs 主要是做 rootfs 挂载。开发板中运行 kernel 做 nfs 客户端,主机 ubuntu 中搭建 nfs 服务器。在主机 ubuntu 的 nfs 服务器中导出我们制作的文件夹形式的 rootfs 目录,则在客户端中就可以去挂载这个文件夹形式的 rootfs 进而去启动系统。

  • 搭建nfs服务器

配置内核以支持 nfs 作为 rootfs,设置 nfs 启动方式的 bootargs,在 menuconfig 中配置支持 nfs 启动方式。

  • 总结

nfs 方式启动相当于开发板上的内核远程挂载到主机上的 rootfs,nfs 方式启动不用制作 rootfs 镜像。nfs 方式不适合真正的产品,一般作为产品开发阶段调试使用。

  • uboot 中的启动参数设置
代码语言:javascript
代码运行次数:0
复制
setenv bootargs root=/dev/nfs nfsroot=192.168.1.104:/home/SummerGift/work/samsung_kernel/rootfs/rootfs ip=192.168.1.88:192.168.1.104:192.168.1.1:255.255.255.0::eth0:off  init=/linuxrc console=ttySAC2,115200 
  • 测试 nfs 挂载
代码语言:javascript
代码运行次数:0
复制
mount -t nfs -o nolock 192.168.1.104:/home/SummerGift/work/samsung_kernel/rootfs/rootfs  /opt
1.4.3 关于 linuxrc
  • /linuxrc 是一个可执行的应用程序 /linuxrc 是应用层的,和内核源码一点关系都没有,/linuxrc 在开发板当前内核系统下是可执行的。因此在 ARM SoC 的 linux 系统下,这个应用程序就是用 arm-linux-gcc 编译链接的;如果是在 PC 机 linux 系统下,那么这个程序就是用 gcc 编译连接的。

/linuxrc如果是静态编译连接的那么直接可以运行;如果是动态编译连接的那么我们还必须给他提供必要的库文件才能运行。但是因为我们 /linuxrc 这个程序是由内核直接调用执行的,因此用户没有机会去导出库文件的路径,因此实际上这个 /linuxrc 没法动态连接,一般都是静态连接的。

  • /linuxrc执行时引出用户界面 操作系统启动后在一系列的自己运行配置之后,最终会给用户一个操作界面(也许是 cmdline,也许是 GUI),这个用户操作界面就是由 /linuxrc 带出来的。

用户界面等很多事并不是在 /linuxrc 程序中负责的,用户界面有自己专门的应用程序,但是用户界面的应用程序是直接或者间接的被 /linuxrc 调用执行的。用户界面程序和其他的应用程序就是进程2、3、4·····,这就是我们说的进程1(init进程,也就是 /linuxrc)是其他所有应用程序进程的祖宗进程。

  • /linuxrc 负责系统启动后的配置 就好像一个房子建好之后不能直接住,还要装修一样;操作系统启动起来后也不能直接用,要配置下。操作系统启动后的应用层的配置(一般叫运行时配置,英文简写 etc)是为了让我们的操作系统用起来更方便,更适合我个人的爱好或者实用性。
  • /linuxrc在嵌入式linux中一般就是busybox busybox 是一个 C 语言写出来的项目,里面包含了很多 .c 文件和 .h 文件。这个项目可以被配置编译成各个平台下面可以运行的应用程序。我们如果用 arm-linux-gcc 来编译busybox就会得到一个可以在我们开发板 linux 内核上运行的应用程序。

busybox 这个程序开发出来就是为了在嵌入式环境下构建 rootfs 使用的,也就是说他就是专门开发的 init 进程应用程序。

busybox 为当前系统提供了一整套的 shell 命令程序集。譬如 vi、cd、mkdir、ls 等。在桌面版的linux 发行版(譬如ubuntu、redhat、centOS等)中 vi、cd、ls 等都是一个一个的单独的应用程序。但是在嵌入式 linux 中,为了省事我们把 vi、cd 等所有常用的 shell 命令集合到一起构成了一个shell 命令包,起名叫 busybox。

1.4.4 rootfs 中的其他目录
  • /linuxrc
  • dev目录下的设备文件

在linux中一切皆是文件,因此一个硬件设备也被虚拟化成一个设备文件来访问,在linux系统中/dev/xxx就表示一个硬件设备,我们要操作这个硬件时就是open打开这个设备文件,然后read/write/ioctl操作这个设备,最后close关闭这个设备。在最小 rootfs 中 /dev 目录也是不可少的,这里面有一两个设备文件是 rootfs 必须的。

  • sys和proc目录

在最小 rootfs 中也是不可省略的,但是这两个只要创建了空文件夹即可,里面是没东西的,也不用有东西。这两个目录也是和驱动有关的。属于linux中的虚拟文件系统。

  • usr是系统的用户所有的一些文件的存放地,这个东西将来 busybox 安装时会自动生成。
  • etc 目录中的所有文件全部都是运行时配置文件。/etc 目录下的所有配置文件会直接或者间接的被 /linuxrc 所调用执行,完成操作系统的运行时配置。etc 目录是制作 rootfs 的关键,所以后面下一个课程专门讲这个 etc 目录。
  • lib 目录也是 rootfs 中很关键的一个,不能省略的一个。lib目录下放的是当前操作系统中的动态和静态链接库文件。我们主要是为了其中的动态链接库。

2. 用 busybox 制作一个简单的 rootfs

2.1 移植 busybox 到开发板

  • busybox源码下载

busybox 是一个开源项目,所以源代码可以直接从网上下载。版本差异不大,版本新旧无所谓。下载busybox可以去 linuxidc 等镜像网站,也可以去 www.busybox.net官方网站下载。

  • 修改Makefile
代码语言:javascript
代码运行次数:0
复制
ARCH = arm
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin//arm-none-linux-gnueabi-
  • make menuconfig 进行配置
代码语言:javascript
代码运行次数:0
复制
Busybox Settings--->
 Build Options--->
  [*]Build BusyBox as a static binary(no shared libs)

Busybox Library Tuning--->
 [*]vi-style line editing commands
 [*]Fancy shell prompts

Linux Module Utilities--->
 [ ]Simplified modutils
 [*]insmod
 [*]rmmod
 [*]lsmod
 [*]modprobe
 [*]depmod

Linux System Utilities--->[*]mdev
 [*]Support /etc/mdev.conf
 [*]Support subdirs/symlinks
 [*]Support regular expressions substitutions when renaming dev
 [*]Support command execution at device addition/removal
 [*]Support loading of firmwares
  • make 然后 make install

make 编译,如果有错误解决之,make install 执行的时候其实是在执行 busybox 顶层目录下的一个目标 install。

make install 在所有的 linux 下的软件中作用都是安装软件。在传统的 linux 系统中安装软件时都是选择源代码方式安装的。我们下载要安装的软件源代码,然后配置、编译、安装。make install的目的就是将编译生成的可执行程序及其依赖的库文件、配置文件、头文件安装到当前系统中指定(一般都可以自己指定安装到哪个目录下,如果不指定一般都有个默认目录)的目录下。

  • 设置 bootargs 挂载添加了busybox移植的rootfs

之前建立了一个空的文件夹然后自己 touch linuxrc 随便创建了一个不能用的 /linuxrc 然后去 nfs 挂载 rootfs,实验结果是:挂载成功,执行 /linuxrc 失败。现在我们移植了 busybox 后 /linuxrc 就可以用了,然后再次去 nfs 挂载这个 rootfs。预计看到的效果是:挂载成功,执行/linuxrc也能成功。

uboot 的 bootargs 设置成:

代码语言:javascript
代码运行次数:0
复制
setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off  init=/linuxrc console=ttySAC2,115200 

挂载成功,执行 /linuxrc(也就是busybox)成功,但是因为找不到 /etc/init.d/rcS 和 /dev/tty2 等文件所以一直在打印错误提示信息,但是其实有进入命令行。

2.2 inittab 详解

  • 添加一个典型的inittab

将一个典型的 inittab 文件复制到我们制作的 rootfs 的根目录下的 /etc/ 目录下,再次启动内核挂载这个 rootfs 看效果,实验现象是成功启动并且挂载 rootfs 进入了控制台命令行。当前制作的最小rootfs 成功了。

  • inittab格式解析

inittab 的工作原理就是被 /linuxrc(也就是busybox)执行时所调用起作用。

inittab 在 /etc 目录下,所以属于一个运行时配置文件,是文本格式的(内容是由一系列的遵照一个格式组织的字符组成的),实际工作的时候 busybox 会(按照一定的格式)解析这个 inittab 文本文件,然后根据解析的内容来决定要怎么工作。

busybox 究竟如何完成解析并且解析结果如何去工作(busybox 中实现 /etc/inittab 的原理)并不是我们的目标,我们的重点是 inittab 的格式究竟怎样的?我们看到一个 inittab 后怎么去分析这个inittab 对启动的影响。inittab 的格式在 busybox 中定义的。

  • 常见的配置项

第一个:#开始的行是注释 第二个:冒号在里面是分隔符,分隔开各个部分。第三个:inittab内容是以行为单位的,行与行之间没有关联,每行都是一个独立的配置项,每一个配置项表示一个具体的含义。第四个:每一行的配置项都是由3个冒号分隔开的4个配置值共同确定的。这四个配置值就是id:runlevels:action:process。值得注意的是有些配置值可以空缺,空缺后冒号不能空缺,所以有时候会看到连续2个冒号。第五个:每一行的配置项中4个配置值中最重要的是 action 和 process,action 是一个条件/状态,process 是一个可被执行的程序的 pathname。合起来的意思就是:当满足 action 的条件时就会执行process 这个程序。

理解 inittab 的关键就是明白“当满足 action 的条件时就会执行 process 这个程序。” 分析 busybox的源代码就会发现,busybox 最终会进入一个死循环,在这个死循环中去反复检查是否满足各个action 的条件,如果某个 action 的条件满足就会去执行对应的 process。

3. busybox 源码分析

3.1 分析 busybox 启动状况

分析一个程序,不管多庞大还是小,最好的路线都是按照程序运行时的逻辑顺序来。所以找到一个程序的入口至关重要。主函数main函数就是整个程序的入口,这种情况适应于操作系统下工作的应用程序的情况。

在 uboot 和 linux kernel 这两个大的 C 语言的项目中,main函数都没有,都不是入口。在我们这种裸机程序中入口不是 main 函数,而是由连接脚本来指定的。

busybox 是 linux 启动起来后工作的一个应用程序,因此其中必然有 main 函数,而且 main 就是入口。

3.2 busybox 中 main 函数全解析

busybox 入口就是 main 函数,其中有很多个 main 但是只有一个起作用了,其他的是没起作用的。真正的busybox工作时的入口是 libbb/appletlib.c 中的 main 函数。

busybox中有很多 xxx_main 函数,这些 main 函数每一个都是 busybox 支持的一个命令的真正入口。例如 ls_main 函数就是 busybox 当作 ls 函数使用时的入口程序。ls 或者 cd 等命令其实都是 busybox 一个程序,但是实际执行时的效果却是各自的效果。

busybox 每次执行时都是先执行其 main,在 main 函数中识别(靠 main 函数的传参 argv[0] 来识别)我们真正要执行的函数(譬如ls)然后去调用相应的xxx_main(譬如ls_main)来具体实现这个命令。

3.3 inittab 解析与执行

inittab 的解析是在 busybox/init/init.c/init_main 函数中。执行逻辑是:先通过 parse_inittab 函数解析 /etc/inittab(解析的重点是将 inittab 中的各个 action 和 process 解析出来),然后后面先直接执行 sysinit 和 wait 和 once(注意这里只执行一遍),然后在 while(1) 死循环中去执行 respwan 和 askfirst。

3.4 busybox 的体积优势原理

busybox 实际上就是把 ls、cd、mkdir 等很多个 linux 中常用的 shell 命令集成在一起了。集成在一起后有一个体积优势:就是 busybox 程序的大小比 busybox 中实现的那些命令的大小加起来要小很多。

busybox 体积变小的原因主要有2个:

  • 第一个是 busybox 本身提供的 shell 命令是阉割版的(busybox 中的命令支持的参数选项比发行版中要少,譬如ls在发行版中可以有几十个 -x,但是在 busybox 中只保留了几个常用的选项,不常用的都删除掉了)
  • 第二个是 busybox 中因为所有的命令的实现代码都在一个程序中实现,而各个命令中有很多代码函数都是通用的(譬如 ls 和 cd、mkdir 等命令都会需要去操作目录,因此在 busybox 中实现目录操作的函数就可以被这些命令共用),共用会降低重复代码出现的次数,从而减少总的代码量和体积。

busybox 的体积优势是嵌入式系统本身的要求和特点造成的。

4. rcS 文件分析与实战

4.1 rcS 文件介绍

/etc/init.d/rcS 文件是linux的运行时配置文件中最重要的一个,其他的一些配置都是由这个文件引出来的。这个文件可以很复杂也可以很简单,里面可以有很多的配置项。

  • PATH=xxx

首先从shell脚本的语法角度分析,这一行定义了一个变量PATH,值等于后面的字符串,后面用export导出了这个PATH,那么PATH就变成了一个环境变量。

PATH 这个环境变量是 linux 系统内部定义的一个环境变量,含义是操作系统去执行程序时会默认到PATH 指定的各个目录下去寻找。如果找不到就认定这个程序不存在,如果找到了就去执行它。将一个可执行程序的目录导出到 PATH,可以让我们不带路径来执行这个程序。

  • rcS 中为什么要先导出 PATH?

因为我们希望一旦进入命令行下时,PATH 环境变量中就有默认的 /bin /sbin /usr/bin /usr/sbin 这几个常见的可执行程序的路径,这样我们进入命令行后就可以 ls、cd 等直接使用了。

为什么rcS 文件还没添加,系统启动就有了 PATH 中的值?原因在于 busybox 自己用代码硬编码为我们导出了一些环境变量,其中就有 PATH。

  • runlevel=

runlevel也是一个shell变量,并且被导出为环境变量,runlevel=S表示将系统设置为单用户模式。

  • umask=

umask 是 linux 的一个命令,作用是设置 linux 系统的 umask 值。umask值决定当前用户在创建文件时的默认权限。

  • mount -a

mount -a 是挂载所有的应该被挂载的文件系统,在 busybox中 mount -a 时 busybox 会去查找一个文件 /etc/fstab 文件,这个文件按照一定的格式列出来所有应该被挂载的文件系统(包括了虚拟文件系统)

  • mdev

mdev 是 udev 的嵌入式简化版本,udev/mdev 是用来配合 linux 驱动工作的一个应用层的软件,udev/mdev 的工作就是配合 linux 驱动生成相应的 /dev 目录下的设备文件。

在 rcS 文件中没有启动 mdev 的时候,/dev 目录下启动后是空的。在 rcS 文件中添加上 mdev 有关的 2 行配置项后,再次启动系统后发现 /dev 目录下生成了很多的设备驱动文件。(4)/dev目录下的设备驱动文件就是mdev生成的,这就是mdev的效果和意义。

  • hostname

hostname 是 linux 中的一个 shell 命令。命令(hostname xxx)执行后可以用来设置当前系统的主机名为 xxx,直接 hostname 不加参数可以显示当前系统的主机名。

/bin/hostname -F /etc/sysconfig/HOSTNAME -F 来指定了一个主机名配置文件(这个文件一般文件名叫 hostname 或者 HOSTNAME)

  • ifconfig

有时候我们希望开机后进入命令行时ip地址就是一个指定的 ip 地址(譬如192.168.1.30),这时候就可以在 rcS 文件中 ifconfig eth0 192.168.1.88

4.2 rcS 文件测试问题记录

  • PATH & runlevel

发现 rcS 文件明明存在但是却提示不存在,问题原因就是 rcS 文件在 windows 下创建的,行尾换行符为 '\r\n',多了点东西。但是因为 ubuntu 中的 vi 对行尾做了优化,所以在 ubuntu 中是看不出来多了东西的。但是在 securecrt 下一看就发现每一行末尾多出来了一个 ^M。

这个情况表明:shell 脚本文件如果格式不对,运行时可能会被提示文件不存在。有时候一个应用程序执行时也会提示文件不存在,问题可能是这个程序所调用的一个动态链接库找不到。

测试结果:PATH 本来在 busybox 中就已经用代码导出过了,所以 rcS 中再次导出没有任何明显的现象,因此看不出什么差别。runlevel 实际执行结果一直是 unknown,问题在于 busybox 并不支持 runlevel 这个特性。

  • umask测试

umask是 022 的时候,默认touch创建一个文件的权限是 644 umask是 044 的时候,默认touch创建一个文件的权限是 622 umask是 444 的时候,默认touch创建一个文件的权限是 222

总结:umask 的规律就是:umask 值和默认创建文件的权限值加起来是 666。

  • mount 测试

挂载时全部出错:

代码语言:javascript
代码运行次数:0
复制
mount: mounting proc on /proc failed: No such file or directory
mount: mounting sysfs on /sys failed: No such file or directory
mount: mounting tmpfs on /var failed: No such file or directory
mount: mounting tmpfs on /tmp failed: No such file or directory
mount: mounting tmpfs on /dev failed: No such file or directory

原因是因为根文件系统中找不到挂载点。所谓挂载点就是我们要将目标文件系统(当然这里都是虚拟文件系统)挂载到当前文件系统中的某一个目录中,这个目录就是挂载点。

解决方案就是自己在制作的 rootfs 根目录下创建这些挂载点目录即可。验证是否挂载成功,可以看挂载时输出信息;还可以启动后去看 proc 和 sys 文件夹,如果有文件出现则证明挂载成功了,如果没东西就证明失败了。

4.3 开机自启动与主流 rcS 格式介绍

  • 修改rcS实现开机自启动

机自启动指的是让一些应用程序能够开机后自动执行,开机自启动的实现原理就是在开机会自动执行的脚本 rcS 中添加上执行某个程序的语句代码即可。

  • 前台运行与后台运行

程序运行时占用了当前的控制台,因此这个程序不结束我们都无法使用控制台,这就叫前台运行。默认执行程序就是前台运行的。

后台运行就是让这个程序运行,并且同时让出控制台。这时候运行的程序还能照常运行而且还能够不影响当前控制台的使用。让一个程序后台运行的方法就是 ./xxx &。开机装载驱动等其他开机自动执行

  • 实际开发中 rootfs 的 rcS 是怎样的

分析 inittab 发现:sysinit 执行 rcS,shutdown 时执行 rcK。

分析 /etc/init.d/rcS 和 rcK 文件发现,rcS 和 rcK 都是去遍历执行 /etc/init.d/ 目录下的S开头的脚本文件,区别是 rcS 传参是 start,rcK 传参是 stop。

由此可以分析出来,正式产品中的 rcS 和 rcK 都是一个引入,而不是真正干活的。真正干活的配置脚本是 /etc/init.d/S??*。这些文件中肯定有一个判断参数是 start 还是 stop,然后 start 时去做一些初始化,stop 时做一些清理工作。

5. 添加动态链接库

  • 静态编译链接 helloworld 程序并执行

自己写一个 helloworld 程序,然后交叉编译连接,然后丢到开发板根文件系统中,开机后去运行。C 程序如果使用 gcc 来编译则可以在主机 ubuntu 中运行,但是不能在开发板运行。要在开发板运行需要用 arm-linux-gcc 来交叉编译,但是这时候就不能在主机 ubuntu 中运行了。可以用 file xx 命令来查看一个 elf 可执行程序是哪个架构的。

静态链接:arm-linux-gcc hello.c -o hello_satic -static

静态编译连接后生成的 hello_satic 可以直接在开发板上运行。

  • 动态编译连接 helloworld 程序并执行

动态链接:arm-linux-gcc hello.c -o hello_dynamic

实验结果:-sh: ./hello_dynamic: not found 运行时提示找不到程序。

原因是动态连接的 hello 程序中调用到了 printf 函数,而 printf 函数在动态连接时要在运行时环境(开发板的rootfs)中去寻找对应的库文件(开发板 rootfs 中部署的动态链接库中包含了 printf 函数的那个库文件)。如果找到了则 printf 函数就会被成功解析,然后 hello_dynamic 程序就会被执行;如果找不到则程序就不能被执行,命令行会提示错误信息

-sh: ./hello_dynamic: not found

解决方案:将 arm-linux-gcc 的动态链接库文件复制到开发板 rootfs 的 /lib 目录下即可解决。

  • 找到并复制动态链接库文件到 rootfs 中

现在使用的 arm-2009q3 这个交叉编译工具链的动态链接库在 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib 目录下。其他的一些交叉编译工具链中动态链接库的目录不一定在这里,要去找一下。找的方法就是 find 命令,find -name *libm.so*

复制动态链接库到 roots/lib 目录下。复制时要注意参数用 -rdf,主要目的就是符号链接复制过来还是符号链接。

复制命令:

cp lib/ /home/SummerGift/work/samsung_kernel/rootfs/rootfs -r

拷贝动态链接库到根文件系统之后,再去测试 ./hello_dynamic 就可以直接运行了。

  • 使用 strip 工具去掉库中符号信息

动态链接库 so 文件中包含了调试符号信息,这些符号信息在运行时是没用的(调试时用的),这些符号会占用一定空间。在传统的嵌入式系统中flash空间是有限的,为了节省空间常常把这些符号信息去掉。这样节省空间并且不影响运行。

去掉符号命令:arm-none-linux-gnueabi-strip *so*

使用该命令后发现库文件由3.8M变成了3.0M,节省了0.8M的空间。

6. 制作 ext2 格式的镜像并烧录启动

  • 确定文件夹格式的 rootfs 可用

设置 bootargs 为 nfs 启动方式,然后从主机 ubuntu 中做好的文件夹格式的 rootfs 去启动,然后看启动效果,作为将来的参照物。

  • 制作 ext2 格式的镜像
代码语言:javascript
代码运行次数:0
复制
dd if=/dev/zero of=rootfs.ext2 bs=1024 count=10240
losetup  /dev/loop1 rootfs.ext2
mke2fs -m 0 /dev/loop1 10240
mount -t ext2 /dev/loop1 ./ext2_rootfs/

向 ./rootfs 中复制内容,用cp ../rootfs/* ./ -rf

umount /dev/loop1

losetup -d /dev/loop1

完成后得到的rootfs.ext2就是我们做好的rootfs镜像。拿去烧录即可。

  • 烧录镜像并设置合适的 bootargs

使用 fastboot 烧录制作好的 rootfs.ext2 到开发板 inand 中

代码语言:javascript
代码运行次数:0
复制
fastboot flash system rootfs.ext2

烧录完成后重启系统,设置 bootargs 为:

代码语言:javascript
代码运行次数:0
复制
set bootargs console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext2

启动后发现现象和之前 nfs 方式启动挂载 rootfs 后一样的,至此 rootfs 制作实验圆满完成。

7. 常见 BSP 结构

7.1 嵌入式 linux 产品的 bsp 介绍

大部分的 ARM 架构的 linux 平台的 bsp 的内容和结构都是相似的,bsp 一般是芯片厂家/板卡厂家提供的。

  • X210的 linux+QT bsp 整体介绍 tslib_x210_qtopia.tgz 是用来支持QT的触摸屏操作的应用层库。

xboot 和 uboot 是 X210 支持的2个 bootloader 源代码。kernel 文件夹中是内核源代码,buildroot 文件夹是用来构建根文件系统的文件夹。tools 里是一些有用工具。

7.2 mk 脚本

mk 脚本是用来管理和编译整个 bsp 的。

  • mk 的帮助信息 linux下的惯例就是,执行程序时加 -h 或者 --help 就可以看到这个程序执行的帮助信息。

mk 脚本的主要作用是编译 bsp 中的所有的源代码(包括bootloader、kernel、rootfs等),但是我们可以完整编译也可以部分编译,我们通过执行 mk 后面加不同的参数来指定 mk 脚本去编译相应的部分。

代码语言:javascript
代码运行次数:0
复制
mk -a 即可编译所有的bsp源代码
mk -x 即可只编译xboot
mk -ui 即可只编译uboot针对inand版本开发板的源代码
mk -r   即可只编译buildroot,-r只是得到了文件夹形式的rootfs,并没有将其制作成镜像文件。
mk -re 即可编译buildroot并且制作得到ext3格式的rootfs镜像
mk -rj 即可编译buildroot并且制作得到jffs2格式的rootfs镜像

注:./mk和 mk 都是执行 mk 这个脚本文件,区别在于 ./mk 是带路径的,mk 是不带路径的。还有 source mk,这个和前两个的区别是 source 执行时不需要 mk 文件具有可执行权限,而前面两种要求 mk 必须在当前用户下具有可执行权限。

7.3 mk 文件分析

  • shell程序结构

shell 脚本程序分为:变量定义、函数、代码。shell脚本程序的结构非常类似于C语言程序。

shell 程序和 C 语言程序很大的一个差别就是 shell 没有 main 函数,shell 脚本执行时也是先执行主函数的,不过主函数没有放在一个类似于 main 这样的函数中,而是直接放在全局下的一些代码。

shell 程序执行时首先执行变量定义,然后执行主函数,其他函数在主函数代码中被调用执行。

  • 主函数 mk 可以编译整个bsp的源代码,也可以只编译其中一部分。实现原理就是:用一个函数来完成编译一个(譬如编译内核用build_kernel函数,编译 inand 的 uboot 用build_bootloader_uboot_inand),然后用相应的一些变量来控制这个函数要不要被编译(譬如uboot_inand 变量=yes就表示要编译inand版本的uboot,=no 就表示不要编译),我们编译时通过-xxxxx来传参时,这些传参会影响这些变量的值=yes或者=no。

如果直接./mk并不传参,则$1为空,这时候按照一套默认的配置来编译。

8. buildroot 使用方法

8.1 buildroot 介绍

之前自己从零开始构建根文件系统,一路下来事情还挺多,步骤比较麻烦。

交叉编译工具链 arm-linux-gcc,我们目前都是从 soc 官方直接拿来使用的,官方的工具链从何而来?实际上交叉编译工具链都是由gcc配置编译生成的,这个配置编译过程比较复杂,一般人自己去配置编译得到自己的交叉编译工具链是比较麻烦的,所以经常都是用别人最好的。

buildroot 就是一个集成包,这个包里集成了交叉编译工具链的制作,以及整个 rootfs 的配置编译过程。也就是说,使用 buildroot 可以很简便的得到一个做好的文件夹形式的根文件系统。

buildroot 将很多东西集成进来后,移植了 linux kernel 的 make xxx_defconfig+make menuconfig的 2 步配置法,我们可以在 buildroot 的配置界面下完成集成在里边的所有东西的配置,然后直接 make 就可以最终得到文件夹形式的 rootfs。

8.2 使用 buildroot 构建根文件系统

编译 buildroot 的错误解决方案(环境为ubuntu14.04)

  • make x210ii_defconfig
代码语言:javascript
代码运行次数:0
复制
make xxx_defconfig
make menuconfig
make
  • make及其错误解决 直接 make 会遇到很多错误,这些错误原因都是因为ubuntu中缺乏一些必要软件包造成的。解决方案是先安装这些必要的软件包。编译过程会需要从网上下载一些软件包,因此整个编译过程需要在联网状态下进行。
  • 安装需要的软件包
代码语言:javascript
代码运行次数:0
复制
sudo apt-get install g++ bison flex texinfo git hgsubversion whois
  • 编译后结果查看与分析

编译后生成的文件夹格式的 rootfs 在 buildroot/output/images/rootfs.tar。我们将其复制到了根目录下的 release 目录下去,这个文件就是一个完整的可以工作的文件夹形式的 rootfs。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式云IOT技术圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 根文件系统原理
    • 1.1 为什么需要根文件系统
    • 1.2 根文件系统的实质
    • 1.3 根文件系统的形式
    • 1.4 自己制作简单的根文件系统
      • 1.4.1 动手制作ext3格式的根文件系统
      • 1.4.2 nfs 方式启动 rootfs
      • 1.4.3 关于 linuxrc
      • 1.4.4 rootfs 中的其他目录
  • 2. 用 busybox 制作一个简单的 rootfs
    • 2.1 移植 busybox 到开发板
    • 2.2 inittab 详解
  • 3. busybox 源码分析
    • 3.1 分析 busybox 启动状况
    • 3.2 busybox 中 main 函数全解析
    • 3.3 inittab 解析与执行
    • 3.4 busybox 的体积优势原理
  • 4. rcS 文件分析与实战
    • 4.1 rcS 文件介绍
    • 4.2 rcS 文件测试问题记录
    • 4.3 开机自启动与主流 rcS 格式介绍
  • 5. 添加动态链接库
  • 6. 制作 ext2 格式的镜像并烧录启动
  • 7. 常见 BSP 结构
    • 7.1 嵌入式 linux 产品的 bsp 介绍
    • 7.2 mk 脚本
    • 7.3 mk 文件分析
  • 8. buildroot 使用方法
    • 8.1 buildroot 介绍
    • 8.2 使用 buildroot 构建根文件系统
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档