【Linux课程学习】:文件第一弹---文件基础(文件描述符的底层设计)

举报
我们的五年 发表于 2024/12/06 16:11:43 2024/12/06
【摘要】 本节重点知识点:1.fopen和fclose属于运行时操作。2.深刻理解先描述,再组织。管理对象时,就要先进行描述。3.理解一切皆文件,硬件设备对于进程来说也是文件。4.文件描述符的底层设计--->进程与文件是怎么进行关联的。一.预备知识文件的分类    位置被打开的文件    内存没有被打开的文件    磁盘在文件中,没有被打开的文件比被打开的文件多的多。下面我们研究的被打开的文件。研究被...

本节重点知识点:
1.fopen和fclose属于运行时操作。

2.深刻理解先描述,再组织。管理对象时,就要先进行描述。

3.理解一切皆文件,硬件设备对于进程来说也是文件。

4.文件描述符的底层设计--->进程与文件是怎么进行关联的。

一.预备知识
文件的分类    位置
被打开的文件    内存
没有被打开的文件    磁盘
在文件中,没有被打开的文件比被打开的文件多的多。下面我们研究的被打开的文件。研究被打开的文件,是在研究文件与进程的关系,但是进程会描述文件,所以我们研究的是文件与file_struct的关系。

1.1文件=内容+属性:
对于我们创建的空文件,也是有大小的。虽然文件没有内容,但是文件有属性,在文件中要保存这些属性,就会有大小。

1.2在访问文件之前,必须先打开它:
在C语言中,我们很明显的感受到,在进行文件操作之前,必须打开文件(fopen)。然后进行一系列的操作,最后关闭文件(fclose)。

FILE* fd=fopen("log.txt","w");
fclose(fd);
1.2.1但是为什么要打开文件呢?
因为文件一开始在磁盘中保存着,要打开文件,也就是把文件加载到内存中,才能对文件进行操作。fopen是运行时操作,当被运行到fopen这一行代码,才被进程所打开。

所以文件被打开时,是进程在进行访问。

但是进程在内存中,CPU要想访问文件,因为冯诺依曼体系,CPU不能直接去磁盘进行访问,所以只能把文件加载到内存,才能被CPU访问。

1.3管理文件---先描述,在组织
凡是要进行管理的,就先要对管理的对象进行描述(struct),描述文件的属性。然后才能对文件进行管理。文件描述的一些属性,可以由文件的属性获得,还有其他很多属性要OS进行描述。


二.C语言文件操作函数
从C语言的角度看文件,和文件接口。有一个预备的知识。也为后面语言层面的封装与系统接口进行区分和比较。

2.1文件打开的方式:
文件打开方式    功能    效果
w    

文件不存在,新建文件。文件存在,清空文件。

w+    读写    文件不存在,新建文件。文件存在,清空文件。
r    读    读取存在的文件,文件不存在,报错。
r+    读写    读写存在的文件,文件不存在,报错。
a    追加    在文件末尾追加,文件不存在,新建文件。
a+    读写    在文件末尾读写,文件不存在,新建文件。
另外还可以增加b,比如wb,ab,rb。有b的,就是对二进制文件进行操作。其他的效果和上面类似,也是读(read),写(write),追加(append)。如果我们使用w方式,如果文件存在,会对文件进行清空(truncate)。


 当我们输入重定向的时候,要以w方式打开文件,如果没有输入什么东西,文件就被我们清空了。

 > log.txt
2.2输出信息到显示器的方法:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

fprintf的用法和printf差不多,只需要在前面指明文件对象,就能向指定的文件进行打印。

size_t fread(void *ptr,         size_t size,         size_t nmemb,         FILE *stream);

size_t fwrite(const void *ptr,         size_t size,         ize_t nmemb,        FILE *stream);

#include <stdio.h>    
#include <string.h>    
    
