您尚未登录。

楼主 #1 2018-08-21 20:28:27

xinxiaoci
会员
注册时间: 2018-04-18
已发帖子: 71
积分: 71

Linux_kernel 简单跟踪分析002 最小根文件系统的制作(网络文件系统挂载)

u-boot 的主要的目的是启动内核

内核的主要目的是挂载文件系统并启动应用程序。

内核怎么启动第一个应用程序:

分析代码:

asmlinkage void __init start_kernel(void)
    static void noinline __init_refok rest_init(void)
        kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); // 启动 kernel_init 内核线程
                prepare_namespace();    // 挂载根文件系统
                init_post();            // 启动应用程序
               
            分析 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)    // 打开标准控制台 对应 printf
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");

    (void) sys_dup(0);    // 拷贝标准控制台"/dev/console" 对应 scanf 
    (void) sys_dup(0);    // 拷贝标准控制台"/dev/console" 对应 err 
    // 我们这里控制台用的是串口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) {         // 搜索 execute_command 它对应的是 "init=" 后面的参数
        run_init_process(execute_command); // 执行 u-boot 传入的参数 init=/linuxrc  run_init_process 函数,成功执行便不会再返回
        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.");
}
-------------------------------------------------

想要知道这个文件系统有哪些东西,我们就得跟着 /sbin/init 进程一路分析下去,看些这个进程需要哪些东西。

根文件系统中许多命令 ls cp ... 这些命令都是应用程序,但是Linux中有许多命令,我们不可能都要把这些命令重新写一遍,
在嵌入式系统里面,我们有一个东西叫做 busybox 它是 ls cp cd 这些命令的合集,我们去编译 busybox ,
会得到一个 busybox 应用程序,ls cp cd 这些命令都是 busybox 的一个链接,到我们执行这些命令时实际运行的是 busybox 应用程序。

我们可以执行如下命令来验证:

ls -l /bin/ls
ls -l /bin/cp

busybox ls 也能实现 ls 的功能


在嵌入式系统中,/sbin/init 也是到 busybox 的链接
ls -l /sbin/init
所以我们想要知道 /sbin/init 进程做了哪些事情,我们需要分析 busybox 的源码。

busybox 中每一个命令都对应一个 .c 文件,我们来分析 init.c
busybox -> init_main
    parse_inittab(); // 解析 init 进程 表
        file = fopen(INITTAB, "r");    //打开文件INITTAB 表  "/etc/inittab"    /* inittab file location */  inittab 的格式请查看文档 /examples/inittab <id>:<runlevels>:<action>:<process>
        文件打开失败,解析默认项
            new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
        文件打开成功,解析配置文件
        new_init_action    // 解析配置文件 把解析的内容放到一个链表中
       
    run_actions(SYSINIT); // 运行这一类动作
    run_actions(WAIT);
    run_actions(ONCE);
   
   
分析:
new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
替换宏:
const char bb_default_login_shell[] ALIGN1 = LIBBB_DEFAULT_LOGIN_SHELL;
#define LIBBB_DEFAULT_LOGIN_SHELL      "-/bin/sh"

#define ASKFIRST    0x004

# define VC_2 "/dev/tty2"


new_init_action(ASKFIRST, "-/bin/sh", /dev/tty2);
------------------------
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);
}



struct init_action {
    struct init_action *next;    // 链表指针
    int action;                    // 执行时机
    pid_t pid;                    // process ID 进程ID
    char command[INIT_BUFFS_SIZE];    // 命令或脚本
    char terminal[CONSOLE_NAME_SIZE];    // 终端
};
------------------------
       
       
inittab 格式: 详见源码下文档 /examples/inittab
<id>:<runlevels>:<action>:<process>
id : id=/dev/id,用做终端:stdin, stdout, stderr :printf , scanf , err 等
runlevels     : 忽略
action        : 执行时机 sysinit, respawn, askfirst, wait, once,restart, ctrlaltdel, and shutdown.
process        : 应用程序或脚本

最小根文件系统需要:
1. dev/console、 dev/null    // 控制台
2. init 应用程序 -> busybox // 第一个应用程序
3. 配置文件 /etc/inittab    // 指定需要启动的程序表 指定ID 启动方式 和 时机
4. 配置文件中的应用程序        // 应用程序
5. 应用程序所需要的 C 库    // 库


