0x00 启动第一个程序

uboot的目的——>启动内核
内核的目的———>启动应用程序
而应用程序位于根文件系统上

看看内核启动之后运行哪个应用程序
(我真的要吐槽一句了:tmd 5.9的内核,都没有这些玩意儿,2.6的东西太tm老了)

init_post

  1. /* This is a non __init function. Force it to be noinline otherwise gcc
  2. * makes it inline to init() and it becomes part of init.text section
  3. */
  4. static int noinline init_post(void)
  5. {
  6. free_initmem();
  7. unlock_kernel();
  8. mark_rodata_ro();
  9. system_state = SYSTEM_RUNNING;
  10. numa_default_policy();
  11. if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
  12. printk(KERN_WARNING "Warning: unable to open an initial console.\n");
  13. (void) sys_dup(0);
  14. (void) sys_dup(0);
  15. if (ramdisk_execute_command) {
  16. run_init_process(ramdisk_execute_command);
  17. printk(KERN_WARNING "Failed to execute %s\n",
  18. ramdisk_execute_command);
  19. }
  20. /*
  21. * We try each of these until one succeeds.
  22. *
  23. * The Bourne shell can be used instead of init if we are
  24. * trying to recover a really broken machine.
  25. */
  26. if (execute_command) {
  27. run_init_process(execute_command);
  28. printk(KERN_WARNING "Failed to execute %s. Attempting "
  29. "defaults...\n", execute_command);
  30. }
  31. run_init_process("/sbin/init");
  32. run_init_process("/etc/init");
  33. run_init_process("/bin/init");
  34. run_init_process("/bin/sh");
  35. panic("No init found. Try passing init= option to kernel.");
  36. }

12行: 打开了/dev/console ——>终端 可能是串口0,1,2
15,16行: printf, scanf err(标准输出,标准输入, 标准错误)在终端显示
30行:

  1. static int __init init_setup(char *str)
  2. {
  3. unsigned int i;
  4. execute_command = str;
  5. /*
  6. * In case LILO is going to boot us with default command line,
  7. * it prepends "auto" before the whole cmdline which makes
  8. * the shell think it should execute a script with such name.
  9. * So we ignore all arguments entered _before_ init=... [MJ]
  10. */
  11. for (i = 1; i < MAX_INIT_ARGS; i++)
  12. argv_init[i] = NULL;
  13. return 1;
  14. }
  15. __setup("init=", init_setup);

16行,就是bootargt 的 “init=” 后面的值

31行:run_init_process

  1. static void run_init_process(char *init_filename)
  2. {
  3. argv_init[0] = init_filename;
  4. kernel_execve(init_filename, argv_init, envp_init);
  5. }

启动 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,依次下去…….

流程

  1. vfs 首先识别挂在根文件系统
  2. 启动 /dev/console
  3. 然后启动 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])

    1. xsetenv("RUNLEVEL", argv[1]);

if !ENABLE_FEATURE_INIT_QUIET

  1. /* Hello world */
  2. message(L_CONSOLE | L_LOG, "init started: %s", bb_banner);

