简易根文件系统构建实验及过程详解

目录

  • 一、根文件系统的构建路线
  • 二、busybox的移植实战
    • 1、busybox源码下载
    • 2、修改Makefile
    • 3、make menuconfig
    • 4、make 然后 make install
    • 5、设置bootargs挂载添加了busybox移植的rootfs
  • 三、inittab详解
    • 1、添加一个典型的inittab
    • 2、inittab格式解析
  • 四、busybox源码分析
    • 1、源码目录梳理、SourceInsight工程建立
    • 2、整个程序入口确认
    • 3、busybox中main函数全解析
    • 4、inittab解析与执行
    • 5、busybox的体积优势原理
  • 五、rcS文件介绍
    • 1、PATH=xxx
    • 2、runlevel=S
    • 3、umask
    • 4、mount -a
    • 5、PATH&runlevel
    • 6、umask测试
    • 7、mount测试
    • 8、mdev
    • 9、hostname
  • 六、profile文件和用户登录理论
    • 1、profile文件添加
    • 2、如何看到用户登录界面
    • 3、用户名和密码的设置
  • 七、用户登录实战
    • 1、添加/bin/login到sysinit
    • 2、添加passwd和shadow文件
    • 3、重置密码实践
    • 4、getty实战
  • 八、动态链接库的拷贝
    • 1、静态编译链接helloworld程序并执行
    • 2、动态编译连接helloworld程序并执行
    • 3、找到并复制动态链接库文件到rootfs中
    • 4、使用strip工具去掉库中符号信息
  • 九、开机自启动与主流rcS格式介绍
    • 1、修改rcS实现开机自启动
    • 2、前台运行与后台运行
    • 3、实际开发中rootfs的inittab、rcS是怎样的
  • 十、制作ext2格式的镜像并烧录启动
    • 1、确定文件夹格式的rootfs可用
    • 2、动手制作ext2格式的镜像
    • 3、烧录镜像并设置合适的bootargs

一、根文件系统的构建路线

1、先逐步构建文件夹形式根文件系统然后制作烧录镜像

(1)rootfs有2种格式:nfs方式启动的文件夹形式的rootfs 和 用来烧录的镜像形式的rootfs

(2)我们先从空文件夹开始,逐步向其中添加一些rootfs中必备的东西,然后做一步就用nfs
方式去挂载启动,观察启动后的现象,并且理论上去分析原因。

2、动手实践和理论分析相结合。

3、逐步添加不断复杂化的rootfs

二、busybox的移植实战

1、busybox源码下载

(1)busybox是一个开源项目,所以源代码可以直接从网上下载。

(2)busybox的版本差异不大,版本新旧无所谓。

(3)下载busybox可以去linuxidc等镜像网站,也可以去www.busybox.net官方网站下载。我做实验下载使用的是最新版1.32.0

链接:https://pan.baidu.com/s/1DIBiydze7P0hHh0cAw1l1Q 
提取码:od43 
--来自百度网盘超级会员V6的分享

  busybox 的源码结构与linux内核很像,其很大程度上是从Linux内核复制过来的。其的许多操作手法与linux内核十分相像,包括配置编译等等。

2、修改Makefile

(1) ARCH = arm
(2) CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-

3、make menuconfig

  源码目录执行make menuconfig,进行配置(由于busybox本身不是很大,所以并未向内核那样,先配置大部分,再make menuconfig精修),参考下面的记录进行配置。

/************************************************************************/
Busybox Settings--->
	Build Options--->
		[*]Build BusyBox  as a static binary(no shared libs)    静态链接

		
Busybox Library Tuning--->
	[*]vi-style line editing commands    vi风格的编辑方式
	[*]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
	[*]Supportma'ke command execution at device addition/removal
	[*]Support loading of firmwares
*************************************************************************/

4、make 然后 make install

(1)make编译,如果有错误解决之

	可能的错误,使用的交叉编译链和busybox的版本不符;源代码一般没问题
	
	错误解决:可将报错的文件不编译,我们所需的是busybox的一部分功能,并不是所有
的,所以部分文件不编译,并不影响达到我们的目的。

	解决方法:打开出错文件所在目录下的Kbuild文件,找到编译出错文件的相关配置项,
