理解 Linux 文件结构:一份简单易懂的入门教程_linux是面向文件的吗
个人主页:chian-ocean
文章专栏-Linux
前言:
Linux 文件系统是指 Linux 操作系统用于组织和管理文件、目录及其元数据(如权限、时间戳等)的系统。文件系统定义了文件的存储、访问和管理的方式,并提供了数据持久性和组织结构。

C语言文件操作
C语言文件
-
FILE*是一个指向文件的结构体,通过它,程序可以与文件进行交互。 -
文件指针由标准库函数如
fopen()创建和返回,文件的读写操作通过这个指针来执行。 -
文件指针常用于文件操作函数,如
fopen()、fread()、fwrite()、fclose()等。
#include #include int main() { // 打开名为 \"log.txt\" 的文件以进行写入(\"w\" 模式) FILE* fd = fopen(\"log.txt\", \"w\"); //路径默认在当前工作路径 if (fd == NULL) // 如果fopen失败(例如文件无法打开) { perror(\"fopen\"); // 输出错误信息,指示fopen失败 return 1; // 返回错误代码(1),表示程序失败 } const char* msg = \"hello linux!\\n\"; // 定义要写入文件的字符串消息 // 使用strlen计算消息长度,并将消息写入文件 fwrite(msg, strlen(msg), 1, fd); fclose(fd); // 写入完成后关闭文件 return 0; // 返回0,表示程序成功执行}
理解当前工作路径:
- 在进程文件读写的时候会进程会记录当前的工作目录(
cwd),如图:在/home/ocean/linux/file/filetest路径下创建文件。

- 我们在代码上加上
chdir(\"/home/ocean/linux/file\");
就会更改当前的工作路径,如图查看到的cwd是: /home/ocean/linux/file

再次执行代码后文件会创建在 /home/ocean/linux/file这个路径下

文件打开方式
r
- 打开文件进行读取,文件指针定位在文件的开始位置。
- 如果文件不存在,打开失败。
r+
- 打开文件进行读取和写入,文件指针定位在文件的开始位置。
- 如果文件不存在,打开失败。
w
- 打开文件进行写入,如果文件存在,则截断文件至零长度;如果文件不存在,创建文件。
- 文件指针定位在文件的开始位置。
w+
- 打开文件进行读取和写入,如果文件不存在,创建文件;如果文件存在,截断文件至零长度。
- 文件指针定位在文件的开始位置。
a
- 打开文件进行附加写入,写入的内容会追加到文件的末尾;如果文件不存在,创建文件。
- 文件指针定位在文件的末尾。
a+
- 打开文件进行读取和附加写入,读取时从文件的开始位置开始,写入时追加到文件末尾;如果文件不存在,创建文件。

本质上没有必要记那么多,如果需要直接去看官方的文档即可
文件的系统调用
在操作系统中,文件操作通常通过系统调用(system calls)进行。这些系统调用直接与操作系统的内核交互,以进行文件的创建、读取、写入、删除等操作。
open()
- 功能:打开文件,返回文件描述符。
- 语法:
int open(const char *pathname, int flags, mode_t mode); - 参数:
pathname:要打开的文件路径。flags:指定文件的打开模式(如只读、写入、追加等)。mode:文件权限,如果文件是新建的,指定文件权限。
- 返回值:成功返回文件描述符(一个非负整数),失败返回 -1,并设置
errno。

理解flags
flags 参数允许通过按位“或”运算符(|)将多个标志组合在一起。这样,可以灵活地指定多种行为。例如,你可以使用 O_WRONLY | O_CREAT | O_TRUNC 来实现“以写模式打开文件,如果文件不存在则创建文件,如果文件已存在则截断文件”的行为。
int fd = open(\"file.txt\", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
为什么会用 |呢
#include #define ONE (1<<0) #define TWO (1<<1) #define THREE (1<<2) #define FOUR (1<<3) // 用于根据标志位显示相应功能的函数void show (int flags) { // 检查 flags 的每一位,如果相应的位被设置,则输出对应的信息 if(flags & ONE) cout << \"func 1\" << endl; // 如果 flags 的最低位(0)被设置,则输出 \"func 1\" if(flags & TWO) cout << \"func 2\" << endl; // 如果 flags 的第二位(1)被设置,则输出 \"func 2\" if(flags & THREE) cout << \"func 3\" << endl; // 如果 flags 的第三位(2)被设置,则输出 \"func 3\" if(flags & FOUR) cout << \"func 4\" << endl; // 如果 flags 的第四位(3)被设置,则输出 \"func 4\"} int main() { // 调用 show 函数,传递不同的 flags,分别测试不同的功能组合 show(ONE | TWO); // 传递 ONE | TWO,即 flags = 0001 | 0010 = 0011 -> func 1 和 func 2 show(ONE | THREE); // 传递 ONE | THREE,即 flags = 0001 | 0100 = 0101 -> func 1 和 func 3 show(ONE | TWO | THREE); // 传递 ONE | TWO | THREE,即 flags = 0001 | 0010 | 0100 = 0111 -> func 1, func 2 和 func 3 return 0; // 程序正常结束}
代码解释:
- 宏定义:使用
#define来定义常量ONE、TWO、THREE和FOUR,它们分别表示不同的二进制位:ONE代表二进制的第 1 位(0001)。TWO代表二进制的第 2 位(0010)。THREE代表二进制的第 3 位(0100)。FOUR代表二进制的第 4 位(1000)。
show函数:这个函数接受一个整数flags,然后检查每一位是否被设置。如果某一位被设置(即该位为1),则打印相应的功能名:- 使用按位与操作符
&来检查每个标志位是否被设置。 - 如果某个标志位被设置,
if (flags & ONE)等判断条件为true,执行相应的cout输出。
- 使用按位与操作符
main函数:调用show()函数并传递不同的flags值,展示不同的功能组合:show(ONE | TWO):将ONE和TWO进行按位“或”运算,得到0011,所以会显示\"func 1\"和\"func 2\"。show(ONE | THREE):将ONE和THREE进行按位“或”运算,得到0101,所以会显示\"func 1\"和\"func 3\"。show(ONE | TWO | THREE):将ONE、TWO和THREE进行按位“或”运算,得到0111,所以会显示\"func 1\"、\"func 2\"和\"func 3\"。
生成:

这个大致就是flag的 |的本质
常用的falg:
理解mode详细:参考
在 open() 函数中,mode 参数是用于指定新创建文件的权限和访问控制。该参数仅在使用 O_CREAT 标志时才需要提供,表示当文件不存在时,open() 会创建新文件并根据 mode 参数设置文件的权限。mode 参数通常是一个三位八进制数字,表示文件所有者、文件所属组和其他用户的权限。
mode 参数的基本结构
mode 参数采用 三位八进制数 的形式,表示文件的权限。每一位代表不同用户类别的访问权限,分别是:
- 第一位:所有者(Owner)的权限 4
- 第二位:所属组(Group)的权限 2
- 第三位:其他用户(Others)的权限 1
777rwxrwxrwx755rwxr-xr-x644rw-r--r--600rw-------write
- 功能:将数据写入文件。
- 语法:
ssize_t write(int fd, const void *buf, size_t count); - 参数:
fd:文件描述符。buf:缓冲区,包含要写入的数据。count:要写入的字节数。
- 返回值:返回实际写入的字节数,或返回
-1表示错误。
close()
- 功能:关闭打开的文件。
- 语法:
int close(int fd); - 参数:
fd:文件描述符。
- 返回值:成功返回 0,失败返回
-1
示例:
#include // 引入标准输入输出库,用于printf和perror等#include // 引入字符串处理库,用于处理字符串(如strlen)#include // 引入POSIX标准库,用于访问系统级函数(如write和close)#include // 引入定义文件操作需要的类型#include // 引入文件状态相关定义#include // 引入文件控制函数,如openint main() { // 使用 O_CREAT、O_TRUNC 和 O_WRONLY 打开文件 \'log.txt\' // O_CREAT:如果文件不存在,创建文件 // O_TRUNC:如果文件已经存在,清空文件内容 // O_WRONLY:只写模式打开文件 // 0666:设置文件权限,所有用户都有读写权限 int fd = open(\"log.txt\", O_CREAT | O_TRUNC | O_WRONLY, 0666); // 检查文件是否成功打开,如果返回值小于0,表示打开失败 if (fd < 0) { perror(\"open\"); // 如果打开文件失败,输出错误信息 return -1; // 返回错误代码,程序退出 } // 定义要写入文件的字符串 const char* str = \"hello linux\\n\"; // 定义要写入文件的次数 int cnt = 5; // 循环写入文件5次 while (cnt--) { // 每次调用 write() 写入字符串到文件 write(fd, str, strlen(str)); } // 关闭文件 close(fd); return 0; // 正常退出程序}
代码解释:
open()函数:使用O_CREAT、O_TRUNC和O_WRONLY标志打开文件log.txt。如果文件不存在,将会创建文件;如果文件已存在,使用O_TRUNC将文件内容清空。文件权限设置为0666,即所有用户都可以读写该文件。- 错误处理:如果
open()调用失败,会返回负值,使用perror(\"open\")输出错误信息并退出程序。 write()函数:将字符串\"hello linux\\n\"写入文件。写入操作会在文件fd中进行,strlen(str)确保写入的字符数是字符串的实际长度。- 文件操作:代码通过
write()循环写入文件 5 次,每次写入字符串\"hello linux\\n\"。 close()函数:在完成写入后,通过close(fd)关闭文件。
这份代码类似于fopen 、fwrite 、fclose,进行文件写入,这里面运用系统调用,但是 f 系类运用的是C语言函数库中函数,是对上述系统调用的再次封装。
文件描述符
文件描述符(File Descriptor) 是操作系统用来表示打开文件的一个整数标识符。它是一个指向内核中文件对象的引用,用于标识进程对文件的访问。每个进程都有一个文件描述符表,记录着当前进程打开的文件及其相关信息。
标准输入、标准输出、标准错误:
- 每个进程启动时,操作系统会为它创建三个标准的文件描述符:
0:标准输入(stdin),通常用于接收输入。1:标准输出(stdout),通常用于显示输出。2:标准错误(stderr),用于输出错误信息。
文件描述符的分配: 当进程通过 open() 打开文件时,操作系统会分配一个文件描述符,该文件描述符对应于该文件的内核级数据结构。文件描述符是进程与操作系统文件系统之间的桥梁,进程通过它进行文件操作(如读取、写入)。

任务结构 task_struct:
task_struct是 Linux 内核中表示进程的数据结构,每个正在运行的进程都有一个对应的task_struct。- 图中提到的
struct files_struct *files表示每个进程都有一个files_struct,它包含了该进程打开的所有文件描述符。
文件描述符数组 fd_array[]:
fd_array[]是一个数组,里面保存了指向文件结构(struct file)的指针。每个struct file代表着一个已打开的文件。- 例如,
fd_array[0]代表标准输入(stdin),fd_array[1]代表标准输出(stdout),fd_array[2]代表标准错误输出(stderr)。 - 数组中的其他位置保存着该进程打开的其他文件。
struct file 结构:
- 每个
struct file结构体代表一个具体的文件,它包含了文件的状态信息,比如文件的当前偏移量、文件访问模式、权限等。 - 这些文件结构会通过
next指针形成一个链表,允许操作系统管理多个文件。
所以0 1 2 对应标准输入、输出、错误流 正好与之对应
0号文件
#include // 引入输入输出流库,用于标准输入输出操作#include // 引入字符串操作库(如 strlen、memcpy 等)#include // 引入 UNIX 标准头文件,包含对系统调用(如 read、write 等)的定义#include // 引入定义系统数据类型的库,如 pid_t、off_t 等#include // 引入文件状态信息定义的库(如文件权限、文件类型等)#include // 引入文件控制操作库,包含文件操作的标志,如 O_CREAT、O_WRONLY 等using namespace std; // 使用标准命名空间,避免每次使用标准库的元素时都要加上 \"std::\"int main () // 主函数{ // 读取标准输入的数据,最多读取 100 个字节,存入缓冲区 buf 中 char buf[1024]; // 定义一个字符数组来存储读取的数据 ssize_t size = read(0, buf, 100); // 0 代表标准输入(stdin) buf[size] = \'\\0\'; // 添加字符串结束符,确保读取的数据是一个有效的 C 风格字符串 close(fd); // 关闭打开的文件描述符,释放资源 cout << buf << endl; // 输出读取到的内容到标准输出(屏幕) return 0; // 程序正常结束,返回 0}
代码功能概述:
- 读取标准输入的数据:
- 从标准输入(如键盘)读取最多 100 个字节的数据,存储到缓冲区
buf中。
- 从标准输入(如键盘)读取最多 100 个字节的数据,存储到缓冲区
- 输出读取的数据:
- 输出读取到的数据内容。

1号文件
#include #include #include #include #include #include int main() // 主函数{ // 定义要写入文件的字符串 const char* str = \"hello linux\\n\"; // 这个字符串将被写入输出(标准输出) // 定义要写入的次数 int cnt = 5; // 设置写入的次数为 5 // 循环写入文件 5 次 while (cnt--) // 当 cnt 不为 0 时,循环执行写入操作 { // 每次调用 write() 将字符串写入标准输出(文件描述符 1 对应标准输出) write(1, str, strlen(str)); // write() 的参数:1 表示标准输出,str 表示要写入的内容,strlen(str) 表示字符串长度 } return 0; // 程序执行完毕,返回 0 表示成功}
代码功能概述:
- 定义字符串
str:
- 程序定义了一个常量字符串
\"hello linux\\n\",它将被重复写入标准输出(通常是屏幕)。
- 定义写入次数
cnt:
cnt初始化为 5,表示要将字符串写入标准输出 5 次。
- 循环写入标准输出:
- 使用
write()系统调用将str内容输出到标准输出(文件描述符 1)。 write(1, str, strlen(str))通过write系统调用将字符串的每次输出写入到标准输出。- 每次循环调用
write(),直到cnt达到 0。
output:



