感谢各位 点赞 收藏 评论 三连支持
本文章收录于专栏【Linux系统编程】
❀希望能对大家有所帮助❀
本文章由 风君子吖 原创
前言
对于文件操作,不知大家是否有过接触,例如(fopen,fclose...等),那么大家在刚接触使用这些文件接口的时候,是否有过一些难以言喻的懵懂感,不仅不太理解,而且难以刻于脑海。
本章内容不仅会讲解文件操作内容知识,还会讲授一个重要的概念,这个概念会影响你对于Linux系统编程的学习!
文件
什么是文件?
对于我们日常使用来言,文件似乎就是我们每天运行的游戏,app(.exe)或者我们写的c语言代码(.c和.cpp等),它们都是在磁盘上存储的。 而这种观点知识狭义的,实际上,对于文件有着更广的范域。
文件不仅仅只是它存储的数据,还有它的属性(例如它的权限,它的拥有者、所有组,它的创建时间,它的类型)。
万物皆文件
对于操作系统而言 ,只要是能够被input读取或者output输出的设备就叫做文件!
而我们的键盘和显示器也有着具备读写的能力,这说明它们也可以被称之为文件,而我们日常使用的狭义上的文件是被存储在磁盘上的,我们如果要进行读写,也是对磁盘进行读写,那么也就说明磁盘也是文件。
而对于硬件的读写操作,只有操作系统才有权限进行读写!
文件的操作
对文件的操作,无非就是对文件的数据和文件的属性操作。
那么对于文件,我们知道了它具备读写的作用,那么这种读写的属性,是针对谁而言呢?
磁盘上的文件: 具备read(读)、write(写)的属性。
显示器:printf、cout ->这是write(写)的属性。
键盘: scanf、cin -> 这是read(读)的属性
是不是对于键盘和显示器的属性有些诧异?,我们在显示器上难道不是读吗,不然怎么看到这篇文章?键盘不是我们一个一个打字才输进去的吗,可为什么却是读?
举一个例子,一个文件在磁盘上存储着,我们如果要访问一个文件,就需要写代码运行起来后才访问这个文件,而本质其实是进程在访问文件!
所以,我们不能站在自己的角度去理解它们的属性,我们应该站在进程的角度去理解,而进程是被加载到内存的。 对于一个进程,我们需要从键盘上读取到数据,而进程可以向显示器输出信息让我们看到。
回顾C语言的文件操作函数
打开一个文件,C语言提供的接口
写文件
#include<stdio.h> #include<unistd.h> #include<string.h> int main() { FILE* fl = fopen("file.txt","w"); if(fl == NULL) { perror("Open"); return 1; } //文件write const char* str1 = "hello fputs\n"; fputs(str1,fl); const char* str2 = "hello fwrite\n"; fwrite(str2,strlen(str2),1,fl); const char* str3 = "hello fprintf\n"; fprintf(fl,"%s",str3); fclose(fl); return 0; }
fopen以w的形式打开,如果该名字文件不存在则会在当前目录下创建一个该名字的文件。
而这个文件是根据什么在哪里创建的呢? 当前目录?谁的当前目录?
答案是进程当前的工作目录!
读文件
效果是不是跟cat指令一样?
那么我们这就来模拟一下cat
cat指令的模拟
系统接口
刚刚我们以上回顾的文件操作的接口函数都是C语言给我们提供的接口,但是通过我们之前已学的知识,如果要对硬件进行读写操作,只有操作系统有这个权限,用户是没有权限的。而我们使用的fopen之类的函数都是语言层面上的接口,所以我们就可以知道语言提供给我们的函数,在它实现的底层必然调用了系统接口。
所以这里就引出了一个我们为什么要学习Linux系统编程的概念,在我们之前学习进程的等待,替换中,我们也接触过了系统提供的接口,我们可以清晰的感受到,对于语言层面上提供的接口,我们虽然说在使用上会简洁,方便许多,但是对于它的实际理解却略有不足,单看我们现在使用的C语言提供文件操作函数,我们虽然知道如何去用,但是难以理解它到底做了什么。
所以,学习Linux系统编程,是为了更好的去理解底层原理,强化我们的知识,我们的视野,为什么? 因为不同的语言,它们对于各自的功能的实现方式是不同的,对于底层实现的系统接口的封装也是不同的,所以,学习了系统接口,是为了更好的理解语言层面的函数在做什么? 而且还有一个好处就是,再去学各种不同的语言提供给你的功能接口,都会显得更加得心应手。
语言的跨平台性
这里又在引出一个概念,语言的跨平台性,什么是跨平台? 比如说你在Linux上写了一个代码,不仅可以在Linux系统下运行,还可以在Windows下也能运行,这就是跨平台性。
那么Windows和Linux提供的系统接口会是一样的吗? 但是是不一样的。
那么语言是如何实现 不同平台提供了不同的系统接口 但却实现了语言的跨平台性的呢?
答案是 实现C语言库的那些高级程序员为了兼容不同的平台,必然每个平台都要实现一份语言层面的函数,再通过条件编译进行动态裁剪就能够实现在不同的平台使用对应的函数。
Linux系统提供的文件操作接口
open
man 2 open
先来看看第一个open
int open(const char *pathname, int flags);
先看返回值 是一个int类型,它返回的是如果文件打开成功,则返回该文件的文件描述符fd(对于文件描述符我们放在下一篇文章讲),而如果打开失败,则返回-1。
第一个参数是pathname就是路径,这个很好理解。
第二个参数是flags(标识),这个是什么呢? 这个就很有意思了,这个涉及到位标识符的知识
先来看看man对于这个参数是干嘛用的?
这些全是大写的单词可以理解为选项,而从命名风格来看就可以知道这些都是宏,对于这些选项,每个选项都代表不同的意义,有的是代表追加,有的是代表创建...(大家下来可以都看看)
那么如果有这么多选项,我们该用什么办法把它们都能传进去呢? 如果是一个功能代表一个参数的话,那么不仅对于函数来说会显得臃肿不好看,更是针对系统接口来讲是不可饶恕的,效率太低了。采用位标识符就很好的解决了这个问题。
位标识符(重点)
以一个int来作为参数的话,他有几个比特位?28个
0000 0000 0000 0000
而如果每个比特位代表一个选项的话,是不是就可以通过一个int 类型来完成28个选项的判断? 是的
这种方法效率高效且占用空间极低,堪称完美!
示例
通过按位与来实现传入多个选项
现在我们来上手一下使用open
我们想对log.txt文件进行只写操作,所以传了O_WRONLY
而我们当前目录下是没有log.txt文件的
运行程序后发现打印出了 没有找到该文件或者文件夹,这是为什么,按照我们使用C语言的fopen,如果没有这个文件难道不是应该会自动创建这个文件吗,为什么这里没有创建?
这就提到了我们刚才说讲的,在语言层面上给你提供的接口对系统接口进行了封装,是为了让我们能够更方便地使用。
所以如果我们想要 如果没有这个文件就自动创建这个文件的效果,那么系统接口函数给了我们O_CREAT这个宏选项。
这个时候我们再运行一下程序
虽然log.txt文件被创建出来了,但是它的文件权限有问题!
这个时候第二个open就有了作用了
int open(const char *pathname, int flags, mode_t mode);
它的第三个参数就是针对如果要创建文件,对该文件的权限进行设置
而对于文件的权限设置我们已经不陌生了。
这个时候我们输入0666(八进制) 代表 -rw-rw-rw
运行程序之后,新生成的文件的权限虽然比上次的正确多了,但是为什么是-rw-rw-r--?我们设置明明的是-rw-rw-rw-
这是因为有掩码的存在!
umask(用户创建文件掩码)
使用umask指令可以查看当前的umask值
这里我的umask值是0002(八进制)转化为二进制就是0 000 000 010
那么掩码是怎么让我设置的0666权限编程0664的呢?
0666: 0 110 110 110 -> 0 110 110 110
0002: 0 000 000 010 -> 按位取反 1 111 111 101
->按位与运算 & -------------------
结果: 0 110 110 100
这就是掩码的作用
那么有没有办法重新设置掩码呢? 系统提供了一个接口
mode_t umask(mode_t mask);
设置umask值为0
这个时候创建的新文件的权限正确了!
close
close没什么好说的,传入文件的文件描述符fd就可以关闭文件
write
fd是要写入的文件的文件描述符,buf是要输入的字符串,count是期望写入的buf的字节数
返回值是实际写入的buf的字节数
先来看一下下面的代码
注意open的参数,想在打开的log.txt文件写入aa。
此时的log.txt已经有数据
运行代码之后
发现log.txt在写入aa之前并没有清空原先log.txt中的数据,这是因为我们在open少传了个选项
这时就实现了fopen("log.txt","w")的完整功能。
而如果要实现fopen("log.txt","a")的功能,则需要把O_TRUNC替换为O_APPEND
多运行几次程序
这就实现了fopen("log.txt","a")的完整功能!
read
参数和返回值域write差不多,只不过这里的buf是需要从文件中读取内容
读取成功!
初识文件描述符fd
对于文件描述符的内容,我会再重新写一篇文章专门针对文件再写一章,里面会详细讲解文件描述符和文件的密切关系,现在我们就来简单认识一下
首先对于我们上面写的代码,我们总能看到我们输出的fd为3
能从结果发现文件描述符的似乎是一串连续的数字
而如果是一串连续的数字,那么为什么又是从3开始的呢?
这是因为 在我们打开c语言程序的时候,会默认打开stdin(标准输入) ,stdout(标准输出),stderr(标准错误),这三个文件,它们分别对应我们的键盘,显示器,显示器。
而它们的文件描述符分别是stdin(0),stdout(1),stderr(2)。
如何证明?
既然1代表标准输出,那么这个程序是不是也能将hello world输出到屏幕上?
正确的!
FILE是什么
先说结论,它是一个结构体,里面存储着一个被打开的文件的几乎所有内容(包括属性)
如何证明?
_fileno代表的就是文件描述符fd
对于FILE我也会在下一篇文章详细讲解!