去源码根目录下进行make menuconfig ,在其中搜索相关配置:
	如:找到CONFIG_FEATURE_IP_LINK
	搜索:FEATURE_IP_LINK
	结果为:
		Symbol:FEATURE_IP_LINK[=y]
		Prompt:ip link                              
			Defined at networking/Config.in:564
			Depends on: IP || IPLINK Location:                                                                                    -> Networking Utilities
		    Selected by: IPLINK 
	根据搜索结果进行修改,这个操作类似于内核配置;
我所使用的版本可能还要进行如下的修改。详情见:
https://blog.csdn.net/weixin_45963171/article/details/105138882

在make menuconfig进行配置:
	Linux Module Utilities--->
		[ ]nsenter

	Coreutils--->
		[ ]sync
	
	根据报错信息的提示修改C文件:
	修改networking/libiproute/ipaddress.c:333:添加条件编译
#ifdef IFA_F_DADFAILED 	//添加
		if (ifa->ifa_flags & IFA_F_DADFAILED) {
				ifa->ifa_flags &= ~IFA_F_DADFAILED;
				printf("dadfailed ")
		}
#endif			//添加

(2)make install执行的时候其实是在执行busybox顶层目录下的一个目标install。

(3)make install
  在所有的linux下的软件中作用都是安装软件。在传统的linux系统中安装软件时都是选择源代码方式安装的。我们下载要安装的软件源代码,然后配置、编译、安装。

  make install的目的就是将编译生成的可执行程序及其依赖的库文件、配置文件、头文件安装到当前系统中指定(一般都可以自己指定安装到哪个目录下,如果不指定一般都有个默认目录)的目录下

  默认安装目录为源代码目录下的_install,我们可以修改makemenuconfig的相关配置项,让其安装到别的目录下。

Busybox Settings--->
 --- Installation Options ("make install" behavior)                                      
What kind of applet links to install (as soft-links)  --->
(/home/rootfs) Destination path for 'make install' 

  下载安装的程序都是个软链接,且都指向了busybox这个程序

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

(1)使用一个空的文件夹然后自己touch linuxrc随便创建了一个不能用的/linuxrc然后去nfs挂载rootfs,实验结果是:挂载成功,执行/linuxrc失败。

(2)现在我们移植了busybox后/linuxrc就可以用了,然后再次去nfs挂载这个rootfs。预计看到的效果是:挂载成功,执行/linuxrc也能成功。

(3)注意uboot的bootargs设置成:

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 

  由于文件目录不同,下面这个是我实验所用的:

setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/home/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 

  eth0 开发板的网卡名称

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

  若从内核启动打印信息发现未成功挂载,则原因可能出现在主机服务器中,在主机测试能否挂载在/opt目录,若不行则应在主机进行相关设置,重启配置nfs服务等等,修改配置文件:

vi /etc/exports
	/home/rootfs *(insecure,rw,sync,no_root_squash,no_subtree_check)
	若根文件系统建立在/root/rootfs则上述应改为:
	/root/rootfs *(insecure,rw,sync,no_root_squash,no_subtree_check)
	在进行挂载之前要确认之前挂载的已经被卸载了
	umount /挂载的目录
	执行该命令要在挂载目录的上层,即该目录之外进行,否则无法成功

/etc/init.d/nfs-kernel-server restart

  此外要确认主机与开发板在同一个网段下,若还未挂载成功,检查内核是否支持nfs挂载,
若不支持,去make menuconfig 开启nfs挂载,完成相应的设置。

烧录最终实验结果:

[   22.118483] Looking up port of RPC 100003/2 on 192.168.1.141
[   22.595854] eth0: link up, 100Mbps, full-duplex, lpa 0xCDE1
[   22.600072] ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[   23.134144] Looking up port of RPC 100005/1 on 192.168.1.141
[   23.160095] VFS: Mounted root (nfs filesystem) on device 0:12.
[   23.164502] Freeing init memory: 172K
can't run '/etc/init.d/rcS': No such file or directory
can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory
can't open /dev/tty2: No such file or directory

can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory
can't open /dev/tty2: No such file or directory

三、inittab详解

1、添加一个典型的inittab

(1)将我提供的典型的 inittab 文件复制到我们制作的rootfs的根目录下的/etc/目录下

#first:run the system script file
::sysinit:/etc/init.d/rcS	#系统启动最先执行
:: :-/bin/sh                #执行了该命令后,就有控制台了
::ctrlaltdel:-/sbin/reboot  #重启,但串口是无法输入组合键的,所以没用
 #umount all filesystem
