本系列主要旨在帮助初学者学习和巩固Linux系统。也是笔者自己学习Linux的心得体会。
这次文章我进行了改进,力求表达的更加清楚,我会引出这篇文章的知识点。和前置知识的准备。
通过这篇文章你能学到什么,一般理论知识点在前,实践在后面

个人主页: 爱装代码的小瓶子
文章系列: Linux
2. C++
文章目录
- [1. 对于前置知识点的回忆,无缝进行衔接:](#1. 对于前置知识点的回忆,无缝进行衔接:)
- 2.Linux最底层的文件打开方式:`open()`
-
- [2-1 参数`const char *pathname`解释:](#2-1 参数
const char *pathname解释:) - [2-2 参数`int flags`标志位的解释:](#2-2 参数
int flags标志位的解释:) - [2-3 第三个参数`mode_t mode`:](#2-3 第三个参数
mode_t mode:)
- [2-1 参数`const char *pathname`解释:](#2-1 参数
- 3.底层写入方式:`write()`
- 4.底层读取方式`read()`:
- 5.三个基本打开的文件描述符:
- 总结:
1. 对于前置知识点的回忆,无缝进行衔接:
在上一篇文章中【C++与Linux基础】文件篇 -语言特性上的文件操作,我们提到了以下知识点:
- Linux一切皆是文件。(只是简单的理解,并没有深入)
- C语言提供的
printf,fprintf,fgets,fread,fwrite,这几种接口 - C语言提供的几种打开文件的模式:只读,只写,追加等等
这里我们将持续深入文件操作结合Linux系统来完成本篇文章,当你读完本篇文章,你会知道以下几点:
- open 函数的参数解析(位图);
- 什么是fd(文件描述符)
- 如何使用write/read/close
- fd的分配规则(重定向的基础)

2.Linux最底层的文件打开方式:open()
open 函数是 Linux 系统中用于打开或创建文件的系统调用接口(System Call),它比 C 标准库的 fopen 更底层。
所需头文件如下:
cpp
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型如下:
对于已经创建好的文件:
cpp
int open(const char *pathname, int flags);
创建新文件(需要指定权限):
cpp
int open(const char *pathname, int flags, mode_t mode);
2-1 参数const char *pathname解释:
这里的路径名称很简单:我们给出指定的文件名,如果前面不加路径,就是默认在进程所处文件下进行创建:
目标文件的路径(绝对路径或相对路径),例如 "data.txt" 或 "/home/user/log.txt"。
2-2 参数int flags标志位的解释:
决定文件的打开方式。必须包含以下访问模式之一(且只能选一个):
O_RDONLY:以只读方式打开。O_WRONLY:以只写方式打开。O_RDWR:以读写方式打开。(这个我们也用的少)
你可以通过按位或 (|) 运算符组合以下可选标志:
O_CREAT:如果文件不存在,则创建它。注意:使用此标志时必须提供第三个参数 mode。O_APPEND:每次写操作都追加到文件末尾。O_TRUNC:如果文件存在且以写方式打开,将其长度截断为 0(清空内容)。O_EXCL:与O_CREAT连用,如果文件已存在,则返回错误(用于确保原子创建
你可能会在像传入这么多参数,计算机内部或者在调用的时候会不会混乱呢?
其实根本不会,这是因为flags 的实现原理基于计算机科学中最经典、最高效的设计之一:位掩码(Bitmask)。
在这里我们来简单的讲讲:
为了让多个参数互不冲突地存放在一个整数里,Linux 内核利用了二进制的特性。每一个标志(Flag)都对应整数中的特定的一位(bit)。
在这里我们假设:
我们规定每一位代表一种功能:
- 第 0 位:
O_WRITE(写模式) -> 0000 0001 (十进制 1) - 第 1 位:
O_CREAT(创建) -> 0000 0010 (十进制 2) - 第 2 位:
O_APPEND(追加) -> 0000 0100 (十进制 4) - 第 3 位:
O_TRUNC(截断) -> 0000 1000 (十进制 8)
我们这里传入不同的值进行按位或,这里只要1就会在不同的位置会保留下来,在内部在通过与特定的位置进行按位与,只要是1,就说明满足了,就开始这个模式。
我们可以来写一个简单的程序来验证一下:
cpp
#define A 1
#define B 2
#define C 4
#define D 8
void print(int flags){
if(flags & A) printf("A is there\n");
if(flags & B) printf("B is there\n");
if(flags & C) printf("C is there\n");
if(flags & D) printf("D is there\n");
}
int main()
{
print(A | D);
return 0;
}
这个代码就巧妙的解释了为什么可以通过位图的方式来完成控制。这段代码非常标准。位运算是 C 语言和系统编程的灵魂之一,它用最少的内存(一个 int)管理了多个开关状态,既高效又优雅。

2-3 第三个参数mode_t mode:
当我们文件不存在的时候,我们进行创建的时候,需要传入这个参数,如果不传入呢,代码如下,结构如下:
c
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h> //这个是用来关闭文件的close
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND);
close(fd);
return 0;
}

我们可以看到的确创建了新的文件在里面,但是权限似乎不大对劲,怎么和其他的普通文件不一样嘞。其实这里就是漏传了 mode的值。我们尝试传入便有:
cpp
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h> //这个是用来关闭文件的close
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
close(fd);
return 0;
}

