手写操作系统(四十八)-文件描述符

代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。

文件描述符即file descriptor,但凡叫“描述符”的数据结构都用于描述一个对象,文件描述符所描述的对象是文件的操作。 为了搞清楚文件描述符的意义,咱们先看下它与inode的区别和联系。

几乎所有的操作系统都允许一个进程同时、多次、打开同一个文件,同样该文件也可以被多个不同的进程同时打开。为实现文件任意位置的读写,执行读写操作时可以指定偏移量作为该文件内的起始地址,此偏移量相当于文件内的指针。文件系统需要把任意时刻的偏移量记录下来。Linux提供了称为文件结构的数据结构(也称为file结构),专门用于记录与文件操作相关的信息,每次打开一个文件就会产生一个文件结构,多次打开该文件就为该文件生成多个文件结构,各自文件操作的偏移量分别记录在不同的文件结构中。

文件结构的逻辑表示如图:

在Linux中,我们读写函数文件时都是通过操作文件描述符来完成的。 拿open函数来说,其原型为int open(const char *pathname, int flags),pathname是待打开的文件路径及文件名, flag是打开标识,调用它之后,系统会返回文件pathname的文件描述符。返回值类型为int,而该数字就是我们所说的文件描述符,它是PCB中文件描述符数组元素的下标,用来表示位置,它是位于进程PCB中的文件描述符数组的元素的下标,而文件描述符数组元素中的信息又指向文件表中的某个文件结构。

在Linux中每个进程都有单独的、完全相同的一套文件描述符,因此它们与其他进程的文件描述符互不干涉,这些文件描述符被组织成文件描述符数组统一管理。文件结构的数量必须是有限的,这就是进程可打开的最大文件数有限的原因(在Linux中可用ulimit命令来修改),文件描述符数组中的前3个都是标准的文件描述符,如文件描述符0表示标准输入,1表示标准输出,2表示标准错误。

Linux通过文件描述符查找文件数据块的过程:

  • 某进程把文件描述符作为参数提交给文件系统时,文件系统用此文件描述符在该进程的PCB中的文件描述符数组中索引对应的元素。
  • 从该元素中获取对应的文件结构的下标,用该下标在文件表中索引相应的文件结构。
  • 从该文件结构中获取文件的inode,最终找到了文件的数据块。
  • 若该inode在inode队列中不存在,文件系统会从硬盘上将该inode加载到inode队列中,并使文件结构中的fd_inode指向它。

这涉及到以下三个数据结构,它们都是位于内存中的:

  • PCB 中的文件描述符数组。
  • 存储所有文件结构的文件表。
  • inode队列,也就是inode 缓存。

如下图:

文件描述符创建过程:

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

 

接下来就是实现文件描述符了

先修改PCB的结构

修改/thread/thread.h

#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];  // 文件描述符数组

    /* 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;    // 用这串数字做栈的边界标记,用于检测栈的溢出
};

fd_table是任务的文件描述符数组,其类型是int32_t,即每个成员都是int32_t整数,其长度是宏MAX_FILES_OPEN_PER_PROC,此宏的值是8,也就是每个任务可以打开的文件数是8。

 

这个数组需要被初始化,在thread_init函数中实现

修改修改/thread/thread.h

/* 初始化线程环境 */
void thread_init(void) {
    put_str("thread_init start\n");

    list_init(&thread_ready_list);
    list_init(&thread_all_list);
    lock_init(&pid_lock);

    /* 将当前main函数创建为线程 */
    make_main_thread();

    /* 创建idle线程 */
    idle_thread = thread_start("idle", 10, idle, NULL);

    put_str("thread_init done\n");
}

有三个标准的文件描述符,0是标准输入,1是标准输出,2是标准错误,因此将fd_table[0~2]分别置为0、1、2,也就是预留出了这3个文件描述符。接着将fd_table中其余的文件描述符初始化为-1,在这里-1表示该文件描述符可分配,为空位。将来会通过此值来找可分配的文件描述符。

 

参考

郑钢著操作系统真象还原

田宇著一个64位操作系统的设计与实现

丁渊著ORANGE’S:一个操作系统的实现

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