一、重谈重定向


- stderr 和 stdout 是打印到显示器文件里面的 , 均访问同一个文件
- 进行重定向的时候,是把3重定向到1 , 但是2依旧是在显示屏

问 : 为什么有了stdout , 还需要有stderr ?

问:如何把stderr 和 stdout 打印到同一个文件 ?


二、理解"一切皆文件"


Linux 把所有资源都抽象成文件,包括:
- 普通磁盘文件(
log.txt)- 设备(键盘、显示器、网卡、磁盘)
- 进程间通信(管道、socket)
- 甚至内核对象
统一接口的意义
不管操作的是什么资源,都用同一套系统调用接口 (
open/read/write/close)来操作,这就是「一切皆文件」的本质:
- 对开发者:只需要学一套 API,就能操作所有资源
- 对内核:通过
struct file_operations函数指针集,为不同资源实现不同的底层操作(比如键盘的read和磁盘的read实现完全不同)- 对系统:架构简洁,可扩展性极强

骗过进程,相当于骗过用户 , 让进程以为 "一切皆文件" ,屏蔽底层的差异。
file_operation 就是把系统调用和驱动程序关联起来的关键数据结构 , 这个结构的每个成员都对应着一个系统调用。读取file _operation 中相应的函数指针,接着把控制权转交给函数。从而完成了Linux设备驱动程序的工作

上图的外设,每个设备都可以有自己的read , write , 但一定是对应着不同的操作方法!!!但通过struct file下file _operation中的各种函数回调,让我们开发者只用file便可以调取Linux系统中绝大部分的资源!!!这就是"Linux下一切皆文件"的核心概念!
三、缓冲区
3.1 什么是缓冲区


缓冲区是内存中预留的一块空间,用于缓存 IO 数据,分为:
- 用户级缓冲区 :由 C 标准库(libc)维护,在用户空间
- 内核级缓冲区 :**由 Linux 内核维护,**在内核空间
3.2 为什么要引入缓冲区机制
核心原因:CPU 速度 ≫ 内存速度 ≫ 磁盘 / 设备 IO 速度,存在严重的速度不匹配。
缓冲区的价值:
- 减少系统调用次数:避免频繁的用户态 ↔ 内核态切换,降低开销
- 批量 IO 提升效率:底层设备(如磁盘)更擅长批量读写,缓冲区可以攒够数据再一次性写入
- 解耦 CPU 与 IO:CPU 只需要操作内存缓冲区,无需等待低速设备完成 IO
想象一下,如果没有菜鸟驿站,快递员要送快递给取件人,就必须直接联系对方:
-
快递员每送到一件快递,都要打电话给取件人,等对方下楼来取。
-
如果取件人不在家,快递员就得改天再跑一趟,或者一直等着。
-
取件人也可能因为不能及时拿到快递而感到不便。
这就像没有缓冲区的文件读写 :每次应用程序要读一个字节,CPU就得发起一次系统调用,切换到内核态,让磁盘控制器去读一个字节,然后切换回用户态。频繁的系统调用和状态切换会消耗大量时间,就像快递员反复跑腿、等待一样,效率极低。
有了菜鸟驿站,流程就变成了:
-
快递员(数据源/输入设备) 不再需要挨个打电话等取件人,而是直接把一批快递(数据块)放到驿站的货架上(缓冲区),然后就可以去处理下一批快递了。
-
取件人(CPU/应用程序) 可以在自己方便的时候(比如下班后)去驿站,一次取走所有自己的快递,或者分次取,完全不用和快递员同步。
-
驿站(缓冲区) 起到了中间暂存的作用,既让快递员能快速卸货,也让取件人能灵活取货。

3.3 缓冲类型