::shutdown:/bin/umount -a -r       #关机之前,奖之前挂载的东西解挂载
#restart init process
::restart:/sbin/init    #操作系统重启的时候

(2)再次启动内核挂载这个rootfs看效果

(3)实验现象是成功启动并且挂载rootfs进入了控制台命令行。当前制作的最小rootfs成功了

2、inittab格式解析

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

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

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

(4)inittab的格式在busybox中定义的,网上可以搜索到详细的格式说明,具体去参考即可:

扩展必学资料:
https://www.cnblogs.com/CrazyCatJack/p/6184564.html
第一个:#开始的行是注释

第二个:冒号在里面是分隔符,分隔开各个部分。

第三个:inittab内容是以行为单位的,行与行之间没有关联,每行都是一个独立的配置项,
每一个配置项表示一个具体的含义。

第四个:每一行的配置项都是由3个冒号分隔开的4个配置值共同确定的。这四个配置值就是
id:runlevels:action:process。值得注意得是有些配置值可以空缺,空缺后冒号不能空
缺,所以有时候会看到连续2个冒号。

第五个:每一行的配置项中4个配置值中最重要的是action和process,action是一个
条件/状态,process是一个可被执行的程序的pathname。合起来的意思就是:当满足
action的条件时就会执行process这个程序。
脚本可以作为一个程序来用。

注意:理解inittab的关键就是明白“当满足action的条件时就会执行process这个程序。” 你去分析busybox的源代码就会发现,busybox最终会进入一个死循环,在这个死循环中去反复检查是否满足各个action的条件,如果某个action的条件满足就会去执行对应的process。inittab不是只在系统启动过程中起一次作用,而是在操作系统工作的过程中都起作用。init进程一不断检查action,满足那个条件就执行那个程序。

第六个:明白各个action什么意思

四、busybox源码分析

1、源码目录梳理、SourceInsight工程建立

2、整个程序入口确认

(1)分析一个程序,不管多庞大还是小,最好的路线都是按照程序运行时的逻辑顺序来。所以找到一个程序的入口至关重要。

(2)学C语言的时候都知道程序的主函数main函数就是整个程序的入口。这种情况适应于操作系统下工作的应用程序的情况。

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

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

3、busybox中main函数全解析

  busybox\busybox-1.32.0\libbb\appletlib.c

(1)busybox入口就是main函数,其中有很多个main但是只有一个起作用了,其他的是没起作用的。真正的busybox工作时的入口是libbb/appletlib.c中的main函数(因为文件被编译生成了一个.o文件,而其他含有main函数的文件没有,除此之外也有一些编译的,但最终并未被链接进来,因为不属于busybox源代码,而是kconfig体系工作的目录)

  busybox\busybox-1.32.0\scripts\kconfig\conf.c

(2)busubox中有很多xxx_main函数,这些main函数每一个都是busybox支持的一个命令的真正入口。譬如ls_main函数就是busybox当作ls函数使用时的入口程序。

(3)ls或者cd等命令其实都是busybox一个程序,但是实际执行时的效果却是各自的效果。busybox是如何实现一个程序化身万千还能各自工作的?答案就是main转xxx_main。也就是说busybox每次执行时都是先执行其main,在main函数中识别(靠main函数的传参argv[0]来识别)我们真正要执行的函数(譬如ls)然后去调用相应的xxx_main(譬如ls_main)来具体实现这个命令。

  我们在busybox命令行下执行pwd命令时实际执行的是pwd_main这个函数。

4、inittab解析与执行

(1)inittab的解析是在busybox/init/init.c/init_main函数中

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

5、busybox的体积优势原理

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

(2)busybox体系变小的原因主要有2个:第一个是busybox本身提供的shell命令是阉割版的(busybox中的命令支持的参数选项比发行版中要少,在busybox中只保留了几个常用的选项,不常用的都删除掉了);

  第二个是busybox中因为所有的命令的实现代码都在一个程序中实现(Ubuntu中是一个命令一个程序),而各个命令中有很多代码函数都是通用的(譬如ls和cd、mkdir等命令都会需要去操作目录,因此在busybox中实现目录操作的函数就可以被这些命令共用),共用会降低重复代码出现的次数,从而减少总的代码量和体积。

(3)经过分析,busybox的体积优势是嵌入式系统本身的要求和特点造成的。

五、rcS文件介绍

#!/bin/sh
#定义了几个变量
PATH=/sbin:/bin:/usr/sbin:/usr/bin