配置和编译busybox

解压 tar xjf busybox-1.7.0.tar.bz2
查看 源文件目录下的 INSTALL 文件 查看编译方法
  make menuconfig     # This creates a file called ".config"
  make                # This creates the "busybox" executable
  make install        # or make CONFIG_PREFIX=/path/from/root install
 
问题 : make menuconfig 报错
Makefile:405: *** mixed implicit and normal rules: deprecated syntax
Makefile:1242: *** mixed implicit and normal rules: deprecated syntax

方法:

在makefile中将405行代码
config %config: scripts_basic outputmakefile FORCE
改为
%config: scripts_basic outputmakefile FORCE
在makefile中将1242行代码
/ %/: prepare scripts FORCE
改为
%/: prepare scripts FORCE

配置:参考韦老师书本 17.2.3 章
    Busybox Setting -->
        Busybox Library Tuning -->
            [*、] Tab completion        // 命令行 tab 补全
           
    Build Options --->
        [ ] Build BusyBox as static binary (no shared libs) // 静态链接, 我们用动态库
    Archival Utilities --> // 压缩命令
        // 保持默认
    Linux Module Utilities ---> // 模块加载命令
        // 保持默认
    Linux System Utilities ---> // 支持mdev , 这可以很方便的构造 /dev 目录
        // 保持默认
    Networking Utilities ---> // 网络
        // 保持默认
       
保存退出

make CROSS_COMPILE=arm-linux-    // 指定工具链 编译 或者在 Makefile 中定义。

安装
    mkdir -p /work/first_fs
    // make CONFIG_PREFIX=/work/first_fs install //安装到指定目录  如果没有修改 Makefile 文件 的编译工具链 请使用下面的命令
    make CROSS_COMPILE=arm-linux- CONFIG_PREFIX=/work/first_fs install
   
查看
    cd /work/first_fs
    ls -l
   
    drwxrwxr-x 2 book book 4096 6月  18 08:20 bin
    lrwxrwxrwx 1 book book   11 6月  18 08:20 linuxrc -> bin/busybox    // u-boot 传递进来第一运行的程序 Linuxrc
    drwxrwxr-x 2 book book 4096 6月  18 08:20 sbin
    drwxrwxr-x 4 book book 4096 6月  18 08:20 usr
   
    ls -l bin/   // 这些命令都是指向 busybox 的链接

构造最小根文件系统
   
    创建设备节点:
   
    先查看本机系统的控制台
    ls /dev/console /dev/null -l

    crw------- 1 root root 5, 1 6月  18 07:26 /dev/console
    crw-rw-rw- 1 root root 1, 3 6月  18 07:26 /dev/null
   
    c 字符设备 5 主设备号 1 次设备号


    mkdir dev
    cd dev/
    sudo mknod console c 5 1
    sudo mknod null c 1 3
    ls -l
   
    创建 inittab 表:
    cd ..    // 返回根目录
    mkdir etc
    vi etc/inittab    添加 console::askfirst:-/bin/sh 项 启动一个 -/bin/sh 并显示到终端 console
   
    安装 C 库:
    创建 lib 目录
    mkdir lib
    cd /work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib
    cp *.so* /work/first_fs/lib/ -d  // -d 参数表示保持链接 不加会把链接也拷贝成链接对应的文件,这样体积会很大
   
最小根文件系统已经做好了,怎么烧写到开发板?
    我们需要做一个映像文件,怎么制作?
    参考书本 17.4.4
    编译 yaffs 工具 ,用编译后的 工具 打包制作根文件系统
   
    cd /work/systems/
    tar xjf yaffs_source_util_larger_small_page_nand.tar.bz2
    cd Development_util_ok/yaffs2/utils/
    make
    sudo cp mkyaffs2image /usr/local/bin/
    sudo chmod +x /usr/local/bin/mkyaffs2image
   
    打包根文件系统
   
    cd /work/
    mkyaffs2image first_fs first_fs.yaffs2
   
    烧写 first_fs.yaffs2
   

    发现 不能执行 ps 命令
    ps: can't open '/proc': No such file or directory
   
    系统的当前状态保存在一个虚拟的 proc 文件系统,并且挂载在 /proc 目录
    在单板上运行
    mkdir proc
    mount -t proc none /proc
    然后再执行 ps 就可以查看进程了
   
    proc 是一个虚拟的文件系统,由内核提供的
   
    cd proc
    cd 1
    ls -l fd
   
    lrwx------    1 0        0              64 Jan  1 00:19 0 -> /dev/console    // 标准输入
    lrwx------    1 0        0              64 Jan  1 00:19 1 -> /dev/console    // 标准输出
    lrwx------    1 0        0              64 Jan  1 00:19 2 -> /dev/console    // 标准错误

   

