# hi3798mv100Jack **Repository Path**: JackGao19700/hi3798mv100-jack ## Basic Information - **Project Name**: hi3798mv100Jack - **Description**: fix all compiling issue on origin version. - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-07-25 - **Last Updated**: 2025-07-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 说明:官方的Makefile有很多问题,直接make有很多的问题或修改配置后,就编译不过,非常不问题。所有本人修改了Makefile及很多底层的Makefile,保证可以编译通过。git clone下载后,按下面的运行,一切都OK: - 安装开发环境 - 运行配置source ./env.sh - make menuconfig配置需要的组件 - make linux_menuconfig配置linux kernel - make build -j16编译整个包 - 制作rootfs - make linux_installKO2_rootfs JACKS_ROOT_IMG=/home/jackgao/rootfs.img JACKS_ROOTFS_SRC_DIR=/home/jackgao/rootfs将 .ko文件安装到rootfs. JACKS_ROOT_IMG指向你的rootfs image, JACKS_ROOTFS_SRC_DIR是mount的目录 - 烧录到MCU - resize2fs rootfs到整个分区,配置wifi,网络 # 一. Linux kernel编译 内核的编译根据不同的情况会有不同的步骤,但其中最主要分别为 3 个步骤:**内核配置**、**建立依赖关系**、 **创建内核映像**,除此之外还有一些辅助功能,如清除文件和依赖关系等。读者在实际编译时若出现错误等 情况,可以考虑采用其他辅助功能。 ## a. 内核配置 第一步内核配置中的选项主要是用户用来为目标板选择**处理器架构**的选项,不同的处理器架构会有不同的 处理器选项,比如ARM 就有其专用的选项如“Multimedia capabilities port drivers”等。因此,在此之前, 必须确保在根目录中makefile 里“ARCH”的值已设定了目标板的类型,如: `ARCH = arm` 接下来就可以进行内核配置了,内核支持 4 种不同的配置方法。每种方法都会通过读入一个默认的配置文件—根目录下`.config`隐藏文件(用户也可以手动修改该文件,但不推荐使用)。当然,用户也可以自己加载其他配置文件,也可以将当前的配置保存为其他名字的配置文件。这4 种方式如下: - make config:基于文本的最为传统的配置界面,不推荐使用。 - make menuconfig:基于文本选单的配置界面,字符终端下推荐使用。 - make xconfig:基于图形窗口模式的配置界面,Xwindow 下推荐使用。 - make oldconfig:自动读入“.config”配置文件,并且只要求用户设定前次没有设定过的选项。 ## b. 建立依赖关系 由于内核源码树中的大多数文件都与一些头文件有依赖关系,因此要顺利建立内核,内核源码树中的每个 Makefile 都必须知道这些依赖关系。建立依赖关系通常在第一次编译内核的时候(或者源码目录树的结构 发生变化的时候)进行,它会在内核源码树中每个子目录产生一个`.depend`文件。运行`make dep`即 可。在编译2.6 版本的内核通常不需要这个过程,直接输入`make`即可。 ## c. 建立内核 建立内核可以使用`make`、`make zImage`或`make bzImage`,这里建立的为压缩的内核映像。通常在 Linux 中,内核映像分为压缩的内核映像和未压缩的内核映像。其中,压缩的内核映像通常名为zImage, 位于`arch/$(ARCH)/boot`目录中。而未压缩的内核映像通常名为`vmlinux`,位于源码树的根目录中。 **说明**:在嵌入式Linux 的源码树中通常有以下几个配置文件`.config`、`autoconf.h`、`config.h`。其中`.config`文件是`make menuconfig`默认的配置文件,位于源码树的根目录中。`autoconf.h`和`config.h`是以宏的形式表示了内核的配置,当用户使用`make menuconfig`做了一定的更改之后,系统自动会在`autoconf.h`和`config.h`中做出相应的更改。它们位于源码树的`/include/linux/`下。 # 二. Hi3987m100 kernel编译实操 ## a. 编译配置 - Host主机工具配置 - **make gcc等工具** ```bash apt install gcc make gettext bison flex bc zlib1g-dev libncurses5-dev lzma ``` - **makefile 自动配置工具** ```bash sudo apt install autoconf automake libtool ``` - **自动找库工具pkg-config** ```bash sudo apt install pkg-config ``` - **qemu ARM平台模拟工具**(用于制作rootfs) ```bash sudo apt install qemu-user-static` ``` - 自定义linux内核 ***提示1***:内核顶层目录的**.config**文件是内核的构建的**配置蓝图**。内核的编译配置,最终都保存在将内核顶层目录的**.config**文件中。 ***提示2***:保护好内核顶层目录的**.config**配置文件很重要。几个make命令会彻底删除**.config**文件,让内核回到原始的、未配置的状态: `make mrproper` `make distclean` ***提示3***:内核顶层目录执行`make help`可以看到如何配置的`Configuration targets` 切换到内核顶层目录,如`cd kernel/linux-4.4.y`,然后执行: - 必要时可以通过www.kernel.org下载对应的内核源码 - 先备份厂家的推荐配置,如arch/**arm**/hi3798mv100_defconfig (其中**arm**如你交叉编译的target 环境有关) - 从defconfig生成标准linux内核配置.config文件 ```bash make ARCH=arm hi3798mv100_defconfig ``` 上述语句以arch/**arm/configs/**hi3798mv100_defconfig为基础,生成`.config`内核配置文件 - 通过文本菜单,修改配置项(内核配置项都保持在`.config`文件中) ```bash make ARCH=arm menuconfig #基于.config, 使用菜单修改内核配置,并保存回.config ``` 修改完成后可以通过`grep PARANOID --color .config`确认某个`PARANOID`配置的情况。 - 重新生成defconfg文件 ```bash make ARCH=arm savedefconfig ``` ***说明***:`.config`文件包括所有Linux内核配置选择,但defconfg已经arch/**arm/configs/***_defconfig文件保存是非Linux缺省值的选项,所有少很多! - 复制defconfig文件到正确的位置,如一般保存到arch/**arm/configs/**对应的内核配置文件中,如`arch/arm/configs/hi3798mv100_defconfig` ```bash cp defconfig arch/arm/configs/hi3798mv100_defconfig ``` ***提示4***:修改配置后,在重新编译linux前,一般先运行make mrproper, 先清空之前编译的残留数据、中间件等。 ## b. 镜像烧录与启动参数修改 - 烧录GUI ![image-20250714115634258](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/image-20250714115634258-1753429145192-1-1753429148005-3-1753429154142-5.png) - **uboot**配置 (Linux启动中`Ctl+C`进入) 1. 分区从1开始算,fastboot 是mmcblk0p1, bootargs是mmcblk0p2...rootfs是mmcblk0p4. 2. `boolloader`从设备加载kernel,需要通过`bootargs`中的`bootcmd`参数指定设备号,从设备开始的`sector`号,如上如**kernel**起始位置是2M=0x200000byte,一个sector=512=0x200,固起始sector为0x200000/0x200=0x1000 ```bash # mmc read 0表示mmcblk0,第一块eMMC # 0x1FFFFC0 0x1000 0x4000中0x1000为起始扇区,从这里加载kernel setenv bootcmd `mmc read 0 0x1FFFFC0 0x1000 0x4000; bootm 0x1FFFFC0` saveenv ``` 3. 修改`bootargs`: ```bash #显示bootargs所有参数 printenv #修改bootargs setenv bootargs console=ttyAMA0,115200 root=/dev/mmcblk0p4 rootfstype=ext4 rootwait blkdevparts=mmcblk0:1M(boot),1M(bootargs),8M(kernel),-(rootfs) saveenv ``` `root`指定**根文件系统/**的设备,根据上面的GUI,对应mmcblk0p4. `rootfstype`说明**根文件系统/**的系统类型。 `blkdevparts`则必须与烧录GUI的分区个数和大小一致。修改完成后通过`saveenv`写回到`bootargs`分区中持久化 4. 修改好`bootargs`后,通过GUI“上载”,将bootargs写回文件中,这样以后烧录就是这正确的kernel启动参数了: ![image-20250714123359423](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/image-20250714123359423-1753429173563-7.png) 5. **直接修改生成bootargs.bin** - 根据烧录GUI的分区大小,同步修改`bootargs.txt` 和`emmc_partitions.xml`. `bootargs.txt`内容如下(相关参数计算参加上面的说明): ```bash bootcmd=mmc read 0 0x1FFFFC0 0x1000 0x4000;bootm 0x1FFFFC0 bootargs=console=ttyAMA0,115200 root=/dev/mmcblk0p4 rootfstype=ext4 rootwait blkdevparts=mmcblk0:1M(fastboot),1M(bootargs),8M(kernel),128M(rootfs),-(system) ``` 上面的`bootcmd`启动参数的说明:从第0个mmc设备块上2M字节处开始(0x1000的十进制4096,4096*512/1024=2M),读取16×512个字节(0x4000的十进制16384*512/1024=8M)到内存0x1FFFFC0处,并从此处引导。 - 使用`mkbootargs`制作`bootargs.bin` ```bash mkbootargs -s 1M -r bootargs.txt -o bootargs.bin ``` `-s`指定输出的文件`bootargs.bin`大小;`-r`指定包含启动参数的txt文件;`-o`指定输出文件 ## c.根文件系统制作 把上一节中所编译的内核压缩映像下载到开发板后会发现,系统在进行了一些初始化的工作之后,并不能正常启动,如下图所示: ![image-20250720124135532](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/image-20250720124135532-1752986497941-1-1753429177798-9-1753429184490-11.png) 可以看到,系统启动时发生了加载文件系统`devfs`的错误。要记住,上一节所编译的仅仅是**内核**,**文件系统**和**内核**是完全独立的两个部分。请回忆一下**Linux** 启动过程的分析,其中在`head.S`中就加载了**根文件系统**。因此,加**载根文件系统**是**Linux** 启动中不可缺少的一部分。本节将讲解嵌入式中**文件系统**的制作方法。 ### (1) rootfs与kernel关系说明 - **`根文件系统`rootfs与`内核`kernel相对独立** - **独立性**:`根文件系统` 主要包含用户空间工具(如 `busybox`、`glibc`)、配置文件、设备节点等,而`内核Kernel`是独立编译的二进制文件(如 `zImage` 或 `uImage`)。两者通过明确的接口(如系统调用、`/dev`、`/sys` 等)交互。 - **动态兼容**:只要`内核Kernel`版本满足`根文件系统`中用户空间工具的最低要求(如系统调用、驱动接口等),即可正常运行。例如: - `Ubuntu-base 镜像`通常标注所需的最低内核版本(如 `Linux 5.4+`)。 - 若你的`内核Kernel`版本(如 `5.10`)高于`Ubuntu-base 镜像`的预期版本(如 `5.4`),一般可以兼容。 - **为何制作 rootfs 时无需内核?** - **工具链兼容性**:`qemu-user-static` 通过二进制翻译(如 `armhf` 程序在 x86 主机上运行)直接处理 rootfs 中的用户态工具(如 `apt`、`chroot`),无需内核参与。 - **内核无关操作**:rootfs 的构建过程(如安装软件包、配置服务)是用户态操作,内核仅在**运行时**需要。 - **`根文件系统`rootfs与`内核`kernel关键兼容性要求** - **ARCH 匹配**:rootfs 的架构(如 `armhf`)必须与内核编译的架构完全一致。 - **内核模块依赖**:需要手工移植编译kernel时编译的驱动模块(如 `/lib/modules/4.4.y/`),保障与内核版本匹配。 - **内核特性支持**: - 文件系统类型(如 `ext4`、`squashfs`)。 - 关键子系统(如网络协议栈、`/dev` 设备节点)。 - 必要的设备节点(如 `/dev/console`)存在。 ### (2) rootfs制作 > 如果需要从0构建`rootfs`,而不是基于ubuntu/debian发行包,请参见[**Linux从0构建rootfs.md**] #### Step1:获取[ubuntu](https://so.csdn.net/so/search?q=ubuntu&spm=1001.2101.3001.7020)-base 到ubuntu官网下载Ubuntu-base https://cdimage.ubuntu.com/ubuntu-base/releases/20.04.5/release/: ![在这里插入图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/36701a39bf9136c0142e803d24c3b59c.png) #### Step2:在host机器解压缩ubuntu-base,作为制作rootfs的基石: ```bash mkdir ubuntu_rootfs sudo tar -xzvf ubuntu-base-20.04.5-base-armhf.tar.gz -C ubuntu_rootfs ``` #### Step3:修改dpkg源 修改rootfs中package源,方便后面为制作的rootfs安装必要的软件 ```bash sudo vi ubuntu_rootfs/etc/apt/sources.list # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释 deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal-backports main restricted universe multiverse ``` *使用清华软件源更多详情查看https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu-ports/* #### Step4:安装QEMU模拟环境 在Host主机上安装Target机器(armhf) QEMU模拟环境 ```bash #1.安装qemu-user-static仿真器 sudo apt install qemu-user-static #2.拷贝 qemu-aarch64-static 到 ubuntu_rootfs/usr/bin/ 目录下。 sudo cp /usr/bin/qemu-aarch64-static ubuntu_rootfs/usr/bin/ ``` **注意**:这个qemu运行在Host主机上,但模拟Target机器。`file /usr/bin/qemu-aarch64-static`会显示时Host架构的ELF文件: ```bash /usr/bin/qemu-aarch64-static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=d4934bfea560431fdd4d2f78b03e52135c65025f, for GNU/Linux 3.2.0, stripped ``` #### Step5:安装动态驱动.ko模块 安装为Target Kernel编译的**驱动模块.ko**文件 ```bash # 指定根文件系统路径(假设根文件系统路径为~/rootfs) sudo make -C /path2kernelSource/kernel/linux-4.4.y ARCH=arm CROSS_COMPILE=arm-histbv310-linux- O=/outputpath2kernelSource/kernel/linux-4.4.y INSTALL_MOD_PATH=~/rootfs modules_install # 或者执行 sudo make install_kernel_modules MODULES_INSTALL_PATH=~/rootfsk ``` 这一步会自动: - 在 `~/rootfs/lib/modules/` 下创建目录,如 `4.4.y` - 将所有 `.ko` 文件复制到该目录 - 生成 `modules.dep`、`modules.alias` 等依赖文件 检查根文件系统内的模块目录: ```bash ls ~/rootfs/lib/modules/4.4.y ``` 应看到类似内容: ```text kernel/ # 模块按子系统分类存放 modules.dep # 模块依赖关系 modules.symbols # 符号信息 modules.alias # 设备别名 ... ``` **提示**:**手动复制模块的注意事项** 如果你选择**手动复制 `.ko` 文件**(而非 `make modules_install`),需确保: 1. **保留目录结构**: 模块应放在 /home/jack/ubuntu_rootfs/lib/modules/4.4.y/`kernel/` 子目录中(如 `kernel/drivers/net/ethernet/your_driver.ko`),否则 `modprobe` 可能无法找到它们。 2. **运行 `depmod`**: 在**目标系统启动后**(或通过 `chroot`/`qemu-user-static`)执行: ``` depmod -a # 自动针对当前内核版本生成依赖 ``` 3. **判断.ko是否与kernel兼容**: `modinfo`输出**vermagic**字段的一个字段必须与`uname -r`完全一致,否则.ko文件无法加载: ```bash modinfo ./kmod/hi_sync.ko filename: ./kmod/hi_sync.kohi_sync.ko license: GPL author: HISILICON depends: hi_mmz,hi_common intree: Y vermagic: 4.4.35_s40 SMP mod_unload ARMv7 p2v8 ``` ```bash uname -r 4.4.35_s40 ``` **vermagic**中的`4.4.35_s40`与**uname -r**`4.4.35_s40`一致,所有ok. 4. **`.ko` 文件直接散落在 `kmod/` 目录**: 当 SDK 编译出的 `.ko` 文件直接散落在 `kmod/` 目录,没有按内核标准路径分类时,确实难以手动创建 `kernel/drivers/` 等子目录结构,按如下操作: ```bash # 1. 在目标设备的 `/lib/modules/4.4.y/` 下创建模块目录(无需分类) mkdir -p /lib/modules/4.4.y/ # 2. 将所有 .ko 文件(包括子目录)复制到目标位置 cp -r /path/to/sdk/kmod/* /lib/modules/4.4.y/ # 3. 强制生成依赖关系(关键步骤!) depmod -a --force # --force 确保即使路径非标准也生成索引 # 4. 验证模块是否能被找到 modprobe hi_sync # 测试加载模块 lsmod # 检查是否加载成功 ``` **`depmod -a --force`** 会扫描 `/lib/modules/4.4.y/` 下所有 `.ko` 文件,生成正确的 `modules.dep` 和 `modules.alias`,即使路径不符合标准。 1. **压缩模块**:`strip --strip-debug /lib/modules/4.4.y/*.ko` 1. **自动加载可能失效** - 如果模块依赖设备树或 udev 规则,可能需要手动加载(如 `modprobe hi_sync`)。 - 若需开机自动加载,将模块名加入 `/etc/modules`: ```bash echo "hi_sync" >> /etc/modules ``` #### Step6:编写挂载脚本 ```bash #!/bin/bash function mnt() { echo "MOUNTING" sudo mount -t proc /proc ${2}proc sudo mount -t sysfs /sys ${2}sys sudo mount -o bind /dev ${2}dev #sudo mount -t devpts -o gid=5,mode=620 devpts ${2}dev/pts sudo mount -o bind /dev/pts ${2}dev/pts sudo chroot ${2} } function umnt() { echo "UNMOUNTING" sudo umount ${2}proc sudo umount ${2}sys sudo umount ${2}dev/pts sudo umount ${2}dev } if [ "$1" == "-m" ] && [ -n "$2" ]; then mnt $1 $2 elif [ "$1" == "-u" ] && [ -n "$2" ]; then umnt $1 $2 else echo "" echo "Either 1'st, 2'nd or both parameters were missing" echo "" echo "1'st parameter can be one of these: -m(mount) OR -u(umount)" echo "2'nd parameter is the full path of rootfs directory(with tralling '/')" echo "" echo "For example: ch-mount -m /media/sdcard" echo "" echo 1st parameter : ${1} echo 2nd parameter : $[2] fi ``` ```bash #增加脚本执行权限 sudo chmod +x mount.sh #挂载根文件系统 ./mount.sh -m ubuntu_rootfs/ #退出根文件系统 exit #卸载根文件系统 ./mount.sh -u ubuntu_rootfs/ - ## 挂载ubuntu_rootfs ```bash ./mount.sh -m ubuntu_rootfs/ ``` #### Step7:挂载ubuntu_rootfs,配置rootfs ```bash ./mount.sh -m ubuntu_rootfs/ ``` 执行上述命令后,Host主机的`/`切换为`ubuntu_rootfs/`,后面的操作就时在修改、配置rootfs,也即是真正制作rootfs. ***注意***:`Step4 安装QEMU模拟环境`确保执行到位,否则后面操作无效。 - 更新软件源 ```bash apt update ``` - `apt update`报证书错误 如果apt update报错` Certificate verification failed: The certificate is NOT trusted...`,按如下步骤解决: - 检查系统时间和时区 证书验证依赖正确的时间,先确保系统时间准确: ```bash # 查看当前时间 date # 如果时间错误,同步时间(需要NTP服务) sudo apt install ntpdate # 如果未安装 sudo ntpdate pool.ntp.org # 或使用 timedatectl(Ubuntu 16.04+) sudo timedatectl set-ntp on ``` - 临时跳过证书验证 `-o`(或 `--option`)参数用于 **临时覆盖 APT 的配置选项**【直接设置 APT 的配置选项,优先级高于配置文件,如 `/etc/apt/apt.conf`】,允许用户在不修改配置文件的情况下,通过命令行动态调整 APT 的行为。下面的命令其核心作用是 **临时禁用 HTTPS 源对服务器证书的验证**。 ```bash sudo apt -o Acquire::https::Verify-Peer=false update ``` - 更新 CA 证书 ```bash sudo apt install --reinstall ca-certificates sudo update-ca-certificates ``` - 检查镜像源配置 确认你的 `/etc/apt/sources.list` 中清华镜像源的 URL 是否正确(Ubuntu 20.04 "focal" 示例): ```bash # 备份原有配置 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak # 使用以下命令生成正确的清华源配置(Ubuntu 20.04 为例) sudo sed -i 's|http://.*archive.ubuntu.com|https://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list sudo sed -i 's|http://.*security.ubuntu.com|https://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list ``` - 验证修复结果 ```bash sudo apt update sudo apt upgrade ``` - 安装常用软件 ```bash apt install sudo vim language-pack-en-base apt install systemd #安装系统服务管理器,安装过程需要手动选择地区 apt install net-tools #包含ifconfig命令 apt install usbutils #包含lsusb命令 apt install iputils-ping #包含ping命令 apt install htop #htop命令查看系统资源情况与负载 apt install ifupdown #网络管理工具,会创建/etc/network目录并生成interfaces配置文件 apt install udev #设备管理,能够将串口作为控制台 apt install openssh-server apt-utils curl network-manager kmod ``` - 启动systemd系统与服务管理器 ```bash ln -s /lib/systemd/systemd /sbin/init ``` - 账号设置 ```bash passwd root #设置root用户密码 adduser ubuntu #新增普通用户“ubuntu” passwd ubuntu ``` - 为普通用户增加sudo权限 ```bash vi /etc/sudoers #在root ALL=(ALL:ALL) ALL下一行添加ubuntu ALL=(ALL:ALL) ALL root ALL=(ALL:ALL) ALL ubuntu ALL=(ALL:ALL) ALL ``` - 网络配置 - 网络 DHCP 配置 ```bash echo allow-hotplug eth0 > /etc/network/interfaces.d/eth0 echo iface eth0 inet dhcp >> /etc/network/interfaces.d/eth0 ``` - DNS 配置 ```bash #删除当前配置 rm /etc/resolv.conf echo "nameserver 8.8.8.8" > /etc/resolv.conf echo "nameserver 114.114.114.114" >> /etc/resolv.conf #锁定DNS配置,为/etc/resolv.conf文件添加不可修改(immutable)属性 #可以使用lsattr /etc/resolv.conf查看底层属性--仅限 ext2/3/4、XFS、Btrfs 等文件系统支持。 chattr +i /etc/resolv.conf ``` - 设置本机名称和ip地址 ```bash echo "hi3798mv100" > /etc/hostname echo "127.0.0.1 localhost" >> /etc/hosts echo "127.0.0.1 hi3798mv100" >> /etc/hosts ``` - 退出根文件系统 ```bash exit ./mount.sh -u ubuntu_rootfs/ ``` #### Step8:打包rootfs ```bash #生成1500M的rootfs.img文件 dd if=/dev/zero of=rootfs.img bs=1M count=1500 #格式化rootfs.img为ext4格式文件系统,Label命名为linuxroot mkfs.ext4 -F -L linuxroot rootfs.img #mount rootfs.img,方便往里面拷贝之前我们的ubuntu_rootfs/制作的内容 mkdir ./rootfs/ mount rootfs.img ./rootfs/ cp -rfp ubuntu_rootfs/* ./rootfs/ umount ./rootfs/ # -p自动修复所有可安全修复的错误,无需交互确认;-f强制检查 e2fsck -p -f rootfs.img #-M的作用是将文件系统缩小到最小可能的大小(即刚好容纳所有数据的最小空间),方便节省烧录时间。 resize2fs -M rootfs.img ``` 使用`make_ext4fs -l 128M -s rootfs_128M.ext4 ./rootfs`也可以直接通过目录生成镜像。 #### Step9:目标系统扩展rootfs到分区大小 将`rootfs`烧录到MCU后,启动MCU, Linux正常运行后,登录系统,执行: ```bash #扩展文件系统到分区大小,这里不能使用-M选项,否则刚好时反的!! #rootfs烧录时在第四个分区,固resize2fs使用/dev/mmcblk0p4 sudo resize2fs /dev/mmcblk0p4 # 写入磁盘,持久化,报证以后启动都正确,而不是仅仅针对此次启动有效,reboot就没有了。 sync sudo reboot # 重启验证 ``` #### Step10:打包kernel-header[可选] 可以打包kernel-header, 这样就可以在MCU上编译或提供给他人,方便给kenerl编译其他东西,如驱动。打包kernel-header,执行: ```bash #从源代码中找出相关头文件,并按标准目录归类 make -C /home/jackgao/HiSTBLinuxV100R005C00SPC060/source/kernel/linux-4.4.y headers_install INSTALL_HDR_PATH=~/kernel-headers # 打包头文件为tarball tar czf kernel-headers.tgz -C ~/kernel-headers . # 传输到MCU设备 scp kernel-headers.tgz ubuntu@<设备IP>:/tmp/ # 在设备上安装 sudo mkdir -p /usr/src/linux-headers-$(uname -r) sudo tar xzf /tmp/kernel-headers.tgz -C /usr/src/linux-headers-$(uname -r) sudo ln -s /usr/src/linux-headers-$(uname -r) /lib/modules/$(uname -r)/build ``` ### (3) rootfs能被内核正确挂载的关键内核选项 我们定制的rootfs能被内核正确挂载,需要注意下面的选项: - 支持`DEVTMPFS` - 支持`CGROUPS` - 关闭`ANDROID_PARANOID_NETWORK` - 打开`IKCONFIG` **配置dev**:键入/搜索devtmp 确保这两个配置项都打开了,按下数字 1 2 可快速定位到配置项 ![在这里插入图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/d4246be1183ad631ddf7352ed0da1d51.png) **配置cgroups**:确保cgroups打开 ![在这里插入图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/38c8a626862cbf359d2798ade5a2c869.png) **配置ANDROID_PARANOID_NETWORK**:ANDROID_PARANOID_NETWORK默认是打开的,这里我们需要关闭它,不然没法正常上网 ![在这里插入图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/537c7923dbf72a51caa43df7637e9676.png) **配置USB**:这三个配置项默认是编译成模块,这里把它们修改成编译进内核 ![在这里插入图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/97a90fc229759e9a13a1be6acf86fcd7.png) **配置内核IKCONFIG**:确保下面两个配置选项全部打开 ![在这里插入图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/e880f6a5d6d743ab8916d081c5ed24c6.jpeg) **Tips**:`zcat /proc/config.gz | grep -E ‘USB|RTL8188’`直接看某个内核配置编译的选项状态。 ### (4) 在MCU Linux中直接烧录 可以通过Hi-Tool工具[烧录](https://so.csdn.net/so/search?q=烧录&spm=1001.2101.3001.7020),也可以拷贝内核镜像到机顶盒后使用dd指令烧录: ```bash $make linux -j16 #scp [选项] <源文件路径> <目标机用户名>@<目标机IP>:<目标路径> #-r递归拷贝目录;-P 指定SSH端口(非默认22时);-v显示详细传输日志 $scp /path/to/hi_kernel.bin root@192.168.1.100:~/hi_kernel.bin ``` 烧录并重启: ```bash root@hi3798mv100:~# dd if=hi_kernel.bin of=/dev/mmcblk0p4 root@hi3798mv100:~# reboot ``` ### (5)制作最简单的cramfs [Optional] - 首先从 busybox 网站下载busybox 源码并解压,接下来,根据实际需要进行busybox 的配置。 ```bash $ tar jxvf busybox-1.00.tar.bz2 $ cd busybox-1.00 $ make defconfig #首先进行默认配置 $ make menuconfig ``` ![image-20250720125222958](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/image-20250720125222958-1752987144379-3.png) 此时需要设置平台相关的交叉编译选项,操作步骤为:先选中`Build Options`项的`Do you want to build Busybox with a Cross Complier?`选项,如`Cross Compiler prefix`设置`/usr/local/arm/3.3.2/bin/arm-linux-` - 编译并安装 busybox ```bash $ make $ make install PREFIX=/home/david/cramfs $ cd /home/david/cramfs && ls bin linuxrc sbin usr ``` - 完善一下文件系统 创建系统所需要的目录和文件来完善一下文件系统的内容: ```bash $ cd /home/david/cramfs $ mkdir mnt root var tmp proc boot etc lib $ mkdir var/{lock,log,mail,run,spool} ``` 如果 busybox 是动态编译的(即在配置busybox 时没选中静态编译),则把所需的交叉编译的动态链接库文 件复制到lib 目录中,如: ```bash cp -rp ~/hello /home/david/cramfs ``` - 设备自动挂载 接下来,需要创建一些重要文件。首先要创建/etc/inittab 和/etc/fstab 文件。inittab 是Linux 启动之后第一个被访问的脚本文件,而fstab 文件是定义了文件系统的各个“挂接点”,需要与实际的系统相配合。接下来 要创建用户和用户组文件。 - 制作cramfs ```bash $ mkcramfs backup_cramfs/cramfs/ new.cramfs ``` 如果在已经做好的cramfs 映像文件的基础上进行适当的改动,方法如下: ```bash $ mkdir cramfs $ mount fs2410.cramgs cramfs –o loop $ tar cvf backup.cramfs.tar cramfs/ $ umount cramfs $ tar zvf backup.cramfs.tar -C cramfs ``` ## d. Wifi配置 ### (1) 编译选项 - 内核配置打开WIFI,确保CFG80211编入内核,选择**M**(Module)方式: ![请添加图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/bf6fca08281948fdb37870f4ea46c1ce.png) - 添加Wifi驱动,选择**M**(Module)方式: 修改SDK配置,添加驱动。板载的WiFi模块的驱动在SDK中可以找到,无需再手动添加驱动。 ```bash $ pwd /home/marsa/hi3798mv/HiSTBLinuxV100R005C00SPC060 $ make menuconfig ``` 打开RTL8188ETV和RTL8188FTV驱动(我的盒子实际是**FV**),并设置[STA](https://so.csdn.net/so/search?q=STA&spm=1001.2101.3001.7020)模式(配置路径在[Linux](https://so.csdn.net/so/search?q=Linux&spm=1001.2101.3001.7020)(REE) System —>Features —>WiFi Support —>![在这里插入图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/f4542925ae5a4500aadeab19d80db772.png) ![在这里插入图片描述](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/22f2e295a22a48a190c34d015e3c29dc.jpeg) 构建输出的内核: ```bash $ pwd /home/marsa/hi3798mv/HiSTBLinuxV100R005C00SPC060 $ make linux -j16 ``` 构建输出的内核以及驱动路径如下: ```bash /home/marsa/hi3798mv/HiSTBLinuxV100R005C00SPC060/out/hi3798mv100/hi3798mdmo1f/kmod/rtl8188eu.ko /home/marsa/hi3798mv/HiSTBLinuxV100R005C00SPC060/out/hi3798mv100/hi3798mdmo1f/kmod/rtl8188fu.ko /home/marsa/hi3798mv/HiSTBLinuxV100R005C00SPC060/out/hi3798mv100/hi3798mdmo1f/kmod/CFG80211.ko ``` `rtl8188eu.ko`,`rtl8188fu.ko`,`CFG80211.ko`驱动拷贝到机顶盒根文件系统,使用: ```bash depmod -a ``` 更详细的操作参见**Linux MCU系统构建** ### (2) WiFi配置 - 测试驱动是否OK ```bash $ ip link show $ insmod /root/modules/rtl8188fu.ko $ ip link show ``` 如果`rtl8188fu.ko` `cfg80211.ko`工作正常,会看到2个`wlx`开头的网卡: ```bash 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether 3e:25:0e:dc:7a:6b brd ff:ff:ff:ff:ff:ff 3: wlx446a2eac5f99: mtu 1500 qdisc mq state DOWN mode DORMANT group default qlen 1000 link/ether 74:c9:a3:70:47:25 brd ff:ff:ff:ff:ff:ff 4: wlx466a2eac5f99: mtu 1500 qdisc mq state DOWN mode DORMANT group default qlen 1000 link/ether 76:c9:a3:70:47:25 brd ff:ff:ff:ff:ff:ff ``` 查看无线网卡是否是同一个网卡: ```bash #iw dev phy#1 Interface wlan1 ifindex 4 wdev 0x100000001 addr 46:6a:2e:ac:5f:99 ssid yourWiF type managed txpower 12.00 dBm phy#0 Interface wlan0 ifindex 3 wdev 0x1 addr 44:6a:2e:ac:5f:99 type managed txpower 12.00 dBm ``` 但上面的输出,按道理这2块网卡是不同的物理网卡——一个是`wifi direct`用户DLNA 无线投屏,一个是真正的上网使用的。**但实际证明他们同一个物理网卡,如果同时启用,会相互影响,wifi变得非常不稳定**!! - 将网卡命名`wlx76c9a3704725`方式改为传统模式 ```bash ln -s /dev/null /etc/udev/rules.d/80-net-setup-link.rules ``` 使用`ip link show`,结果输出: ```bash ip link show 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether 7e:23:b2:98:57:91 brd ff:ff:ff:ff:ff:ff 5: wlan0: mtu 1500 qdisc mq state DOWN mode DORMANT group default qlen 1000 link/ether 44:6a:2e:ac:5f:99 brd ff:ff:ff:ff:ff:ff 6: p2p0: mtu 1500 qdisc mq state DOWN mode DORMANT group default qlen 1000 link/ether 46:6a:2e:ac:5f:99 brd ff:ff:ff:ff:ff:ff ``` - 强制`p2p0`下线 ```bash # 在NetworkManager中上p2p0下线 $ sudo ip link set p2p0 down $ sudo nmcli device set p2p0 managed no # udev规则永久阻止p2p0 $ echo 'ACTION=="add", SUBSYSTEM=="net", KERNEL=="p2p0", RUN+="/usr/bin/ip link set %k down"' | sudo tee /etc/udev/rules.d/81-p2p-kill.rules $ sudo udevadm control --reload $ sudo udevadm trigger --action=add ``` - 配置wlan0, 并让NetworkManager接管所有接口 ```bash # 传统网络配置工具ifupdown, 通过/etc/network/interfaces.d/*来配置 # 通过 ifup/ifdown 或 networking 服务管理 # 所以确保不重复、冲突管理,请清空这个配置问题 $ sudo nano /etc/network/interfaces # 只保留 loopback # 现代网络管理(NetworkManager) # /etc/NetworkManager/NetworkManager.conf为它的全局的主配置文件 # /etc/NetworkManager/conf.d/下的子配置文件覆盖默认设置,.conf 结尾(99-custom.conf) # /etc/NetworkManager/system-connections/ 网络连接配置文件 # 配置主文件:忽略已在/etc/network/interfaces 中定义的接口(标记为 unmanaged) $ sudo cat /etc/NetworkManager/NetworkManager.conf [main] plugins=ifupdown,keyfile [ifupdown] managed=true # 重启 NetworkManager $ sudo systemctl restart NetworkManager # 优化 wlan0 配置 # 激活 wlan0 $sudo ip link set wlan0 up # 通过NetworkManager连接 WiFi,并且开机会自动连接wifi $ sudo nmcli device wifi connect "yourWiF" password "yourWiFiPassword" ifname wlan0 # 静态ip编辑配置文件/etc/NetworkManager/system-connections/.nmconnection [ipv4] dns-search= method=manual addresses1=192.168.1.100/24,192.168.1.1 dns=192.168.1.1 # 手工重新启用下,连接wifi $ nmcli connection reload "wifi名字" $ nmcli connection down "wifi名字" $ nmcli connection up "wifi名字" # 检查连接状态 $ nmcli device status $ ip addr show wlan0 # 应显示分配的 IP $ sudo iw dev wlan0 set power_save off # 关闭省电模式 $ sudo nmcli connection modify "yourWiF" ipv4.dhcp-timeout 30 # 延长 DHCP 超时 ``` # 三. Kernel启动过程 ![image-20250720081845032](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/image-20250720081845032.png) ## a. start_kernel() `start_kernel()`最后调用: ## b. 第一个用户进程Init 由内核执行引导的第一个进程是 init 进程,该进程号始终是“1”,主要完成系统的一系列初始化的任务。**init 进程根据其配置文件`/etc/inittab` 和`/etc/rc.d/rcN.d`【依据`inittab`中的`initdefault` 级别】**。由于该配置文件是init 进程执行的惟一依据。 inittab 文件中除了注释行外,每一行都有如下格式: `id:runlevels:action:process` - **id**: `id`是配置记录标识符,由1~4 个字符组成,对于`getty`或`mingetty`等其他`login`程序项,要求`id`与`tty` 的编号相同,否则`getty`程序将不能正常工作。 - **runlevels** `runlevels`是运行级别记录符,一般使用0~6 以及S 和s。其中,0、1、6 运行级别为系统保留:0 作为shutdown动作,1 作为重启至单用户模式,6 为重启;S 和s 意义相同,表示单用户模式,且无需`inittab`文件,因此也不在inittab 中出现。7~9 级别也是可以使用的,传统的UNIX 系统没有定义这几个级别。`runlevel` 可以是并列的多个值,对大多数`action`来说,仅当`runlevel` 与当前运行级别匹配成功才会执行: - 0 - halt (Do NOT set initdefault to this) - 1 - Single user mode - 2 - Multiuser, without NFS (The same as 3, if you do not have networking) - 3 - Full multiuser mode (文本界面启动模式) - 4 - unused - 5 - X11 (图形界面启动模式) - 6 - reboot (Do NOT set initdefault to this) - `action` `action`字段用于描述系统执行的特定操作,它的常见设置有:`initdefault`、`sysinit`、`boot`、`bootwait`、`respawn`等。`initdefault` 用于标识系统缺省的启动级别。当init 由内核激活以后,它将读取inittab 中的initdefault 项,取得其中的`runlevel`,并作为当前的运行级别。如果没有`inittab`文件,或者其中没有`initdefault` 项,`init`将在控制台上请求输入`runlevel`。`sysinit`、`boot`、`bootwait` 等`action` 将在系统启动时无条件运行,忽略其中的`runlevel`。`respawn`字段表示该类进程在结束后会重新启动运行。 - `process` `process`字段设置启动进程所执行的命令。以下结合笔者系统中的`inittab`配置文件详细讲解该配置文件完成的功能。 `/etc/rc.d/init.d`目录存放**系统服务**的脚本,`/etc/rc.d/rcN.d`【其中的N 分别对应不用的运行级别】存放的是运行级别**N**真正需要运行的**系统服务**,每个对应的服务都以“K”或“S”开头,其中的K 代表关闭(kill),其中的S 代表启动(start),用户可以使用命令“+start|stop|status|restart”来对相应的服务进行操作。 ```bash [root@localhost rc3.d]# ls /etc/rc.d/rc3.d K02NetworkManager K35winbind K89netplugd S10networ S28autofs S95anacron K05saslauthd K36lisa K90bluetooth S12syslog S40smartd S95atd K10dc_server K45named K94diskdump S13irqbalance S44acpid S97messagebus K10psacct K50netdump K99microcode_ctl S13portmap S55cups S97rhnsd ``` 在执行完相应的`/etc/rc.d/rcN.d`目录下的脚本文件后,`init` 最后会执行`rc.local` 来启动本地服务,因此,用户若想把某些非系统服务设置为自启动,可以编辑`rc.local` 脚本文件,加上相应的执行语句即可: ```bash [root@localhost xinetd.d]# service xinetd restart ``` # 四. 创建守护进程Daemon ![image-20250720205640334](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/image-20250720205640334.png) 编写守护进程看似复杂,但实际上也是遵循一个特定的流程。只要将此流程掌握了,就能很方便地编写出用 户自己的守护进程。 ## a. 创建子进程,父进程退出 由于守护进程是脱离控制终端的,因此,完成第一步后就会在shell 终端里造成一种程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在shell 终端里则可以执行其他的命令,从而在形式上做到了与控制终端的脱离。 ## b. 在子进程中创建新会话 这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但它的意义却非常重大。在这里使用 的是系统函数setsid(),在具体介绍setsid()之前,读者首先要了解两个概念:进程组和会话期。 - 进程组 进程组是一个或多个进程的集合。进程组由进程组 ID 来惟一标识。除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组 ID。且该进程ID 不会因组长进程的退出而受到影响。 - 会话期 会话组是一个或多个进程组的集合。通常,一个会话开 始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期,它们之间的关系如图下: ![image-20250720210000606](LinuxMCU%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA/image-20250720210000606.png) setsid()函数用于创建一个新的会话,并担任该会话组的组长。调用setsid()有下面的3 个作用: - 让进程摆脱原会话的控制 - 让进程摆脱原进程组的控制 - 让进程摆脱原控制终端的控制 ## c.改变当前目录为根目录 这一步也是必要的步骤。使用fork()创建的子进程继承了父进程的当前工作目录。由于在进程运行过程中,当前目录所在的文件系统(比如“/mnt/usb”等)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让“/”作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir()。 ## d. 重设文件权限掩码 文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有一个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork()函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为 0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask()。在这里,通常的使用方法为umask(0)。 ## e. 关闭文件描述符 同文件权限掩码一样,用fork()函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法被卸载。 在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf())输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1 和2 的3 个文件(常说的输入、输出和报错这3 个文件)已经失去了存在的价值,也应被关闭。 参考: ```bash sudo make -C /home/jackgao/HiSTBLinuxV100R005C00SPC060/source/kernel/linux-4.4.y ARCH=arm O=/home/jackgao/HiSTBLinuxV100R005C00SPC060/out/hi3798mv100/hi3798mdmo1g/obj/source/kernel/linux-4.4.y INSTALL_MOD_PATH=~/rootfs modules_install ``` [README.md · JackGao/hi3798mv100 - 码云 - 开源中国](https://gitee.com/JackGao19700/hi3798mv100/blob/master/README.md) [hi3798mv100开发笔记(一)SDK编译_hi3798mv100 sdk-CSDN博客](https://blog.csdn.net/m0_48931482/article/details/136980382) [hi3798mv100开发笔记(二)Ubuntu20.04 rootfs移植_hi3798mv100 ubuntu-CSDN博客](https://blog.csdn.net/m0_48931482/article/details/136988649)