构造根文件系统

一、流程分析

回顾Linux内核启动流程中提到的流程:

start_kernel:

  1. 中断初始化、时钟初始化、打印内核版本信息

  2. 处理uboot传进来的启动信息

  3. 处理命令行信息

  4. 调度初始化等各种初始化

  5. 挂接根文件系统rest_init

  6. 创建kernel_init线程

    1. 早期的用户空间初始化prepare_namespace

      1. 挂载根文件系统mount_root
    2. 初始化某些设备init_post

      1. 打开/dev/console设备

      2. 标准设备设置

        1
        2
        (void) sys_dup(0);
        (void) sys_dup(0);
      3. 执行应用程序

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        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.");

发现内核有如下现象:

  1. 打开设备/dev/console(即终端,在开发板上为串口0),并将标准输入输出错误信息指向这里

  2. 如果uboot传入的命令行参数(bootargs)中有init参数时,会执行该程序,通常该程序不会返回

    init参数最后会赋值给execute_command,即上述代码的第1行

    通常,init=/linuxrc,实际上是链接到了busybox

  3. 如发生意外,会依次查找以下程序并执行(通常程序不会返回):

    • /sbin/init
    • /etc/init
    • /bin/init
    • /bin/sh
  4. 若上述程序全部执行失败,则输出No init found. Try passing init= option to kernel.

二、init进程分析

busybox是各种常用命令的组合,一般来说常见命令都是通过链接来间接调用busybox并传入相关参数。如:ls实际是busybox ls

通过上述分析,最后会进入busybox中的init程序,经过搜索,得出会进入busybox中的init_main函数:

  1. init_main

  2. 根据内核中的设置初始化控制台(sys_dup已经指定)

  3. parse_inittab

    1. 打开/etc/inittab配置文件,若无则选取默认项

      详细格式见:busybox/examples/inittab.txt


      :::


      :会自动加上/dev/前缀并用于终端(stdin、stdout、stderr),可省略

      :忽略

      :何时执行,包括:sysinit, respawn, askfirst, wait, once , restart, ctrlaltdel,shutdown

      :应用程序或脚本

      默认项反向解析后得出配置文件:

      1
      2
      3
      4
      5
      6
      7
      8
      ::ctrlaltdel:reboot
      ::shutdown:umount -a -r
      ::restart:init
      ::askfirst:-/bin/sh
      tty2::askfirst:-/bin/sh
      tty3::askfirst:-/bin/sh
      tty4::askfirst:-/bin/sh
      ::sysinit:/etc/init.d/rcS(初始化脚本文件,其他相关的配置可以在这这个脚本中运行)
    2. 解析配置文件

    3. 调用new_init_action函数

      1
      static void new_init_action(int action, const char *command, const char *cons)

      action:脚本中的,执行时机

      command:脚本中的,应用程序或脚本

      cons:脚本中的,用作终端

      1. 创建一个init_action结构存储action、command、cons等数据
      2. 将该结构放入init_action_list链表
  4. 调用run_actions函数一次执行脚本中参数所代表的一类应用程序或脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /* Now run everything that needs to be run */
    /* First run the sysinit command */
    run_actions(SYSINIT);//首先执行SYSINIT类应用程序或脚本
    /* Next run anything that wants to block */
    run_actions(WAIT);//接着执行WAIT类应用程序或脚本
    /* Next run anything to be run only once */
    run_actions(ONCE);//再执行ONCE类应用程序或脚本
    /* Now run the looping stuff for the rest of forever */
    while (1) {
    /* run the respawn stuff */
    run_actions(RESPAWN);//执行RESPAWN类应用程序或脚本
    /* run the askfirst stuff */
    run_actions(ASKFIRST);//执行ASKFIRST类应用程序或脚本
    /* Wait for a child process to exit */
    wpid = wait(NULL);
    while (wpid > 0) {
    ......
    }
    }

    SYSINIT、WAIT类:

    1. 从之前的init_action_list链表中取出对应的项
    2. 通过waitfor函数运行对应应用程序或脚本
      1. 调用run函数创建子进程运行对应应用程序或脚本
      2. 通过waitpid函数等待子进程结束
    3. 调用delete_init_action从链表中删除

    ONCE类:

    1. 从之前的init_action_list链表中取出对应的项
    2. 调用run函数创建子进程运行对应应用程序或脚本
    3. 调用delete_init_action从链表中删除

    RESPAWN类:

    1. 调用run函数创建子进程运行对应应用程序或脚本
    2. 子进程退出后再次调用run函数

    ASKFIRST类:

    1. 调用run函数创建子进程运行对应应用程序或脚本
    2. 子进程退出后打印:Please press Enter to activate this console,并等待回车
    3. 再次调用run函数

