文件分析——分析/linuxrc文件(即分析busybox的源码)

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

参考博客

(1)init进程的详解_天糊土的博客-CSDN博客_init进程

(2)文件分析——根文件系统的目录结构

(3)busybox详解_linuxarmsummary的博客-CSDN博客

前言

uboot给内核传参的bootargs中有“init=/linuxrc”这个项目。

由参考博客(2)可知 /linuxrc 这个二进制文件位于根文件系统中。

由参考博客(1)可知它是一个软连接,指向了根文件系统中的/bin/busybox这个命令。

接下来先总结性地介绍 /linuxrc 这个文件,然后再分析busybox的源码。

一、/linuxrc文件简介

1.1 /linuxrc文件的本质

(1)/linuxrc是一个应用层的程序,和内核没有关系。

(2)/linuxrc在开发板当前内核系统下是可执行的。如果在ARM SoC的linux系统上运行,那么这个程序就是用arm-linux-gcc编译链接的;如果是在PC机linux系统下运行,那么这个程序就是用gcc编译连接的。

(3)/linuxrc如果是静态编译连接的,则直接可以运行;如果是动态编译连接的,必须提供必要的库文件才能运行。但实际上/linuxrc由内核直接调用执行,因此用户没有机会去导出库文件的路径,因此/linuxrc没法动态连接,一般都是静态连接的。

1.2 /linuxrc文件的作用

(1)执行时引出用户界面。/linuxrc应用程序,也就是我们认识中的进程1,是其他所有应用程序进程的祖宗进程。比如用户操作界面由/linuxrc引出,应用程序是直接或者间接地被/linuxrc调用执行的。用户界面程序、其他的应用程序就是进程2、3、4等等。

(2)负责系统启动后的配置。为了让操作系统用起来更方便,更具实用性,操作系统启动后还需要进行配置(一般叫做运行时配置,英文简写是etc),/linuxrc负责系统启动后的配置。

1.3 /linuxrc文件的种类

在嵌入式linux中,/linuxrc文件一般就是busybox(执行busybox),下面是它的简单介绍

(1)busybox是一个C语言写出来的项目,里面包含了很多.c文件和.h文件。此项目可以被配置编译成各个平台下面可以运行的应用程序。如果用arm-linux-gcc来编译busybox就会得到一个可以在我们开发板linux内核上运行的应用程序。

(2)busybox是为了在嵌入式环境下构建rootfs而开发的,换言之,它是为了构建根文件系统而专门开发的init进程应用程序。比如海思SDK提供的文件夹形式的根文件系统,其目录中的/linuxrc文件,它里面只有一行代码“bin/busybox”。

(3)busybox同时也为当前系统提供了一整套的shell命令程序集,比如vi、cd、ls等。在linux发行版(比如ubuntu、redhat、centOS等)中,vi、cd、ls等命令都是一个个单独的应用程序,但是在嵌入式linux中,为了省事,把vi、cd等所有常用的shell命令集合到一起,构成一个shell命令包,起名叫busybox。如下所示,嵌入式linux中的shell命令都是busybox的软连接。

xjh@ubuntu:~/iot/embedded_basic/rootfs/rootfs_xjh/bin$ ls
ash      chown     dmesg          fdflush   hush      ln        mktemp      nice           reformime     setarch    touch
base64   conspy    dnsdomainname  fgrep     ionice    login     more        pidof          rev           setserial  true
busybox  cp        dumpkmap       fsync     iostat    ls        mount       ping           rm            sh         umount
cat      cpio      echo           getopt    ipcalc    lsattr    mountpoint  ping6          rmdir         sleep      uname
catv     cttyhack  ed             grep      kbd_mode  lzop      mpstat      pipe_progress  rpm           stat       usleep
chattr   date      egrep          gunzip    kill      makemime  mt          printenv       run-parts     stty       vi
chgrp    dd        false          gzip      linux32   mkdir     mv          ps             scriptreplay  su         watch
chmod    df        fatattr        hostname  linux64   mknod     netstat     pwd            sed           tar        zcat
xjh@ubuntu:~/iot/embedded_basic/rootfs/rootfs_xjh/bin$ ls -l
total 600
lrwxrwxrwx 1 root root      7 十一  9 16:32 ash -> busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 base64 -> busybox
-rwxr-xr-x 1 root root 613584 十二  6 00:03 busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 cat -> busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 catv -> busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 chattr -> busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 chgrp -> busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 chmod -> busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 chown -> busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 conspy -> busybox
lrwxrwxrwx 1 root root      7 十一  9 16:32 cp -> busybox
###忽略部分输出
xjh@ubuntu:~/iot/embedded_basic/rootfs/rootfs_xjh/bin$ 

