0x00 启动第一个程序
uboot的目的——>启动内核
内核的目的———>启动应用程序
而应用程序位于根文件系统上
看看内核启动之后运行哪个应用程序
(我真的要吐槽一句了:tmd 5.9的内核,都没有这些玩意儿,2.6的东西太tm老了)
init_post
/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
*/
static int noinline init_post(void)
{
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) < 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);
}
/*
* 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(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.");
}
12行: 打开了/dev/console ——>终端 可能是串口0,1,2
15,16行: printf, scanf err(标准输出,标准输入, 标准错误)在终端显示
30行:
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);
16行,就是bootargt 的 “init=” 后面的值
31行:run_init_process
static void run_init_process(char *init_filename)
{
argv_init[0] = init_filename;
kernel_execve(init_filename, argv_init, envp_init);
}
启动 init = xxxx的这个应用程序
35 ~ 38行
run_init_process(“/sbin/init“);
run_init_process(“/etc/init“);
run_init_process(“/bin/init“);
run_init_process(“/bin/sh“);
如果boot没有指定,就启动 /sbin/init,如果过这个也没有,就启动 /etc/init,依次下去…….
流程
- vfs 首先识别挂在根文件系统
- 启动 /dev/console
然后启动 boot指定的应用程序或者 /sbin/init /etc/init /bin/init /bin/sh
0x01 init进程分析
在shell里的命令 如 ls cp … 都是调用 busybox ls……
而我们在shell里输入的命令其实是对应busybox里的软连接
上文的 /sbin/init ———-> /bin/busybox
所以来看busybox的源码init/init.c/init_main
```c int init_main(int argc UNUSED_PARAM, char *argv) { 。。。。。。 / Figure out where the default console should be */ console_init(); set_sane_term(); xchdir(“/“); setsid();
/ 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? /
if (argv[1])
xsetenv("RUNLEVEL", argv[1]);
if !ENABLE_FEATURE_INIT_QUIET
/* Hello world */
message(L_CONSOLE | L_LOG, "init started: %s", bb_banner);
endif
/* 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();
}
if ENABLE_SELINUX
if (getenv("SELINUX_INIT") == NULL) {
int enforce = 0;
putenv((char*)"SELINUX_INIT=YES");
if (selinux_init_load_policy(&enforce) == 0) {
BB_EXECVP(argv[0], argv);
} else if (enforce > 0) {
/* SELinux in enforcing mode but load_policy failed */
message(L_CONSOLE, "can't load SELinux Policy. "
"Machine is in enforcing mode. Halting now.");
return EXIT_FAILURE;
}
}
endif
if ENABLE_FEATURE_INIT_MODIFY_CMDLINE
/* Make the command line just say "init" - that's all, nothing else */
strncpy(argv[0], "init", strlen(argv[0]));
/* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
while (*++argv)
nuke_str(*argv);
endif
。。。。。。
/* Now run everything that needs to be run */
/* First run the sysinit command */
run_actions(SYSINIT);
check_delayed_sigs(&G.zero_ts);
/* Next run anything that wants to block */
run_actions(WAIT);
check_delayed_sigs(&G.zero_ts);
/* Next run anything to be run only once */
run_actions(ONCE);
/* Now run the looping stuff for the rest of forever */
while (1) {
/* (Re)run the respawn/askfirst stuff */
run_actions(RESPAWN | ASKFIRST);
/* Wait for any signal (typically it's SIGCHLD) */
check_delayed_sigs(NULL); /* NULL timespec makes it wait */
/* Wait for any child process(es) to exit */
while (1) {
pid_t wpid;
struct init_action *a;
wpid = waitpid(-1, NULL, WNOHANG);
if (wpid <= 0)
break;
a = mark_terminated(wpid);
if (a) {
message(L_LOG, "process '%s' (pid %u) exited. "
"Scheduling for restart.",
a->command, (unsigned)wpid);
}
}
/* Don't consume all CPU time - sleep a bit */
sleep(1);
} /* while (1) */
}
5行: console_init();控制台中断初始化<br />38行: parse_inittab(); 解析inittab
<a name="QyRMI"></a>
### parse_inittab()
```c
static void parse_inittab(void)
{
#if ENABLE_FEATURE_USE_INITTAB
char *token[4];
parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
if (parser == NULL)
#endif
{
/* No inittab file - set up some default behavior */
/* Sysinit */
new_init_action(SYSINIT, INIT_SCRIPT, "");
/* Askfirst shell on tty1-4 */
new_init_action(ASKFIRST, bb_default_login_shell, "");
//TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
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);
/* Reboot on Ctrl-Alt-Del */
new_init_action(CTRLALTDEL, "reboot", "");
/* Umount all filesystems on halt/reboot */
new_init_action(SHUTDOWN, "umount -a -r", "");
/* Swapoff on halt/reboot */
new_init_action(SHUTDOWN, "swapoff -a", "");
/* Restart init when a QUIT is received */
new_init_action(RESTART, "init", "");
return;
}
#if ENABLE_FEATURE_USE_INITTAB
/* optional_tty:ignored_runlevel:action:command
* Delims are not to be collapsed and need exactly 4 tokens
*/
while (config_read(parser, token, 4, 0, "#:",
PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
/* order must correspond to SYSINIT..RESTART constants */
static const char actions[] ALIGN1 =
"sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
"ctrlaltdel\0""shutdown\0""restart\0";
int action;
char *tty = token[0];
if (!token[3]) /* less than 4 tokens */
goto bad_entry;
action = index_in_strings(actions, token[2]);
if (action < 0 || !token[3][0]) /* token[3]: command */
goto bad_entry;
/* turn .*TTY -> /dev/TTY */
if (tty[0]) {
tty = concat_path_file("/dev/", skip_dev_pfx(tty));
}
new_init_action(1 << action, token[3], tty);
if (tty[0])
free(tty);
continue;
bad_entry:
message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",
parser->lineno);
}
config_close(parser);
#endif
}
5行:打开一个配置文件
/etc/inittab (一般etc下是配置文件)
inittab的配值格式
Format for each entry: <id>:<runlevels>:<action>:<process>
- 指定程序 2. 何时启动
shutdown, restart and ctrlaltdel.
ok 我们继续看pars_inittab()
7 ~ 28行,如果没有配置文件的话有一套默认的配值项
34~51行 解析这个配置文件
52 行 调用 new_init_action(1 << action, token[3], tty);
我们来看一下咋解析的
eg.16行: new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
#define LIBBB_DEFAULT_LOGIN_SHELL "-/bin/sh"
extern const char bb_default_login_shell[] ALIGN1;
# define VC_2 "/dev/tty2"
/* Like RESPAWN, but wait for <Enter> to be pressed on tty */
#define ASKFIRST 0x10
new_init_action(0x10, “-/bin/sh” ,”/dev/tty2”);
看一下new_init_action
static void new_init_action(uint8_t action_type, const char *command, const char *cons)
{
struct init_action *a, **nextp;
/* Scenario:
* old inittab:
* ::shutdown:umount -a -r
* ::shutdown:swapoff -a
* new inittab:
* ::shutdown:swapoff -a
* ::shutdown:umount -a -r
* On reload, we must ensure entries end up in correct order.
* To achieve that, if we find a matching entry, we move it
* to the end.
*/
nextp = &G.init_action_list;
while ((a = *nextp) != NULL) {
/* Don't enter action if it's already in the list.
* This prevents losing running RESPAWNs.
*/
if (strcmp(a->command, command) == 0
&& strcmp(a->terminal, cons) == 0
) {
/* Remove from list */
*nextp = a->next;
/* Find the end of the list */
while (*nextp != NULL)
nextp = &(*nextp)->next;
a->next = NULL;
goto append;
}
nextp = &a->next;
}
a = xzalloc(sizeof(*a) + strlen(command));
/* Append to the end of the list */
append:
*nextp = a;
a->action_type = action_type;
strcpy(a->command, command);
safe_strncpy(a->terminal, cons, sizeof(a->terminal));
dbg_message(L_LOG | L_CONSOLE, "command='%s' action=%x tty='%s'\n",
a->command, a->action_type, a->terminal);
}
16行,一个链表 init_action类型的
/* A linked list of init_actions, to be read from inittab */
struct init_action {
struct init_action *next;
pid_t pid;
uint8_t action_type;
char terminal[CONSOLE_NAME_SIZE];
char command[1];
};
总的来说就是把 action 应用程序 还有选的终端 填入 init_action这个结构体
把init_action放到init_action_list这个链表
那我们就把上述默认的inittab反推出来吧
::SYSINIT:/etc/init.d/rcS
::ASKFIRST:-/bin/sh
tty2::ASKFIRST:-/bin/sh
tty3::ASKFIRST:-/bin/sh
tty4::ASKFIRST:-/bin/sh
::CTRLALTDEL:reboot
::SHUTDOWN:umount -a -r
::SHUTDOWN:swapoff -a
::RESTART:init
现在如果没有配置文件,init_action_list链表里就放着这些
现在我们回到init_main里
看69行, run_action
/* Run all commands of a particular type */
static void run_actions(int action_type)
{
struct init_action *a;
for (a = G.init_action_list; a; a = a->next) {
if (!(a->action_type & action_type))
continue;
if (a->action_type & (SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN)) {
pid_t pid = run(a);
if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN))
waitfor(pid);
}
if (a->action_type & (RESPAWN | ASKFIRST)) {
/* Only run stuff with pid == 0. If pid != 0,
* it is already running
*/
if (a->pid == 0)
a->pid = run(a);
}
}
}
大致规整如下
run_actions(SYSINIT); //
run_actions(WAIT);
run_actions(ONCE);
while (1) {
run_actions(RESPAWN | ASKFIRST);
while (1) {
wpid = waitpid(-1, NULL, WNOHANG);
if (wpid <= 0)
break;
a = mark_terminated(wpid);
if (a) {
message(L_LOG, "process '%s' (pid %u) exited. "
"Scheduling for restart.",
a->command, (unsigned)wpid);
}
}
}
run_actions执行链表里为对应action的init_action
大致就是,
- 执行应用程序run(a),
fork(); //创建子进程
- 等待waitfor(a),程序结束后杀掉进程,
- 在链表里删除节点
```c
run_actions(SYSINIT); //执行 sysinit的应用程序
run_actions(WAIT); //执行 sysinit的应用程序
run_actions(ONCE); //这个没有waitefor(),就让他跑,不等他停
while (1) {
run_actions(RESPAWN | ASKFIRST); //如果过pid == 0 ,才会让他run,不等他停
while (1) {//如果是ASKFIRST的话 //打印 “Please press Enter to activate this console.” //还需要敲回车
}wpid = waitpid(-1, NULL, WNOHANG);//等某个子进程退出的时候 if (wpid <= 0) break; //wpid<=0 ,死循环就退出来,然后继续执行 run_actions(RESPAWN | ASKFIRST); 把pid设置为0. }
```c
# define update_utmp_DEAD_PROCESS(pid) ((void)0)
static struct init_action *mark_terminated(pid_t pid)
{
struct init_action *a;
if (pid > 0) {
update_utmp_DEAD_PROCESS(pid);
for (a = G.init_action_list; a; a = a->next) {
if (a->pid == pid) {
a->pid = 0;
return a;
}
}
}
return NULL;
}
分析完毕
小结
busybox init结构图
最小的根文件系统
/dev/console /dev/null init ——-> busybox /etc/inittab
配置文件里指定的应用程序
c库
0x02 busybox
编译busybox
到https://www.busybox.net/下载源码,本文使用的是1.32.1版本
(1)make xxxxxxconfig
busybox提供了几种配置:defconfig (缺省配置)、allyesconfig(最大配置)、 allnoconfig(最小配置),一般选择缺省配置即可。
这一步结束后,将生成.config
(2)make menuconfig
这一步是可选的,当你认为上述配置中还有不尽如意的地方,可以通过这一步进行微调,加入或去除某些命令。
这一步实际上是修改.config
(3)make CROSS_COMPILE=arm-linux-gnueabi-
这一步就是根据.config,生成busybox,当然你也可以指定其他的编译器, 如arm-linux-gnueabi-。(”make CROSS_COMPILE=”将用gcc编译PC机上运行的busybox.
结束
(4)make install
我们现在在交叉编译,所以千万不要只 make install 这样会破坏本身PC的环境
所以我门自己建个文件夹,扔到那个里面
然后
make CONFIG_PREFIX=/your/path install
我们来看一下,文件夹里都有啥
0x03 构建根文件系统
最小的根文件系统
根据源码我们推导出最小根文件系统的组成,就按这个来构建
/dev/console /dev/null init ——-> busybox /etc/inittab
配置文件里指定的应用程序
c库
1.创建 /dev/console /dev/null
我们用mknod来创建设备,首先我们看一下信息
console字符设备的主设备号5,次设备号1
null字符设备主设备号1,次设备号3
然后我们来建字符设备
2. init —-> busybox
见上一小节,已经搞定
3. /etc/inittab
可以看一下我们分析的busybox里init.c的默认配置
::SYSINIT:/etc/init.d/rcS
::ASKFIRST:-/bin/sh
tty2::ASKFIRST:-/bin/sh
tty3::ASKFIRST:-/bin/sh
tty4::ASKFIRST:-/bin/sh
::CTRLALTDEL:reboot
::SHUTDOWN:umount -a -r
::SHUTDOWN:swapoff -a
::RESTART:init
咱们用不到这么多,能开个shell就行
console::ASKFIRST:-/bin/sh
在console中断显示
4. C库
直接找到对应的编译链(我就用arm-linux-gnueabi-对应的了)
现建lib文件夹
之后 cp过来
最终是这些
制作映象文件
其实
在荔枝派的构建中,最后镜像打包就有
#!/bin/sh
dd if=/dev/zero of=f1c100s_spiflash_16M.bin bs=1M count=16 &&\
dd if=u-boot/u-boot-sunxi-with-spl.bin of=f1c100s_spiflash_16M.bin bs=1K conv=notrunc &&\
dd if=linux-f1c100s-480272lcd-test/arch/arm/boot/dts/suniv-f1c100s-licheepi-nano.dtb of=f1c100s_spiflash_16M.bin bs=1K seek=1024 conv=notrunc &&\
dd if=linux-f1c100s-480272lcd-test/arch/arm/boot/zImage of=f1c100s_spiflash_16M.bin bs=1K seek=1088 conv=notrunc &&\
mkfs.jffs2 -s 0x100 -e 0x10000 --pad=0xAF0000 -d rootfs/ -o rootfs.jffs2 &&\
dd if=rootfs.jffs2 of=f1c100s_spiflash_16M.bin bs=1k seek=5184 conv=notrunc &&\
sync
这里就直接copy一下其他文章的了
=====================================================================
上传并解压yaffs2压缩包
进入此目录执行make
拷贝编译出的工具到系统目录
最后制作yaffs2文件
然后烧录到单板上,uboot下选择 y 进行烧录
0x04 完善根文件系统
1.proc
系统的一个虚拟的根文件系统
建一个 proc的文件夹
修改我们的配置文件 inittab
加上::SYSINIT:/etc/init.d/rcS
就是运行一个脚本
新建这个脚本文件
在文件中加上mount -t proc none/proc命令
来挂载proc
记得加上权限
或者rcS中写入 mount -a同样可以挂载proc根文件系统
此命令依赖etc/fstab这个文件
# device mount-point type options dump fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
/etc/fstab 文件被用作定义文件系统的“静态信息”,这些信息被用来控制 mount 命令的行为。
第一列 Device
磁盘设备文件或者该设备的Label或者UUID
第二列 Mount point
设备的挂载点,就是你要挂载到哪个目录下。
第三列 filesystem
磁盘文件系统的格式,包括ext2、ext3、reiserfs、nfs、vfat等
第四列 parameters
文件系统的参数
| Async/sync | 设置是否为同步方式运行,默认为async |
| —- | —- |
| auto/noauto | 当下载mount -a 的命令时,此文件系统是否被主动挂载。默认为auto |
| rw/ro | 是否以以只读或者读写模式挂载 |
| exec/noexec | 限制此文件系统内是否能够进行”执行”的操作 |
| user/nouser | 是否允许用户使用mount命令挂载 |
| suid/nosuid | 是否允许SUID的存在 |
| Usrquota | 启动文件系统支持磁盘配额模式 |
| Grpquota | 启动文件系统对群组磁盘配额模式的支持 |
| Defaults | 同事具有rw,suid,dev,exec,auto,nouser,async等默认参数的设置 |
第五列:能否被dump备份命令作用
dump是一个用来作为备份的命令。通常这个参数的值为0或者1
| 0 | 代表不要做dump备份 |
| —- | —- |
| 1 | 代表要每天进行dump的操作 |
| 2 | 代表不定日期的进行dump操作 |
第六列 是否检验扇区
开机的过程中,系统默认会以fsck检验我们系统是否为完整(clean)。
| 0 | 不要检验 |
| —- | —- |
| 1 | 最早检验(一般根目录会选择) |
| 2 | 1级别检验完成之后进行检验 |
2.udev
linux下udev 自动创建 /dev/设备节点
在busybox里mdev为udev的简化版
在busybox里看一下mdev.txt
细节参考:
https://blog.csdn.net/hugerat/article/details/3437099
制作 jfss2 文件系统映像
最后制作应该改为 mkfs.jffs2 -n -s 2048 -e 128KiB -d first_fs -o first_fs.jffs2
然后需要在uboot下更改文件系统格式 ,让系统以jfss2格式挂在=