总结

最小根文件系统需要有:

  1. /dev/console文件、/dev/null文件

    /etc/inittab配置文件中没有设置项时会默认到/dev/null文件

  2. init程序(一般来源于busybox)

    /linuxrc、/sbin/init、/etc/init、/bin/init、/bin/sh这些文件最后均会链接到busybox

  3. 配置文件/etc/inittab

  4. 配置文件中指定的应用程序

  5. C库

三、配置编译busybox

可以参考busybox/INSTALL中的说明

  1. make defconfig采取默认配置项

  2. make menuconfig配置

或者直接修改makefile,找到CROSS_COMPILE设置为交叉工具链:arm-linux-

  1. make编译

  2. make CONFIG_PREFIX=目录:指定编译结果存放的目录

make install 为安装到PC机上

eg:

在/home/null/nfs_root目录下安装编译出来的busybox

make install CONFIG_PREFIX=/home/null/nfs_root

四、构建根文件系统

最精简的根文件系统

  1. 创建设备文件/dev/console/dev/null

    通过观察PC机上相同的设备来创建(在/dev/目录下):

    1
    2
    sudo mknod console c 5 1
    sudo mknod null c 1 3
  2. 创建配置文件/etc/inittab(最小的配置文件)

    1
    console::askfirst:-/bin/sh
  3. 安装C库(glibc库)

    在开发板上只需要加载器和动态库,假设要构建的根文件系统目录为/ work/nfs_root/fs_mini,操作如下:

    1
    2
    3
    mkdir -p /work/nfs_root/fs_mini/lib
    cd /work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib
    cp *.so* /work/nfs_root/fs_mini/lib -d
  4. 通过mkyaffs2image工具打包成镜像下载至开发板(需要自己编译,支持NorFlash)

    1
    2
    mkyaffs2image test_fs/ test_fs.yaffs2
    #mkyaffs2image 待打包文件 生成的镜像

完善根文件系统

挂载相关文件系统(2种方法)

  1. 手动创建proc目录并挂载虚拟文件系统

    1
    2
    mkdir proc#创建
    mount -t proc none /proc#挂载

    默认配置文件中会执行/etc/init.d/rcS脚本文件,也可以在这里边执行挂载命令mount -t proc none /proc挂载proc虚拟文件系统,创建后记得chmod +x /etc/init.d/rcS

  2. mount -a(该命令也是在/etc/init.d/rcS中)

    1. 该命令会读出/etc/fstab配置文件中的内容来挂载文件系统

    etc/fstab文件被用来定义文件系统的“静态信息”,这些信息被用来控制 mount命令的行为。/etc/fstab配置文件格式:


    device mount-point type options dump fsck order


    device :要挂载的设备

    mount-point:挂载到哪,挂载点

    type:文件系统类型

    options:挂接参数,以逗号隔开

    dump:

    fsck:

    order:


    例子:

    1
    2
    3
    #device mount-point type options dump fsck order
    proc /proc proc defaults 0 0
    tmpfs /tmp tmpfs defaults 0 0
2. 可以通过`cat /proc/mounts`命令来查看当前系统中已经挂载的文件系统

