操作系统真象还原:文件描述符简介

14.3 文件描述符简介

14.3.1 文件描述符原理

inode 是操作系统为自己的文件系统准备的数据结构,它用于文件存储的管理,与用户关系不大,咱们要介绍的文件描述符才是与用户息息相关的。

文件描述符即 file descriptor,但凡叫"描述符"的数据结构都用于描述一个对象,文件描述符所描述的对象是文件的操作。

Linux 提供了称为"文件结构"的数据结构(也称为file结构),专门用于记录与文件操作相关的信息,每次打开一个文件就会产生一个文件结构,多次打开该文件就为该文件生成多个文件结构,各自文件操作的偏移量分别记录在不同的文件结构中,从而实现了"即使同一个文件被同时多次打开,各自操作的偏移量也互不影响"的灵活性

inode用于描述文件存储相关信息,文件结构用于描述"文件打开"后,文件读写偏移量等信息。文件与 inode一一对应,一个文件仅有一个 inode , 一个 inode 仅对应一个文件。一个文件可以被多次打开,因此一个inode 可以有多个文件结构,多个文件结构可以对应同一个 inode。

其实 open 操作的本质就是创建相应文件描述符的过程,剧透一下, PCB 中文件描述符数组是提前在 task_struct 中构建好的,文件表也是提前构建好的全局数据结构, inode 队列也已经构建好了,因此笼统地说,创建文件描述符的过程就是逐层在这三个数据结构中找空位,在该空位填充好数据后返回该位置的地址,比如:

  1. 在全局的 inode 队列中新建一 inode (这肯定是在空位置处新建),然后返回该 inode 地址。
  2. 在全局的文件表中的找一空位,在该位置填充文件结构,使其 inode 指向上一步中返回的 inode地址,然后返回本文件结构在文件表中的下标值 。
  3. 在 PCB 中的文件描述符数组中找一空位,使该位置的值指向上一步中返回的文件结构下标,井返回本文件描述符在文件描述符数组中的下标值。
14.3.2 文件描述符的实现

为了支持文件描述符,我们要取修改task_struct结构体的定义。(还新增了parent_idcwd_inode_nr

c 复制代码
#define MAX_FILES_OPEN_PER_PROC 8

/*进程或线程的pcb,程序控制块*/
struct task_struct{
    uint32_t* self_kstack;  //各内核线程都用自己的内核栈
    pid_t pid;
    enum task_status status;
    char name[16];
    uint8_t priority;     //线程优先级
    uint8_t ticks;  //每次在处理器上执行的时间滴答数
    /*任务自上 cpu 运行后至今占用了多少 cpu 嘀嗒数,也就是此任务执行了多久**/
    uint32_t elapsed_ticks;
    int32_t fd_table[MAX_FILES_OPEN_PER_PROC];  //文件描述符数组
    uint32_t cwd_inode_nr;                        // 进程所在的工作目录的inode编号
    int16_t parent_pid;                           // 父进程pid
    /* general_tag的作用是用于线程在一般的队列中的结点*/
    struct list_elem general_tag;
    
    /*all_list_tag的作用是用于线程队列 thread_all_list 中的结点*/
    struct list_elem all_list_tag;

    uint32_t* pgdir;    //进程自己的页表的虚拟地址
    struct virtual_addr userprog_vaddr; //用户进程的虚拟地址
    struct mem_block_desc u_block_desc[DESC_CNT];    //用户进程内存块描述符
    uint32_t stack_magic;   ///栈的边界标记,用于检测栈的溢出
};

初始化:

c 复制代码
/* 初始化线程基本信息 , pcb中存储的是线程的管理信息,此函数用于根据传入的pcb的地址,线程的名字等来初始化线程的管理信息*/
void init_thread(struct task_struct *pthread, char *name, int prio)
{
    memset(pthread, 0, sizeof(*pthread)); // 把pcb初始化为0
    pthread->pid = allocate_pid();
    strcpy(pthread->name, name); // 将传入的线程的名字填入线程的pcb中

    if (pthread == main_thread)
    {
        pthread->status = TASK_RUNNING; // 由于把main函数也封装成一个线程,并且它一直是运行的,故将其直接设为TASK_RUNNING */
    }
    else
    {
        pthread->status = TASK_READY;
    }
    pthread->priority = prio;
    /* self_kstack是线程自己在内核态下使用的栈顶地址 */
    pthread->ticks = prio;
    pthread->elapsed_ticks = 0;
    pthread->pgdir = NULL;                                            // 线程没有自己的地址空间,进程的pcb这一项才有用,指向自己的页表虚拟地址
    pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE); // 本操作系统比较简单,线程不会太大,就将线程栈顶定义为pcb地址
                                                                      //+4096的地方,这样就留了一页给线程的信息(包含管理信息与运行信息)空间
    /* 标准输入输出先空出来 */
    pthread->fd_table[0] = 0;
    pthread->fd_table[1] = 1;
    pthread->fd_table[2] = 2;
    /* 其余的全置为-1 */
    uint8_t fd_idx = 3;
    while (fd_idx < MAX_FILES_OPEN_PER_PROC)
    {
        pthread->fd_table[fd_idx] = -1;
        fd_idx++;
    }
    pthread->cwd_inode_nr = 0;         // 以根目录做为默认工作路径
    pthread->parent_pid = -1;          // -1表示没有父进程
    pthread->stack_magic = 0x19870916; // 定义的边界数字,随便选的数字来判断线程的栈是否已经生长到覆盖pcb信息了
}
相关推荐
敲上瘾2 小时前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
不爱学习的YY酱19 小时前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
钰爱&1 天前
【操作系统】Linux之网络编程(UDP)(头歌作业)
linux·操作系统
清酒伴风(面试准备中......)1 天前
操作系统基础——针对实习面试
笔记·面试·职场和发展·操作系统·实习
架构师Wu老七6 天前
【软考】系统架构设计师-计算机系统基础(2):操作系统
系统架构·操作系统·软考·系统架构设计师
不爱学习的YY酱6 天前
【操作系统不挂科】<线程概念(6)>选择题&简答题(带答案与解析)
linux·开发语言·操作系统
修修修也9 天前
【Linux】进程间通信
linux·运维·服务器·操作系统·进程通信
Pandaconda11 天前
【操作系统】每日 3 题(十八)
linux·服务器·开发语言·数据结构·笔记·后端·操作系统
vincent_woo11 天前
再学安卓 - 系统环境安装
操作系统
Raymond运维11 天前
第一章 Linux安装 -- 安装Debian 12操作系统(四)
linux·运维·服务器·操作系统·debian