当前位置:首页 > > 充电吧
[导读]整体框架:1)在Linux kernel的源代码中,对如何启动应用程序有着明确的定义。首先我们需要挂载根文件系统,只有正确挂载了根文件系统,才能够从根文件系统中读出应用程序。我们启动的第一个程序就是i

整体框架:


1)在Linux kernel的源代码中,对如何启动应用程序有着明确的定义。首先我们需要挂载根文件系统,只有正确挂载了根文件系统,才能够从根文件系统中读出应用程序。我们启动的第一个程序就是init程序。init进程完成了对应用程序的各项配置(进程ID、执行时机、命令、终端、下一个执行的进程等),并最终依据配置执行了应用程序。 2)要执行应用程序,首先进行配置。配置文件inittab里有着对应用程序的详细配置,这些都是C文件。init进程读出配置、分析配置并配置应用程序、配置C库(用到很多C库里的函数)。最后执行程序。 3)Busybox是一个遵循GPL v2协议的开源项目。Busybox将众多的UNIX命令集合进一个很小的可执行程序中,可以用来替换GNU fileutils、shellutils等工具集。Busybox中各种命令与相应的GNU工具相比,所能提供的选项较少,但是能够满足一般应用。Busybox为各种小型的或者嵌入式系统提供了一个比较完全的工具集。更多详细介绍参考README。

我们执行命令的时候实际是执行busybox 命令

我们查看软连接


内核检测根文件系统并启动init

内核启动的最后一步就是启动init进程,代码在init/main.c/init_post函数

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 %sn",
                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.");
}

内核启动init进程的过程如下: (1)打开标准输入、标准输出、标准错误设备open("/dev/console") 尝试打开/dev/console设备文件,如果成功即为init进程标准输入设备。(void) sys_dup(0); (void) sys_dup(0);将文件描述符0复制给文件描述符1、2,所以标准输入、输出、错误都对应同一个文件(设备) (2)如果execute_command变量指定了要运行的程序,启动它。

  if (execute_command) {
    run_init_process(execute_command);
  }

其中execute_command为命令行参数,在我们uboot传给内核的参数中,init设置了init=/linuxrc,所以这里的execute_command就等于/linuxrc。
 如果传值成功则执行run_init_process,否则打印printk(KERN_WARNING “Failed to execute %s.  Attempting “”defaults…n”, execute_command);
 并接着往下执行,接着检测其他位置的init进程,若成功则执行,失败则接着往下检测,直到找到合适的init进程或者没找到则打印panic(“No init found.  Try passing init= option to kernel.”);

那么这里我们可以先使用nand erase root擦除root分区,也就是说擦除根文件系统,然后启动只有bootloader和kernel的系统。在结果是否和代码中说明的一致,结果Linux kernel在启动过程中,打印出了如下的信息:

VFS: Mounted root (yaffs filesystem).  
Freeing init memory: 140K
Warning: unable to open an initial console.
Failed to execute /linuxrc.  Attempting defaults...
Kernel panic - not syncing: No init found.  Try passing init= option to kernel.

首先是VFS:挂载了根文件系统,可能大家会问,不是刚刚已经擦除了根文件系统,为什么说这里挂载了?
 这是因为当我们擦除了根文件系统的root分区后,Linux kernel认为它是任意格式的根文件系统(其实分区里面什么都没有),而默认的又是yaffs格式,所以这里说挂载了yaffs格式的根文件系统。
 这里的warning难道不是和我们init_post函数中的printk(KERN_WARNING “Warning: unable to open an initial console.n”);相对应吗?
 同理,Failed to execute /linuxrc.  Attempting defaults…和printk(KERN_WARNING “Failed to execute %s.  Attempting “”defaults…n”, execute_command);相对应。
 Kernel panic - not syncing: No init found.  Try passing init= option to kernel.和panic(“No init found.  Try passing init= option to kernel.”);相对应。
 so=>这证明我们的分析是正确的。

Busybox init进程的启动过程