(4)集成后busybox程序的大小比这些常用的命令的大小加起来要小很多。这是因为busybox本身提供的shell命令是阉割版的。busybox中的命令支持的参数选项比发行版中要少,比如ls在发行版中可以有几十个参数选项,但是在busybox中只保留了几个常用的选项,不常用的都删除掉了。另外busybox中所有命令的实现代码都在一个程序中实现,而各个命令中有很多代码函数都是通用的,通用会降低重复代码出现的次数,从而减少总的代码量和体积。比如ls、cd、mkdir等命令都会操作目录,因此在busybox中实现目录操作的函数就可以被这些命令通用。busybox的体积优势是嵌入式系统本身的要求和特点造成的。

xjh@ubuntu:~/iot/embedded_basic/rootfs/rootfs_xjh$ du -h ./bin/ #嵌入式linux的
604K	./bin/
xjh@ubuntu:~/iot/embedded_basic/rootfs/rootfs_xjh$ du -h /bin  #ubuntu的
9.3M	/bin
xjh@ubuntu:~/iot/embedded_basic/rootfs/rootfs_xjh$ du -h /sbin/
13M	    /sbin/
xjh@ubuntu:~/iot/embedded_basic/rootfs/rootfs_xjh$ du -h /usr/sbin/
15M	    /usr/sbin/

二、busybox源码分析

上面提到,/linuxrc在嵌入式linux中一般就是busybox,因此接下来我们分析busybox的源码。 

2.1 源码目录结构

busybox的源码目录如下:

xjh@ubuntu:~/iot/embedded_basic/rootfs/busybox/busybox-1.24.1$ ls
applets     configs        editors    libbb       Makefile.custom  printutils              selinux       util-linux
applets_sh  console-tools  examples   libpwdgrp   Makefile.flags   procps                  shell
arch        coreutils      findutils  LICENSE     Makefile.help    qemu_multiarch_testing  sysklogd
archival    debianutils    include    loginutils  miscutils        README                  testsuite
AUTHORS     docs           init       mailutils   modutils         runit                   TODO
Config.in   e2fsprogs      INSTALL    Makefile    networking       scripts                 TODO_unicode
xjh@ubuntu:~/iot/embedded_basic/rootfs/busybox/busybox-1.24.1$

我们进入coreutils目录,发现该目录下的文件都是一些shell命令的实现:

xjh@ubuntu:~/iot/embedded_basic/rootfs/busybox/busybox-1.24.1$ cd coreutils/
xjh@ubuntu:~/iot/embedded_basic/rootfs/busybox/busybox-1.24.1/coreutils$ ls
basename.c  comm.c      du.c      hostid.c           ls.c            od.c        shuf.c   tail.c           uname.c     yes.c
cal.c       Config.src  echo.c    id.c               md5_sha1_sum.c  printenv.c  sleep.c  tee.c            uniq.c
cat.c       cp.c        env.c     id_test.sh         mkdir.c         printf.c    sort.c   test.c           unlink.c
catv.c      cut.c       expand.c  install.c          mkfifo.c        pwd.c       split.c  test_ptr_hack.c  usleep.c
chgrp.c     date.c      expr.c    Kbuild.src         mknod.c         readlink.c  stat.c   touch.c          uudecode.c
chmod.c     dd.c        false.c   length.c.disabled  mv.c            realpath.c  stty.c   tr.c             uuencode.c
chown.c     df.c        fold.c    libcoreutils       nice.c          rm.c        sum.c    true.c           wc.c
chroot.c    dirname.c   fsync.c   ln.c               nohup.c         rmdir.c     sync.c   truncate.c       whoami.c
cksum.c     dos2unix.c  head.c    logname.c          od_bloaty.c     seq.c       tac.c    tty.c            who.c
xjh@ubuntu:~/iot/embedded_basic/rootfs/busybox/busybox-1.24.1/coreutils$

