前面已经搭建了一个简单的只有系统命令的根文件系统,还登不上大雅之堂,我们还要继续添加内容。

根文件系统是Linux内核的运行的环境,内核启动之后就会在根文件系统中寻找要执行的文件。接着第一篇文章中的做法,使用mkdir创建了根文件系统中该有的文件夹之后还不能用,出现kernel panic错误,提示没有找到init进程,然后编译busybox将生成的_install文件夹中的内容拷贝(覆盖)到创建的根文件系统中去,这样系统上就有init(指向了/bin/busybox)程序了,系统就能简单的启动了,但是内核会给出一个提示:

VFS: Mounted root (nfs filesystem) on device 0:12.
Freeing init memory: 272K
can't run '/etc/init.d/rcS': No such file or directory

显然挂载了根文件系统之后系统寻找到了init进程并执行了init进程,但是试图运行/etc/init.d/rcS程序失败,也就是说/etc/init.d/rcS是启动必要的程序。

Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。下面先简单的看一下init进程的启动过程。

Helper2416开发板学习②配置/etc文件夹_Helper2416开发板

我们看一下上诉几个重要过程的函数源码,在内核源码/kernel/init/main.c文件中,kernel_init函数如下:

 

static int __init kernel_init(void * unused)  
{  
    /* 
     * Wait until kthreadd is all set-up. 
     */  
    wait_for_completion(&kthreadd_done);  
    /* 
     * init can allocate pages on any node 
     */  
    set_mems_allowed(node_states[N_HIGH_MEMORY]);  
    /* 
     * init can run on any cpu. 
     */  
    set_cpus_allowed_ptr(current, cpu_all_mask);  

    cad_pid = task_pid(current);  

    smp_prepare_cpus(setup_max_cpus);  

    do_pre_smp_initcalls();  
    lockup_detector_init();  

    smp_init();  
    sched_init_smp();  

    do_basic_setup();  

    /* Open the /dev/console on the rootfs, this should never fail */  
    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);  
    /* 
     * check if there is an early userspace init.  If yes, let it do all 
     * the work 
     */  

    if (!ramdisk_execute_command)  
        ramdisk_execute_command = "/init";  

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {  
        ramdisk_execute_command = NULL;  
        prepare_namespace();  
    }  

    /* 
     * Ok, we have completed the initial bootup, and 
     * we're essentially up and running. Get rid of the 
     * initmem segments and start the user-mode stuff.. 
     */  

    init_post();  
    return 0;  
}  

kernel_init函数最后调用的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 noinline int init_post(void)  
{  
    /* need to finish all async __init code before freeing the memory */  
    async_synchronize_full();  
    free_initmem();  
    mark_rodata_ro();  
    system_state = SYSTEM_RUNNING;  
    numa_default_policy();  


    current->signal->flags |= SIGNAL_UNKILLABLE;  

    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. "  
          "See Linux Documentation/init.txt for guidance.");  
}  

第一眼我就看到了么最后一句话“panic(“No init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance.”);”很眼熟是不是,这不就是在之前根文件系统中文件夹都是空的情况下启动内核找不到init程序之后报出的错误么?哈哈,可找到你了!

execute_command是由uboot传过来的命令,一般传过来的是“init=/linuxrc”或者“init=/bin/init”,如果uboot没有传递execute_command过来的话前面的if判断就不会通过转而执行后面的三个默认目录下的init程序,注意看程序注释上有一段话“We try each of these until one succeeds”,也就是调用那么多次run_init_process函数只要有一个能运行的话就可以,也就是run_init_process函数不会再返回了。run_init_process函数如下:

static void run_init_process(const char *init_filename)  
{  
    argv_init[0] = init_filename;  
    kernel_execve(init_filename, argv_init, envp_init);  
}  

内核的执行顺序很清晰了:内核启动的最后会调用run_init_process实现init进程的调用,init进程启动其他进程,例如刚才看到的/etc/init.d/rcS程序,init进行想找/etc/init.d/rcS进程但是没有找到所以提示“can’t run ‘/etc/init.d/rcS’”。

现在有一个思路就是跟着init进程的执行顺序来搭建根文件系统中需要的内容。那么还是需要了解init进程的执行过程。还是参考上面的参考文章对init进程的执行有一个了解。但是参考文章的系统不是使用busybox生成的,和我们使用的有一些差别,但是整体原理是类似的。需要注意的是init进程只是一个运行在用户空间的一个进程而不属于内核,完全可以由我们自己来编写init程序,但是我猜基本没人这么做。

我们需要找到完成init进程的源代码,由于我们使用的是busybox,所有命令功能都是有busybox一个可执行文件完成的,init程序也是在由busybox实现的。在busybox中init功能的程序位置在busybox-1.27.2/init/init.c文件中,我们找到了一个宏定义:

#ifndef INIT_SCRIPT
# define INIT_SCRIPT  "/etc/init.d/rcS"
#endif

将”/etc/init.d/rcS”定义成了INIT_SCRIPT,就是初始化脚本的意思,那么”/etc/init.d/rcS”就是完成初始化工作的脚本呗!看看哪里调用了INIT_SCRIPT了,在函数parse_inittab中:

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;
    }
...
}

如果没有使用”/etc/inittab”的话就会调用new_init_action(SYSINIT, INIT_SCRIPT, “”),暂不分析new_init_action函数的功能,很显然的一点是这里将INIT_SCRIPT设置成了SYSINIT,就是系统的初始化的脚本。好了好了现在找到了”/etc/init.d/rcS”文件我们就重点分析它的作用然后创建我们自己的”/etc/init.d/rcS”文件。