runlevel=S
prevlevel=N

umask 022

#导出环境变量
export PATH runlevel prevlevel

mount -a

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

/bin/hostname -F /etc/sysconfig/HOSTNAME

ifconfig eth0 192.168.1.10

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

1、PATH=xxx

(1)首先从shell脚本的语法角度分析,这一行定义了一个变量PATH,值等于后面的字符串

(2)后面用export导出了这个PATH,那么PATH就变成了一个环境变量。

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

在ubuntu16.04中:
root@ubuntu:/home/rootfs/etc/init.d# echo $PATH
/usr/local/arm/arm-2009q3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

其中部分路径是我们自己添加的,有的是rcS脚本导出的
export PATH=$PATH:new就可以叠加导出新的PATH

(4)rcS中为什么要先导出PATH?就是因为我们希望一旦进入命令行时,PATH环境变量中就有默认的。

  /bin /sbin /usr/bin /usr/sbin,这几个常见的可执行程序的路径,这样我们进入命令行后就可以ls、cd等直接使用了。若未导出路径,则使用这几个命令需要加上路径执行命令对应的程序。

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

/* Make sure environs is set to something sane */
putenv((char *) "HOME=/");//放置一些环境变量
putenv((char *) bb_PATH_root_path);
putenv((char *) "SHELL=/bin/sh");
putenv((char *) "USER=root"); /* needed? why? */

2、runlevel=S

linux 运行级别(runlevel)
运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动
运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆
运行级别2:多用户状态(没有NFS)
运行级别3:完全的多用户状态(有NFS),登陆后进入控制台命令行模式
运行级别4:系统未使用,保留
运行级别5:X11控制台,登陆后进入图形GUI模式
运行级别6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动
详解:https://www.cnblogs.com/yingsong/p/6012180.html

(1)runlevel也是一个shell变量,并且被导出为环境变量。

(2)runlevel这个环境变量到底有什么用?

(3)runlevel=S表示将系统设置为单用户模式

3、umask

(1)umask是linux的一个命令,作用是设置linux系统的umask值。

(2)umask值决定当前用户在创建文件时的默认权限。

4、mount -a

(1)mount命令是用来挂载文件系统的

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

# /etc/fstab: static file system information.
#
# Use 'vol_id --uuid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# 	 	 	 	 	 	
	proc 			/proc 			proc 	defaults 	0 		0
	sysfs 			/sys 			sysfs 	defaults 	0 		0
	tmpfs 			/var 			tmpfs 	defaults 	0 		0
	tmpfs 			/tmp 			tmpfs 	defaults 	0 		0
	tmpfs 			/dev 			tmpfs 	defaults 	0 		0

5、PATH&runlevel

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

(2)这个故事告诉我们:shell脚本文件如果格式不对,运行时可能会被提示文件不存在。

(3)扩展讲一个:有时候一个应用程序执行时也会提示文件不存在,问题可能是这个程序所调用的一个动态链接库找不到。

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

  Linux通过nfs挂载根文件系统报错:若出现:can‘t run ‘/etc/init.d/rcS‘: Permission denied表明权限不够,需要修改权限chmod -R 777 etc/

6、umask测试

(1)umask是022的时候,默认touch创建一个文件的权限是644

(2)umask是044的时候,默认touch创建一个文件的权限是622

(3)umask是444的时候,默认touch创建一个文件的权限是222

  总结:umask的规律就是:umask值和默认创建文件的权限值加起来是666.其只会影响其修改后创建的文件权限 在命令行输入umask,打印出来的信息有四位,第一位是s id

7、mount测试

(1)挂载时全部出错:

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

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

(3)解决方案就是自己在制作的rootfs根目录下创建这些挂载点目录即可。

(4)验证是否挂载成功,可以看挂载时输出信息;还可以启动后去看proc和sys文件夹,如果有文件出现则证明挂载成功了,如果没东西就证明失败了。(只有在开发板端才能看到有文件出现,而在ubuntu是无法看到的)

8、mdev

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

(2)因为这个问题涉及到驱动,因此详细讲解要等到驱动部分。这里我们只是通过一些直观的现象来初步理解udev/mdev的工作效果。

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

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

(4)/dev目录下的设备驱动文件就是mdev生成的,这就是mdev的效果和意义。

出现:cannot create /proc/sys/kernel/hotplug: nonexistent directory”错误
原因1:https://blog.csdn.net/dongliqiang2006/article/details/4504932
原因2:未创建/dev /proc /sys /var /tmp目录以及fstab文件(我的是这个)

