orangepi构建根文件系统

目录结构说明

一、基础说明

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 
#include 
#include 
#include 
#include 
#define oops(msg) { perror(msg); exit(1);}
#include
#include 
#include 
#include 


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构建根文件系统)