用STM32F103驱动HT1621段码屏,我踩过的那些时序坑(附完整FreeRTOS工程)
2026/6/4 8:39:55
Linux 在open一个文件时,有一个铁律:
给新文件分配的 fd,永远是当前files_struct数组中 最小的、未被占用的 下标。
利用这个规则,我们可以玩一个魔术:
printf默认是往stdout(也就是fd 1) 打印数据。close(1),把 1 号下标空出来。open("log.txt", ...)。log.txt。printf依然傻傻地往 fd 1 写数据,但 fd 1 已经不再指向显示器,而是指向了log.txt。代码验证:
#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main() { // 1. 关闭标准输出 (显示器) close(1); // 2. 打开新文件 // 系统发现 1 号坑是空的,于是把 fd 1 给到了 log.txt int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); // 3. 正常打印 // printf 底层是 write(1, ...),它不知道 1 号变了 printf("fd: %d\n", fd); printf("hello redirection\n"); // 4. 刷新缓冲区 (重要!如果是文件,默认是全缓冲,不刷新可能写不进去) fflush(stdout); close(fd); return 0; }现象:屏幕上什么都没有,但cat log.txt会发现内容都在里面。这就是>的雏形。
dup2系统调用手动close再open这种方法有风险(比如多线程环境下可能有竞争,或者代码写起来麻烦)。Linux 提供了一个专门的系统调用来做这件事:dup2。
#include <unistd.h> int dup2(int oldfd, int newfd);很多人容易搞混参数顺序。记忆口诀:让newfd成为oldfd的副本。
newfd已经被打开了,先把它close掉。oldfd指向的那个file结构体指针,复制到newfd的下标位置。newfd和oldfd现在同时指向同一个文件(原来oldfd打开的那个文件)。oldfd是刚打开的文件(如 fd 3),newfd是 1。这样 1 就指向了 3 指向的文件。#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main() { int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { perror("open"); return 1; } // 【核心】把 fd(3) 的内容复制给 1 // 此时 1 号下标也指向了 log.txt dup2(fd, 1); printf("This will go to file!\n"); fprintf(stdout, "This too!\n"); // 现在 1 和 3 都指向 log.txt,关闭 3 不影响 1 close(fd); return 0; }我们在 Linux 命令中常看到> log.txt 2>&1,这是什么意思?
./program > log.txt时,Shell 只把fd 1重定向到了文件。如何把错误也写进文件?./program > log.txt 2>&1
看下面这段诡异的代码:
#include <stdio.h> #include <string.h> #include <unistd.h> int main() { // C库函数 const char *s1 = "hello printf\n"; printf("%s", s1); // 系统调用 const char *s2 = "hello write\n"; write(1, s2, strlen(s2)); // 创建子进程 fork(); return 0; }实验:
./test):屏幕上打印两行,非常正常。./test > log.txt):打开log.txt,你会发现:hello write出现1 次。hello printf竟然出现了2 次!这跟fork无关,跟C 语言标准库 (FILE) 的缓冲策略有关。
\n才刷新。(显示器默认是行缓冲)。分析案发现场:
printf遇到\n,触发行缓冲,立马把数据刷给内核(write)。fork时,C 库缓冲区是空的。父子进程各自退出,没啥可刷的。write是系统调用,直接写内核。重定向时(向文件写):
printf虽然有\n,但因为目标变成了普通文件,策略变为全缓冲。数据暂存在 C 库的用户级缓冲区里,没有刷给内核。write直接写内核,不受影响(先写进去了)。结论:
printf,fwrite)自带用户级缓冲区,操作文件时是全缓冲。write)没有用户级缓冲区。fork会拷贝用户级缓冲区的数据。