其中与构建根文件系统关系密切的是控制台的初始化、对inittab文件的解释执行。

内核启动init进程时已经打开“/dev/console”设备作为控制台,一般情况下Busybox init程序就使用/dev/console。
 但是如果内核启动init进程的同时设置了环境变量CONSOLE或console,则使用环境变量所指定的设备。
 在Busybox init程序中,还会检查这个设备是否可以打开,如果不能打开则使用”/dev/null”。

/etc/inittab文件的相关文档和示例代码都在Busybox的examples/inittab文件中,我们来一探究竟

查看inittab文件得知inittab格式:

Format for each entry:
#:::#id:        The id field is used by BusyBox init to specify the controlling tty for the specified process to run on.  
#runlevels:  The runlevels field is completely ignored.
#action:     Valid actions include:   sysinit, respawn, askfirst, wait, once,
#                                            restart, ctrlaltdel, and shutdown.

#process:    Specifies the process to be executed and it's command line.
/*******************************解析************************************/
从默认的new_init_action反推出默认的配置文件:
# inittab格式:
#:::# id => /dev/id,用作终端:stdin,stdout,stderr:printf, scanf, err(即标准输入、输出、错误设
# 备),如果省略,则使用与Init进程一样的控制台。
# runlevels : 忽略 
# action      :执行时机 sysinit, respawn, askfirst, wait, once,
#                        restart, ctrlaltdel, and shutdown.
# process     :应用程序或脚本,如果前有“-”字符,这个程序被称为“交互的”。

在init_main函数中,调用了parse_inittab函数来读取配置文件inittab。如果根文件系统中没有/etc/inittab文件,Busybox init程序将使用默认的inittab条目。这里我们可以通过默认的配置语句,倒推出默认的配置文件内容。

DIR: init.c-parse_inittab函数
     /* 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 */
        if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");
        /* 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, "");
/*******************************解析************************************/
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
::restart:/sbin/init
::askfirst:-/bin/sh
tty2::askfirst:-/bin/sh
tty3::askfirst:-/bin/sh
tty4::askfirst:-/bin/sh 
::sysinit:/etc/init.d/rcS

这里涉及到了一个函数 new_init_action 。 它实际上的工作就是把各个程序的执行时机、命令行、控制台参数分别赋值给结构体,并把这些结构体组成一个单链表。这也就是我们所说的配置。 它的声明是:static void new_init_action(int action, const char *command, const char *cons);这三个参数不正是inittab配置文件中的配置命令吗?他们分别对应于来看看new_init_action函数:

DIR:init.c-new_init_action函数

static void new_init_action(int action, const char *command, const char *cons)
{
    struct init_action *new_action, *a, *last;

    if (strcmp(cons, bb_dev_null) == 0 && (action & ASKFIRST))
        return;

    /* Append to the end of the list */
    for (a = last = init_action_list; a; a = a->next) {
        /* don't enter action if it's already in the list,
         * but do overwrite existing actions */
        if ((strcmp(a->command, command) == 0)
         && (strcmp(a->terminal, cons) == 0)
        ) {
            a->action = action;
            return;
        }
        last = a;
    }

    new_action = xzalloc(sizeof(struct init_action));
    if (last) {
        last->next = new_action;
    } else {
        init_action_list = new_action;
    }
    strcpy(new_action->command, command);
    new_action->action = action;
    strcpy(new_action->terminal, cons);
    messageD(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'n",
        new_action->command, new_action->action, new_action->terminal);
}