创建生成的设备文件在Ubuntu查看显示没有,而在开发板端ls可以查看

9、hostname

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

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

  可根据/etc/sysconfig/HOSTNAME这个提示创建/etc/sysconfig目录下的HOSTNAME文件在其中输入主机名, $HOSTNAME查看设置结果。

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

六、profile文件和用户登录理论

# Ash profile
# vim: syntax=sh

# No core files by default
ulimit -S -c 0 > /dev/null 2>&1

USER="`id -un`"
LOGNAME=$USER
PS1='[\u@\h \W]\# '              #设置命令行提示符
PATH=$PATH

HOSTNAME=`/bin/hostname`

export USER LOGNAME PS1 PATH

PS1设置详解:https://blog.csdn.net/sinat_16181325/article/details/78660067

1、profile文件添加

(1)之前添加了/bin/hostname在/etc/sysconfig/HOSTNAME文件中定义了一个hostname(aston210),实际效果是:命令行下hostname命令查到的host名字确实是aston210。但是问题就是命令行的提示符是没有显示的。

(2)这个问题的解决就要靠profile文件。将上边提供的profile文件(自己复制内容创建一个)放入/etc/目录下即可。

(3)添加了之后的实验现象:命令行提示符前面显示:[@aston210 ]#
结论是:第一,profile文件起了作用,hostname显示出来了。第二,还有个问题,登录用户名没显示出来。原因就是我们直接进入了命令行而没有做登录。等我们添加了用户登录功能,并且成功登陆后这个问题就能解决。

(4)profile文件工作原理是:profile文件也是被busybox(init进程)自动调用的,所以是认名字的。

profile文件详解:
https://blog.csdn.net/weixin_42525096/article/details/109666648
https://www.jb51.net/LINUXjishu/409403.html

2、如何看到用户登录界面

(1)linux中有一个原则就是用一个小程序来完成一个功能。如果我们产品确实需要很复杂的综合型的功能,我们倾向于先使用很多个小程序完成其中的一个功能,然后再将这些小程序集成起来完成整个大功能的产品。

(2)这种集成很多个小程序来完成一个大的功能,有很多种技术实现。譬如shell脚本,还有一些别的技术,譬如linux启动中的inittab。

(3)因为我们之前intttab中有一个配置项 ::askfirst:-/bin/sh,这个配置项作用就是当系统启动后就去执行/bin/sh,执行这个就会出现命令行。因此我们这样的安排就会直接进入命令行而不会出现登录界面。

(4)我们要出现登录界面,就不能直接执行/bin/sh,而应该执行一个负责出现登录界面并且负责管理用户名和密码的一个程序,busybox中也集成了这个程序(就是/bin/login和/sbin/getty),因此我们要在inittab中用/bin/login或者/sbin/getty去替代/bin/sh

3、用户名和密码的设置

(1)用户名和密码的设置是和登录程序有关联的,但是/bin/login和/sbin/getty在用户名和密码的管理上是一样的。其实常见的所有的linux系统的用户名和密码的管理几乎都是一样的。

(2)密码一般都是用加密文字的,而不是用明文。意思就是系统中的密码肯定是在系统中的一个专门用来存密码的文件中存储的,用明文存密码有风险,因此linux系统都是用密文来存储密码的。

七、用户登录实战

1、添加/bin/login到sysinit

(1)在inittab中修改,去掉/bin/sh,换上/bin/login,则系统启动后出现登录界面。可以输入用户名和密码。

(2)实验现象:成功出现用户登录界面,但是输入的用户以及密码总是不对。

2、添加passwd和shadow文件

Linux下/etc/passwd和/etc/shadow文件详解:https://blog.csdn.net/u012501054/article/details/71522278

(1)为什么用户名和密码不对?因为我们根本没有为root用户设置密码。

(2)linux系统中用来描述用户名和密码的文件是passwd和shadow文件,这两个文件都在etc目录下。passwd文件中存储的是用户的密码设置,shadow文件中存储的是加密后的密码。

(3)我们直接复制ubuntu系统中的/etc/passwd和/etc/shadow文件到当前制作的rootfs目录下,然后再做修改即可。

1、删除掉passwd文件中其他无用的,只保留下root相关的。
root:x:0:0:root:/root:/bin/bash 每一行通过[:]分为七个部分。

