14.3 文件描述符简介
14.3.1 文件描述符原理
inode 是操作系统为自己的文件系统准备的数据结构,它用于文件存储的管理,与用户关系不大,咱们要介绍的文件描述符才是与用户息息相关的。
文件描述符即 file descriptor,但凡叫“描述符”的数据结构都用于描述一个对象,文件描述符所描述的对象是文件的操作。
Linux 提供了称为“文件结构”的数据结构(也称为file结构),专门用于记录与文件操作相关的信息,每次打开一个文件就会产生一个文件结构,多次打开该文件就为该文件生成多个文件结构,各自文件操作的偏移量分别记录在不同的文件结构中,从而实现了“即使同一个文件被同时多次打开,各自操作的偏移量也互不影响”的灵活性
inode用于描述文件存储相关信息,文件结构用于描述“文件打开”后,文件读写偏移量等信息。文件与 inode一一对应,一个文件仅有一个 inode , 一个 inode 仅对应一个文件。一个文件可以被多次打开,因此一个inode 可以有多个文件结构,多个文件结构可以对应同一个 inode。
其实 open 操作的本质就是创建相应文件描述符的过程,剧透一下, PCB 中文件描述符数组是提前在 task_struct 中构建好的,文件表也是提前构建好的全局数据结构, inode 队列也已经构建好了,因此笼统地说,创建文件描述符的过程就是逐层在这三个数据结构中找空位,在该空位填充好数据后返回该位置的地址,比如:
- 在全局的 inode 队列中新建一 inode (这肯定是在空位置处新建),然后返回该 inode 地址。
- 在全局的文件表中的找一空位,在该位置填充文件结构,使其 inode 指向上一步中返回的 inode地址,然后返回本文件结构在文件表中的下标值 。
- 在 PCB 中的文件描述符数组中找一空位,使该位置的值指向上一步中返回的文件结构下标,井返回本文件描述符在文件描述符数组中的下标值。
14.3.2 文件描述符的实现
为了支持文件描述符,我们要取修改task_struct
结构体的定义。(还新增了parent_id
与cwd_inode_nr
)
#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; ///栈的边界标记,用于检测栈的溢出
};
初始化:
/* 初始化线程基本信息 , 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信息了
}