用户级缓冲区(语言层缓冲区)
由C标准库提供(如
printf、fwrite等函数内部维护的缓冲区)。当我们调用这些函数时,数据并不会立即通过系统调用写入内核,而是先暂存在用户空间的一块内存中(即用户级缓冲区)。例如,多次调用
printf("a"),字符'a'可能先被收集到用户缓冲区,直到满足一定条件才一次性通过write系统调用发送给内核。内核级缓冲区(文件内核缓冲区)
当用户程序通过**系统调用(如
write)**将数据交给操作系统后,操作系统并不一定会立即将数据写入硬件(如磁盘),而是先将数据拷贝到内核空间的缓冲区(即内核缓冲区)。操作系统根据自身的策略(如缓存、合并写入)决定何时真正将数据刷到硬件设备。
| 缓冲类型 | 刷新策略 | 典型场景 |
|---|---|---|
| 全缓冲 | 缓冲区满才刷新 | 普通磁盘文件(默认) |
| 行缓冲 | 遇到 \n 或缓冲区满才刷新 |
标准输出 stdout(终端) |
| 无缓冲 | 直接写入,不缓冲 | 标准错误 stderr |
解释:


这是由于我们将1号描述符重定向到磁盘文件后,缓冲区的刷新方式成为了全缓冲。而 我们写入的内容并没有填满整个缓冲区,导致并不会将缓冲区的内容刷新到磁盘文件中。
怎么办呢?
可以使用fflush强制刷新下缓冲区。


还有一种解决方法 , 刚好可以验证 一下stderr是不带缓冲区的 :


这种方式便可以将2号文件描述符重定向至文件 ,由于stderr没有缓冲区 ,****"hello world"不用fflush就可以写入文件
3.4 FLIE(库封装系统调用)



很正常,四条消息,但是由于write是系统调用 , 直接到文件内核缓冲区 ; 其他的库函数是先放在C标准库的的缓冲区,后面才被刷到os。
我们再接着看下面的例子:




3.5 简单设计一下libc库

mystdio.c
#include "mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
static MYFILE *BuyFile(int fd, int flag)
{
MYFILE *f = (MYFILE*)malloc(sizeof(MYFILE));
if(f == NULL) return NULL;
f->bufferlen = 0;
f->fileno = fd;
f->flag = 0;
f->flush_method = LINE_FLUSH;
memset(f->outbuffer, 0, sizeof(f->outbuffer));
return f;
}
MYFILE *MyFopen(const char* path, const char *mode)
{
int fd = -1;
int flag = 0;
if(strcmp(mode, "w") == 0)
{
flag = O_WRONLY | O_CREAT | O_TRUNC;
fd = open(path, flag, 0666);
}
else if(strcmp(mode, "a") == 0)
{
flag = O_WRONLY | O_CREAT | O_APPEND;
fd = open(path, flag, 0666);
}
else if(strcmp(mode, "r") == 0)
{
flag = O_RDONLY;
fd = open(path, flag);
}
else
{
//todo
}
if(fd < 0) return NULL;
return BuyFile(fd, flag);
}
void *MyClose(MYFILE *file)
{
if(file->fileno < 0) return NULL;
MyFflush(file);
close(file->fileno);
free(file);
}
int MyFwrite(MYFILE *file, void *str, int len)
{
//1.写入就是拷贝
memcpy(file->outbuffer + file->bufferlen, str, len);
file->bufferlen += len;
//2.尝试判断是否满足刷新条件?
if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n')
{
MyFflush(file);
}
return 0;
}
void MyFflush(MYFILE *file)
{
if(file->bufferlen <= 0) return;
int n = write(file->fileno, file->outbuffer, file->bufferlen);
(void)n;
fsync(file->fileno);
file->bufferlen = 0;
}
mystdio.h
#pragma once
#include <stdio.h>
#define MAX 1024
#define NONE_FLUSH (1<<0)
#define LINE_FLUSH (1<<1)
#define FULL_FLUSH (1<<2)
typedef struct IO_FILE
{
int fileno;
int flag;
char outbuffer[MAX];
int bufferlen;
int flush_method;
}MYFILE;
MYFILE *MyFopen(const char* path, const char *flag);
void *MyClose(MYFILE *file);
int MyFwrite(MYFILE *file, void *str, int len);
void MyFflush(MYFILE *file);
usercode.c
#include "mystdio.h"
#include <string.h>
int main()
{
MYFILE * filep = MyFopen("./log.txt", "a");
if(!filep)
{
printf("fopen error!\n");
return 1;
}
char* msg = "hello myfile!\n";
MyFwrite(filep, msg, strlen(msg));
MyClose(filep); //FILE *fp
return 0;
}
