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

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_id与cwd_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信息了
}
相关推荐
流浪0014 小时前
Linux系统篇39——线程(四) pthread库的由来和线程的创建与等待
linux·操作系统·线程·pthread库
流浪00112 小时前
Linux系统篇42——线程(七) pthread库管理线程的工作流,从创建到回收
linux·操作系统·线程·线程创建到销毁
云计算练习生16 小时前
什么是 Linux?从 Unix 到开源生态的完整脉络
linux·开源·操作系统·unix
云计算练习生2 天前
什么是上下文切换?进程切换时 CPU 到底在保存什么
linux·服务器·操作系统·进程切换
CS创新实验室3 天前
为什么三台机器就能选出一个“带头大哥“?——Raft 一致性算法,一次讲透
分布式·操作系统
sweet丶3 天前
iOS 为什么没有内存交换分区(swap)
操作系统
我爱cope9 天前
【操作系统 | 计算机硬件:CPU、内存与 I/O 如何支撑操作系统?】
学习·操作系统
NineData10 天前
NineData 与统信服务器操作系统完成兼容认证
数据库·oracle·操作系统·软件·数据库管理工具·ninedata·统信
DataScience_201910 天前
系统|dup2:文件描述符复制的艺术
操作系统
Tairitsu_H10 天前
[Linux系统] 自动化构建make / Makefile | 进度条
linux·操作系统·makefile·make