根文件系统--代码简单分析

本文内容:

1- 内核启动最后阶段调用的第一个应用程序接口

2- 制作最小根文件系统的内容

1- 终端设备: /dev/console, /dev/null
2- init程序,这里指busybox程序
3- 配置文件,这里指/etc/inittab 文件
4- 配置文件指定的应用程序,例如ls, cp, init等
5- C库文件--busybox会用到printf, 需要由c库等其他库文件

3- 根文件系统中启动应用程序配置/etc/inittab

1- 内核接口分析init_post

uboot启动内核,内核启动应用程序,应用程序存在于根文件系统中。

1.1- init_post()

内核启动应用程序的接口为init_post().

该函数打开输出终端,并且启动应用程序run_init_process(execute_command);

其中ramdisk_execute_command是uboot传给内核的,再uboot阶段输入print可以看到该值为:/linuxrc

对于内核启动的应用程序是死循环不会返回,如果应用程序出错则依次执行/sbin/init; /etc/init;/bin/init;/bin/sh。

如果这几个都失败则打印异常“Failed to execute %s.  Attempting  defaults...”

static int noinline init_post(void)
{
	
    /*1- 打开终端输出,并复制标注输出,标准错误输出 */ 
	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) 
		printk(KERN_WARNING "Warning: unable to open an initial console.\n");

	(void) sys_dup(0);
	(void) sys_dup(0);

	if (ramdisk_execute_command) {
		run_init_process(ramdisk_execute_command);
		printk(KERN_WARNING "Failed to execute %s\n",
				ramdisk_execute_command);
	}
    
    
    /*2-  run_init_process启动命令行传入的程序或者/sbin/init */
	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {   run_init_process为死循环
		run_init_process(execute_command);
		printk(KERN_WARNING "Failed to execute %s.  Attempting "
					"defaults...\n", execute_command);
	}
	run_init_process("/sbin/init");
	run_init_process("/etc/init");
	run_init_process("/bin/init");
	run_init_process("/bin/sh");

	panic("No init found.  Try passing init= option to kernel.");
}



/* 3- execute_command 值为init=xxx */
static int __init init_setup(char *str)
{
	unsigned int i;

	execute_command = str;
	/*
	 * In case LILO is going to boot us with default command line,
	 * it prepends "auto" before the whole cmdline which makes
	 * the shell think it should execute a script with such name.
	 * So we ignore all arguments entered _before_ init=... [MJ]
	 */
	for (i = 1; i < MAX_INIT_ARGS; i++)
		argv_init[i] = NULL;
	return 1;
}
__setup("init=", init_setup);

查看uboot传递的内核参数,其中传入的应用程序名字为/linuxrc。

OpenJTAG> print
bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200
bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
bootdelay=2
baudrate=115200
ethaddr=08:00:3e:26:0a:5b
netmask=255.255.255.0
mtdids=nand0=nandflash0
mtdparts=mtdparts=nandflash0:256k@0(bootloader),128k(params),2m(kernel),-(root)
ipaddr=192.168.31.16
serverip=192.168.31.15
stdin=serial
stdout=serial
stderr=serial
partition=nand0,0
mtddevnum=0
mtddevname=bootloader

Environment size: 452/131068 bytes

1.2- 擦除、烧录文件系统

1.2.1 删除nand flash上的文件系统

开发板重新上电,上电过程中进入uboot ,输入擦除文件系统命令:nand erase root

 操作后,开发板重新上电,发现开发板无法进入到linux系统,打印如下:

和init_post()函数中的异常打印一致。

1.2.2 重新烧写文件系统

重新给开发板烧写跟文件系统: fs_mini.yaffs2

烧写完成再uboot中输入b重启系统,发现linux系统正常启动。

烧写yaffs文件系统

重启系统

系统启动成功后,可以发现有init程序和shell程序

2- busybox源码分析

系统起来后,我们可以使用ls, cp等命令,这些命令都指向busybox,甚至/sbin/init程序也指向busybox,即这些程序都是由busybox实现的。

源码中可以搜索到ls.c, cp.c, init.c等文件,这些文件用于实现ls,cp,/sbin/init等命令。

