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

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信息了
}
相关推荐
WZF-Sang1 天前
Linux—进程学习-01
linux·服务器·数据库·学习·操作系统·vim·进程
Goboy2 天前
0帧起步:3分钟打造个人博客,让技术成长与职业发展齐头并进
程序员·开源·操作系统
结衣结衣.2 天前
【Linux】Linux管道揭秘:匿名管道如何连接进程世界
linux·运维·c语言·数据库·操作系统
OpenAnolis小助手2 天前
龙蜥副理事长张东:加速推进 AI+OS 深度融合,打造最 AI 的服务器操作系统
ai·开源·操作系统·龙蜥社区·服务器操作系统·anolis os
小蜗的房子3 天前
SQL Server 2022安装要求(硬件、软件、操作系统等)
运维·windows·sql·学习·microsoft·sqlserver·操作系统
邂逅岁月5 天前
【多线程奇妙屋】 Java 的 Thread类必会小技巧,教你如何用多种方式快速创建线程,学并发编程必备(实践篇)
java·开发语言·操作系统·线程·进程·并发编程·javaee
CXDNW6 天前
【系统面试篇】进程和线程类(1)(笔记)——区别、通讯方式、同步、互斥、死锁
笔记·操作系统·线程·进程·互斥·死锁
Anemone_6 天前
MIT 6.S081 Lab3
操作系统
掘了7 天前
持久化内存 | Persistent Memory
c++·架构·操作系统
结衣结衣.8 天前
【Linux】掌握库的艺术:我的动静态库封装之旅
linux·运维·服务器·c语言·操作系统·