🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
文章目录
- 一、对系统调用进行封装的理由
- 二、文件的系统调用接口
- ① open
- open的选项--位图
- open的权限
- 程序中设置umask权限掩码
- ②close
- ③write
- ④read
- 三、文件描述符
- 默认打开的三个流
- 什么是FILE
- 文件的分类
- 文件打开所对应的file结构体
- 文件为什么一定要在内存中打开
- 进程和文件的对应关系
- open&&fwrite所做的事
- 四、文件描述符的分配规则
一、对系统调用进行封装的理由
①.系统调用对于我们来说是比较难的,因为我们需要系统要去干什么,需要传哪些参数,所以学习成本太高,所以语言层次,对系统调用接口进行了封装,导致了不同的语言,有不同的语言级别的文件访问接口
封装出来的接口是有多个的,但是文件类的系统调用接口,在Linux上,只有一套
②跨平台
如果语言不提供对文件的系统封装接口,那么所有的访问文件操作,必须使用操作系统提供的系统调用接口,一旦这样,那编写的代码在其他平台就运行不了了,而我如果进行封装,不同的平台进行不同的封装,但是提供给用户的接口都是一样的,这样,就实现了跨平台
比如说把每个平台的代码都实现一遍,采用条件编译动态裁剪的方式,在哪个平台就用哪个平台的接口,这样就跨平台了,所以项目中也推荐使用语言级别的文件接口,就是因为跨平台
二、文件的系统调用接口
fopen -> open
fclose -> close
fread -> read
fwrite -> write
① open
#include <sys/types.h>
#include <sys/stst.h>
#include <fcntl.h>
int open(const char*pathname,int flags,mode_t mode);
//第一个参数是路径,第二个是选项,第三个是设置权限
open返回的东西叫做文件描述符
对于三个参数
第一个就不用说了,是打开文件的路径
第二个是选项,以下三个接口中必须要有一个,其次还有O_APPEND,O_TRUNC,C_CREAT等
第三个是设置文件权限,比如0666
比如打开文件log.txt
int fd/*文件描述符*/ = open("log.txt",O_WRONLY|O_TRUNC|O_CREAT,0666)
//三个选项分别表示 写,覆盖,创建
那open的选项为什么是这样传?我们来了解一下位图
open的选项–位图
#define ONE 0x1 //0001
#define TWO 0x2 //0010
#define THREE 0x4//0100
void show(int flags)
{
if(flags & ONE)//说明有没有ONE
...
if(flags & TWO)
...
if(flags & THREE)
...
}
int main()
{
show(ONE);
show(TWO);
show(ONE | TWO);
show(ONE | TWO | THREE);
return 0;
}
open的权限
不加权限时生成的文件 的权限时随机的
因为open有都和读和写两种方式,所以他的权限也就只能是默认值(随机)
int fd = open("log.txt",O_RDONLY);//读方式打开
而写的时候有可能需要创建文件,当创建文件的时候,就需要带上权限
比如0666,一个八进制数,0666时默认的普通文件的权限(第一位是我们不关心的)
代表的权限是 -rx-rx-rx-,因为会被umask权限掩码给修饰掉
具体的请看Linux权限详解
程序中设置umask权限掩码
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t umask);
//mode_t是被typedef过的,特就是一个unsigned int类型的数
umask(0);//这样即可
②close
#include<unistd.h>
int close(int fd);
//用法:
int fd = open("log.txt",O_WRONLY|O_TRUNC|O_CREAT,0666);
close(fd);//关闭文件描述符
③write
//用法:
const char* buffer="hello write";
write(fd,buffer,strlen(buffer));
//表示写入多大的一个char*的数据
④read
#include <unistd.h>
ssize_t/*无符号整数*/ read(int fd,void *buf,size_t count);
//表示从文件中读到buffer
三、文件描述符
文件描述符(file descriptor)
当我们可以打开多个文件时
可以发现,第一个文件打开的时候就是3了,那0 1 2呢?
默认打开的三个流
C/C++会默认打开三个流:
①stdin 对应文件描述符为 0 标准输入流
②stdout 对应文件描述符为 1 标准输出流
①stderr 对应文件描述符为 2 标准错误流
他们都是FILE类型的,那说明是FILE呢
什么是FILE
FILE*叫做文件指针,又叫做文件句柄
FILE是一个C标准库提供的结构体
C的文件类函数一定要第哦啊用系统接口(不是所有的库函数都要调用系统第哦啊用),对于系统,是只认识文件描述符fd的,所以FILE中必定也封装了fd,通过查看源码可以发现,FILE中有成员 _fileno,也就是fd
可以通过下面的方式打引出来看
int main()
{
printf("stdin:%d,stdout:%d,stderr:%d\n",
stdin->_fileno,stdout->_fileno,stderr->_fileno);
//FILE* -> _fileno
return 0;
}
文件的分类
文件被分为两类:
1、被打开的文件(因为要加载到内存中,所以也叫做内存文件)
对硬件的任何操作,都会经过操作系统,操作系统去调用驱动
2、没有打开的文件,也叫做磁盘文件
文件 = 内容+属性
文件打开所对应的file结构体
对文件的操作都是要CPU参与的,所以想访问文件,要先加载到内存,那系统中就存在这大量的被打开的文件,OS要想办法把这些打开的文件也组织起来
//文件对应的file结构体
struct file
{
struct file*next;
struct file*prev;
//...
//包含了一个被打开文件的几乎所有的内容
//不仅仅是包含属性,而是各种信息
}
文件为什么一定要在内存中打开
因为打开文件就是为了读写,读写文件是需要通过代码来访问的,通过代码访问前提就是代码要被执行,根据冯诺依曼体系,执行代码的机构一定是CPU,访问数据必须先加载到内存,没有l加载到内存,CPU也就访问不到,软件也跑不起来,所以文件必须先在内存中打开。
冯诺依曼
进程和文件的对应关系
进程的PCB中有一个fs指针,指向的是files结构体,files结构体中就有fd_array,称作文件映射表或者文件描述符表)
fd本质就是一个数组的下标
open&&fwrite所做的事
open是系统调用接口,创建文件对象file插入list,用hashtable(指针数组),叫做fd_array(文件描述符表),来记录文件对象的指针,把这个结构体对象指针的fd_array下标返回。(这个fd_array的首地址保存在files_struct当中,files_struct的地址保存在PCB当中)
fwrite是函数调用,通过FILE*中的_fileno(也就是fd),调用write这个OS提供的系统调用接口,找到进程的task_strcut(PCB),找到fs指针,找到files_struct,找到fd_array文件描述符表,找到file,就可以写入了
四、文件描述符的分配规则
int main()
{
close(0);//关闭0号文件描述符
int fd1 = open("log1.txt",O_WRONLY|O_CREAT|O_TURNC,0666);
printf("fd1:%d\n",fd1);//0
close(2);//关闭2号文件描述符
int fd2 = open("log2.txt",O_WRONLY|O_CREAT|O_TURNC,0666);
printf("fd2:%d\n",fd2);//2
int fd3 = open("log3.txt",O_WRONLY|O_CREAT|O_TURNC,0666);
printf("fd3:%d\n",fd3);//3
return 0;
}
从上面的代码以及结果中可以看出:
fd的分配规则:是分配最小的,没有被占用的文件描述符