busybox源码路径:\002_JZ2440资料光盘_20200423(免费)\资料光盘\B盘\uboot+kernel+filesystem的镜像源码及补丁\busybox\busybox-1.7.0_patched.tar.bz2

2.1- init程序分析

busybox源码init_main()是init程序的实现。

主要做3件事:设置信号量程序;读取、解析配置文件/etc/inittab;运行配置文件中应用程序

对于信号量程序不解读,下面重点解读下如何读取配置文件/etc/inittab并执行程序。

int init_main(int argc, char **argv)
{
	/*1- 设置信号量程序 */
	/* Set up sig handlers  -- be sure to
	 * clear all of these in run() */
	signal(SIGHUP, exec_signal);
	signal(SIGQUIT, exec_signal);
	signal(SIGUSR1, shutdown_signal);
	signal(SIGUSR2, shutdown_signal);
	signal(SIGINT, ctrlaltdel_signal);
	signal(SIGTERM, shutdown_signal);
	signal(SIGCONT, cont_handler);
	signal(SIGSTOP, stop_handler);
	signal(SIGTSTP, stop_handler);

    
    /*2- parse_inittab()读取、解析配置文件/etc/inittab */
	/* Check if we are supposed to be in single user mode */
	if (argc > 1
	 && (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))
	) {
		/* 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., runs INIT_SCRIPT and then starts a pair
		 * of "askfirst" shells */
		parse_inittab();
	}


    /* 3- 运行配置文件中应用程序 */
	/* Now run everything that needs to be run */
	/* First run the sysinit command */
	run_actions(SYSINIT);

	/* Next run anything that wants to block */
	run_actions(WAIT);

	/* Next run anything to be run only once */
	run_actions(ONCE);


	/* Now run the looping stuff for the rest of forever */
	while (1) {
		/* run the respawn stuff */
		run_actions(RESPAWN);

		/* run the askfirst stuff */
		run_actions(ASKFIRST);

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

		/* Wait for a child process to exit */
		wpid = wait(NULL);
		while (wpid > 0) {
			/* Find out who died and clean up their corpse */
			for (a = init_action_list; a; a = a->next) {
				if (a->pid == wpid) {
					/* Set the pid to 0 so that the process gets
					 * restarted by run_actions() */
					a->pid = 0;
					message(L_LOG, "process '%s' (pid %d) exited. "
							"Scheduling it for restart.",
							a->command, wpid);
				}
			}
			/* see if anyone else is waiting to be reaped */
			wpid = waitpid(-1, NULL, WNOHANG);
		}
	}
}

2.2- 配置文件分析

如果配置文件/etc/inittab不存在,则添加默认的应用程序。

如果配置文件存在,则读取配置文件,并添加配置文件中的应用程序。


/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
 * then parse_inittab() simply adds in some default
 * actions(i.e., runs INIT_SCRIPT and then starts a pair
 * of "askfirst" shells).  If CONFIG_FEATURE_USE_INITTAB
 * _is_ defined, but /etc/inittab is missing, this
 * results in the same set of default behaviors.
 */