mdev

linux操作系统中有udev,它能够自动在/dev/下创建设备节点。在嵌入式中,有一个简化版本:mdev(busybox中自带),使用方法:

  1. 挂载相关文件系统(这里采用/etc/fstab配置文件,直接添加)

    命令:

    1
    2
    mount -t sysfs sysfs /sys
    mount -t tmpfs mdev /dev

    /etc/fstab配置文件中添加:

    1
    2
    sysfs /sys sysfs defaults 0 0
    tmpfs /dev tmpfs defaults 0 0
  2. 执行相关命令(这里采用/etc/init.d/rcS脚本文件,直接添加)

    1
    2
    3
    4
    mkdir /dev/pts
    mount -t devpts devpts /dev/pts
    echo /sbin/mdev > /proc/sys/kernel/hotplug#热插拔
    mdev -s#先将系统中已有的设备节点创建出来

五、NFS服务

服务器设置——PC机

  1. 安装NFS服务、创建共享文件夹

    1
    2
    3
    sudo apt-get install nfs-kernel-server
    mkdir -p nfs_root
    chmod 777 -R nfs_root#修改权限
  2. 修改NFS配置文件/etc/exports(自行修改nfs挂载目录)

    1
    sudo vim /etc/exports

    添加如下内容(绝对路径)

    1
    2
    3
    4
    5
    6
    7
    /home/null/NFS_Share *(sync,rw,insecure,no_subtree_check,no_root_squash)
    #挂载目录 所有人都可以访问
    #rw:读/写权限
    #sync:数据同步写入内存和硬盘
    #insecure:端口号大于1024时的非法设置
    #no_root_squash:服务器允许远程系统以root特权存取该目录
    #no_subtree_check:关闭子树检查
  3. 重启服务

    1
    2
    3
    4
    5
    service rpcbind start rpcbi
    sudo service nfs-kernel-server restart

    sudo /etc/init.d/nfs-kernel-server start
    sudo /etc/init.d/portmap start
  4. 检查是否配置成功(自己挂接自己)(绝对路径)

    1
    2
    3
    sudo mount -t nfs -o nolock localhost://home/null/NFS_Share/nfsroot/tmp/fs_mini_mdev /mnt/nfs_test/#挂接
    ls -l /mnt/nfs_test#检查
    sudo umount /mnt/nfs_test#卸载

客户端设置——开发板

启动linux后手动挂接

  1. 开发板和服务器能够互ping

  2. 挂接(绝对路径)

1
mount -t nfs -o nolock 192.168.1.111:/home/null/NFS_Share/nfsroot/tmp/fs_mini_mdev /mnt

这里192.168.1.111为服务器ip地址,两者需要在同一网段

  1. 查看

    1
    ls /mnt

启动linux后自动挂接

这里是利用了启动后的初始化脚本/etc/init.d/rcS自动挂载:

修改内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ifconfig eth0 192.168.1.17

mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
if [ ! -e /etc/pointercal ]
then
/bin/ts_cal.sh
fi
/bin/qpe.sh &
mount -t nfs -o nolock 192.168.1.111:/home/null/NFS_Share /mnt &#增加这一条
#mount -t nfs -o nolock 服务器ip:挂载目录(绝对目录) 开发板上的挂载点

如果是通过删除脚本再新建的,最后务必修改执行权限:

1
chmod 777 /etc/init.d/rcS

启动linux前自动挂接(必须要路由器)

  1. uboot中修改启动参数bootargs

    1
    set bootargs noinitrd root=/dev/nfs nfsroot=192.168.2.101:/home/null/nfs_root ip=192.168.2.17:192.168.2.101:192.168.2.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200;save

    nfsroot=服务器ip:nfs绝对路径 ip=开发板ip:服务器ip:网关:子网掩码::网卡:off

    注:原来从flash上启动的参数为

    1
    2
    3
    set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200;save

    mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.2.111:/home/null/NFS_Share/ /mnt &