2.2 源码的入口

我们对程序进行分析时,需要按照程序运行时的逻辑顺序来分析,因此要找到入口地址。

在uboot和kernel这两个大的C语言项目中,入口地址由链接脚本指定。而操作系统下的应用程序,它们的入口地址一般是main函数。busybox是linux启动后运行的第一个应用程序,因此其中必然有main函数,而且main就是入口地址。

对busybox源码建立SI工程,搜索main符号时,发现它出现在不同文件中(条件编译的缘故),哪个文件的main函数才是入口地址?我们可以在编译之后再查看这些文件是否有对应的.o文件,进而得知该文件是否被编译。如何编译busybox源码,见博客从零开始构建根文件系统

我们发现这些包含main函数的文件中,只有/busybox-1.24.1/libbb/appletlib.c这个文件被编译(严谨地说,这些文件中也有其他几个文件被编译生成.o文件,但是它们对busybox这体系只是起到辅助作用,不会被链接。具体过程可以研究busybox的配置与编译体系)。因此busybox的入口地址就是/busybox-1.24.1/libbb/appletlib.c的main函数。

#if ENABLE_BUILD_LIBBUSYBOX
int lbb_main(char **argv)
#else
int main(int argc UNUSED_PARAM, char **argv)
#endif
{
	/* Tweak malloc for reduced memory consumption */
#ifdef M_TRIM_THRESHOLD
	/* M_TRIM_THRESHOLD is the maximum amount of freed top-most memory
	 * to keep before releasing to the OS
	 * Default is way too big: 256k
	 */
	mallopt(M_TRIM_THRESHOLD, 8 * 1024);
#endif
#ifdef M_MMAP_THRESHOLD
	/* M_MMAP_THRESHOLD is the request size threshold for using mmap()
	 * Default is too big: 256k
	 */
	mallopt(M_MMAP_THRESHOLD, 32 * 1024 - 256);
#endif

#if !BB_MMU
	/* NOMMU re-exec trick sets high-order bit in first byte of name */
	if (argv[0][0] & 0x80) {
		re_execed = 1;
		argv[0][0] &= 0x7f;
	}
#endif

#if defined(SINGLE_APPLET_MAIN)
	/* Only one applet is selected in .config */
	if (argv[1] && is_prefixed_with(argv[0], "busybox")) {
		/* "busybox <applet> <params>" should still work as expected */
		argv++;
	}
	/* applet_names in this case is just "applet\0\0" */
	lbb_prepare(applet_names IF_FEATURE_INDIVIDUAL(, argv));
	return SINGLE_APPLET_MAIN(argc, argv);
#else
	lbb_prepare("busybox" IF_FEATURE_INDIVIDUAL(, argv));

	applet_name = argv[0];   //在命令行输入命令时,该命令存储在argv[0]中
	if (applet_name[0] == '-')
		applet_name++;
	applet_name = bb_basename(applet_name);

	parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */

	run_applet_and_exit(applet_name, argv);//寻找输入的命令所对应的函数并执行

	/*bb_error_msg_and_die("applet not found"); - sucks in printf */
	full_write2_str(applet_name);
	full_write2_str(": applet not found\n");
	/* POSIX: "If a command is not found, the exit status shall be 127" */
	exit(127);
#endif
}

2.3 xxx_main函数

我们注意到,busubox中有很多 xxx_main 函数,它们是busybox所支持的xxx命令的真正入口。

当执行xxx命令时,busybox内部都是先执行appletlib.c中的main函数,然后通过参数argv[0]判别要执行xxx函数,接着调用xxx_main函数来具体实现这个命令。这过程见上面第2点的代码注释。

比如执行pwd命令时,先执行/busybox-1.24.1/libbb/appletlib.c的main函数,然后调用pwd_main函数来完成pwd的命令。pwd_main函数位于busybox-1.24.1\coreutils\pwd.c文件中。