static void parse_inittab(void)
{
    /*1- 读取配置文件 /etc/inittab */
	file = fopen(INITTAB, "r");
    
    /*1.1- 如果没有配置文件,则执行默认程序 */
	if (file == NULL) {
		/* No inittab file -- set up some default behavior */

		/* Reboot on Ctrl-Alt-Del */
		new_init_action(CTRLALTDEL, "reboot", "");
		/* Umount all filesystems on halt/reboot */
		new_init_action(SHUTDOWN, "umount -a -r", "");

		/* Prepare to restart init when a HUP is received */
		new_init_action(RESTART, "init", "");
		/* Askfirst shell on tty1-4 */
		new_init_action(ASKFIRST, bb_default_login_shell, "");
		new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
		new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
		new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
		/* sysinit */
		new_init_action(SYSINIT, INIT_SCRIPT, "");

		return;
	}
    
    
    /* 2- 如果存在配置文件则解析配置文件 */
	while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) {
		/* Skip leading spaces */
		for (id = buf; *id == ' ' || *id == '\t'; id++);

		/* Skip the line if it's a comment */
		if (*id == '#' || *id == '\n')
			continue;

		/* Trim the trailing \n */
		//XXX: chomp() ?
		eol = strrchr(id, '\n');
		if (eol != NULL)
			*eol = '\0';

		/* Keep a copy around for posterity's sake (and error msgs) */
		strcpy(lineAsRead, buf);

		/* Separate the ID field from the runlevels */
		runlev = strchr(id, ':');
		if (runlev == NULL || *(runlev + 1) == '\0') {
			message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
			continue;
		} else {
			*runlev = '\0';
			++runlev;
		}

		/* Separate the runlevels from the action */
		action = strchr(runlev, ':');
		if (action == NULL || *(action + 1) == '\0') {
			message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
			continue;
		} else {
			*action = '\0';
			++action;
		}

		/* Separate the action from the command */
		command = strchr(action, ':');
		if (command == NULL || *(command + 1) == '\0') {
			message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
			continue;
		} else {
			*command = '\0';
			++command;
		}

		/* Ok, now process it */
		for (a = actions; a->name != 0; a++) {
			if (strcmp(a->name, action) == 0) {
				if (*id != '\0') {
					if (strncmp(id, "/dev/", 5) == 0)
						id += 5;
					strcpy(tmpConsole, "/dev/");
					safe_strncpy(tmpConsole + 5, id,
						sizeof(tmpConsole) - 5);
					id = tmpConsole;
				}
				new_init_action(a->action, command, id);
				break;
			}
		}
		if (a->name == 0) {
			/* Choke on an unknown action */
			message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
		}
	}
	fclose(file);

}

配置文件格式:

在busybox源码路径:\busybox-1.7.0\examples\inittab,可以找到配置文件的示例。

示例中说,配置文件格式为:<id>:<runlevels>:<action>:<process>

id:表示输出,可以为tty2
runlevels:运行等级,可以忽略
action:程序运行时机
process:程序入口
文档中给出示例程序: tty2::askfirst:/bin/sh


详细说明见文档,摘抄部分如下:

# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# <id>: WARNING: This field has a non-traditional meaning for BusyBox init!
#
#	The id field is used by BusyBox init to specify the controlling tty for
#	the specified process to run on.  The contents of this field are
#	appended to "/dev/" and used as-is.  There is no need for this field to
#	be unique, although if it isn't you may have strange results.  If this
#	field is left blank, it is completely ignored.  Also note that if
#	BusyBox detects that a serial console is in use, then all entries
#	containing non-empty id fields will be ignored.  BusyBox init does
#	nothing with utmp.  We don't need no stinkin' utmp.
#
# <runlevels>: The runlevels field is completely ignored.
#
# <action>: Valid actions include: sysinit, respawn, askfirst, wait, once,
#                                  restart, ctrlaltdel, and shutdown.
#
#       Note: askfirst acts just like respawn, but before running the specified
#       process it displays the line "Please press Enter to activate this
#       console." and then waits for the user to press enter before starting
#       the specified process.
#
#       Note: unrecognised actions (like initdefault) will cause init to emit
#       an error message, and then go along with its business.
#
# <process>: Specifies the process to be executed and it's command line.

如何处理配置文件中的程序?

parse_inittab()接口中会读取文件,new_init_action()将每一行的程序加入到init_action_list链表中。

init_action_list链表格式为:

/* Set up a linked list of init_actions, to be read from inittab */
struct init_action {
	struct init_action *next;
	int action;
	pid_t pid;
	char command[INIT_BUFFS_SIZE];
	char terminal[CONSOLE_NAME_SIZE];
};

从init_action_list数据可以知道,将配置文件中读取的时机、命令、输出终端参数传入到对应的action,command, terminal中。

2.3- 执行配置文件中程序

上面已经读取配置文件/etc/inittab,并将配置文件的参数添加到链表init_action_list中。

接下来在init_main()中调用run_actions()执行init_action_list中的程序。

就是遍历init_action_list中的元素,根据元素的执行时机不同进行不同操作。

SYSINIT、WAIT、ONCE的程序只执行一次;RESPAWN、ASKFIRST程序则会在死循环中一直执行,即使程序退出,下次再重新启动。