这里为什么会变成0664 ,当然是我们系统自带的掩码了,其实我们也可以在自己的程序里面也是用掩码。这里本质的来说还是shell设置了掩码,掩码是使用就近原则的。
3.底层写入方式:write()
需要的头文件:
cpp
#include <unistd.h>
函数参数如下:
cpp
ssize_t write(int fd, const void *buf, size_t count);
参数详解:
| 参数 | 含义 |
|---|---|
fd |
文件描述符 。由 open 返回的那个整数。 |
buf |
缓冲区首地址。指向你要写入的数据存放的地方(通常是字符串指针或数组)。 |
count |
字节数。期望写入的字节长度。 |
这个还是比较简单的,我们直接来应用一手:
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND);
if(fd == -1){
perror("open fail");
exit(1);
}
char* msg = "hello world\n";
int cnt = 5;
while(cnt--){
ssize_t ret = write(fd,msg,strlen(msg));//注意这里是ssize_t。还有我需要打印给我看,不需要/0
if(ret == -1){
perror("write fail");
exit(1);
}
}
close(fd);
return 0;
}
这里我们就打开创建了log.txt这个文件,完成向里面写入:

可以看到里面的确写入完成了。
4.底层读取方式read():
函数原型:
cpp
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数详解:
- fd 还是文件描述符,从指定的文件中读取。
- buf 缓冲区,从fd读取到这里来,
- count 请求读取的字节大小,通常是buf的字节大小
read 的返回值 (ssize_t) 有三种情况,这是写代码时的核心逻辑:
- 大于 0 (> 0):成功读取的字节数。
注意:这个数字可能小于你请求的 count(例如文件快读完了,或者你请求读100个字节但文件只剩5个字节)。 - 等于 0 (= 0):End of File (EOF)。
代表文件已经读完了,后面没有数据了。这是结束读取循环的标志。 - 等于 -1 (= -1):出错。
比如文件被以外关闭、硬件错误等。需要检查 errn
这个也是比较简单的,这里我们也来尝试简单使用一下:
cpp
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main()
{
int fd = open("log.txt",O_RDONLY);
if(fd == -1){
perror("open error");
exit(1);
}
char buff[1024];
ssize_t ret = read(fd,buff,sizeof(buff) -1);//留一个给/0
if(ret < 0){
perror("read error");
exit(2);
}
if(ret == 0){
printf("over");
}
if(ret > 0){
buff[ret] = 0;
printf("%s",buff);
printf("%d",fd);//顺便来看看fd是什么?
}
close(fd);
return 0;
}

我们可以看到打印成功了,同时也显示了fd 的值为3.那么这个fd是什么呢?
5.三个基本打开的文件描述符:
代码最后问了 fd 是什么。 在 Linux 中,进程启动时默认打开三个文件描述符:
0: 标准输入 (stdin)
1: 标准输出 (stdout)
2: 标准错误 (stderr)
这三个是基本打开的,我们的程序在跑起来的时候,基本就是打开的。
| FD | 名称 | 默认设备 | 你的理解 | 对应的库函数 |
|---|---|---|---|---|
| 0 | stdin | 键盘 | 这里进数据 | scanf, getchar, cin |
| 1 | stdout | 显示器 | 这里出正常结果 | printf, cout |
| 2 | stderr | 显示器 | 这里出报错信息 | perror, cerr |
有了这个基础,我们后面就可以后面就可以讲重定向了。重定向是怎么实现的了。
总结:
write/read/close 是 Linux I/O 体系的"骨架"。它们既是用户程序与硬件交互的唯一通道,也是操作系统抽象硬件差异、统一资源管理接口的具体实现载体。虽然直接使用它们可能不如库函数高效(缺乏缓冲),但它们提供了对 I/O 行为最精确的控制能力。
已经完成了,感谢各位。希望有所收获。
感谢各位对本篇文章的支持。谢谢各位点个三连吧!