根据这句/root,还需再在根文件目录下创建root文件夹,即账号的家目录,家目录就是你登陆
系统后默认的那个目录,登陆后自动跳转到这个目录下,还需将bash改成sh,busybox是不支
持bash的,支持sh
root:x:0:0:root:/root:/bin/sh

2、修改shadow文件,只保留下root相关的
root:$6$idBVcLme$HyHM/Sq8fRmIQugIUm0h8Z0sVFA6q0.kjKW8ZQqcfDL7GG8MWLlnS0O9NI8TUAJejkRkqGYSUinRHZ5r.Il6U1:18331:0:99999:7:::

(4)/etc/passwd和/etc/shadow修理好后,shadow中默认有一个加密的密码口令,这个口令和你拷贝的shadow本身有关,像我的ubuntu中root用户的密码就是root,因此复制过来后登陆时的密码还是root。

3、重置密码实践

(1)ubuntu刚装好的时候默认登录是用普通用户登录的,默认root用户是关闭的。普通用户的密码是在装系统的时候设置的,普通用户登陆后可以使用su passwd root给root用户设置密码,设置了密码后root用户才可以登录。

(2)其实这个原因就是root用户在/etc/shadow文件中加密口令是空白的。所以是不能登录的。

(3)busybox中因为没有普通用户,所以做法是:默认root用户如果加密口令是空的则默认无密码直接登录。等我们登陆了之后还是可以用passwd root给root用户设置密码。passwd root进行设置,开头不需要su,su是进行权限管控的

(4)平时有时候我们忘记了自己的操作系统的密码,怎么办?有一种解决方法就是用其他系统(WindowsPE系统或者ubuntu的单用户模式等···)来引导启动,启动后挂载到我们的硬盘上,然后找到/etc/shadow文件,去掉密文密码后保存。然后再重启系统后密码就没了。

4、getty实战

(1)大家后面做项目会发现,inittab中最常见的用于登录的程序不是/bin/login,反而是/sbin/getty。

(2)这两个的差别不详,但是在busybox中这两个是一样的。这两个其实都是busybox的符号链接而已。因此不用严格区分这两个

(3)我们可以在inittab中用getty替换login程序来实现同样的效果。

八、动态链接库的拷贝

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

一个程序使用gcc在Ubuntu中编译,在开发板中执行:则会报错
[root@zhangzheng ~]# ./hello.o
./hello.o: line 1: syntax error: unexpected "(
架构和平台不同导致的错误。不能使用gcc编译器

(1)任务:自己写一个helloworld程序,然后交叉编译连接,然后丢到开发板根文件系统中,开机后去运行。

(2)C程序如果使用gcc来编译则可以在主机ubuntu中运行,但是不能在开发板运行;要在开发板运行需要用arm-linux-gcc来交叉编译,但是这时候就不能在主机ubuntu中运行了。我们可以用file xx命令来查看一个elf可执行程序是哪个架构的。

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

(4)实验结果:静态编译连接后生成的hello_satic已经可以成功运行。

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

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

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

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

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

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

(1)我用的arm-2009q3这个交叉编译工具链的动态链接库在/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib目录下。其他的一些交叉编译工具链中动态链接库的目录不一定在这里,要去找一下。找的方法就是find(查找一个每个动态链接库必有的文件,比如libm.so.6)

(2)复制动态链接库到roots/lib目录下。复制时要注意参数用-rdf,主要目的就是符号链接复制过来还是符号链接。否则会将符号链接对应的实体文件给复制过来,使得文件较大