int pwd_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
	char *buf;

	if (ENABLE_DESKTOP) {
		/* TODO: assume -L if $POSIXLY_CORRECT? (coreutils does that)
		 * Rationale:
		 * POSIX requires a default of -L, but most scripts expect -P
		 */
		unsigned opt = getopt32(argv, "LP");
		if ((opt & 1) && logical_getcwd())
			return fflush_all();
	}

	buf = xrealloc_getcwd_or_warn(NULL);//buf中的内容是pwd命令的结果

	if (buf) {
		puts(buf);//输出pwd命令的结果到屏幕
		free(buf);
		return fflush_all();
	}

	return EXIT_FAILURE;
}

2.4 对inittab文件进行解析与执行

busybox-1.24.1/init/init.c中的init_main函数,负责对根文件系统的/etc/inittab文件进行解析与执行。

(1)init_main函数首先调用parse_inittab函数对inittab文件进行解析,这里只是将inittab文件中的各个action和process解析出来并放在链表中,并没有执行。

(2)init_main函数的后面部分,先执行sysinit、wait和once(注意这里只执行一遍),接着在while(1)死循环中去执行respwan和askfirst。

int init_main(int argc UNUSED_PARAM, char **argv)
{
	if (argv[1] && strcmp(argv[1], "-q") == 0) {
		return kill(1, SIGHUP);
	}

#if DEBUG_SEGV_HANDLER     //设置信号处理函数?
	{
		struct sigaction sa;
		memset(&sa, 0, sizeof(sa));
		sa.sa_sigaction = handle_sigsegv;
		sa.sa_flags = SA_SIGINFO;
		sigaction(SIGSEGV, &sa, NULL);
		sigaction(SIGILL, &sa, NULL);
		sigaction(SIGFPE, &sa, NULL);
		sigaction(SIGBUS, &sa, NULL);
	}
#endif

####忽略部分代码

/* Figure out where the default console should be */  //初始化控制台
	console_init();
	set_sane_term();
	xchdir("/");
	setsid();

####忽略部分代码
	/* Check if we are supposed to be in single user mode */
	if (argv[1]
	 && (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
	) {
		/* ??? shouldn't we set RUNLEVEL="b" here? */
		/* Start a shell on console */
		new_init_action(RESPAWN, bb_default_login_shell, "");
	} else {
		/* Not in single user mode - see what inittab says */

		/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
		 * then parse_inittab() simply adds in some default
		 * actions (i.e., INIT_SCRIPT and a pair
		 * of "askfirst" shells) */
		parse_inittab();//解析inittab文件
	}

####忽略部分代码
/* Now run everything that needs to be run */  //执行各活动
	/* First run the sysinit command */
	run_actions(SYSINIT);
	check_delayed_sigs();
	/* Next run anything that wants to block */
	run_actions(WAIT);
	check_delayed_sigs();
	/* Next run anything to be run only once */
	run_actions(ONCE);

	/* Now run the looping stuff for the rest of forever.
	 */
	while (1) {
		int maybe_WNOHANG;

		maybe_WNOHANG = check_delayed_sigs();

		/* (Re)run the respawn/askfirst stuff */
		run_actions(RESPAWN | ASKFIRST);
		maybe_WNOHANG |= check_delayed_sigs();

		/* Don't consume all CPU time - sleep a bit */
		sleep(1);
		maybe_WNOHANG |= check_delayed_sigs();

		/* Wait for any child process(es) to exit.
		 *
		 * If check_delayed_sigs above reported that a signal
		 * was caught, wait will be nonblocking. This ensures
		 * that if SIGHUP has reloaded inittab, respawn and askfirst
		 * actions will not be delayed until next child death.
		 */
		if (maybe_WNOHANG)
			maybe_WNOHANG = WNOHANG;
		while (1) {
			pid_t wpid;
			struct init_action *a;

			/* If signals happen _in_ the wait, they interrupt it,
			 * bb_signals_recursive_norestart set them up that way
			 */
			wpid = waitpid(-1, NULL, maybe_WNOHANG);
			if (wpid <= 0)
				break;

			a = mark_terminated(wpid);
			if (a) {
				message(L_LOG, "process '%s' (pid %d) exited. "
						"Scheduling for restart.",
						a->command, wpid);
			}
			/* See if anyone else is waiting to be reaped */
			maybe_WNOHANG = WNOHANG;
		}
	} /* while (1) */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值