endif

  1. /* Check if we are supposed to be in single user mode */
  2. if (argv[1]
  3. && (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
  4. ) {
  5. /* ??? shouldn't we set RUNLEVEL="b" here? */
  6. /* Start a shell on console */
  7. new_init_action(RESPAWN, bb_default_login_shell, "");
  8. } else {
  9. /* Not in single user mode - see what inittab says */
  10. /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
  11. * then parse_inittab() simply adds in some default
  12. * actions (i.e., INIT_SCRIPT and a pair
  13. * of "askfirst" shells) */
  14. parse_inittab();
  15. }

if ENABLE_SELINUX

  1. if (getenv("SELINUX_INIT") == NULL) {
  2. int enforce = 0;
  3. putenv((char*)"SELINUX_INIT=YES");
  4. if (selinux_init_load_policy(&enforce) == 0) {
  5. BB_EXECVP(argv[0], argv);
  6. } else if (enforce > 0) {
  7. /* SELinux in enforcing mode but load_policy failed */
  8. message(L_CONSOLE, "can't load SELinux Policy. "
  9. "Machine is in enforcing mode. Halting now.");
  10. return EXIT_FAILURE;
  11. }
  12. }

endif

if ENABLE_FEATURE_INIT_MODIFY_CMDLINE

  1. /* Make the command line just say "init" - that's all, nothing else */
  2. strncpy(argv[0], "init", strlen(argv[0]));
  3. /* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
  4. while (*++argv)
  5. nuke_str(*argv);

endif

  1. 。。。。。。
  2. /* Now run everything that needs to be run */
  3. /* First run the sysinit command */
  4. run_actions(SYSINIT);
  5. check_delayed_sigs(&G.zero_ts);
  6. /* Next run anything that wants to block */
  7. run_actions(WAIT);
  8. check_delayed_sigs(&G.zero_ts);
  9. /* Next run anything to be run only once */
  10. run_actions(ONCE);
  11. /* Now run the looping stuff for the rest of forever */
  12. while (1) {
  13. /* (Re)run the respawn/askfirst stuff */
  14. run_actions(RESPAWN | ASKFIRST);
  15. /* Wait for any signal (typically it's SIGCHLD) */
  16. check_delayed_sigs(NULL); /* NULL timespec makes it wait */
  17. /* Wait for any child process(es) to exit */
  18. while (1) {
  19. pid_t wpid;
  20. struct init_action *a;
  21. wpid = waitpid(-1, NULL, WNOHANG);
  22. if (wpid <= 0)
  23. break;
  24. a = mark_terminated(wpid);
  25. if (a) {
  26. message(L_LOG, "process '%s' (pid %u) exited. "
  27. "Scheduling for restart.",
  28. a->command, (unsigned)wpid);
  29. }
  30. }
  31. /* Don't consume all CPU time - sleep a bit */
  32. sleep(1);
  33. } /* while (1) */

}

  1. 5行: console_init();控制台中断初始化<br />38行: parse_inittab(); 解析inittab
  2. <a name="QyRMI"></a>
  3. ### parse_inittab()
  4. ```c
  5. static void parse_inittab(void)
  6. {
  7. #if ENABLE_FEATURE_USE_INITTAB
  8. char *token[4];
  9. parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
  10. if (parser == NULL)
  11. #endif
  12. {
  13. /* No inittab file - set up some default behavior */
  14. /* Sysinit */
  15. new_init_action(SYSINIT, INIT_SCRIPT, "");
  16. /* Askfirst shell on tty1-4 */
  17. new_init_action(ASKFIRST, bb_default_login_shell, "");
  18. //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
  19. new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
  20. new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
  21. new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
  22. /* Reboot on Ctrl-Alt-Del */
  23. new_init_action(CTRLALTDEL, "reboot", "");
  24. /* Umount all filesystems on halt/reboot */
  25. new_init_action(SHUTDOWN, "umount -a -r", "");
  26. /* Swapoff on halt/reboot */
  27. new_init_action(SHUTDOWN, "swapoff -a", "");
  28. /* Restart init when a QUIT is received */
  29. new_init_action(RESTART, "init", "");
  30. return;
  31. }
  32. #if ENABLE_FEATURE_USE_INITTAB
  33. /* optional_tty:ignored_runlevel:action:command
  34. * Delims are not to be collapsed and need exactly 4 tokens
  35. */
  36. while (config_read(parser, token, 4, 0, "#:",
  37. PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
  38. /* order must correspond to SYSINIT..RESTART constants */
  39. static const char actions[] ALIGN1 =
  40. "sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
  41. "ctrlaltdel\0""shutdown\0""restart\0";
  42. int action;
  43. char *tty = token[0];
  44. if (!token[3]) /* less than 4 tokens */
  45. goto bad_entry;
  46. action = index_in_strings(actions, token[2]);
  47. if (action < 0 || !token[3][0]) /* token[3]: command */
  48. goto bad_entry;
  49. /* turn .*TTY -> /dev/TTY */
  50. if (tty[0]) {
  51. tty = concat_path_file("/dev/", skip_dev_pfx(tty));
  52. }
  53. new_init_action(1 << action, token[3], tty);
  54. if (tty[0])
  55. free(tty);
  56. continue;
  57. bad_entry:
  58. message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",
  59. parser->lineno);
  60. }
  61. config_close(parser);
  62. #endif
  63. }

5行:打开一个配置文件
/etc/inittab (一般etc下是配置文件)

inittab的配值格式

  1. Format for each entry: <id>:<runlevels>:<action>:<process>
  1. 指定程序 2. 何时启动

: /dev/id, 用作终端:stdin, stdout, stderr: printf, scanf, err
The runlevels field is completely ignored.(233333)
: 执行时机 sysinit, wait, once, respawn, askfirst,
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);

  1. #define LIBBB_DEFAULT_LOGIN_SHELL "-/bin/sh"
  2. extern const char bb_default_login_shell[] ALIGN1;
  3. # define VC_2 "/dev/tty2"
  4. /* Like RESPAWN, but wait for <Enter> to be pressed on tty */
  5. #define ASKFIRST 0x10

new_init_action(0x10, “-/bin/sh” ,”/dev/tty2”);
看一下new_init_action

  1. static void new_init_action(uint8_t action_type, const char *command, const char *cons)
  2. {
  3. struct init_action *a, **nextp;
  4. /* Scenario:
  5. * old inittab:
  6. * ::shutdown:umount -a -r
  7. * ::shutdown:swapoff -a
  8. * new inittab:
  9. * ::shutdown:swapoff -a
  10. * ::shutdown:umount -a -r
  11. * On reload, we must ensure entries end up in correct order.
  12. * To achieve that, if we find a matching entry, we move it
  13. * to the end.
  14. */
  15. nextp = &G.init_action_list;
  16. while ((a = *nextp) != NULL) {
  17. /* Don't enter action if it's already in the list.
  18. * This prevents losing running RESPAWNs.
  19. */
  20. if (strcmp(a->command, command) == 0
  21. && strcmp(a->terminal, cons) == 0
  22. ) {
  23. /* Remove from list */
  24. *nextp = a->next;
  25. /* Find the end of the list */
  26. while (*nextp != NULL)
  27. nextp = &(*nextp)->next;
  28. a->next = NULL;
  29. goto append;
  30. }
  31. nextp = &a->next;
  32. }
  33. a = xzalloc(sizeof(*a) + strlen(command));
  34. /* Append to the end of the list */
  35. append:
  36. *nextp = a;
  37. a->action_type = action_type;
  38. strcpy(a->command, command);
  39. safe_strncpy(a->terminal, cons, sizeof(a->terminal));
  40. dbg_message(L_LOG | L_CONSOLE, "command='%s' action=%x tty='%s'\n",
  41. a->command, a->action_type, a->terminal);
  42. }

16行,一个链表 init_action类型的

  1. /* A linked list of init_actions, to be read from inittab */
  2. struct init_action {
  3. struct init_action *next;
  4. pid_t pid;
  5. uint8_t action_type;
  6. char terminal[CONSOLE_NAME_SIZE];
  7. char command[1];
  8. };

总的来说就是把 action 应用程序 还有选的终端 填入 init_action这个结构体
把init_action放到init_action_list这个链表
那我们就把上述默认的inittab反推出来吧

  1. ::SYSINIT:/etc/init.d/rcS
  2. ::ASKFIRST:-/bin/sh
  3. tty2::ASKFIRST:-/bin/sh
  4. tty3::ASKFIRST:-/bin/sh
  5. tty4::ASKFIRST:-/bin/sh
  6. ::CTRLALTDEL:reboot
  7. ::SHUTDOWN:umount -a -r
  8. ::SHUTDOWN:swapoff -a
  9. ::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
大致就是,

  1. 执行应用程序run(a),

fork(); //创建子进程

  1. 等待waitfor(a),程序结束后杀掉进程,
  2. 在链表里删除节点 ```c run_actions(SYSINIT); //执行 sysinit的应用程序 run_actions(WAIT); //执行 sysinit的应用程序 run_actions(ONCE); //这个没有waitefor(),就让他跑,不等他停 while (1) { run_actions(RESPAWN | ASKFIRST); //如果过pid == 0 ,才会让他run,不等他停
                                     //如果是ASKFIRST的话
                                     //打印 “Please press Enter to activate this console.”
                                     //还需要敲回车
    
    while (1) {
     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结构图

根文件系统 - 图1

最小的根文件系统

/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.
根文件系统 - 图2
结束

(4)make install
我们现在在交叉编译,所以千万不要只 make install 这样会破坏本身PC的环境
所以我门自己建个文件夹,扔到那个里面
然后
make CONFIG_PREFIX=/your/path install
根文件系统 - 图3
我们来看一下,文件夹里都有啥

根文件系统 - 图4
现在已经配置好busybox了

0x03 构建根文件系统

最小的根文件系统

根据源码我们推导出最小根文件系统的组成,就按这个来构建

/dev/console /dev/null init ——-> busybox /etc/inittab

配置文件里指定的应用程序

c库

1.创建 /dev/console /dev/null

我们用mknod来创建设备,首先我们看一下信息
根文件系统 - 图5
console字符设备的主设备号5,次设备号1
null字符设备主设备号1,次设备号3
然后我们来建字符设备
根文件系统 - 图6

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中断显示

整体的流程
根文件系统 - 图7

4. C库

直接找到对应的编译链(我就用arm-linux-gnueabi-对应的了)
根文件系统 - 图8
现建lib文件夹
根文件系统 - 图9
之后 cp过来
根文件系统 - 图10
最终是这些
根文件系统 - 图11

制作映象文件

其实
在荔枝派的构建中,最后镜像打包就有

#!/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压缩包
根文件系统 - 图12
进入此目录执行make
根文件系统 - 图13
拷贝编译出的工具到系统目录
根文件系统 - 图14
最后制作yaffs2文件
根文件系统 - 图15
然后烧录到单板上,uboot下选择 y 进行烧录

0x04 完善根文件系统

1.proc

系统的一个虚拟的根文件系统
建一个 proc的文件夹
修改我们的配置文件 inittab
加上::SYSINIT:/etc/init.d/rcS
就是运行一个脚本
新建这个脚本文件
在文件中加上mount -t proc none/proc命令
来挂载proc
记得加上权限
根文件系统 - 图16
或者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级别检验完成之后进行检验 |


操作:
根文件系统 - 图17

2.udev

linux下udev 自动创建 /dev/设备节点
在busybox里mdev为udev的简化版
在busybox里看一下mdev.txt
根文件系统 - 图18
细节参考:
https://blog.csdn.net/hugerat/article/details/3437099

根文件系统 - 图19

制作 jfss2 文件系统映像

根文件系统 - 图20
最后制作应该改为 mkfs.jffs2 -n -s 2048 -e 128KiB -d first_fs -o first_fs.jffs2
然后需要在uboot下更改文件系统格式 ,让系统以jfss2格式挂在=