
简介《I.MX6U 嵌入式 Linux C 应用编程指南 V1.4》是正点原子面向嵌入式 Linux 应用开发初学者与进阶工程师编写的实战型教程以 I.MX6U 微处理器及 ALPHA/Mini 开发板为硬件背景帮助读者从标准 C 与系统调用逐步过渡到板级外设编程。内容覆盖文件 I/O、进程管理、信号处理、Socket 与网络基础、GPIO、FrameBuffer、PWM、串口、V4L2 摄像头、音频、CAN、MQTT 物联网及 CMake 工程组织并含视频监控实战项目章节按入门篇到实战递进便于按模块查阅。压缩包内含 1 个 PDF 文件约 36.27MB版本更新记录与完整目录齐备。目前已有 1539 人学习。读者可从中获得系统调用与库函数的对照理解、外设接口的调用范例以及从单点实验到综合项目的组织思路适合作为案头手册与课程配套资料。1. 板子能跑起来但自己的程序怎么往上写串口终端刷完一屏启动日志最后停在login:很多人到这一步就卡住了内核有了、驱动也编进去了接下来要写自己的业务程序open一个设备节点该传什么标志、屏幕怎么刷、摄像头出图走哪几个ioctl全靠东拼西凑。这份《嵌入式 Linux C 应用编程指南 V1.4》补的正是这一段34 章正文从系统调用、文件 I/O 一路铺到 FrameBuffer、V4L2、Socket、CAN、MQTT最后拿一个视频监控小项目收尾全篇对着 Cortex-A7 开发板跑ARM 平台上的交叉编译和板端调试贯穿始终。适合刚做完驱动移植、准备往上写应用层的人也适合写过 STM32 裸机、想把手上的 C 语言底子迁到嵌入式 Linux 上的开发者。它不讲内核源码讲的是你和内核之间的那层接口怎么用对。2. 章节地图与技术分层34 章内容是怎么长出来的2.1 从版本轨迹看文档的组织逻辑翻更新说明能看出一条很清楚的生长线V1.0 只有入门、文件 I/O、标准 I/O、文件属性这几个基础章节V1.1 一次性补进了点亮 LED、GPIO 应用编程、输入设备、tslib、FrameBuffer、JPEG/PNG 显示、横竖屏切换、LCD 显示字符、PWM、V4L2 摄像头、串口、看门狗V1.2 加了音频、网络基础和 socketV1.3 补 CAN、CMake 和 MQTT 物联网V1.4 只加了一章——视频监控实战。这个节奏说明它不是先定目录再填内容而是跟着开发板上一个一个外设长出来的。对读者来说这是好事每一章基本对应一个能上手验证的外设或接口读完就能在那块板子上跑一遍不存在「理论讲了三百页、实验要等第五章」的情况。也正因为是逐版追加知识点有一定的线性依赖第 1 到第 5 章的文件操作概念会在后面 GPIO、FrameBuffer、V4L2 里反复出现前面偷懒后面必然卡。2.2 五个层次按依赖关系排把 34 章按依赖关系重新归一下比按原书顺序读更省力层次章节范围核心内容学完能干什么概念层第 1 章系统调用、库函数、main 函数、开发环境分清用户态与内核态的边界文件层第 2 至 5 章open/read/write/lseek、inode、errno、stdio 缓冲、stat用「一切皆文件」的思路操作资源设备层第 15 至 28 章GPIO、输入设备、FrameBuffer、PWM、V4L2、串口、看门狗、音频直接读写设备节点控制硬件通信层第 29 至 31 章网络基础、socket、CAN板子和板子、板子和服务器对话工程层第 32 至 34 章CMake、MQTT、视频监控项目把散落的 .c 组织成能交付的工程这张表最有用的地方在于如果你只做数据采集上报文件层加通信层就够了设备层挑串口和 GPIO 两章如果你做的是带屏的终端FrameBuffer 和输入设备必须啃透tslib 那章还得反复看因为触摸校准的坑多半出在那里。别按顺序从头读到尾先确认自己的目标落在哪一层。2.3 开发环境Ubuntu 装 Eclipse 加交叉编译器第 1 章花了不小篇幅讲 Ubuntu 下装 Eclipse这在当年是主流选择现在用 VS Code 加 C/C 插件的人更多但底层那套东西没变交叉编译工具链、头文件路径、板端运行。装好工具链后先验证版本# 确认交叉编译器能跑前缀随厂商提供的不一样 arm-linux-gnueabihf-gcc -v # 单独编一个文件看产物架构对不对 arm-linux-gnueabihf-gcc -o hello hello.c file hello # 期望输出里能看到 ARM, EABI5 之类的字样file那一步别跳我见过太多次拿 x86 的 gcc 编完拷到板子上跑不起来的情况报的还是No such file or directory看着像文件不存在其实是架构不对内核连加载都做不到。编译通过只是第一步程序得送到板子上。常见做法是 NFS 挂载根目录Ubuntu 上把编译输出目录导出板子启动参数里指定nfsroot改完代码在 PC 上重新make板子上直接执行不用每次拷贝。另一条路是 tftp 传输加串口调试适合不方便搭网络的场景。这两套环境一旦搭好后面 30 章的实验都能复用值得在开头多花半天。2.4 三条阅读路线按目标选手上是块带屏的板子、要做人机界面路线是第 1-5 章打底然后跳到第 15 章 GPIO、第 17 章输入设备、第 18 章 tslib、第 19 章 FrameBuffer、第 20 至 23 章的图片和字符显示最后看第 22 章横竖屏切换因为实际项目里屏的方向经常和出厂默认不一致。做网关或数据采集路线是第 1-5 章、第 26 章串口、第 29-31 章网络与 socket、第 33 章 MQTT第 27 章看门狗顺手看一眼产品化时要用。做视频相关路线短但深第 1-5 章、第 19 章 FrameBuffer、第 25 章 V4L2、第 34 章视频监控中间第 32 章 CMake 必须看不然多文件工程编不起来。3. 文件 I/O 与系统调用从 open 到原子操作的完整链路3.1 系统调用和库函数的分界在哪第 1 章把这条线划得很清楚系统调用是内核暴露给用户态的唯一入口open、read、write、ioctl这些最终都会陷入内核库函数是 C 库在系统调用之上包的一层printf底下是writefopen底下是open。理解这层的意义不在概念本身而在于出问题时知道往哪查。举个常见的printf打印不出东西你会怀疑程序没跑到那一行实际上可能只是数据还躺在 stdio 的用户态缓冲区里没刷下去。而write是直接进内核的写完就在内核缓冲区里了。两者的行为差异决定了调试期该用哪个。3.2 一个最小可跑的文件读写闭环第 2 章开头那个例子值得完整抄一遍因为它把六个基础接口串成了一条链#include stdio.h #include string.h #include fcntl.h #include unistd.h #include errno.h #define BUF_SIZE 64 int main(void) { int fd; char wbuf[BUF_SIZE] hello embedded linux; char rbuf[BUF_SIZE] {0}; ssize_t n; /* O_CREAT 不存在则创建O_TRUNC 打开即清空0664 是文件权限 */ fd open(./demo.txt, O_RDWR | O_CREAT | O_TRUNC, 0664); if (fd 0) { perror(open); return -1; } n write(fd, wbuf, strlen(wbuf)); if (n 0) { perror(write); close(fd); return -1; } /* 关键一步把读写位置拨回开头否则 read 读到的是文件末尾 */ if (lseek(fd, 0, SEEK_SET) (off_t)-1) { perror(lseek); close(fd); return -1; } n read(fd, rbuf, sizeof(rbuf) - 1); if (n 0) { perror(read); close(fd); return -1; } rbuf[n] \0; printf(read back: %s\n, rbuf); close(fd); return 0; }几个参数得说透。open的第三个参数mode只有在带了O_CREAT时才生效不带O_CREAT时传了也会被忽略这一点新手容易记反。write的返回值是实际写入的字节数不是「成功/失败」理论上可能小于请求长度写大文件时要循环。lseek返回新的偏移量失败返回-1所以判断时要强转成off_t再比否则在某些平台上off_t是无符号的比较会失效。read同理返回 0 表示读到文件末尾返回负数才是出错。这段代码里最容易翻车的是漏掉lseek。写完立刻读读到的是空字符串因为文件偏移已经跑到末尾了。这不是 bug是文件描述符自带位置的正常行为。3.3 errno 与错误处理别把错误码扔了第 3 章专门讲了errno。系统调用失败时返回-1具体原因放在全局变量errno里可以用strerror转成字符串或者直接用perror打印int fd open(/dev/not_exist, O_RDONLY); if (fd 0) { /* 两种写法等价perror 会自动拼上 : 错误描述 */ fprintf(stderr, open failed: errno%d (%s)\n, errno, strerror(errno)); perror(open); return -1; }注意errno只在函数失败的那一刻有效任何后续的库函数调用都可能把它覆盖掉。所以要么立刻打印要么先存到局部变量里。我见过在错误分支里先printf一堆上下文再perror结果打出来的是别的错误码排查方向直接跑偏。错误处理要写在紧挨着调用的位置这不是代码风格问题是正确性问题。另外输出走stderr而不是stdout在嵌入式场景里很实际stdout可能被重定向到别处stderr默认还是终端。3.4 空洞文件、O_APPEND 与 dup2第 3 章有几个实验挺值得动手。空洞文件就是lseek跳着写int fd open(./hole.bin, O_RDWR | O_CREAT | O_TRUNC, 0644); write(fd, x, 1); /* 位置 0 */ lseek(fd, 4095, SEEK_SET); /* 跳到 4095中间 4094 字节没写 */ write(fd, y, 1); /* 位置 4095 */ close(fd); /* ls -l 显示 4096 字节du 只算实际占用的块 */文件长度是 4096但中间那段没落盘不占实际存储。这个特性在做大文件预分配或者稀疏存储时会用到但也要知道拷到不支持稀疏的文件系统上空洞会被填成 0体积瞬间涨上去。O_APPEND是另一个常被误解的标志。它保证每次write都追加到文件末尾而且是原子操作——定位和写入在同一个内核动作里完成。多进程往同一个日志文件写时这个标志比手动lseek到末尾再写要安全得多后者在并发下会出现互相覆盖。dup2用来复制文件描述符最实用的场景是把标准输出接到文件上int fd open(./app.log, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd 0) { perror(open); return -1; } /* 让 1 号描述符指向这个文件之后所有 printf 都落盘 */ if (dup2(fd, STDOUT_FILENO) 0) { perror(dup2); return -1; } close(fd); /* 已经复制给 1 号了原来的 fd 可以关 */ printf(this line goes to app.log\n); fflush(stdout);dup2执行后两个描述符指向同一个打开文件表项共享偏移量所以关掉原fd不影响stdout。这一招在守护进程里几乎是标配程序启动时就把输出重定向到日志文件不依赖外部/path。3.5 标准 I/O 的缓冲printf 为什么没输出第 4 章讲 stdio 缓冲这是嵌入式调试阶段的头号疑案。printf默认是行缓冲遇到换行才刷重定向到文件后变成全缓冲缓冲区满了才写。程序崩在printf之后缓冲区没刷日志就丢了。/* 调试期直接关掉 stdout 缓冲每写一次就落一次 */ setvbuf(stdout, NULL, _IONBF, 0); /* 或者保留缓冲在关键位置手动刷 */ printf(camera init ok\n); fflush(stdout);第 4 章还区分了内核缓冲和stdio缓冲以及用O_DIRECT绕过内核缓冲的直接 I/O。后者在要求低延迟、数据不能缓存的场景才有意义日常应用别碰对齐要求能把人折腾够呛。实际项目里我一般是调试期关缓冲上线后恢复默认并加fflush两害相权。4. 设备节点与实战链路GPIO、FrameBuffer、V4L2 到 Socket4.1 GPIOsysfs 和 libgpiod 是两代写法第 15、16 章讲点亮 LED 和 GPIO 应用编程。这一块最大的坑是书里演示的/sys/class/gpio接口在新内核上已经被标记为废弃很多发行版的默认配置里直接关掉了。先看内核版本再决定走哪条路。老接口长这样#include fcntl.h #include unistd.h int fd, val_fd; /* 第一步把引脚号导出到用户空间编号按芯片手册和板子原理图换算 */ fd open(/sys/class/gpio/export, O_WRONLY); if (fd 0) { perror(export); return -1; } write(fd, 139, 3); close(fd); /* 第二步设方向 */ fd open(/sys/class/gpio/gpio139/direction, O_WRONLY); if (fd 0) { perror(direction); return -1; } write(fd, out, 3); close(fd); /* 第三步写电平写 1 拉高 */ val_fd open(/sys/class/gpio/gpio139/value, O_WRONLY); if (val_fd 0) { perror(value); return -1; } write(val_fd, 1, 1);这套接口的优点是简单直观缺点也明显每次操作都要openwriteclose开销大做不了高频翻转而且要手动管理 export/unexport程序异常退出没清理下次就导不进去。新内核推荐用libgpiod命令行侧是gpioset、gpioget代码侧是gpiod_chip_open加gpiod_line_request_output直连字符设备/dev/gpiochipN性能和资源管理都规范得多。还有一层更深的坑引脚能不能用不取决于你的程序取决于设备树里的 pinmux 有没有把它分给 GPIO 控制器。如果这个脚已经被别的驱动占用export会直接失败或者导进去也控制不了。排查时看/sys/kernel/debug/pinctrl/下的引脚状态比在程序里反复试有效得多。4.2 FrameBuffermmap 一块显存按像素点画第 19 章是显示这条线的地基。核心思路是/dev/fb0对应一块物理显存用mmap映射到用户空间之后往里写字节就是在改屏幕内容。#include linux/fb.h #include sys/ioctl.h #include sys/mman.h int fd open(/dev/fb0, O_RDWR); if (fd 0) { perror(open fb0); return -1; } struct fb_fix_screeninfo finfo; struct fb_var_screeninfo vinfo; /* 先拿固定信息显存起始地址、每行字节数 line_length */ ioctl(fd, FBIOGET_FSCREENINFO, finfo); /* 再拿可变信息分辨率、bpp、像素格式 */ ioctl(fd, FBIOGET_VSCREENINFO, vinfo); size_t screensize finfo.line_length * vinfo.yres_virtual; unsigned char *fbp mmap(NULL, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (fbp MAP_FAILED) { perror(mmap); return -1; }两个结构体别搞混fix里是硬件固定的东西line_length尤其关键它不一定等于xres * bpp / 8有些控制器会做行对齐按分辨率算偏移会花屏var里是可以改的显示参数bits_per_pixel决定每个像素占几个字节。画一个像素/* 32bpp 下按 ARGB 写先定位到行再定位到列 */ unsigned int *pixel (unsigned int *)(fbp y * finfo.line_length x * (vinfo.bits_per_pixel / 8)); *pixel 0x00FF0000; /* 不透明红 */16bpp 的 RGB565 就是unsigned short *值要拆成 5-6-5 位。第 20 至 23 章讲的 JPEG/PNG 解码显示、横竖屏切换、字符显示底层都是这套 mmap 加指针偏移。横屏转竖屏那一章尤其值得看它不是简单调个参数涉及分辨率交换后重新计算line_length和像素落点改错了画面就是斜的。提示vinfo.bits_per_pixel和finfo.line_length一定要从驱动里读不能按经验值写死。同一份代码换块屏就不对十有八九是这里。4.3 V4L2摄像头采集的最小闭环第 25 章的 V4L2 是全书步骤最多的一个实验但顺序是固定的查询能力、设置格式、申请缓冲、映射、入队、开流、出队、处理。int fd open(/dev/video0, O_RDWR | O_NONBLOCK); struct v4l2_capability cap; ioctl(fd, VIDIOC_QUERYCAP, cap); /* 确认支持 capture 和 streaming */ struct v4l2_format fmt {0}; fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width 640; fmt.fmt.pix.height 480; fmt.fmt.pix.pixelformat V4L2_PIX_FMT_YUYV; /* 驱动不支持会回填实际格式 */ fmt.fmt.pix.field V4L2_FIELD_NONE; ioctl(fd, VIDIOC_S_FMT, fmt); struct v4l2_requestbuffers req {0}; req.count 4; /* 缓冲区数量太少会丢帧 */ req.type V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory V4L2_MEMORY_MMAP; ioctl(fd, VIDIOC_REQBUFS, req);接下来对每个 buffer 做VIDIOC_QUERYBUF拿到长度和偏移mmap进用户空间然后全部VIDIOC_QBUF入队最后VIDIOC_STREAMON开流。采集循环里select等可读VIDIOC_DQBUF取出一个填好数据的 buffer用完再VIDIOC_QBUF塞回去。顺序错一步就卡住先开流再入队DQBUF会一直阻塞只入队一部分 buffer帧率上不去。VIDIOC_S_FMT传进去的格式驱动不一定支持它会回填实际使用的格式后面解析数据必须按回填的值来不能按你请求的来。4.4 Socket 与 MQTT把数据送出去第 29、30 章讲网络基础和 socket第 33 章用 MQTT 做物联网小项目。TCP 客户端和服务端之间的连接建立就几行#include sys/socket.h #include arpa/inet.h int sock socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr {0}; addr.sin_family AF_INET; addr.sin_port htons(8888); /* 端口要转网络字节序 */ inet_pton(AF_INET, 192.168.1.100, addr.sin_addr); if (connect(sock, (struct sockaddr *)addr, sizeof(addr)) 0) { perror(connect); return -1; } /* 之后 write/read 就是收发数据 */第 33 章把 MQTT 接在这上面用的是一个轻量客户端库#include mosquitto.h struct mosquitto *mosq mosquitto_new(node-01, true, NULL); mosquitto_connect(mosq, 192.168.1.100, 1883, 60); const char *payload {\temp\:26.5}; mosquitto_publish(mosq, NULL, dev/telemetry, strlen(payload), payload, 1, false); /* qos1retainfalse */ mosquitto_loop(mosq, -1, 1);参数里qos值得展开0 是最多发一次丢了不管1 是至少一次可能重复2 是恰好一次开销最大。设备上报遥测数据用 1 就够服务端按时间戳去重下发控制指令才考虑 2。retain设成 true 时broker 会保留最后一条消息新订阅者一连上就能收到当前状态做设备影子很方便。注意连接要带心跳mosquitto_connect的最后一个参数是 keepalive 秒数。无线网络下超过这个时间没交互broker 会认为客户端掉线并触发遗嘱消息。心跳设太短会频繁重连设太长掉线发现慢一般 30 到 60 秒之间。4.5 CMake把散落的 .c 组织起来第 32 章的 CMake 是为最后那个视频监控项目服务的。前面每章都是单文件gcc编真做成项目就得分模块。交叉编译的CMakeLists.txt关键是开头这几行cmake_minimum_required(VERSION 3.10) project(video_node C) # 交叉编译三件套告诉 CMake 别用主机编译器 set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) find_package(Threads REQUIRED) add_executable(video_node src/main.c src/camera.c src/fb.c src/net.c) target_link_libraries(video_node PRIVATE Threads::Threads mosquitto)CMAKE_SYSTEM_NAME设成Linux是开启交叉编译模式的开关不设这一行后面指定的编译器会被当成普通编译器用编出来的还是 x86 的产物。target_link_libraries比全局link_libraries好依赖关系明确模块多了不会互相污染。采集、显示、网络拆成独立.c接口在头文件里后面想换传输协议只改net.c采集和显示一行不动。5. 避坑与排查嵌入式 Linux 应用编程的五个翻车现场5.1 板子上运行没输出重定向到文件反而有现象程序在开发板的串口终端跑printf一句都不显示echo $?返回值还是 0但写成./app out.txt再cat就有内容。原因stdout连终端时是行缓冲连文件或管道时是全缓冲。如果代码里printf结尾没带\n或者程序在缓冲区刷新前就退出了比如调了_exit数据就丢了。第 3 章讲exit和_exit的区别正是这里exit会刷标准 I/O 缓冲_exit直接走系统调用不刷。解决调试期用setvbuf(stdout, NULL, _IONBF, 0)关掉缓冲或者在每个关键printf后面补fflush(stdout)日志统一走stderr它默认无缓冲。程序退出路径上用exit别用_exit除非你确定缓冲区已经清空。5.2 mmap 显存成功但屏幕是花屏或偏移现象open(/dev/fb0)和mmap都返回成功往映射区写数据屏幕上是斜的条纹或者颜色全错。原因有三种。一是按xres * bpp / 8算行偏移而实际每行长度是finfo.line_length两者因为行对齐经常不等二是像素格式判断错按 RGB565 的unsigned short去写一块 ARGB8888 的屏颜色和位置全乱三是mmap的长度用了xres * yres * bpp / 8忽略了yres_virtual可能大于yres尾部越界。解决坐标换算一律用finfo.line_length长度用line_length * vinfo.yres_virtual写像素前printf一下bits_per_pixel和line_length确认格式用ioctl(FBIOGET_VSCREENINFO)读出来的值不要写死。5.3 V4L2 的 DQBUF 一直阻塞不返回现象摄像头的VIDIOC_STREAMON返回成功进入采集循环后VIDIOC_DQBUF卡死select也一直超时。原因多数是 buffer 没有全部入队就开了流驱动手里没有可写的 buffer自然不会产出数据。另一种是VIDIOC_REQBUFS申请的count被驱动降到了更小的值比如请求 4 个实际只给 2 个代码里还按 4 个去循环QBUF多出来的两个是无效的。解决REQBUFS之后读回req.count按实际值循环严格按「申请 → QUERYBUF → mmap → QBUF 全部 → STREAMON」的顺序DQBUF用非阻塞加select超时了打日志而不是死等方便定位是驱动没数据还是流程错了。5.4 交叉编译的程序拷到板子上报「No such file or directory」现象ls -l看文件明明在权限也是可执行一运行就报这个错甚至./app补全都正常。原因这个报错很多时候不是说文件不存在而是说动态链接器不存在。readelf -l app看interpreter那一行指向的路径比如/lib/ld-linux-armhf.so.3在板子的根文件系统里没有或者版本对不上。也可能是拿 x86 工具链编的架构根本不匹配。解决先用file app确认是 ARM 架构再用readelf -l看解释器路径和板子上ls /lib/ld-*对一下路径不一致就换匹配的工具链或者用-static静态链接绕开动态库依赖体积会大不少但调试期省事。5.5 gpio export 失败或者导进去也控制不了现象往/sys/class/gpio/export写引脚号返回Device or resource busy或者 export 成功direction也设成了 out写value电平不变。原因前者通常是这个引脚已经被设备树分配给别的驱动了比如被复用成 I2C 或者 PWM后者多半是 pinmux 没配成 GPIO 功能或者引脚号换算错了不同芯片的 GPIO 编号基数不一样。解决先看cat /sys/kernel/debug/pinctrl/*/pinmux-pins确认目标引脚当前归谁再翻芯片手册核对引脚编号的基址和偏移实在不行改设备树把冲突的节点status disabled掉再试。新内核上直接换libgpiod的gpioset命令验证硬件通不通比在程序里反复编快得多。6. 二次开发把第 34 章的项目拆成自己的骨架第 34 章的视频监控是个完整闭环V4L2 采集、FrameBuffer 本地显示、socket 传到远端。拿它当起点比自己从零搭要快但直接改成产品会遇到一个问题——采集、显示、传输三件事全缠在main的循环里想加个录像或者换个协议就得动主流程。我的做法是先做一次解耦拆成三个线程加两个队列模块职责对外接口cameraV4L2 初始化、取帧、归还 buffercamera_get_frame()返回一帧的指针和时间戳displayFrameBuffer 映射、YUV 转 RGB、刷屏display_show(frame)netsocket 发送、断线重连、心跳net_send(frame)队列用固定长度的环形缓冲满了丢最旧的一帧而不是阻塞采集实时性优先。三个线程用条件变量同步采集线程出队唤醒显示和网络主线程只负责信号处理和退出清理。改造时有个细节要盯住V4L2 的 buffer 是驱动拥有的DQBUF之后你只有很短的时间窗口能用这块内存处理完必须QBUF还回去否则采集会停。所以不能把 buffer 指针直接扔进队列让别的线程慢慢用要么拷贝一份要么做引用计数处理完最后一个使用者再归还。我第一版就是直接把指针传出去跑了十几秒画面就冻住查了半天才发现 buffer 全被占着没还。验证方式分两步。先在开发板上单独跑每个模块camera 打印每秒帧数display 刷纯色测试映射和格式net 用nc起个假服务端看数据到不到。三个都通了再接起来出了问题也知道往哪个模块查。第二步加异常注入拔网线看重连拔摄像头看错误处理kill -9看资源有没有残留。提示设备节点打开失败、ioctl返回EINVAL、buffer 数量不足这三个错误占了嵌入式应用调试的大半时间。每个失败点都打一条带errno的日志比事后猜高效太多。从那以后我每次接一个新板子第一件事就是写个最小程序把open、ioctl、mmap这条链路单独跑通确认设备节点和数据结构都对再往里填业务逻辑绝不把环境问题和逻辑问题混在一起查。希望帮到你。本文还有配套的精品资源点击获取