013 Linux环境熟悉——应用、驱动、系统构建流程

013 Linux环境熟悉——应用、驱动、系统构建流程 01开发板的第1个APP实验gcc -o hello hello.c ./hello Hello, world! ./hello weidongshan Hello, weidongshan! 要想给 ARM 板编译出 hello 程序需要使用交叉编译工具链 arm-buildroot-linux-gnueabihf-gcc -o hello hello.c 这样编译出来的 hello 程序才可以在 ARM 板子上运行02开发板的第1个驱动实验编译内核use_toolchain arm32-linux-hf6ull # 自己的工具链环境很关键 cd ~/linux/6ull/100ask_imx6ull-sdk/Linux-4.9.88 make mrproper make 100ask_imx6ull_mini_defconfig make ARCHarm CROSS_COMPILEarm-buildroot-linux-gnueabihf- HOST_EXTRACFLAGS-fcommon -j4 mkdir -p ~/nfs/mini cp arch/arm/boot/zImage ~/nfs/mini cp arch/arm/boot/dts/100ask_imx6ull_mini.dtb ~/nfs/mini26.4.22填坑这里有个坑我WSL2环境当初不懂加上环境问题都是找AI帮忙配置make 100ask_imx6ull_defconfig但我针对自己的开发板应该选下面这个make 100ask_imx6ull_mini_defconfig导致后续驱动跑不起来。当然还是建议跟着教材的环境一比一学习Ubuntu 高版本 老内核——编译内核一律加make xxx HOST_EXTRACFLAGS-fcommon如make zImage -j4 HOST_EXTRACFLAGS-fcommon编译驱动1.改makefile实际的内核源码根目录KERN_DIR /home/luo/linux/6ull/100ask_imx6ull-sdk/Linux-4.9.882.make前先配置环境变量make 后移到开发板可以操作的地方即可编译第一个hello驱动程序对开发板[root100ask:~]# echo 7 4 1 7 /proc/sys/kernel/printk [root100ask:~]# cd /mnt [root100ask:/mnt]# ls 100ask_imx6ull-14x14.dtb framebuffer lib tmp type_row HZK16 hello_drv.ko mini tslib uart client hello_drv_test show tslibpro zImage emmc input simsun.ttc type [root100ask:/mnt]# insmod hello_drv.ko [15071.262202] hello_drv: loading out-of-tree module taints kernel. [15071.271046] /home/luo/linux/6ull/drv/01_hello_drv/hello_drv.c hello_init line 70 [root100ask:/mnt]# ls /dev/hello -l crw------- 1 root root 245, 0 Jan 1 12:11 /dev/hello [root100ask:/mnt]# ./hello_drv_test Usage: ./hello_drv_test -w string ./hello_drv_test -r [root100ask:/mnt]# ./hello_drv_test -w 666iamok [15152.073071] /home/luo/linux/6ull/drv/01_hello_drv/hello_drv.c hello_drv_open line 45 [15152.081137] /home/luo/linux/6ull/drv/01_hello_drv/hello_drv.c hello_drv_write line 38 [15152.093975] /home/luo/linux/6ull/drv/01_hello_drv/hello_drv.c hello_drv_close line 51 [root100ask:/mnt]# ./hello_drv_test -r [15164.376714] /home/luo/linux/6ull/drv/01_hello_drv/hello_drv.c hello_drv_open line 45 [15164.385268] /home/luo/linux/6ull/drv/01_hello_drv/hello_drv.c hello_drv_read line 30 APP read : 666iamok[15164.397328] /home/luo/linux/6ull/drv/01_hello_drv/hello_drv.c hello_drv_close line 51 [root100ask:/mnt]# ls 100ask_imx6ull-14x14.dtb framebuffer lib tmp type_row HZK16 hello_drv.ko mini tslib uart client hello_drv_test show tslibpro zImage emmc input simsun.ttc type [root100ask:/mnt]# ls mini/ 100ask_imx6ull-14x14.dtb zImage [root100ask:/mnt]# echo 7 4 1 7 /proc/sys/kernel/printkecho 7 4 1 7 /proc/sys/kernel/printk内核打印和用户空间打印完全是两套独立系统。用户空间用 printf 直接输出到终端内核打印默认不会显示在普通终端上需要手动查看。最常用的方法是通过dmesg命令在开发板终端输入这个命令就能看到内核启动以来的所有打印信息包括你驱动里用 printk 输出的内容。[root100ask:/mnt]# cat /proc/devices Character devices: 1 mem 4 /dev/vc/0 4 tty 5 /dev/tty 5 /dev/console 5 /dev/ptmx 7 vcs 10 misc 13 input 29 fb 81 video4linux 89 i2c 90 mtd 108 ppp 116 alsa 128 ptm 136 pts 166 ttyACM 180 usb 188 ttyUSB 189 usb_device 207 ttymxc 216 rfcomm 226 drm 245 hello 246 ttyGS 247 ttyLP 248 watchdog 249 tee 250 iio 251 ptp 252 pps 253 rtc 254 gpiochip Block devices: 1 ramdisk 259 blkext 7 loop 8 sd 31 mtdblock 65 sd 66 sd 67 sd 68 sd 69 sd 70 sd 71 sd 128 sd 129 sd 130 sd 131 sd 132 sd 133 sd 134 sd 135 sd 179 mmc [root100ask:/mnt]# lsmod Module Size Used by hello_drv 3542 0 inv_mpu6050_spi 2052 0 inv_mpu6050 11012 2 inv_mpu6050_spi evbug 2078 0卸载驱动流程[root100ask:/mnt]# rmmod hello_drv [16850.592059] /home/luo/linux/6ull/drv/01_hello_drv/hello_drv.c hello_exit line 90 [root100ask:/mnt]# lsmod Module Size Used by inv_mpu6050_spi 2052 0 inv_mpu6050 11012 2 inv_mpu6050_spi evbug 2078 0同理驱动led程序对开发板fs /mntmx6ull:~]# mount -t nfs -o nolock,vers3 192.168.5.11:/home/book/nfs_rootf [rootimx6ull:~]# cp /mnt/100ask_led.ko ./ [rootimx6ull:~]# cp /mnt/ledtest ./ [rootimx6ull:~]# echo none /sys/class/leds/cpu/trigger -bash: /sys/class/leds/cpu/trigger: No such file or directory报错不影响 [rootimx6ull:~]# insmod 100ask_led.ko [ 3702.921183] 100ask_led: loading out-of-tree module taints kernel.提示内核污染不影响 [rootimx6ull:~]# lsmod查看是否安装成功 Module Size Used by 100ask_led 3995 0 inv_mpu6050_spi 2320 0 inv_mpu6050 11894 2 inv_mpu6050_spi evbug 2282 0 [rootimx6ull:~]# chmod x ./ledtest [rootimx6ull:~]# ./ledtest Usage: ./ledtest dev on | off [rootimx6ull:~]# ./ledtest /dev/100ask_led0 on [rootimx6ull:~]# ./ledtest /dev/100ask_led0 off以下为早期WSL2环境下实验记录记录一下自己的稚嫩我尝试用WSL2重走一遍时发现LED驱动加载Unable to handle kernel NULL pointer dereference然后是lsmod显示100ask_led -2 -2rmmod 100ask_led提示没有下意识重启然后内核崩了[ 6.802241] Code: e1520005 0a000052 e5982008 e5921000 (e4956004)[ 6.842720] ---[ end trace 7ec5c5c932c2a2f6 ]---/etc/init.d/S09modload: line 3: 184 Segmentation faultmodprobe100ask_ds18b20[ 68.694203] random: crng init done复位串口按住回车进单用户模式很遗憾试过很多软件自救都失败了所有软件修改命令都绕不过崩溃的开机流程不过幸运的是我们下一个学习内容刚好是系统烧制记录正常解决方案——SD卡挂载emmc——移除异常开机自启——卸载分区——emmc重启fdisk -l /dev/mmcblk1 Disk /dev/mmcblk1: 3728 MB, 3909091328 bytes, 7634944 sectors 119296 cylinders, 4 heads, 16 sectors/track Units: sectors of 1 * 512 512 bytes Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type /dev/mmcblk1p1 1,70,6 7,165,30 20480 122879 102400 50.0M c Win95 FAT32 (LBA) /dev/mmcblk1p2 7,165,31 268,186,46 122880 4317183 4194304 2048M 83 Linux [root100ask:~]# # 先清空挂载点 [root100ask:~]# umount -f /mnt/emmc umount: cant unmount /mnt/emmc: Invalid argument [root100ask:~]# # 挂载eMMC的根分区注意是mmcblk1p2不是mmcblk0 [root100ask:~]# mount /dev/mmcblk1p2 /mnt/emmc [ 47.738777] EXT4-fs (mmcblk1p2): couldnt mount as ext3 due to feature incompatibilities [ 47.988463] EXT4-fs (mmcblk1p2): recovery complete [ 47.999566] EXT4-fs (mmcblk1p2): mounted filesystem with ordered data mode. Opts: (null) [root100ask:~]# [ 69.114215] random: crng init done cd /mnt/emmc/etc/init.d [root100ask:/mnt/emmc/etc/init.d]# ls S01syslogd S09modload S30dbus S48sntp S50nginx S53neard autofs rcS S02klogd S10udev S40network S49ntp S50sshd S91smb disabled_S45connman S07hmi S20urandom S46ofono S50lighttpd S50telnet S95mpd rcK [root100ask:/mnt/emmc/etc/init.d]# rm -f S09modload [root100ask:/mnt/emmc/etc/init.d]# ls S01syslogd S10udev S40network S49ntp S50sshd S91smb disabled_S45connman S02klogd S20urandom S46ofono S50lighttpd S50telnet S95mpd rcK S07hmi S30dbus S48sntp S50nginx S53neard autofs rcS [root100ask:/mnt/emmc/etc/init.d]# sync [root100ask:/mnt/emmc/etc/init.d]# cd / [root100ask:/]# umount /mnt/emmc复盘:时间线你的操作技术后果崩溃风险节点 1编译并加载100ask_led.ko驱动插入内核触发NULL 指针解引用Kernel Oops⚠️ 内核已经报错但系统还没完全死节点 2执行lsmod看到100ask_led -2 -2驱动标记为 “使用中 / 错误”但没有被卸载(卸载失败提示找不到)⚠️ 坏驱动还在内核里节点 3驱动早就添在/etc/init.d/S09modload开机脚本系统启动时会自动执行modprobe 100ask_led 埋下 “开机自动崩溃” 的炸弹节点 4下意识重启系统进入init进程执行/etc/init.d/下的脚本 炸弹引爆坏驱动自动加载内核直接崩溃节点 5尝试改 U-Boot 参数、单用户模式但init进程强制执行开机脚本坏驱动一加载就崩 系统彻底锁死软件绕不过去拆解节点 1为什么驱动加载会导致NULL pointer dereference【代码是资料源码具体暂时还不确定等我再加载一次】但是先前我们用户态访问时因为zImage没有先删除原来的而是直接覆盖导致仅提示--- Segmentation fault段错误来看——用户态程序有内存保护崩溃只会杀死进程不会影响系统。而因为驱动运行在内核态Kernel Space内核态是 “特权模式”没有内存保护一旦访问非法地址整个内核就会挂掉节点 2lsmod显示-2 -2是什么意思lsmod的输出格式是模块名 大小 使用计数 依赖模块-2表示“模块处于错误状态无法卸载”这说明内核已经识别到这个驱动有问题但因为它在初始化时就崩了没有正确注册清理函数module_exit所以无法自动卸载坏驱动一直残留在内核里。节点 3为什么把驱动加到S09modload是 “致命操作”嵌入式 Linux 的开机启动流程1. 上电 → U-Boot初始化硬件 → 加载内核、设备树 2. 内核启动 → 挂载根文件系统 → 运行 init 进程PID1 3. init 进程 → 进入 runlevel 3 → 执行 /etc/init.d/ 下的所有 S 开头脚本按数字顺序执行 4. S09modload 脚本 → 执行 modprobe 100ask_led → 坏驱动加载 → 内核崩溃把坏驱动加到了开机自动执行的脚本里相当于 “每次开机都主动触发一次内核崩溃”系统根本没机会给你命令行。这也是为什么 “单用户模式”、“改 U-Boot 参数” 都没用因为只要 init 进程跑起来就会强制执行这些脚本坏驱动一加载就崩。节点 5为什么系统会 “彻底锁死软件绕不过去”因为内核崩溃是 “不可恢复的错误”内核一旦 Oops/Panic所有的系统调用、进程调度都会停止你根本没机会输入命令删除脚本。这就是为什么我们最后必须用物理启动方式SD 卡 / NFS救砖只有绕过坏掉的 eMMC 系统从外部启动一个干净的系统才能挂载 eMMC 分区删除坏文件。经验教训 铁律 1测试驱动绝对不要放到开机脚本重复测试 3 次以上先手动加载驱动insmod 100ask_led.ko 观察内核日志dmesg 或 cat /proc/kmsg确认没有 Oops/Panic 测试驱动功能echo 1 /dev/led 等确认功能正常 手动卸载驱动rmmod 100ask_led确认能正常卸载如果加了坏驱动到开机脚本设备一上电就崩根本没法远程修复只能返厂拆芯片烧录成本极高内核 Oops/Panic 后先排查问题绝对不要直接重启 铁律 2内核 Oops/Panic 后先排查问题绝对不要直接重启内核报错后第一时间做这 3 件事保存内核日志dmesg crash.log把报错信息存下来方便后续排查尝试卸载坏驱动rmmod 100ask_led如果能卸载的话检查开机脚本cat /etc/init.d/S09modload确认坏驱动没被加进去你当时的错误是 “下意识重启”直接把 “小问题” 变成了 “系统锁死的大问题”。 铁律 3开发阶段必须准备一张 “救砖 SD 卡”这是嵌入式开发的 “保命符”你现在已经体会到了→ 只要有一张能正常启动的 SD 卡不管系统崩成什么样都能通过 SD 卡启动挂载 eMMC 分区修复。建议你现在就把这张烧好的 SD 卡专门标上 “IMX6ULL 救砖卡”放在开发板旁边以后每次改驱动、改开机脚本前都先确认救砖卡能用。 铁律 4写驱动时必须加 “指针合法性检查”这是避免NULL pointer dereference的最基本方法int *p get_some_pointer(); if (p NULL) { // 一定要检查指针是否为NULL pr_err(Pointer is NULL!\n); return -EINVAL; } *p 10; // 确认指针有效后再访问这也是面试常考题“如何避免驱动里的 NULL 指针解引用”——检查是否为空 铁律 5学会看 “内核调用栈Call Trace”这是排查驱动问题的核心技能你之前的报错里有一段[801e2914] (trace_event_enum_update) from [801d194c] (trace_module_notify0x5c/0x60) [801d194c] (trace_module_notify) from [80157260] (notifier_call_chain0x58/0x94) ...这就是内核调用栈它能告诉你崩溃发生在哪个函数trace_event_enum_update这个函数是被谁调用的trace_module_notify整个调用链路是什么样的学会看调用栈能快速定位驱动里的问题这是嵌入式开发工程师的核心能力。03 u-boot、内核、文件系统构建组合镜像Linux 启动的完整链路是U-Boot → Linux 内核 → 根文件系统三者缺一不可U-Boot初始化硬件加载内核内核初始化系统挂载根文件系统根文件系统用户空间的起点包含所有命令、库、服务、应用程序没有它内核会直接Kernel panic启动失败启动方式 选择从哪个 “硬盘” 启动系统所有配置、文件、环境全都存在对应的 “硬盘” 里eMMC 内置硬盘 C 盘SD 卡 外接 U 盘 / 移动硬盘「SD 卡救砖」的完整逻辑1. eMMC系统崩死 → 软件无法进入内核一加载就崩溃 2. 用SD卡独立纯净系统启动 → 绕过坏eMMC获得命令行 3. 挂载eMMC分区 → 像打开U盘一样访问eMMC的文件 4. 只删除崩溃的2个坏文件 → 微创修复 5. 卸载eMMC → 切回eMMC启动 → 原有环境完全恢复1.Uboot编译新老版本有兼容性问题咱们就先当这关过了投降了原本cd Ubootxxx make distclean make mx6ull_14x14_evk_defconfig makeHOST_EXTRACFLAGS-fcommon 加上也没用用的是高版本 Ubuntu20.04/22.04GCC 版本≥11新版 GCC 移除了老 Uboot 的 tools 目录依赖的内部头文件才会一直报compiler.h缺失的错假设成功2.编译内核见02 开发板的第 1 个驱动实3.构建根文件系统4.builfroot快捷构建与一键镜像发现这里需要懂得原理而非单纯敲一遍代码或者复制粘贴一遍都是没有意义的记不住或者根本不理解04 知识点需理解Uboot命令setenv # 设置环境变量 printenv # 查看所有环境变量 saveenv # 保存环境变量到存储 ping # 测试和Ubuntu的网络连通性 tftpboot # 从TFTP服务器下载文件到开发板内存 bootz # 启动Linux内核系统框架Buildroot 的核心作用嵌入式 Linux 的「根文件系统自动化构建工具」根据自己的需要帮你一键编译生成根文件系统不用手动一个个编译 busybox、库、服务大幅提高效率。Buildroot 的核心操作命令命令作用实际使用场景make menuconfig打开 Buildroot 配置界面裁剪系统、加 / 删软件包比如要不要 Qt5、要不要 SSH 服务、配置系统参数make linux-rebuild单独重新编译内核改了内核驱动 / 配置后不用全编 Buildroot只重编内核节省几小时make uboot-rebuild单独重新编译 U-Boot改了 U-Boot 配置 / 驱动后只重编 U-Boot不用全编make pkg-rebuild单独编译某个软件包改了自己写的应用、Qt 库等只重编对应包不用全编make linux-menuconfig直接打开内核配置界面从 Buildroot 里直接配置内核不用单独进内核目录make busybox-menuconfig打开 busybox 配置界面busybox 是根文件系统的「小工具集合」ls/cp/cat 等命令都来自它用来加 / 删命令output/images/目录编译产物目录所有烧录用的镜像sdcard.img、rootfs.ext4、u-boot-dtb.imx、内核镜像都在这里烧录开发板全靠它inti系统组件systemd 的核心操作systemctl start/stop/restart/status 服务名 # 启动/停止/重启/查看服务状态比如重启SSHsystemctl restart sshd systemctl enable/disable 服务名 # 设置开机自启/关闭自启 systemctl list-units # 查看所有运行的服务核心概念target对应 SysV 的运行级别必须懂multi-user.target多用户命令行模式开发板默认常用graphical.target图形界面模式带 Qt / 桌面的系统用服务文件位置/etc/systemd/system/自己加开机自启服务必须在这里写.service文件然后systemctl enable这是做项目的刚需。根文件系统的核心目录结构目录作用/bin//sbin系统核心命令ls、cp、cat 等busybox 提供/etc系统配置文件网络、服务、开机自启等/dev设备文件开发板的串口、SD 卡、屏幕等设备都在这里/proc内核信息虚拟文件系统查看 CPU、内存、进程等/sys设备驱动信息虚拟文件系统查看硬件驱动状态/usr用户安装的应用、库、工具/var系统日志、临时文件、运行时数据做项目 / 应用开发时再学Buildroot 添加自定义软件包的方法教材里说会提供教程等你要把自己写的应用加到根文件系统、开机自启时再学。systemd 服务文件编写自己写.service文件给自定义应用加开机自启做项目刚需。make sdk生成系统 SDK用来交叉编译 PC 上的应用比如 Qt 程序编译后直接在开发板上跑做应用开发时学。busybox 深度配置裁剪系统、加 / 删命令做最小系统时学。