代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。
我们已经在根目录下创建了文件file1,接下来就是实现文件打开和关闭了
1.文件打开
打开文件的核心操作是file_open函数完成的。
修改/fs/file.c
添加file_open函数
/* 打开编号为inode_no的inode对应的文件,若成功则返回文件描述符,否则返回-1 */
int32_t file_open(uint32_t inode_no, uint8_t flag) {
int fd_idx = get_free_slot_in_global();
if (fd_idx == -1) {
printk("exceed max open files\n");
return -1;
}
file_table[fd_idx].fd_inode = inode_open(cur_part, inode_no);
file_table[fd_idx].fd_pos = 0; // 每次打开文件,要将fd_pos还原为0,即让文件内的指针指向开头
file_table[fd_idx].fd_flag = flag;
bool* write_deny = &file_table[fd_idx].fd_inode->write_deny;
if (flag & O_WRONLY || flag & O_RDWR) { // 只要是关于写文件,判断是否有其它进程正写此文件
// 若是读文件,不考虑write_deny
/* 以下进入临界区前先关中断 */
enum intr_status old_status = intr_disable();
if (!(*write_deny)) { // 若当前没有其它进程写该文件,将其占用.
*write_deny = true; // 置为true,避免多个进程同时写此文件
intr_set_status(old_status); // 恢复中断
} else { // 直接失败返回
intr_set_status(old_status);
printk("file can`t be write now, try again later\n");
return -1;
}
} // 若是读文件或创建文件,不用理会write_deny,保持默认
return pcb_fd_install(fd_idx);
}file_open 接受 2个参数, inode 编号 inode_no 和打开标识 flag,函数功能是打开编号为 inode_no 的 inode对应的文件,若成功则返回文件描述符,否则返回-1。
函数开头调用get_free_slot_in_global从文件表file_table中获取空位的下标。
bool* write_deny = &file_table[fd_idx].fd_inode->write_deny使变量write_deny指向该inode的write_deny位,下面进行判断和处理。if (flag & O_WRONLY || flag & O_RDWR)如果此次以写 文件的方式打开文件,也就是flag中包含O_WRONLY (只写)或O_RDWR (读和写),为了避免多个任务同 时写该文件而引起相互覆盖的混乱,if (!(*write_deny))对指针write_deny取值,判断是否为true,检查是否已有别的任 务正在写该文件,如果为false,将其置为true,表示本任务要对其执行写操作,否则就简单处理,输出提示信息“file can`t be write now, try again later”,也就是目前文件不能写入,然后返回-1。
如果flag是O_RDONLY读文件或O_CREAT创建文件,就不用理会 write_deny的值了,我们允许写文件时对其执行读操作。
最后return pcb_fd_install(fd_idx)将 fd_idx 安装到 fd_table 并返回结果,若成功则返回文件描述符,否则返回-1。
更改/fs/fs.c的sys_open函数
只要修改对flag判断的switch结构,增加其他标识的支持,即O_RDONLY、O_WRONLY和O_RDWR默认都由函数file_open处理。
switch (flags & O_CREAT) {
case O_CREAT:
printk("creating file\n");
fd = file_create(searched_record.parent_dir, (strrchr(pathname, '/') + 1), flags);
dir_close(searched_record.parent_dir);
break;
default:
/* 其余情况均为打开已存在文件:
* O_RDONLY,O_WRONLY,O_RDWR */
fd = file_open(inode_no, flags);
}
2.文件关闭
接下来就是实现文件关闭的逻辑了
Linux 中文件关闭是 close 函数。
close函数原型是”int close(int fd)”,关闭成功则返回0,否则返回-1。
打开文件的核心操作是file_close函数完成的。
修改/fs/file.c
添加file_close函数
/* 关闭文件 */
int32_t file_close(struct file* file) {
if (file == NULL) {
return -1;
}
file->fd_inode->write_deny = false;
inode_close(file->fd_inode);
file->fd_inode = NULL; // 使文件结构可用
return 0;
}file_close 接受 1 个参数,文件 file,功能是关闭文件,成功则返回 0,否则返回-1。
file_close唯一失败返回的情况就是file为null。其余的代码就是恢复inode的状态.
修改/fs/fs.c,添加两个函数:
/* 将文件描述符转化为文件表的下标 */
static uint32_t fd_local2global(uint32_t local_fd) {
struct task_struct* cur = running_thread();
int32_t global_fd = cur->fd_table[local_fd];
ASSERT(global_fd >= 0 && global_fd < MAX_FILE_OPEN);
return (uint32_t)global_fd;
}
/* 关闭文件描述符fd指向的文件,成功返回0,否则返回-1 */
int32_t sys_close(int32_t fd) {
int32_t ret = -1; // 返回值默认为-1,即失败
if (fd > 2) {
uint32_t _fd = fd_local2global(fd);
ret = file_close(&file_table[_fd]);
running_thread()->fd_table[fd] = -1; // 使该文件描述符位可用
}
return ret;
}fd_local2global接受1个参数,文件描述符local_fd,功能是将文件描述符转化为文件表的下标。 原理就是将local_fd作为下标代入数组fd_table,fd_table[local_fd]的值便是文件表的下标。
函数sys_close接受1个参数,文件描述符fd,功能是关闭文件描述符fd指向的文件,成功返回0,否则返回-1。 通过”fd_local2global(fd)”获取file_table的下标,存入变量_fd,然后用_fd索引文件表中的相应文件结构,在下一行把文件结构的指针作为参数调用file_close关闭文件。 然后把本地文件描述符置为-1,使其为空可分配。
对应file.h
int32_t file_open(uint32_t inode_no, uint8_t flag); int32_t file_close(struct file* file);
接下来就是修改main文件测试一下了
int main(void) {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
uint32_t fd = sys_open("/file1", O_RDONLY);
printf("fd:%d\n", fd);
sys_close(fd);
printf("%d closed now\n", fd);
while(1);
return 0;
}
执行结果如下:

3.参考
郑钢著操作系统真象还原
田宇著一个64位操作系统的设计与实现
丁渊著ORANGE’S:一个操作系统的实现