run_actions()接口对于不同时机的函数由不同的处理:

对于时机为SYSINIT ,WAIT, CTRLALTDEL,SHUTDOWN , RESTART的程序,需要等待程序结束后,从init_action_list链表删除

对于时机为ONCE的程序,则创建子线程后就从init_action_list链表删除;

对于RESPAWN 、 ASKFIRST程序则创建子线程后直接返回,不从init_action_list链表删除;


static int waitfor(const struct init_action *a, pid_t pid)
{
	int runpid;
	int status, wpid;

	runpid = (NULL == a)? pid : run(a);
	while (1) {
		wpid = waitpid(runpid, &status, 0);
		if (wpid == runpid)
			break;
		if (wpid == -1 && errno == ECHILD) {
			/* we missed its termination */
			break;
		}
		/* FIXME other errors should maybe trigger an error, but allow
		 * the program to continue */
	}
	return wpid;
}

/* Run all commands of a particular type */
static void run_actions(int action)
{
	struct init_action *a, *tmp;

	for (a = init_action_list; a; a = tmp) {
		tmp = a->next;
		if (a->action == action) {
			/* a->terminal of "" means "init's console" */
			if (a->terminal[0] && access(a->terminal, R_OK | W_OK)) {
				delete_init_action(a);
			} else if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) {
				waitfor(a, 0);
				delete_init_action(a);
			} else if (a->action & ONCE) {
				run(a);
				delete_init_action(a);
			} else if (a->action & (RESPAWN | ASKFIRST)) {
				/* Only run stuff with pid==0.  If they have
				 * a pid, that means it is still running */
				if (a->pid == 0) {
					a->pid = run(a);
				}
			}
		}
	}
}

2.4- 最小根文件系统组成

从内核接口init_post() 到busybox分析,我们可以得出最小跟文件系统的组成为:

1- 终端设备: /dev/console, /dev/null

2- init程序,这里指busybox程序

3- 配置文件,这里指/etc/inittab文件

4- 配置文件指定的应用程序,例如ls, cp, init等

5- 库文件--busybox会用到printf, 需要由c库等其他库文件

3- busybox编译安装

busybox 源码文件INSTALL里由说明

3.1-  make menuconfig配置busybox

选择上TBL自动补全命令,其他基本不用管,直接保存

3.2- 编译。

我们使用交叉编译工具arm-linux编译工具链编译busybox,需要修改makefile文件

CROSS_COMPILE	?= arm-linux-

执行编译命令 

make

3.3- 安装busybox。

我们在虚机上不能直接make install,这样会直接替换到虚机的busybox,破坏虚机!

我们需要安装到指定路径: make CONFIG_PREFIX=xxxt install

The BusyBox build process is similar to the Linux kernel build:

  make menuconfig     # This creates a file called ".config"
  make                # This creates the "busybox" executable
  make install        # or make CONFIG_PREFIX=/path/from/root install

 安装busybox后的文件

bin  linuxrc  sbin  usr

3.4- 遇到问题

建议使用韦东山光盘里提供的ubuntu,里面的编译环境都配置好了,自己配置编译环境可能出现一堆奇奇怪怪问题,增加学习成本!

1- 编译busybox失败,报错:

Makefile:405: *** mixed implicit and normal rules: deprecated syntax
Makefile:1242: *** mixed implicit and normal rules: deprecated syntax
make: *** No rule to make target 'menuconfig'。 停止。

解决方案参考:最小根文件系统制作(jffs2(NOR Flash)及yaffs2(NAND Flash))_Leon的博客-CSDN博客

busybox的编译使用及安装_whatday的专栏-CSDN博客_busybox编译

2- 安装busybox到指定路径出错

问题场景即解决方案:我使用vmtool在虚机和windows机器间传输文件,busybox放在了二者的交换文件夹中。可以把busybox放在/home路径。

 /mnt/hgfs/xxx/bin/addgroup -> busybox
ln: failed to create symbolic link '/mnt/hgfs/xxx/bin/addgroup': Operation not supported
/mnt/hgfs/xxx/busybox-1.7.0/Makefile.custom:16: recipe for target 'install' failed
make: *** [install] Error 1



 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值