复制命令:cp lib/*so* /root/porting_x210/rootfs/rootfs/lib/ -rdf

(3)现在再去测试./hello_dynamic看看是否可以运行,实验结果是可以运行。有了动态链接库可以解析了。

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

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

去掉符号命令:arm-linux-strip *so*
在我的实际操作后发现库文件由3.8M变成了3.0M,节省了0.8M的空间。

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

1、修改rcS实现开机自启动

(1)开机自启动指的是让一些应用程序能够开机后自动执行。开机装载一些模块驱动,自动执行,也是修改rcS文件。

(2)开机自启动的实现原理就是在开机会自动执行的脚本rcS中添加上执行某个程序的语句代码即可。但要确保能找到该程序,解决方法有两种,一个是导出环境变量PATH,一个是进入到该程序所在目录下执行。

  实验结果:rcS文件的执行在登陆之前。故还没登陆就已经执行了该程序。因为在inittab中可知,我们先执行了rcS,然后再去执行登陆程序的。

2、前台运行与后台运行

(1)程序运行时占用了当前的控制台,因此这个程序不结束我们都无法使用控制台,这就叫前台运行。默认执行程序就是前台运行的。如果程序中有一个while(1)死循环,则其无法在执行其他程序。可通过ctrl+c结束运行
usleep:系统的延时函数可调用,毫秒级

(2)后台运行就是让这个程序运行,并且同时让出控制台。这时候运行的程序还能照常运行而且还能够不影响当前控制台的使用。

(3)让一个程序后台运行的方法就是 ./可执行程序名 &

3、实际开发中rootfs的inittab、rcS是怎样的

九鼎的inittab文件:

# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen 
#
# Note: BusyBox init doesn't support runlevels.  The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: :::
#
# id        == tty to run on, or empty for /dev/console
# runlevels == ignored
# action    == one of sysinit, respawn, askfirst, wait, and once
# process   == program to run

# Startup the system
null::sysinit:/bin/mount -t proc proc /proc
null::sysinit:/bin/mount -o remount,rw / # REMOUNT_ROOTFS_RW
null::sysinit:/bin/mkdir -p /dev/pts
null::sysinit:/bin/mkdir -p /dev/shm
null::sysinit:/bin/mount -a
null::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS

# Put a getty on the serial port
ttySAC2::respawn:/sbin/getty -L ttySAC2 115200 vt100 # GENERIC_SERIAL
# Stuff to do for the 3-finger salute
::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
null::shutdown:/etc/init.d/rcK
九鼎的init.d目录:rcK  rcS  S01logging(系统日志相关的)
S10mdev  S20urandom  S40network(网络相关的)  S50sshd(远程登陆服务相关的)
S99qttest

rcS文件

#!/bin/sh

# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do
     # Ignore dangling symlinks (if any).不执行符号链接,执行普通文件
     [ ! -f "$i" ] && continue

     case "$i" in
        *.sh)
            # Source shell script for speed.
            (
                trap - INT QUIT TSTP
                set start
                . $i
            )
            ;;
        *)
            # No sh extension, so fork subprocess.
            $i start
            ;;
    esac
done

rcK文件

#!/bin/sh

# Stop all init scripts in /etc/init.d
# executing them in reversed numerical order.
#
for i in $(ls -r /etc/init.d/S??*) ;do

     # Ignore dangling symlinks (if any).
     [ ! -f "$i" ] && continue

     case "$i" in
        *.sh)
            # Source shell script for speed.
            (
                trap - INT QUIT TSTP
                set stop
                . $i
            )
            ;;
        *)
            # No sh extension, so fork subprocess.
            $i stop
            ;;
    esac
done

(1)我们以九鼎科技X210开发板做的rootfs中rcS部分来分析

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

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

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

十、制作ext2格式的镜像并烧录启动

1、确定文件夹格式的rootfs可用

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

2、动手制作ext2格式的镜像

(1)dd if=/dev/zero of=rootfs.ext2 bs=1024 count=10240 
   块的大小,块的数量 创建空的镜像文件,这条命令的参数要注意,确保可以放的下根文件
   系统(rootfs文件夹)

	losetup  /dev/loop1 rootfs.ext2  将镜像文件虚拟成块设备

	mke2fs -m 0 /dev/loop1 10240  专门管理ext系列的文件的命令,明确指定预留
给超级管理员用的块数百分比 

	mount -t ext2 /dev/loop1 ./ext2_rootfs/ (挂载块设备,挂载点ext2_rootfs)
		mount可将指定设备中指定的文件系统加载到Linux目录下(也就是装载点)。

(2)进入ext2_rootfs文件夹,向./rootfs中复制内容,用cp ../rootfs/* ./ -rf,
将根文件系统放到镜像文件中

(3)	umount /dev/loop1   解除挂载
	losetup -d /dev/loop1   卸除设备,制作完根文件系统后卸载
	
(4)完成后得到的rootfs.ext2就是我们做好的rootfs镜像。拿去烧录即可。

3、烧录镜像并设置合适的bootargs

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

fastboot flash system rootfs.ext2
烧录完成后重启系统

(2)设置bootargs为:

set bootargs console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext2

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

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

你可能感兴趣的:(初窥uboot与Linux内核,嵌入式Linux小项目,linux,arm开发,驱动开发,BSP开发,嵌入式)