美文网首页
orangepi构建根文件系统

orangepi构建根文件系统

作者: vencol | 来源:发表于2020-08-08 18:26 被阅读0次

    目录结构说明

    一、基础说明

    1、环境介绍

    2、linux的根文件结构

    3、瑞士军刀busybox

    4、busybox源码准备

    二、静态编译并测试busybox

    1、配置busybox静态编译选项

    2、编译busybox

    3、安装busybox

    4、测试busybox

    三、动态编译并测试busybox

    1、配置、编译、安装busybox

    2、测试busybox

    四、构建根文件系统

    1、根文件系统的目录

    2、具体创建步骤

    3、写成根文件系统创建脚本

    五、无法解析域名

    目录结构说明

    一、基础说明

    1、环境介绍

       首先这里我们需要安装一些基础的编译环境,安装tftp服务器,安装nfs服务器等,这里我们可以参考文章《orangpione利用usb共享网络(RNDIS)实现tftp加载内核挂载到NFS根文件系统》中的环境准备章节,自行安装相关服务,在这里不展开。后续的开发是基于tftp和nfs传输的条件下进行,如果没有实现相关传输,也可以通过烧录sd卡的形式进行。

    2、linux的根文件结构

    目录结构
       linux的根文件目录遵循的FHS,而FHS是Filesystem Hierarchy Standard(文件系统层次化标准)的缩写,多数Linux版本采用这种文件组织形式,类似于Windows操作系统中c盘的文件目录,FHS采用树形结构组织文件。FHS定义了系统中每个区域的用途、所需要的最小构成的文件和目录,同时还给出了例外处理与矛盾处理。
       这里如果我们想深入了解根文件系统的结构,我们可以到下面的访问网址https://refspecs.linuxfoundation.org/fhs.shtml,也可以从该网址下载具体的pdf说明文档,现在最新的文档是3.0的版本,地址是https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.pdf,文档中有各个目录的具体介绍,这里不详细介绍,后面我们制作根文件系统的时候,会对应的说明

    3、瑞士军刀busybox

      BusyBox 是一个集成了三百多个最常用Linux命令和工具的软件。BusyBox 包含了一些简单的工具,例如ls、cat和echo等等,还包含了一些更大、更复杂的工具,例grep、find、mount以及telnet。有些人将 BusyBox 称为 Linux 工具里的瑞士军刀。简单的说BusyBox就好像是个大工具箱,它集成压缩了 Linux 的许多工具和命令,也包含了 Android 系统的自带的shell。简单来说就是我们在linux系统中常用的命令都是可以从busybox里面编译集成的,比如cp、mkdir、ls、mv、rm、adduser、deluser等等

    4、busybox源码准备

    1. 新建目录mkdir mkrootfs
    2. 进入目录mkrootfs,并且下载busybox源码
      我们从码云下载源码,上面的busybox是github上的源码镜像,每天更新一次
      git clone https://gitee.com/mirrors/busyboxsource.git busybox,源码下载好后,我们在源码的上层mul
    3. 在mkrootfs目录下,新建脚本vi build.sh,这里主要是把busybox的编译输出到目录bboxbuild目录中,把busybox的安装到目录rootfs中。脚本的ARCH和CROSS_COMPILE定义了相关架构和需要用到的工具链
    #! /bin/bash
    JOBNUM=4
    NPWD=`realpath .`
    
    export ARCH=arm
    export CROSS_COMPILE=$NPWD/../gcc/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
    
    if [ $1_x == cp_x ];then
            echo $PWD
            cp $NPWD/rootfs/build/linux-custom/arch/arm/boot/zImage ~/tftp
            cp $NPWD/rootfs/build/linux-custom/arch/arm/boot/dts/sun8i-h3-orangepi-one.dtb ~/tftp
    elif [ $1_x == busybox_x ];then
            cd $NPWD/../busybox
            echo $PWD
            mkdir -p $NPWD/bboxbuild
            if [ $2_x == install_x ];then
                    mkdir -p $NPWD/rootfs
                    make O=$NPWD/bboxbuild CONFIG_PREFIX=$NPWD/rootfs  $2 -j$JOBNUM
            else
                    if [ $3_x == _x ];then
                            make O=$NPWD/bboxbuild $2 -j$JOBNUM
                    else
                            make O=$NPWD/bboxbuild $2 $3
                    fi
            fi
    else
            echo "help"
    fi
    

    二、静态编译并测试busybox

    1、配置busybox静态编译选项

    关于配置busybox这里我们可以根据自己的需求配置相关的小工具,静态编译主要关注的选项是CONFIG_STATIC,对应于下图Build static binary (no shared libs),执行命令./build.sh busybox menuocnfig

    static

    2、编译busybox

    执行./build.sh busybox对busybox进行编译,编译完成后出现类似提示Static linking against glibc,这里是glibc还是uclibc,具体看工具链编译时指定的c库是什么

    build static

    3、安装busybox

    使用./build.sh busybox install安装busybox到脚本目录下的rootfs目录,这是因为我们在脚本中指定了安装目录就是当前的rootfs目录,可以看到我们安装的目录rootfs,及其下方的

    static rootfs

    4、测试busybox

    这里测试我们通过之前文章的nfs根文件的映射进行测试,由于我们之前创建的nfs文件系统时通过软连接的方式进行连接的,这里我们只需要把,软件重新制定到mkrootfs目录下的rootfs文件夹即可
    ln -snf mkrootfs/rootfs nfs把nfs连接重新连接到nfs根文件系统中,由于开发板的配置本来就是nfs根文件目录,我们这里只需要重启开发板就可以加载到nfs了

    1. can't run '/etc/init.d/rcS': No such file or directory 和 can't open /dev/tty2: No such file or directory ,这里是因为busybox上电会先执寻找 /etc/init.d/rcS脚本,但是因为我们的根文件系统只有busybox编译出的东西,没有脚本也没有终端设备目录,我们可以先建立一个dev目录在rootfs中,然后重启开发板


      image.png
    2. 虽然还是提示can't run '/etc/init.d/rcS': No such file or directory,但是很明显,已经进入了一个可以使用的shell终端了,后续具体的配置在跟文件系统制作章节具体介绍,这里我们先研究动态编译busybox并加载测试


      image.png

    三、动态编译并测试busybox

    1、配置、编译、安装busybox

    1、关于配置busybox这里我们可以根据自己的需求配置相关的小工具,busybox的默认配置就是动态编译,只要把CONFIG_STATIC选项去掉即可,对应于Build static binary (no shared libs),执行命令./build.sh busybox menuocnfig配置去掉CONFIG_STATIC选项
    2、通过./build.sh busybox编译动态的busybox,这里会出现Trying libraries:表面是动态连接到后面的库

    image.png
    3、移除之前静态的rootfssudo rm -r rootfs
    4、通过./build.sh busybox install安装动态的busybox到rootfs
    5、在rootfs创建dev目录mkdir -p rootfs/dev

    2、测试busybox

    这里的测试方法和前面介绍的动态busybox一样,我们先重启开发板

    1. Kernel panic - not syncing: No working init found,这里是因为我们使用了动态连接,但是没有把相应的动态库放入根文件系统,导致busybox运行时没有对应的库,同时我们可以看到,如果我们在没有指定init的情况下,内核会自动的按照/sbin/init、/etc/init、/bin/init、/bin/sh顺序去查找初始程序并运行


      image.png
    2. 加载动态库
    1. 通过readelf查找busybox的动态库arm-linux-gnueabihf-readelf -d rootfs/bin/busybox | grep NEEDED
      vencol@pcvencol:~/code/self$ arm-linux-gnueabihf-readelf -d rootfs/bin/busybox | grep NEEDED
      0x00000001 (NEEDED) Shared library: [libm.so.6]
      0x00000001 (NEEDED) Shared library: [libresolv.so.2]
      0x00000001 (NEEDED) Shared library: [libc.so.6]

    2.通过objdump查找busybox的动态库arm-linux-gnueabihf-objdump -x rootfs/bin/busybox | grep NEEDED
    vencol@pcvencol:~/code/self$ arm-linux-gnueabihf-objdump -x rootfs/bin/busybox | grep NEEDED
    NEEDED libm.so.6
    NEEDED libresolv.so.2
    NEEDED libc.so.6

    3.通过strings 查找busybox的动态库strings busybox | grep ^lib
    vencol@pcvencol:~/code/self$ strings rootfs/bin/busybox | grep ^lib
    libm.so.6
    libresolv.so.2
    libc.so.6
    lib32
    lib64

    4.通过ldd查找,但是这里交叉工具链中并没有提供ldd工具,需要在开发板上执行,因为主机架构一般和开发板不同,所以才要交叉编译

    最后我们可以确定动态库如下,这里需要注意的是ld-linux-armhf.so.3和ld-2.25.so,因为这两个库是执行时连接需要用到的库,无论是从readlf、objdump还是string都没有体现出来,很多小伙伴,也是因为缺少工具链的这里动态连接库,导致动态连接的时候失败,这个只能通过ldd才能体现出来。但是几乎所有通过交叉工具链动态编译的软件,都会依赖这两个库,所以,如果不确定的时候就把他们都加到根文件的lib目录吧。一般对应的库文件都会出现在交叉工具链了目录下的arm-linux-gnueabihf/libc/lib路径下

    vencol@pcvencol:~/code/self$ ll rootfs/lib/
    total 20816
    drwxrwxr-x 2 vencol vencol     4096 Aug  1 05:55 ./
    drwxrwxr-x 7 vencol vencol     4096 Aug  1 05:56 ../
    -rwxr-xr-x 1 vencol vencol  1111436 Aug  1 05:55 ld-2.25.so*
    lrwxrwxrwx 1 vencol vencol       10 Aug  1 05:55 ld-linux-armhf.so.3 -> ld-2.25.so*
    -rwxr-xr-x 1 vencol vencol 13877292 Aug  1 05:55 libc-2.25.so*
    lrwxrwxrwx 1 vencol vencol       12 Aug  1 05:55 libc.so.6 -> libc-2.25.so*
    -rwxr-xr-x 1 vencol vencol  5948304 Aug  1 05:55 libm-2.25.so*
    lrwxrwxrwx 1 vencol vencol       12 Aug  1 05:55 libm.so.6 -> libm-2.25.so*
    -rwxr-xr-x 1 vencol vencol   359708 Aug  1 05:55 libresolv-2.25.so*
    lrwxrwxrwx 1 vencol vencol       17 Aug  1 05:55 libresolv.so.2 -> libresolv-2.25.so*
    

    添加了对应库后,一般就可以成功加载了,但是返现动态库加载的根文件要比静态链接的大的多,这个是因为现在的软件只有busybox,后面工具软件多了,肯定会使用动态连接,可以根据需要适当做精简

    四、构建根文件系统

    1、根文件系统的目录

    这里目录bin、linuxrc、sbin、usr是我们用busybox生成的目录,我们主要需要修改的目录有dev、etc和lib,lib主要是需要拷贝库文件,这里主要展开需要修改的文件

    ├── bin
    ├── dev
    │   ├── console
    │   ├── null
    │   └── zero
    ├── etc
    │   ├── fstab
    │   ├── group
    │   ├── hostname
    │   ├── init.d
    │   ├── inittab
    │   ├── network
    │   ├── passwd
    │   ├── passwd-
    │   └── profile
    ├── lib
    ├── linuxrc -> bin/busybox
    ├── mnt
    ├── proc
    ├── root
    ├── sbin
    ├── sys
    ├── tmp
    ├── usr
    └── var
    

    2、具体创建步骤

    1. 进入根文件系统目录,创建文件系统的顶层目录
      cd rootfs && mkdir -p dev etc mnt proc var tmp sys root lib
    2. 创建dev目录相关设备
    mknod dev/null c 1 3
    mknod dev/zero c 1 5
    mknod dev/console c 5 1
    
    1. 设置etc目录相关配置

    1.创建etc/inittab文件,该文件是系统启动后,按照上面的描述进行启动的配置文件

    ::sysinit:/etc/init.d/rcS #系统启动脚本
    ::ctrlaltdel:/sbin/reboot #组合键Ctrl+Alt+Del组合键,重启系统
    ::shutdown:/bin/umount -a -r #关机前umount所有挂载
    ttyS0::askfirst:-/bin/sh #启动串口终端,如果需要登录改为/bin/login
    ::shutdown:/etc/init.d/rcK #系统关机脚本
    ::shutdown:/sbin/swapoff -a
    ::shutdown:/bin/umount -a -r #关机前umount所有挂载

    1. 创建 etc/init.d目录,并新建文件etc/init.d/rcS,并给文件执行权限sudo chmod a+x etc/init.d/rcS

    mount -a #首先挂载所有在fstab定义的内容
    mkdir /dev/pts
    mount -t devpts devpts /dev/pts
    /bin/hostname -F /etc/hostname
    ifconfig usb0 up 192.168.137.2
    route add default gw 192.168.137.1 usb0

    1. 创建 etc/init.d目录,并新建文件etc/init.d/rcK,并给文件执行权限sudo chmod a+x etc/init.d/rcK

    ifconfig usb0 down

    1. 新建etc/fstab文件

    proc /proc proc defaults 0 0
    sysfs /sys sysfs defaults 0 0
    mdev /dev tmpfs defaults 0 0
    none /tmp tmpfs defaults 0 0
    none /var tmpfs defaults 0 0

    1. 新建etc/profile,其中的PS1参数可以参考网站做相应修改https://blog.csdn.net/litao31415/article/details/50188243
    USER="`id -un`"
    LOGNAME=$USER #登录之后使用用户名显示
    HOSTNAME="rootfs_by_vencol"     #主机名
    PS1="[\u@\h \w]# "      #终端显示信息
    if [ ! -z ${SSH_TTY} ]; then
       export PATH=/sbin:/usr/sbin:/bin:/usr/bin
    fi
    
    1. 设置lib目录
      lib目录主要是库文件和模块文件的所在目录,一般模块文件可以通过源码make modules_install安装到lib目录,这样就可以不用拷贝模块文件了,所以我们主要介绍的是拷贝libc,libc一般在工具链目录下有,比如我这里的工具链是arm-linux-gnueabihf。
    cp -a arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib/ rootfs/lib
    rm -f rootfs/lib/*.a
    #去除符号表,节省空间,但是不能反汇编了
    #${COMPILE_DIR}arm-none-linux-gnueabi-strip rootfs/lib/*
    
    1. 拷贝虚拟机上的/etc/group和/etc/passwd到rootfs/etc
      修改passwd为root:FMKTwEUCSZm9Q:0:0:root:/root:/bin/sh,即只保存与root相关项,而且最后改成/bin/sh
      修改group为root:x:0:root
      这样登录的时候就是用和虚拟机root用户一样的密码登录,如果不行,可以先修改/etc/init.d/rcS文件,开机直接进入shell,在shell里面修改root用户密码,之后再修改/etc/init.d/rcS文件为开机login。

    3、写成根文件系统创建脚本

    #! /bin/bash
    JOBNUM=4
    NPWD=`realpath .`
    
    #mkdir -p roottemp
    cd rootfs
    mkdir -p dev etc mnt proc var tmp sys root lib
    sudo mknod dev/null c 1 3
    sudo mknod dev/zero c 1 5
    sudo mknod dev/console c 5 1
    
    cat << EOF > etc/inittab
    ::sysinit:/etc/init.d/rcS #系统启动脚本
    ::ctrlaltdel:/sbin/reboot #组合键Ctrl+Alt+Del组合键,重启系统
    ttyS0::respawn:-/bin/login #启动串口终端,如果需要登录改为/bin/login
    #ttyS0::askfirst:-/bin/login #启动串口终端,如果需要登录改为/bin/login
    #ttyS0::askfirst:-/bin/sh #启动串口终端,如果需要登录改为/bin/login
    
    ::shutdown:/etc/init.d/rcK #系统关机脚本
    #::shutdown:/sbin/swapoff -a
    ::shutdown:/bin/umount -a -r #关机前umount所有挂载
    EOF
    
    mkdir -p etc/init.d
    cat << EOF > etc/init.d/rcS
    # !/bin/sh
    mount -a #首先挂载所有在fstab定义的内容
    mkdir /dev/pts
    mount -t devpts devpts /dev/pts
    /bin/hostname -F /etc/hostname
    ifconfig usb0 up 192.168.137.2
    route add default gw 192.168.137.1 usb0
    EOF
    sudo chmod +x etc/init.d/rcS
    
    cat << EOF > etc/init.d/rcK
    # !/bin/sh
    ifconfig usb0 down
    EOF
    
    cat << EOF > etc/fstab
    proc /proc proc defaults 0 0
    sysfs /sys sysfs defaults 0 0
    mdev /dev tmpfs defaults 0 0
    none /tmp tmpfs defaults 0 0
    none /var tmpfs defaults 0 0
    EOF
    
    mkdir -p etc/network
    cat << EOF > etc/network/interfaces
    # interface file auto-generated by buildroot
    
    auto lo
    iface lo inet loopback
    
    #auto eth0
    #iface eth0 inet dhcp
    #  pre-up /etc/network/nfs_check
    #  wait-delay 15
    #  hostname $(hostname)
    
    
    auto usb0
    iface usb0 inet static
    pre-up /etc/network/nfs_check
    address 192.168.137.2
    netmask 255.255.255.0
    gateway 192.168.137.1
    #dns-nameservers 8.8.8.8 192.168.137.1 211.136.20.203
    EOF
    
    cat << EOF > etc/resolv.conf
    nameserver 192.168.137.1
    nameseverr 8.8.8.8
    nameseverr 10.8.16.30
    EOF
    
    cat << EOF > etc/hostname
    vencolfs
    EOF
    
    #cp -a $NPWD/../gcc/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib/ lib
    #rm -f lib/*.a
    #去除符号表,节省空间,但是不能反汇编了
    #${COMPILE_DIR}arm-none-linux-gnueabi-strip rootfs/lib/*
    
    #下面的是用户相关的信息,自行修改
    cat << EOF > etc/passwd
    root:FMKTwEUCSZm9Q:0:0:root:/root:/bin/sh
    EOF
    
    cat << EOF > etc/group
    root:x:0:
    EOF
    
    #cat << EOF > etc/shadow
    #root:$6$B7gzTyFF$Zm2fC5EQYdqupH.BwccJv0YX4XingPOqsMcu1vlWC4AaKof4ycDGlXooMs2m5ZxfDPvjhDicnkt/PuGBDqZtD1:18316:0:99999:7:::
    #EOF
    
    cat << EOF > etc/profile
    USER="`id -un`"
    LOGNAME=$USER #登录之后使用用户名显示
    HOSTNAME="rootfs_by_vencol"     #主机名
    #HOSTNAME='/bin/hostname'
    PS1="[\u@\h \w]# "      #终端显示信息
    
    if [ ! -z ${SSH_TTY} ]; then
       export PATH=/sbin:/usr/sbin:/bin:/usr/bin
    fi
    EOF
    

    五、无法解析域名

    用了一段时间后发现,可以ping通ip但是无法DNS,经过一番折腾后发现,是因为没有支持DNS,因为编译出来的nslookup是可以解析域名的,但是直接ping却是不行的

    image.png
    这里找到了两个方案
    1. 安装文章https://www.cnblogs.com/liangwode/p/5584099.html中说的,把busybox编译成动态连接,同时添加相应的库文件,其中DNS需要用到的文件有:/lib/libnss_dns.so.2、/lib/libnss_files.so.2、/lib/libresolv.so.2、/etc/resolv.conf、/etc/nsswitch.conf
    2. https://www.dazhuanlan.com/2020/01/06/5e12988c64495/这个文章中,提到添加route gateway,并把ip添加到/etc/resolv.conf文件中,但是实际使用失败了,后来发现https://blog.csdn.net/u013625451/article/details/79007441这文章中提到了,busybox官网的说法是需要动态库
    3. 后来在下面的网址发现了解决方案,其思路是,在ip连接前,先自己模拟dns的协议,解析/etc/resolv.conf并向里面的服务器发送数据进行dns解析。https://blog.csdn.net/bingyu9875/article/details/104684985

    1、把下面的文件内容,添加到busybox源码的libbb/xconnect.c文件的开头
    2、修改该文件中str2sockaddr函数中getaddrinfo改为hgetaddrinfo。

    #include <arpa/inet.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define oops(msg) { perror(msg); exit(1);}
    #include<ctype.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    
    void fillip(char* buffer, const char* ip);
    char* name2ip(const char* name);
    /**
     *  * head len: 12
     *   * query: ? + 4
     *    * total: strlen(query.buffer) + 16
     *     */
    typedef struct{
            u_short txid;
            u_short flag;
            u_short question;
            u_short answer;
            u_short authority;
            u_short additional;
            char buffer[256];
            struct {
                    u_short type;
                    u_short cls;
            }query;
    }dns_req;
    /** length of resp: 12 */
    typedef struct{
            u_short txid;
            u_short flag;
            u_short question;
            u_short answer;
            u_short authority;
            u_short addtional;
            char buffer[1024];
            struct{
                    u_short name;
                    u_short type;
                    u_short cls;
                    u_short live_l;
                    u_short live_h;
                    u_short len;
                    struct in_addr addr;
            }resp;
    }dns_res;
    
    int hgetaddrinfo(const char *node, const char *service,
                    const struct addrinfo *hints,
                    struct addrinfo **res){
            /*    int rc = getaddrinfo(node,service,hints,res);
             *            if(rc == 0)//todo: != -> ==
             *                            return rc;
             *                                            */
            char *ip = name2ip(node);                               /// 如果 ping www.baidu.com
            return getaddrinfo(ip,service,hints,res);   /// 那么此时 node 指向字符串 "www.baidu.com"
    }
    
    char *get_dns_name()
    {
            /*
            static char buf[8]="8.8.8.8";
                    return buf;
            */
            int fd;
            static char     buf[1024],*pstr,*pdns;
    
            fd = open("/etc/resolv.conf", O_RDWR);
            read(fd,buf,1020);
            pstr = strstr(buf,"nameserver");
            pstr += strlen("nameserver");
            while(!isdigit(*pstr)){
                    pstr ++;
            }
            pdns = pstr;
    
            while(isdigit(*pstr) || (*pstr == '.') ){
                    pstr ++;
            }
            *pstr = '\0';
            close(fd);
            return pdns;
    
    }
    
    
    char * name2ip(const char *node){
            struct sockaddr_in dns;
            dns_req req;
            dns_res res;
            int sockid, len, index;
                    get_dns_name();
                    char  dnsip[32];
    
                    strcpy(dnsip,get_dns_name());
    
            sockid = socket(PF_INET, SOCK_DGRAM, 0);
            if(sockid == -1)
                    oops("socket");
            memset((void*)&dns, 0, sizeof(dns));
            dns.sin_family = AF_INET;
            dns.sin_port = htons(53);
            dns.sin_addr.s_addr = inet_addr(dnsip);
    
            memset((void*)&req, 0, sizeof(req));
            req.txid = htons(0x4419);
            req.flag = htons(0x0100);
            req.question = htons(1);
            fillip(req.buffer,node);
            req.query.type=htons(1);
            req.query.cls=htons(1);
            memcpy(req.buffer+strlen(req.buffer)+1, (void*)(&req.query), sizeof(req.query));
    
            sendto(sockid, (void*)&req, strlen(req.buffer)+17, 0,
                            (struct sockaddr*)&dns, sizeof(dns));
            recvfrom(sockid, (void*)&res, sizeof(res), 0,
                            (struct sockaddr*)&dns, &len);
            index = strlen(res.buffer)+5;
            while(1){
                    memcpy((void*)&(res.resp), res.buffer+index, 12);
                    if(ntohs(res.resp.type)==1){
                            memcpy((void*)&(res.resp.addr), res.buffer+index+12, 4);
                            break;
                    }
                    index += ntohs(res.resp.len) + 12;
            }
    
            return inet_ntoa(res.resp.addr);
    }
    
    void fillip(char* buffer, const char* ip){
            int i,j=0;
            for(i = 0; ip[i] != 0; i ++){
                    if(ip[i] != '.'){
                            buffer[i+1] = ip[i];
                    }
                    else{
                            buffer[j] = i - j;
                            j = i + 1;
                    }
            }
            buffer[j] = i - j;
    }
    

    相关文章

      网友评论

          本文标题:orangepi构建根文件系统

          本文链接:https://www.haomeiwen.com/subject/vqryrktx.html