完善文件系统
    mkdir proc
    添加自动挂载功能
    vi etc/inittab
    添加启动项
    ::sysinit:/etc/init.d/rcS
   
    创建
    mkdir etc/init.d
    vi etc/init.d/rcS
   
    添加
    mound -t proc none /proc
    添加可执行权限
    chmod +x etc/init.d/rcS
   
    或者
    vi etc/init.d/rcS
    # mound -t proc none /proc
    mount -a // 读取 /etc/fstab 中的内容
    根据 etc/fstab 中的内容来挂载
    vi etc/fstab
   
    # <file system> <mount point>   <type>  <options>       <dump>  <pass>
    proc            /proc           proc    defaults        0       0
   
    cat /proc/mounts    // 查看系统已经挂在的文件系统
   
   
继续完善文件系统
   
    在 /dev 目录下有两个设备,这是我们手动创建的
    # ls dev/ -l
    crw-r--r--    1 0        0          5,   1 Jan  1 00:01 console
    crw-r--r--    1 0        0          1,   3 Jun 18  2018 null
    如果一个系统里面有成千上万个设备,我们不可能全部手工来创建,这里介绍 udev
    udev : 自动创建 /dev 下的设备节点,busybox 中有一个 udev 的简化版本 mdev
    我们来介绍 mdev 的用法,查看文档 busybox-1.7.0\docs\mdev.txt 或书本 17.4.2.2
   
    mkdir sys
   
    vi /etc/fstab
    添加       
    sysfs            /sys            sysfs    defaults        0       0
    tmpfs            /dev            tmpfs    defaults        0       0
   
    vi    /etc/init.d/rcS
    添加
    mkdir /dev/pts
    mount -t devpts devpts /dev/pts
    echo /sbin/mdev > /proc/sys/kernel/hotplug    // 热拔插 指向 /sbin/mdev 当系统设备有变化时,会调用 /sbin/mdev
    mdev -s // 创建现有设备节点

    重新烧录
    ls dev/     查看设备节点发现自动创建了很多
    cat /proc/mounts    // 查看系统已经挂载的文件系统
   
到此我们已经制作了一个较为完善的最小根文件系统了!

扩展知识:
    果我们想使用其他的文件系统 如 jffs2 韦老师书本17.4.5
    jffs2 文件系统适用于 NOR Flash ,也可以使用在 NAND Flash 上
   
    cd work/GUI/xwindow/X/deps/
    tar xzvf zlib-1.2.3.tar.gz
    cd zlib-1.2.3/
    ./configure --shared --prefix=/usr //  --shared 表示动态库 --prefix=/usr 表示安装目录
    make
    sudo make install
   
    zlib 是一个压缩库,编译 jffs2 工具要用到
    编译 mkfs.jffs2
   
    cd /work/tools/
    tar vxjf mtd-utils-05.07.23.tar.bz2
    cd mtd-utils-05.07.23/util/
    make
    sudo make install
   
    制作文件系统镜像
   
    cd /work/
    mkfs.jffs2 -n -s 2048 -e 128KiB -d first_fs -o first_fs.jffs2 // -s 页大小 -e 块大小 -d 目录 -o 输出文件名
   
    烧写后不能正常启动,还是以yaffs2 方式识别文件系统,我们可以修改启动参数 强制以 jffs2 方式启动
    set bootargs noinitrd root=/dev/mtdblock3 rootfstype=jffs2 init=/linuxrc console=ttySAC0
    save
    boot
