• Linux文件系统调用接口&&文件描述符的理解


    🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
    在这里插入图片描述

    一、对系统调用进行封装的理由

    ①.系统调用对于我们来说是比较难的,因为我们需要系统要去干什么,需要传哪些参数,所以学习成本太高,所以语言层次,对系统调用接口进行了封装,导致了不同的语言,有不同的语言级别的文件访问接口

    封装出来的接口是有多个的,但是文件类的系统调用接口,在Linux上,只有一套

    ②跨平台
    如果语言不提供对文件的系统封装接口,那么所有的访问文件操作,必须使用操作系统提供的系统调用接口,一旦这样,那编写的代码在其他平台就运行不了了,而我如果进行封装,不同的平台进行不同的封装,但是提供给用户的接口都是一样的,这样,就实现了跨平台

    比如说把每个平台的代码都实现一遍,采用条件编译动态裁剪的方式,在哪个平台就用哪个平台的接口,这样就跨平台了,所以项目中也推荐使用语言级别的文件接口,就是因为跨平台

    二、文件的系统调用接口

    fopen -> open
    fclose -> close
    fread -> read
    fwrite -> write

    ① open

    #include 
    #include 
    #include 
    int open(const char*pathname,int flags,mode_t mode);
    //第一个参数是路径,第二个是选项,第三个是设置权限
    
    • 1
    • 2
    • 3
    • 4
    • 5

    open返回的东西叫做文件描述符

    对于三个参数
    第一个就不用说了,是打开文件的路径
    第二个是选项,以下三个接口中必须要有一个,其次还有O_APPEND,O_TRUNC,C_CREAT等
    在这里插入图片描述
    第三个是设置文件权限,比如0666

    比如打开文件log.txt

    int fd/*文件描述符*/ = open("log.txt",O_WRONLY|O_TRUNC|O_CREAT,0666)
    //三个选项分别表示 写,覆盖,创建
    
    
    • 1
    • 2
    • 3

    那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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    open的权限

    不加权限时生成的文件 的权限时随机的
    因为open有都和读和写两种方式,所以他的权限也就只能是默认值(随机)

    int fd = open("log.txt",O_RDONLY);//读方式打开
    
    • 1

    而写的时候有可能需要创建文件,当创建文件的时候,就需要带上权限

    比如0666,一个八进制数,0666时默认的普通文件的权限(第一位是我们不关心的)
    代表的权限是 -rx-rx-rx-,因为会被umask权限掩码给修饰掉
    具体的请看Linux权限详解

    程序中设置umask权限掩码

    #include 
    #include 
    mode_t umask(mode_t umask);
    //mode_t是被typedef过的,特就是一个unsigned int类型的数
    umask(0);//这样即可
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ②close

    #include
    int close(int fd);
    //用法:
    int fd = open("log.txt",O_WRONLY|O_TRUNC|O_CREAT,0666);
    close(fd);//关闭文件描述符
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ③write

    //用法:
    const char* buffer="hello write";
    write(fd,buffer,strlen(buffer));
    //表示写入多大的一个char*的数据
    
    • 1
    • 2
    • 3
    • 4

    ④read

    #include 
    ssize_t/*无符号整数*/ read(int fd,void *buf,size_t count);
    //表示从文件中读到buffer
    
    • 1
    • 2
    • 3

    三、文件描述符

    文件描述符(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
    • 3
    • 4
    • 5
    • 6
    • 7

    文件的分类

    文件被分为两类:
    1、被打开的文件(因为要加载到内存中,所以也叫做内存文件)
    对硬件的任何操作,都会经过操作系统,操作系统去调用驱动
    2、没有打开的文件,也叫做磁盘文件
    文件 = 内容+属性

    文件打开所对应的file结构体

    对文件的操作都是要CPU参与的,所以想访问文件,要先加载到内存,那系统中就存在这大量的被打开的文件,OS要想办法把这些打开的文件也组织起来

    //文件对应的file结构体
    struct file
    {
    	struct file*next;
    	struct file*prev;
    	//...
    	//包含了一个被打开文件的几乎所有的内容
    	//不仅仅是包含属性,而是各种信息
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    文件为什么一定要在内存中打开

    因为打开文件就是为了读写,读写文件是需要通过代码来访问的,通过代码访问前提就是代码要被执行,根据冯诺依曼体系,执行代码的机构一定是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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    从上面的代码以及结果中可以看出:
    fd的分配规则:是分配最小的,没有被占用的文件描述符

    在这里插入图片描述

  • 相关阅读:
    软件测试工具有什么作用?有哪些好用的测试工具推荐?
    金仓数据库KingbaseES sys_prewarm 扩展
    抗锯齿渲染
    【C# 7.0 in a Nutshell】第4章 C#的高级特性——委托
    线性代数的学习和整理21,向量的模,矩阵的模,矩阵的模和行列式比较(未完成)
    Mongodb 副本集名称重命名
    Spring Data Redis概述及用法
    力扣HOT100 - 994. 腐烂的橘子
    面试集中营—场景面试题A
    前端面试题
  • 原文地址:https://blog.csdn.net/zhu_pi_xx/article/details/128164879