【Linux系统】文件结构体 struct file 和文件描述符表

发布于:2025-02-10 ⋅ 阅读:(42) ⋅ 点赞:(0)




在这里插入图片描述



文件结构体


描述进程的结构体:在 Linux 系统中,操作系统管理各种软硬件的本质是先描述再组织,对于进程管理,操作系统会将进程描述成一个结构体对象,即进程PCBtask_struct);

描述文件的结构体:对于文件管理也一样,打开文件的本质是将文件从磁盘中加载到内存中,操作系统会通过将加载到内存中的文件描述成一个结构体对象 struct file ,这个结构体会存储该文件加载到内存中的相关属性,例如:文件当前位置、文件标志(如 O_RDONLYO_WRONLY 等),要注意的是,文件由文件属性和文件内容组成,而文件结构体中的文件属性和组成文件的文件属性不一样,文件结构体中的文件属性大部分是描述该文件在内存中的行为与数据。

链表管理结构:同时,文件结构体中还存在有一个内置的文件结构体指针字段,作为链表链接指针,文件结构体通过这个文件指针串联成一个链表结构 ,这就是操作系统的文件管理(和操作系统的进程管理相似,都是将用于描述的结构体链接成链表,将对进程和文件的管理转变成对链表数据结构的增删查改工作)。


OS文件管理图示

在操作系统进行文件管理时,会默认先将三个文件打开,即三个标准文件流:stdinstdoutstderr 。这三个文件流通常对应文件描述符0、1和2,分别对应标准输入、标准输出和标准错误。

如下图,相关结构都展示清楚了,CPU调度进程,运行成功进程中的 open(log.txt) 后,文件 log.txt 会从磁盘中加载到内存中,被描述成文件结构体 struct file ,并链接到文件链表上被操作系统进行文件管理。


在这里插入图片描述



多个进程PCB链接在一条链表上,用于进程管理

多个文件结构体链接在一条链表上,用于文件管理

进程管理和文件管理属于操作系统的两个管理单元,不能发生强耦合关系,操作系统是如何保证两者的轻耦合关系的,进程打开的 log.txt 又是如何和进程产生关联的,既要轻耦合又要有关联,这就和文件描述符表有关了:



文件描述符表

进程管理和文件管理的解耦合

一个进程可以打开多个文件,相当于进程和文件的数量关系是:(进程 : 文件)= (1 : n)。

这种一对多的情况,操作系统如何有序的管理每个进程自己打开的文件。操作系统会为进程创建一个指针数组表,这个表存储在进程 PCB 中。这个指针数组中的,每个指针会指向文件管理链表上的某个文件结构体 struct file ,表示该进程打开的文件。进程运行过程中会通过该进程的文件指针数组,在文件管理链表上找到自己打开的文件, 这就完成了进程管理和文件管理之间的解耦合!:通过一个指针数组的指针关系将进程和文件关联起来,而并非将文件管理嵌套在进程管理中的这种强耦合关系。

进程文件指针数组就是文件描述符数组,我们使用系统调用 open 返回给用户层的 int 类型的这个文件描述符实际上就是文件描述符数组的数组下标!

其中三个标准文件流:stdinstdoutstderror ,对于的文件描述符是 0、1、2,也就代表这 三个文件在文件描述符数组的前三个元素数组下标位置。



在这里插入图片描述


在这里插入图片描述




我们使用系统调用 open ,创建新文件或打开磁盘中旧文件,就是在内存中为该文件新建一个文件结构体 struct file 并为其分配文件描述符(也就是该进程中文件描述符表中的一个位置),此时会将分配的文件描述符作为 系统调用 open 的返回值返回给上层

上层使用系统调用 write 的过程,就是通过该文件描述符,查询索引文件描述符表,找到对应文件的文件结构体,向其写入



源码

让我们看一下源码:

(1)进程 PCB task_struct

都是用于描述进程的字段

struct task_struct {
    volatile long state;    // 进程状态
    pid_t pid;              // 进程ID
    struct task_struct *parent; // 父进程指针
    struct task_struct *children; // 子进程链表头
    struct task_struct *sibling; // 兄弟进程链表
    struct mm_struct *mm;   // 内存描述符
    struct files_struct *files; //文件描述符表 //***证明表结构的存在***//
    struct list_head tasks; // 进程链表节点
    // 更多字段...
};


(2)文件描述符表指针 struct files_struct *files

存在于 进程 PCB 中,用于指向内存中该进程管理的文件描述表


(3)文件结构体 struct file :用于描述一个打开的文件

struct file {
    struct path f_path;              // 文件路径
    unsigned int f_flags;            // 文件标志(如O_RDONLY、O_WRONLY等)
    fmode_t f_mode;                  // 文件打开模式
    loff_t f_pos;                    // 文件当前位置
   	// 其他字段....
};



在系统层面,文件描述符 fd 是访问文件的唯一方式!

因为用户想要访问文件,就必须和硬件,即磁盘进行交互,但是用户不能和硬件进行直接交互,必须通过操作系统提供的系统调用进行间接的访问。

C语言是如何通过下面这几个文件访问函数进行访问文件操作的:那必然是封装了相关系统调用接口,而 ”文件描述符的使用“ 同样也被封装来使用!

// C语言几个文件访问函数

// 打开文件
FILE *fopen(const char *path, const char *mode);
// 关闭文件
int fclose(FILE *stream);
//....

C语言几个文件访问函数中的 FILE 类型是什么意思,它和文件描述符有关吗?



C语言的 FILE 类型

C语言的文件访问函数封装了对于的系统调用接口,同时还对文件相关类型进行封装,封装成一个结构体类型 struct FILE ,这就是 C语言的 FILE 类型。(注意:此 struct FILE 不是描述文件的那个文件结构体,两者没关系(就和老婆饼和老婆一样没关系))


这个结构体类型 struct FILE 包含了很多和文件访问相关的属性字段,其中就包括了文件描述符!


所有的编程语言,如 C++Javapython ….只要需要访问文件的操作,就必须通过操作系统提供的系统调用接口和磁盘的文件进行交互,而在系统层面,文件描述符 fd 是访问文件的唯一方式!因此这些语言都会对系统调用和文件描述符进行封装后使用。


在C语言中,可以通过访问 FILE 结构体的成员 _fileno 来得知当前该文件的文件描述符!!

int main()
{
	printf("stdin: %d\n", stdin->_fileno);
    printf("stdout: %d\n", stdout->_fileno);
    printf("stderr: %d\n", stderr->_fileno);
    
    FILE* fp = fopen("log.txt", "r");
    if(fp != NULL) printf("fp: %d\n", fp->_fileno);
}




网站公告

今日签到

点亮在社区的每一天
去签到