我们每次修改文件系统都要重新烧写,有没有更好的办法?
    答:有,NFS 网络文件系统
    ifconfig eth0 up
    ifconfig eth0 192.168.1.12 //同一网段
   
    NFS:
    a. 从 Flash 上启动根文件系统,在用命令挂接NFS
    b. 直接从NFS 启动
   
    挂接 NFS 条件:
    1. 服务器允许那个目录可以被挂接
       
        指定被挂接的目录
            修改 sudo vi /etc/exports
            /work/first_fs  *(rw,sync,no_root_squash)
        重启启动NFS服务
            sudo /etc/init.d/nfs-kernel-server restart
        本机挂接测试
            sudo mount -t nfs 192.168.100.101:/work/first_fs /mnt
            -----------------------------
            a. 首先创建挂载点: mkdir /mnt/test1
          b. 然后挂载nfs: mount -t nfs 192.168.1.3:/nfs_test /mnt/test1
          c. 挂载成功之后通过 df -h 可以查看挂载的情况,nfs可用空间就是服务端/nfs_test目录所能使用的最大空间
            d. 卸载nfs和普通文件系统一样,使用: umount /mnt/test1
            -----------------------------
           
        开发版挂接
            mkdir mnt
            mount -t nfs -o nolock 192.168.100.101:/work/first_fs /mnt
------------------------------------------------------------       
直接从NFS 启动文件系统:
    1. 服务器IP 根文件目录
    2. 裸板IP
   
    修改启动参数,参考内核源码\Documenttation\nfsroot.txt
    bootargs=noinitrd root=/dev/mtdblock3 rootftype=jffs2 init=/linuxrc console=ttySAC0
   
    set bootargs noinitrd root=/dev/nfs nfsroot=192.168.123.200:/work/first_fs ip=192.168.123.201:192.168.123.200:192.168.123.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0
    save
   
也可以开启路由器的NFS
http://www.right.com.cn/forum/thread-182695-1-1.html

我的路由器可以直接在 USB应用程序 -> NFS 服务器

将ubuntu 下的busybox 创建的根文件系统拷贝到路由器的U盘上
sudo cp -r /work/first_fs/* first_fs/ -d
登陆路由器添加共享的文件夹
vi /etc/exports
/media/upan/first_fs      192.168.123.0/24(rw,async,insecure,no_root_squash,no_subtree_check)

串口登陆开发板,修改保存参数
    set bootargs noinitrd root=/dev/nfs nfsroot=192.168.123.1:/media/upan/first_fs ip=192.168.123.201:192.168.123.1:192.168.123.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0
    save
这样路由器下的电脑都可以把这个网络文件系统挂载在自己的电脑上,然后进行驱动调试,只需要把开发板网线连接路由器上然后通过telnet登陆开发板即可。
--------------------------------------------------------------   
   
测试:在主机上编译文件,在开发板上运行。
    vi hello.c
   
    #include <stdio.h>
    int main()
    {
        printf("xiaoci,hello!\n");
        return 0;
    }
   
arm-linux-gcc -o hello hello.c

在开发板对应目录下执行
./hello

xiaoci,hello!

文件系统更详细配置请看:http://bbs.100ask.net/forum.php?mod=viewthread&tid=17389

修改密码出现:
passwd:unknown uid 0

解决方法:

自动生成是使用了busybox提供的adduser工具和passwd工具。
在文件系统正常运行起来后,使用adduser命令,使用方法为:

#adduser root

然后就会在etc目录下自动生成passwd 、group和shadow3个文件。但是运行该命令后会打印出如下消息:

passwd:unknown uid 0

这表示不能为该用户设置密码,此时你会发现要passwd命令也无法使用。
解决的办法是,打开passwd文件,其内容为:
root:x:1000:1000:Linux User…:/home/root:/bin/sh

将用户ID和组ID均更改为0

打开group文件,其内容为:

root:x:1000:

同样将组ID改为0
然后,passwd命令就可以正常使用了。这时为root用户设置口令:

#passwd root

根据提示输入密码。其中,root用户登陆后的目录可以手动进行更改。

参考:https://www.cnblogs.com/liangwode/p/5710343.html?utm_source=itdadao&utm_medium=referral
   


telnet 远程登陆

/etc/init.d/rcS
中加上
telnetd& // 开机启动 telnetd 服务

就可以用 telnet IP 命令进行登陆 输入账号 密码
这样就不需要串口了
但是 telnet 无法显示内核打印信息

方法1:
可以通过 cat /proc/kmsg& 来查看 内核输出信息
ps
kill 11021

方法2:
dmesg -c //显示最新的信息 并清除

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn