文章目录
理解“文件”
狭义理解
- 文件存储在磁盘里
- 因为磁盘是永久性的存储介质,所以文件在磁盘上的存储是永久性的
- 磁盘是外设(即是输出设备也是输入设备)
- 因此,对磁盘的文件的所有操作,都属于是对外设的输入(I)或输出(O)
广义理解
Linux下一切皆文件,对于键盘、显示器、网卡、磁盘……都可以将它们抽象化为文件。
后面我们会体会出来。
共识原理
- 文件 = 内容 + 属性
- 文件分为打开的文件和未打开的文件
- 打开的文件:谁打开呢?进程打开!因此,我们本质上是研究进程和文件的关系!
- 根据冯诺依曼体系结构,文件被打开,必须先被加载到内存
- 一个进程,可以打开多个文件,进程和文件是1对n的关系
- 操作系统内部,一定存在大量被打开的文件,OS要不要对这些打开的文件进行管理呢?怎么管理?
先描述,再组织!, 再OS内核中,一个被打开的文件都必须有自己的文件打开对象(struct XXX),包含文件的很多属性和指向下一个被打开文件的指针(struct XXX *next)
- 未打开的文件:放在哪呢?在磁盘上。我们最需要关注什么问题呢?
- 未打开的文件非常多,文件如何被分门别类的存储好?
- 如何快速的对文件进行增删查改,快速的找到文件?
这里的问题,在后续讲解文件系统时,会揭晓。
注意:
- 对于0KB的空文件,也是占用磁盘空间的
- ⽂件的读写本质不是通过C语⾔/C++/Java等语言的库函数来操作的,这些库函数本质是通过对⽂件相关的系统调⽤接⼝封装来实现的。
回顾C语言文件接口
在学习 系统级文件操作 前,需要先回顾一下 C语言 中的文件操作
文件打开
输入指令:man 3 fopen,即可查看man手册
FILE *fopen(const char *path, const char *mode);
- 目标文件名(参数1)
- 直接使用文件名,默认当前路径下的文件
- 进程打开该文件时,如何得知当前路径?
- 进程的cwd存储了当前路径,如果我们更改了当前进程的cwd,就可以把打开或新建文件到其他目录
- 如果想指定目录存放,可以使用绝对路径
- 直接使用文件名,默认当前路径下的文件
- 打开方式(参数2)
w只写,如果文件不存在,会新建文件,文件写入前,会清空内容a追加,如果文件不存在,会新建文件,在文件末尾,对文件进行追加写入,追加前不会清空内容r只读,打开已存在的文件进行读取,若文件不存在,会打开失败w+、a+、r+读写兼具,r+不会新建文件
注意:若文件打开失败,会返回NULL,可以在打开后判断一下是否成功
文件关闭
文件打开并使用后需要关闭,就像动态内存申请后需要释放一样
int fclose(FILE *fp);
关闭已打开文件,只需通过 FILE* 指针进行操作即可
注意:只能对已打开的文件进行关闭,若文件已关闭或不存在,会报错,就像动态内存申请后只能释放一次一样
文件写入
C语言的文件写入接口有:
//写入一个字符
int fputc ( int character, FILE * stream );
//行写入,写入一个字符串
int fputs ( const char * str, FILE * stream );
//格式化写入
int fprintf(FILE *stream, const char *format, ...);
//块写入,可以一次性写入大批数据
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
int snprintf ( char * s, size_t n, const char * format, ... );
这里主要介绍一下过去我们不常用的两个接口:fwrite、snprintf
- 对于fwrite
- 参数1:要写入数据的缓冲区的指针
- 参数2:每个要写入的元素的大小,单位为字节
- 参数3:要写入的元素个数
- 参数4:文件指针
- 返回值:返回实际写入的元素个数(可能小于参数3)
注意:fwrite为二进制格式的写入,也就是说,C语言中的格式符(\0、\n、\t等)不必传入,因为这个东西C语言认识,但文件并不认识,否则会导致写入我们看不懂的符号。
特别注意这种情况:
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fp = fopen("log.txt", "w");
if(fp == NULL) return 1;
const char* msg = "hello linux!";
//这里strlen(msg)不能加1,因为文件不认识\0
fwrite(msg, strlen(msg), 1, fp);
fclose(fp);
}
- 对于snprintf,其实是sprintf的升级版,添加了读取字符长度的控制,更加安全
- 参数1:目标字符数组(缓冲区),常写做 buffer 数组
- 参数2:缓冲区的最大容量(包括终止空字符)
- 参数3:格式化输入
使用 snprintf 函数写入数据至缓冲区后,可以再次通过 fputs/fwrite 函数,将缓冲区中的数据真正写入文件中
文件读取
读取与写入配套出现
int fgetc ( FILE * stream );
char * fgets ( char * str, int num, FILE * stream );
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
int fscanf ( FILE * stream, const char * format, ... );
int sscanf ( const char * s, const char * format, ...);
函数参数与写入差不多,平移使用即可。
如需详细学习更多C文件接口,推荐大佬文章:C语言进阶——文件操作
过度到系统
我们知道,文件是在磁盘上的,磁盘是外部设备,访问磁盘文件其实就是在访问硬件。而用户访问硬件的的途径如图:

上文回顾的C文件接口就是用户操作接口层的libc,而库函数是通过调用系统调用接口,让操作系统来获取硬盘中的文件并对其进行操作。
认识Linux系统级文件操作
现在我们知道,各类语言的IO库的文件操作相关的库函数,本质都是在调用系统调用接口。究竟是什么样的接口?下面我们窥其全貌。
open——打开文件
函数原型:
//需包头文件如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode); //可以修改文件权限
- 返回值:不同于fopen的返回值类型是FILE*,open的返回值是int类型,称为文件描述符( file descriptor ),文件打开失败返回-1。
看似文件描述符与FILE没有一点关系,其实它们之间有着莫大的关系。文件描述符非常重要,下篇文章我会重点讲解。 - 参数1:
pathname待操作文件名,和fopen一样 - 参数2:
flags打开选项,使用标记比特位的方式传递选项信号,下文会重点讲解 - 参数3:
mode权限设置,通常在需要创建文件时使用,文件的起始权限位0666
参数2有点复杂,使用了 位图 的方式进行多参数传递。这种方式甚是玄妙,值的我们去学习这个方法,日后定有大用。
一个int整型,大小为4个字节,就用32个比特位,每个比特位对应一个开关(0为关,1为开),就可以构成32个选项(开关信号),如图:

利用这个特性,我们来写一个关于位图的小demo
位图demo
#include <stdio.h>
#include <stdlib.h>
#define ONE 1//标记低第1位
#define TWO 2//标记低第2位
#define THREE 4//标记低第3位
void Test(int flags)
{
//模拟实现三种选项传递
if(flags & ONE)
printf("This is one\n");
if(flags & TWO)
printf("This is two\n");
if(flags & THREE)
printf("This is three\n");
}
int main()
{
Test(ONE | TWO | THREE);
printf("-----------------------------------\n");
Test(ONE); //位图使得选项传递更加灵活
return 0;
}
在open中,提供了宏,便于我们传参:
O_RDONLY //只读
O_WRONLY //只写
O_APPEND //追加
O_CREAT //新建
O_TRUNC //清空
C语言 中的 fopen 调用 open 函数,其中的选项对应关系如下:
w->O_WRONLY | O_CREAT | O_TRUNCa->O_WRONLY | O_CREAT | O_APPENDr->O_RDONLY- 其他
注意:
- 如果文件不存在,open的参数3一定要设置,否则创建出来的文件权限为随机值,不安全。
- 掩码默认为 0002,我们也可以通过umask()系统调用自定义掩码。
close——关闭文件
close 函数根据文件描述符关闭文件
#include <unistd.h>
int close(int fildes);
write——写入文件
write 函数的返回值类型(ssize_t)有点特殊,但使用方法与 fwrite 基本一致
#include <unistd.h>
ssize_t write(int fildes, const void *buf, size_t nbyte);
ssize_t与size_t的区别:

read——读取文件
read 读取很淳朴,只支持指定字符数读取
#include <unistd.h>
ssize_t read(int fildes, void *buf, size_t nbyte);
下篇预告:重定向与缓冲区
有错误欢迎指出,万分感谢
创作不易,三连支持一下吧~
不见不散!

被折叠的 条评论
为什么被折叠?