/* 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];
  };

new_init_action函数用于配置,参数为执行时机、命令行、控制台。
 结构体指针new_action开始指向上一个配置过的程序(其存储在结构体,参数是上一个程序的执行时机、命令行、控制台),这里首先进行了一个If判断,如果控制台等于bb_dev_null(宏定义等于 /dev/null)且action为ASKFIRST那么直接返回,不进行任何配置。
 接着这个for循环算是这个函数的一个重点吧,首先令结构体指针init_action_list赋值给a和last。
 这里的init_action_list(宏定义为NULL)开始为NULL,后来指向第一个配置的程序。
 也就是说,遍历所有配置过的程序,如果这个程序之前被配置过(命令行和控制台同时等于当前遍历的程序),那么执行时机action被重新赋值为当前值。
 通俗的说,这个for为了避免程序重复配置,查找之前配置过的程序有没有当前要配置的程序,如果有,则只改变其执行时机action。命令行和控制台不变。
 如果没有,接下来为new_action重新分配内存,并且给它赋值,令它的各项信息等于当前的程序。在上面的if语句中,last->next=new_action,也就是说,将所有程序的配置结构体连成一个单链表。

new_init_action函数讲解完毕。

经过上面的讲解,我们明白了Linux根文件系统中,对于程序的配置是在parse_inittab函数完成的,它打开配置文件inittab,将程序信息一一填入结构体init_action,并将它们连接成单链表。现在配置已经完成,下一步是执行了。接着看init_main中的代码是怎样执行应用程序的:

busybox-> init_main
            parse_inittab
                file = fopen(INITTAB, "r"); //打开配置文件/etc/inittab

                new_init_action     // ① 创建一个init_action结构,填充
                                    // ② 把这个结构放入init_action_list链表
            run_actions(SYSINIT);
                waitfor(a, 0);      // 执行应用程序,等待它执行完毕
                    run(a)          // 创建process子进程
                    waitpid(runpid, &status, 0); // 等待它结束
                delete_init_action(a);// 在init_action_list链表里删除             
            run_actions(WAIT);
                waitfor(a, 0);      // 执行应用程序,等待它执行完毕
                    run(a)          // 创建process子进程
                    waitpid(runpid, &status, 0); // 等待它结束
                delete_init_action(a);// 在init_action_list链表里删除 
            run_actions(ONCE);
                run(a);
                delete_init_action(a);
            while(1) {
                run_actions(RESPAWN);
                    if (a->pid == 0) {
                        a->pid = run(a);
                    }
                run_actions(ASKFIRST);
                    if (a->pid == 0) {
                        a->pid = run(a);
                                打印:Please press Enter to activate this console.
                                等待回车
                                创建子进程
                    }
                wpid = wait(NULL);  // 等待子进程退出
                while (wpid > 0) {
                    a->pid = 0;     // 退出后,就设置pid=0
                }
            }

在/etc/inittab文件的控制下,init进程的行为总结如下: ① 在系统启动前期,init进程首先启动为sysinit、wait、once的3类子进程。 ② 在系统正常运行期间,init进程首先启动为respawn、askfirst的两类子进程。 ③ 在系统退出时,执行为shutdown、restart、ctrlaltdel的三类子进程(之一或全部) 从而我们可以总结出来最小的根文件系统由5部分组成: 1./dev/console or/dev/null 2.init => busybox 3./etc/inittab 4.配置文件指定的程序 5.C库 busybox的配置、编译和安装 打开busybox自带的INSTALL文件查看我们该怎样配置、编译和安装busybox。

Building:
=========

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

The full list of configuration and install options is available by typing:

  make help

1.配置 进入busybox文件夹make menuconfig生成配置文件.config

2.编译

由于我们文件系统是给嵌入式板子用的,先修改Busybox的Makefile,使用交叉编译器。

修改前

ARCH        ?= $(SUBARCH)
CROSS_COMPILE   ?=

修改后

ARCH        ?= $(SUBARCH)
CROSS_COMPILE   ?= arm-linux-

然后make3.安装 注意:如果你是在虚拟机上安装busybox,安装不可直接执行make INSTALL,必须在虚拟机下自己创建一个文件夹,将安装路径指向这个文件夹的路径。再执行make CONFIG_PREFIX=dir_path install否则会破坏系统。 注:除bin/busybox外,其他文件都是到bin/busybox的符号连接。busybox是所有命令的集合体,这些符号连接文件可以直接运行。比如在开发板上,运行“ls”命令和”busybox ls”命令是一样的。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