int main()    
{    
    FILE* fd=fopen("log.txt","w");    
    if(fd==NULL)    
    {    
        printf("open error!\n");    
    }    
    fprintf(stdout,"hello fprintf!\n");    
    
    const char* message="hello fwrite!\n";    
    fwrite(message,strlen(message),1,stdout);       
    fclose(fd);    


三.进程默认打开的三个标准流
在下面我们会讲解如何理解键盘,显示器这些硬件也是文件。即一切皆文件。

stdin    标准输入    键盘
stdout    标准输出    显示器
stderror    标准错误    显示器


四.系统调用接口---系统文件I/O
我们使用的C语言文件操作函数,底层是封装了系统类的文件调用。比如C语言的w方式打开,可能是在封装open,并且使用O_WRONLY  |  O_CREAT   |   O_TRUNC。mode设置为0666。

4.1open系统调用
4.1.1函数原型:
头文件:

#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);

4.1.2函数理解:
pathname:

和C语言的的fopen一样,也是文件的名称,是要打开或者创建的文件,名称。

flags:

是标记位,是int类型,表示要以什么方式进行打开,其本质是位图的思想。某个比特位上有1就表示一个功能。后面我们要使用多个功能时,是需要把代表不同功能的整数,进行按位或就可以达到目的。

flags分类有:

O_RDONLY    只读方式打开
O_WRONLY    只写方式打开
O_WDWR    读写方式打开
上面的这三个参数,必须要有一个,并且只能有一个,要不就是只读,要不就是只写,要不就是读写。

O_CREAT    如果文件不存在,创建文件。需要使用mode函数。
O_APPEND    以追加的方式进行写。
O_TRUNC    对文件进行清空。
返回值:

如果打开成功,就返回新打开的文件描述符。

如果失败就返回-1。

mode选项的函数,表示新创建文件的起始权限。文件的起始权限一般是0666,目录的起始权限一般是0777。所以我们在有O_CREAT时,应该加上对应的权限。但是最终的权限会&(~umask)。也可以设置文件的umask,最终可以让权限是0666。系统有umask,进程也有umask,最终使用的umaks是就近原则。

int ft=open("log.txt",O_WRONLY|O_TRUNC);
五.文件描述符fd
5.1见一见其他文件的文件描述符fd:
文件描述符是整数。让我们看看打开其他文件时的文件描述符是多少?

#include <stdio.h>    
#include <string.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>        
    
int main()    
{    
    
    int fd1=open("log1.txt",O_WRONLY|O_TRUNC|O_CREAT);                                                                                                                                                             
    int fd2=open("log2.txt",O_WRONLY|O_TRUNC|O_CREAT);    
    printf("fd1:%d\n",fd1);    
    printf("fd2:%d\n",fd2);    
 
    close(fd1);    
    close(fd2);    
    return 0;
}

 

我们发现文件描述符fd不是从0开始的,而是从3开始的,为什么呢?

5.2stdin,stdout,stderror的文件描述符:
名称    文件描述符    硬件
stdin    0    键盘
stdout    1    显示器
stderror    2    显示器
在打开我们的文件时,进程已经帮我们打开这个三个文件。这三个文件的文件描述符是0,1,2。所以我们打开其他文件的时候,就只能从3开始了。在后面的文件描述符的分配规则是占据最小空的元素。

5.3文件描述符的底层设计:
我们观察文件描述符是从0开始的,这很像数组。就可能存在一个表结构,让进程与文件管理起来。

5.3.1文件管理:
被打开的文件,会加载到内存中,加载之前就会对文件进行先描述,然后再进行组织。最后在内存中就会有很多的file_struct,他们之间的联系用某种数据结构进行关联起来。最后形成了一张file list的表。

5.3.2进程中的文件描述指针的数组:
在进程中,会存在一个文件描述指针数组的指针。这个指针指向是一个文件描述指针数组。这个数组从0开始。这个数组的第一个元素,也就是0号下标,指向的是stdin文件。

第二个元素,指向的是stdout文件。第三个元素就是指向就是stderror。

其他被打开的文件被串在file list中。如果被该进程打开,就会把这个文件的描述信息的指针放在文件描述符表中。

关闭文件本质就是在文件描述符表中删除某个文件的指针。

打开一个文件本质就是把文件指针放在文件描述符表中。

这样就可以让进程与文件关联起来。

5.3.3每个进程都会有文件描述符表:
每个进程中的文件描述符表中的元素都是指向文件的。应该文件可以被多个进程指向。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。