看了网上不少资料,总的来说”/etc/init.d/rcS”就是一个初始化脚本,但是它做的工作很重要,建立系统运行环境,挂载必要的文件系统到特定的文件夹,设置网络参数,配置mdev等等。创建rcS文件然后修改权限直接至777,文件内容示例如下:

#!/bin/sh
ifconfig eth0 192.168.1.111  ---配置eth0网卡IP地址
mount -a  ---Mount all filesystems (of the given  types)  mentioned  in  fstab
mkdir /dev/pts  ---创建pts目录
mount -t devpts devpts /dev/pts  ---挂载devpts目录(不知道其的作用)
echo /sbin/mdev > /proc/sys/kernel/hotplug  ---配置mdev到系统hotplug
mdev –s  ---生效mdev

rcS文件里面有一个mount -a是挂载所有/etc/fstab文件中说明的目录,创建一个/etc/fstab文件如下:

proc    /proc   proc    defaults        0       0
tmpfs   /tmp    tmpfs   defaults        0       0
sysfs   /sys    sysfs   defaults        0       0
tmpfs   /dev    tmpfs   defaults        0       0

创建“/etc/init.d/rcS”文件之后进行开发板挂载实验,目录挂载都没有问题,但是会一直输出:

can't open /dev/tty2: No such file or directory
can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory

估计是哪里有问题先不予理睬了,我们使用下面的方法。其实直接可以不使用fstab文件进行系统目录挂载,直接在”/etc/init.d/rcS”文件里面写命令进行挂载就行了,下面是开发板公司提供的根文件系统中现成的”/etc/init.d/rcS”文件,没有使用fstab文件:

#! /bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel

#
#       Trap CTRL-C &c only in this shell so we can interrupt subprocesses.
#
trap ":" INT QUIT TSTP
/bin/hostname tangquan

/bin/mount -n -t proc none /proc
/bin/mount -n -t sysfs none /sys
/bin/mount -n -t usbfs none /proc/bus/usb
/bin/mount -t ramfs none /dev

echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s
# mounting file system specified in /etc/fstab
mkdir -p /dev/pts
mkdir -p /dev/shm
/bin/mount -n -t devpts none /dev/pts -o mode=0622
/bin/mount -n -t tmpfs tmpfs /dev/shm
/bin/mount -n -t ramfs none /tmp
/bin/mount -n -t ramfs none /var
mkdir -p /var/empty
mkdir -p /var/log
mkdir -p /var/lock
mkdir -p /var/run
mkdir -p /var/tmp

/sbin/hwclock -s

syslogd
/etc/rc.d/init.d/netd start
#echo "                        " > /dev/tty1
#echo "Starting networking..." > /dev/tty1
#sleep 1
/etc/rc.d/init.d/httpd start
#echo "                        " > /dev/tty1
#echo "Starting web server..." > /dev/tty1  
#sleep 1                                    


/sbin/ifconfig lo 127.0.0.1
/etc/init.d/ifconfig-eth0

这个rcS文件和前面的rcS文件有一个最大不同的地方是前者的dev文件夹挂载类型为tmpfs,后者则是ramfs,后者是可以用的,但是不知道是不是由于这个导致的,ramfs和tmpfs的区别可以参考文章:http://www.cnblogs.com/dosrun/p/4057112.html。我直接使用后面的rcS文件,系统正常启动:

     device=eth0, addr=192.168.1.194, mask=255.255.255.0, gw=192.168.1.1,
     host=192.168.1.194, domain=, nis-domain=(none),
     bootserver=0.0.0.0, rootserver=192.168.1.106, rootpath=
VFS: Mounted root (nfs filesystem) on device 0:12.
Freeing init memory: 272K
/etc/init.d/rcS: line 38: /etc/rc.d/init.d/netd: not found
/etc/init.d/rcS: line 42: /etc/rc.d/init.d/httpd: not found
/etc/init.d/rcS: line 49: /etc/init.d/ifconfig-eth0: not found

Please press Enter to activate this console.  
/ # 

提示netd、httpd和ifconfig-eth0没有找到,这个很正常,因为没有这些命令,/etc/rc.d/init.d/这个文件夹都没有。我们可以自己创建这些文件,其实这些文件不是必须的,可以先去掉rcS文件中调用这些命令的代码,后面需要的时候再加上也不妨。

补充一点,init进程至少要用到设备文件:/dev/console 、/dev/null ,所以要创建这两个设备文件(网上的说法,具体为什么我还不太清楚,在kernel_init函数里面有一段“Open the /dev/console on the rootfs, this should never fail ”,貌似console是必须的,null我没看见哪里有需要,但是我不创建这两个文件貌似也没有问题):

sudo mknod console c 5 1
sudo mknod null c 1 3

有人就疑惑了,不是上面挂载了tmpfs到了dev目录下去了,不就覆盖了我们创建在dev文件夹下的内容了么,我猜测是在挂载tmpfs到dev之前系统就需要console和null设备文件,挂载了tmpfs之后dev下也有console和null设备,似乎解释的通哈。

现在创建好了“/etc/init.d/rcS”文件,可以在“/etc/init.d/rcS”文件里面添加自己需要执行的命令,例如系统初始化命令和开机自启动任务等等。基本上根文件系统就能用了,etc文件夹就是系统配置文件夹,etc文件夹中的内容可以根据需要添加。

然后我们如果要运行自己程序的话还需要搭建/lib文件夹。/lib中都是系统使用的C语言动态链接库文件,我们不需要自己编译得到这些库文件,可以把交叉编译器里面的库文件拷贝过来就行了。如果没有这些链接库文件的话很多程序是没法运行的,这个问题放在下一篇文章里在介绍。