手写操作系统(五十二)-创建文件

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

接下来就可以来创建文件了

创建文件的步骤如下:

  • 文件需要inode来描述大小、位置等属性,所以创建文件就要创建其inode,向inode_bitmap申请位图来获得inode号,因此inode_bitmap会被更新,inode_table数组中的某项也会由新的 inode 填充。
  • inode_i_sectors是文件具体存储的扇区地址,这需要向block_bitmap申请可用位来获得可用的块(为简化处理,1块等于1扇区),因此block_bitmap会被更新,分区的数据区data_start_lba以后的某个扇区会被分配。
  • 新增加的文件必然存在于某个目录,所以该目录的inode->i_size会增加个目录项的大小。此新增加的文件对应的目录项需要写入该目录的inode->i_sectors[]中的某个扇区,原有扇区可能已满,所以有可能要申请新扇区来存储目录项。
  • 若其中某步操作失败,需要回滚之前已成功的操作。
  • inode_bitmap、block_bitmap、新文件的inode及文件所在目录的inode,这些位于内存中已经被改变的数据要同步到硬盘。

修改/fs/file.c

/* 创建文件,若成功则返回文件描述符,否则返回-1 */
int32_t file_create(struct dir *parent_dir, char *filename, uint8_t flag) {
    /* 后续操作的公共缓冲区 */
    void *io_buf = sys_malloc(1024);
    if (io_buf == NULL) {
        printk("in file_creat: sys_malloc for io_buf failed\n");
        return -1;
    }

    uint8_t rollback_step = 0;           // 用于操作失败时回滚各资源状态

    /* 为新文件分配inode */
    int32_t inode_no = inode_bitmap_alloc(cur_part);
    if (inode_no == -1) {
        printk("in file_creat: allocate inode failed\n");
        return -1;
    }

    /* 此inode要从堆中申请内存,不可生成局部变量(函数退出时会释放)
    * 因为file_table数组中的文件描述符的inode指针要指向它.*/
    struct inode *new_file_inode = (struct inode *) sys_malloc(sizeof(struct inode));
    if (new_file_inode == NULL) {
        printk("file_create: sys_malloc for inode failded\n");
        rollback_step = 1;
        goto rollback;
    }
    inode_init(inode_no, new_file_inode);        // 初始化i结点

    /* 返回的是file_table数组的下标 */
    int fd_idx = get_free_slot_in_global();
    if (fd_idx == -1) {
        printk("exceed max open files\n");
        rollback_step = 2;
        goto rollback;
    }

    file_table[fd_idx].fd_inode = new_file_inode;
    file_table[fd_idx].fd_pos = 0;
    file_table[fd_idx].fd_flag = flag;
    file_table[fd_idx].fd_inode->write_deny = false;

    struct dir_entry new_dir_entry;
    memset(&new_dir_entry, 0, sizeof(struct dir_entry));

    create_dir_entry(filename, inode_no, FT_REGULAR, &new_dir_entry);    // create_dir_entry只是内存操作不出意外,不会返回失败

    /* 同步内存数据到硬盘 */
    /* a 在目录parent_dir下安装目录项new_dir_entry, 写入硬盘后返回true,否则false */
    if (!sync_dir_entry(parent_dir, &new_dir_entry, io_buf)) {
        printk("sync dir_entry to disk failed\n");
        rollback_step = 3;
        goto rollback;
    }

    memset(io_buf, 0, 1024);
    /* b 将父目录i结点的内容同步到硬盘 */
    inode_sync(cur_part, parent_dir->inode, io_buf);

    memset(io_buf, 0, 1024);
    /* c 将新创建文件的i结点内容同步到硬盘 */
    inode_sync(cur_part, new_file_inode, io_buf);

    /* d 将inode_bitmap位图同步到硬盘 */
    bitmap_sync(cur_part, inode_no, INODE_BITMAP);

    /* e 将创建的文件i结点添加到open_inodes链表 */
    list_push(&cur_part->open_inodes, &new_file_inode->inode_tag);
    new_file_inode->i_open_cnts = 1;

    sys_free(io_buf);
    return pcb_fd_install(fd_idx);

    /*创建文件需要创建相关的多个资源,若某步失败则会执行到下面的回滚步骤 */
    rollback:
    switch (rollback_step) {
        case 3:
            /* 失败时,将file_table中的相应位清空 */
            memset(&file_table[fd_idx], 0, sizeof(struct file));
        case 2:
            sys_free(new_file_inode);
        case 1:
            /* 如果新文件的i结点创建失败,之前位图中分配的inode_no也要恢复 */
            bitmap_set(&cur_part->inode_bitmap, inode_no, 0);
            break;
    }
    sys_free(io_buf);
    return -1;
}

函数file_create接受3个参数,父目录partent_dir、文件名filename、创建标识flag,功能是在目录 parent_dir中以模式flag去创建普通文件filename,若成功则返回文件描述符,即pcb->fd_table中的下标,否则返回-1。

file_create是基于函数inode_sync和sync_dir_entry。 这两个是往硬盘上写数据的函数,其原理是需要先把原扇区的数据读到内存,在内存中将数据变更后再写入硬盘扇区,所以用于变更数据的内存缓冲区是不可少的。 往硬盘上同步数据的操作往往是诸多步骤中的最后一步,如果在这类函数内部申请内存作为缓冲区,万一内存不足,则往硬盘上同步数据就会失败,那之前所做的所有工作都会白费,所以在创建文件之初就应该把缓冲区准备好,如果申请内存失败了也不会多做无用功,也避免了最后无法同步到硬盘而造成数据不一致、回滚操作过多的情况发生。 一般情况下硬盘操作都是一次读写一个扇区,考虑到有数据会跨扇区的情况,故申请2个扇区大小的缓冲区,因此在函数开头就先申请了1024字节的缓冲区io_buf。

现在再说一下回滚,回滚就是资源变更后,将资源恢复到未修改前的状态。 文件系统是一套资源管理的方法,每变动一种数据就会涉及到多种资源的“联动”,这里所说的“联动”意指一个事物的状态改变后,周边事物也要一同跟着变动的连锁反应。 按理说相关资源的联动应该是一个事务,具有原子性,

创建文件包括多个修改资源的步骤,我们创建新文件的顺序是:创建文件i结点->文件描述符fd->目录项。 这种从后往前创建步骤的好处是每一步创建失败时回滚操作少。 不过随着步骤的递增,失败时回滚的步骤也将递增。 这里uint8_t rollback_step = 0定义了变量rollback_step用于记录回滚步骤。 真正用于回滚的代码是在标签rollback:处,那里有3部分的回滚操作,只列出了有可能会失败的操作,从上到下依次是case 3、 case2、 case1,各case之间没有break,它们是一种累加的回滚,因此case3执行的回滚操作最多,casel最少,需要回滚时,只要将rollback_step置为相应的case就好了。

int32_t inode_no = inode_bitmap_alloc(cur_part)调用inode_bitmap_alloc为新文件分配inode,struct inode *new_file_inode = (struct inode *) sys_malloc(sizeof(struct inode))为新文件的inode也就是new_file_inode申请内存,如果内存申请失败,rollback_step置为1,程序跳转到rollback处,根据rollback_step的值,会执行分支case 1,即执行bitmap_set(&cur_part->inode_bitmap, inode_no, 0);的bitmap_set回滚位图状态。如果内存分配成功的话,执行inode_init 初始化 new_file_inode。

接下来调用get_free_slot_in_global从file_talbe中获取空闲文件结构的下标,写入变量fd_idx中。如果 file_table中没有空闲位则返回-1,于是将rollback_step置为2,依然是通过goto语句跳转到rollback处,执行 case2处的代码,执行sys_free(new_file_inode)释放new_file_inode的内存,然后执行casel恢复inode位图。

file_table[fd_idx]初始化文件表中的文件结构, struct dir_entry new_dir_entry和memset(&new_dir_entry, 0, sizeof(struct dir_entry))为文件创建新目录项new_dir_entry,并将其清0,create_dir_entry(filename, inode_no, FT_REGULAR, &new_dir_entry)调用create_dir_entry用filename、inode_no和FT_REGULAR填充new_dir_entry。准备好目录项后,接着在if (!sync_dir_entry(parent_dir, &new_dir_entry, io_buf))通过函数sync_dir_entry (parent_dir, &new_dir_entry, io_buf)将其写入到父目录parent_dir 中,如果失败,rollback_step 置为3,执行回滚。

sync_dir_entry 会改变父目录 inode中的信息,因此在inode_sync(cur_part, parent_dir->inode, io_buf)调用函数 inode_sync将父目录 inode同步到硬盘。接着在第inode_sync(cur_part, new_file_inode, io_buf)和bitmap_sync(cur_part, inode_no, INODE_BITMAP)分别将新文件的inode同步到硬盘,将inode_bitmap位图同步到硬盘。list_push(&cur_part->open_inodes, &new_file_inode->inode_tag)将新文件的inode添加到inode列表,也就是cur_part->open_inodes,随后在其i_open_cnts置为1。以上几个同步操作一般不会出问题,因此这并未有相应的回滚。硬盘操作到这就结束了,在sys_free(io_buf)将io_buf释放,然后在return pcb_fd_install(fd_idx)调用peb_fd_install(fd_idx),在数组peb->fd_table中找个空闲位安装fd_idx,若成功则返回空闲位的下标,若失败则返回-1,用return将其返回值返回。

 

接着就是实现 sys_open,也就是创建文件的真正逻辑,是open函数的内核级实现。

修改/fs/fs.c

/* 打开或创建文件成功后,返回文件描述符,否则返回-1 */
int32_t sys_open(const char* pathname, uint8_t flags) {
    /* 对目录要用dir_open,这里只有open文件 */
    if (pathname[strlen(pathname) - 1] == '/') {
        printk("can`t open a directory %s\n",pathname);
        return -1;
    }
    ASSERT(flags <= 7);
    int32_t fd = -1;       // 默认为找不到

    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record));

    /* 记录目录深度.帮助判断中间某个目录不存在的情况 */
    uint32_t pathname_depth = path_depth_cnt((char*)pathname);

    /* 先检查文件是否存在 */
    int inode_no = search_file(pathname, &searched_record);
    bool found = inode_no != -1 ? true : false;

    if (searched_record.file_type == FT_DIRECTORY) {
        printk("can`t open a direcotry with open(), use opendir() to instead\n");
        dir_close(searched_record.parent_dir);
        return -1;
    }

    uint32_t path_searched_depth = path_depth_cnt(searched_record.searched_path);

    /* 先判断是否把pathname的各层目录都访问到了,即是否在某个中间目录就失败了 */
    if (pathname_depth != path_searched_depth) {   // 说明并没有访问到全部的路径,某个中间目录是不存在的
        printk("cannot access %s: Not a directory, subpath %s is`t exist\n", \
        pathname, searched_record.searched_path);
        dir_close(searched_record.parent_dir);
        return -1;
    }

    /* 若是在最后一个路径上没找到,并且并不是要创建文件,直接返回-1 */
    if (!found && !(flags & O_CREAT)) {
        printk("in path %s, file %s is`t exist\n", \
        searched_record.searched_path, \
        (strrchr(searched_record.searched_path, '/') + 1));
        dir_close(searched_record.parent_dir);
        return -1;
    } else if (found && flags & O_CREAT) {  // 若要创建的文件已存在
        printk("%s has already exist!\n", pathname);
        dir_close(searched_record.parent_dir);
        return -1;
    }

    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);
            // 其余为打开文件
    }

    /* 此fd是指任务pcb->fd_table数组中的元素下标,
     * 并不是指全局file_table中的下标 */
    return fd;
}

sys_open函数接受2个参数,pathname是待打开的文件,其为绝对路径,flags是打开标识,其值便 是之前在fs.h头文件中提前放入的enum oflags。函数功能是打开或创建文件成功后,返回文件描述符,即 pcb中fd_table中的下标,否则返回-1。

目录以字符”/”结尾,如”/a/”,a便是指目录,sys_open只支持文件打开,不支持目录打开,因此程序 开头判断pathname是否为目录,这里是对pathname的最后一个字符判断,即若pathname[strlen(pathname) -1]等于”/”,就表示pathname为目录,打印提示信息并返回-1。

ASSERT(flags <= 7)是限制flags的值在O_RDONLY|O_WRONLY|O_RDWR| O_CREAT 之内。int32_t fd = -1声明了变量fd,为其初始化为-1,即默认找不到文件。

struct path_search_record searched_record生成了路径搜索记录变量searched_record,path_search_record用来记录文件查找时所遍历过的目录,它会作为参数传给函数search_file,其值由函数search_file填充。当查找失败时,主调函数可以根据此结构了解在哪层子目录下失败了。若是创建文件,便可直接获得所创建文件的父目录,即在哪个目录下创建文件。path_search_record中一个很重要的成员是searched_path,它用来记录所处理过的路径,可以通过该路径的长度来判断查找是否成功。举个例子,若查找目标文件c,它的绝对路径是”/a/b/c”,查找时若 发现 b 目录不存在,存入 path_search_record.searched_path的内容便是”/a/b”,若按照此路径找到了c,path_search_record.searched_path 的值便是完整路径”/a/b/c”。

memset(&searched_record, 0, sizeof(struct path_search_record))将searched_record清0。由于searched_record位于栈中,栈中数据并不会自动清0,这非常容易出问题。尤其是在连续无间隔调用sys_open打开不同文件的时候,如果有的文件不存在,有的中间路径缺失,有的成功打开, search_file函数会在 searched_record中记录不同的状态,下一次调用sys_open,searched_recored还是指向栈中相同的位置,栈中数据不会自动清0,所以数据还是上一次调用中的结果,这影响sys_open中相关代码的判断结果,所以在使用前一定要先清0。

uint32_t pathname_depth = path_depth_cnt((char*)pathname)通过path_depth_ent计算pathname的深度,深度值写入变量pathname_depth,计算目录深度 的目的是帮助判断某个目录不存在的情况。无论是打开文件,还是创建文件,都要先判断文件是否已存在,在int inode_no = search_file(pathname, &searched_record)调用search_file 搜索文件 pathname,搜索结果存入 searched_record。bool found = inode_no != -1 ? true : false为 bool 变量 found 赋值。

if (searched_record.file_type == FT_DIRECTORY)判断 pathname 的判断,若为目录,在printk(“can`t open a direcotry with open(), use opendir() to instead\n”)打印提示信息,接着调用 dir_close 关闭目录 searched_record.parent_dir,并返回-1。search_file返回的path_search_record.parent_dir由 主调函数负责关闭,原因是主调函数有可能会用到此目录,也许会在该目录下创建文件。

uint32_t path_searched_depth = path_depth_cnt(searched_record.searched_path)调用 path_depth_cnt 计算 searched_record.searched_path 的路径深度,值写入变量 path_searched depth 中。接着在if (pathname_depth != path_searched_depth)用原路径 pathname的深度值 pathname_depth 与 path_searched_depth 对比,若不相等,说明查找文件的过程中并没有访问到全部的路径,某个中间目录或最后目标不存在,总之检索失败, 于是输出报错信息,告诉用户哪个子目录不存在,便于用户知道在哪里输错了。接着关闭目录, 返回-1。

当flags包含 O_CREAT时, open函数可以创建文件。在if (!found && !(flags & O_CREAT))判断,若目标文件未找到,并且 flags 不包含 O_CREAT,这说明想打开的文件不存在,并不是想创建文件,因此在下一行输出报错信息,然后关闭目录searched_record.parent_dir并返回-1。

else if (found && flags & O_CREAT)判断,若找到了文件并且flags包含O_CREAT,这说明想创建的文件名已存在,相同目录下不允许同名文件存在,因此输出报错并关闭目录,返回-1。

switch (flags & O_CREAT)的switch 结构根据flags中是否包括O_CREAT 的情况暂时只建立了。_CREAT分支,即目前只支持sys_open(“xxx”,O_CREAT|O_XXX)的用法。创建文件是用file_create实现的,因此在fd = file_create(searched_record.parent_dir, (strrchr(pathname, ‘/’) + 1), flags)调用file_create创建文件,返回文件描述符存入变量fd,完成后关闭目录searched_record.parent_dir,并返回fd,至此创建文件完成。

 

继续修改/fs/fs.c

#include "file.h"

更改filesys_init函数

/* 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统 */
void filesys_init() {
   uint8_t channel_no = 0, dev_no, part_idx = 0;

   /* sb_buf用来存储从硬盘上读入的超级块 */
   struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);

   if (sb_buf == NULL) {
      PANIC("alloc memory failed!");
   }
   printk("searching filesystem......\n");
   while (channel_no < channel_cnt) {
      dev_no = 0;
      while(dev_no < 2) {
     if (dev_no == 0) {   // 跨过裸盘hd60M.img
        dev_no++;
        continue;
     }
     struct disk* hd = &channels[channel_no].devices[dev_no];
     struct partition* part = hd->prim_parts;
     while(part_idx < 12) {   // 4个主分区+8个逻辑
        if (part_idx == 4) {  // 开始处理逻辑分区
           part = hd->logic_parts;
        }
     
     /* channels数组是全局变量,默认值为0,disk属于其嵌套结构,
      * partition又为disk的嵌套结构,因此partition中的成员默认也为0.
      * 若partition未初始化,则partition中的成员仍为0. 
      * 下面处理存在的分区. */
        if (part->sec_cnt != 0) {  // 如果分区存在
           memset(sb_buf, 0, SECTOR_SIZE);

           /* 读出分区的超级块,根据魔数是否正确来判断是否存在文件系统 */
           ide_read(hd, part->start_lba + 1, sb_buf, 1);   

           /* 只支持自己的文件系统.若磁盘上已经有文件系统就不再格式化了 */
           if (sb_buf->magic == 0x19590318) {
         printk("%s has filesystem\n", part->name);
           } else {            // 其它文件系统不支持,一律按无文件系统处理
         printk("formatting %s`s partition %s......\n", hd->name, part->name);
         partition_format(part);
           }
        }
        part_idx++;
        part++;    // 下一分区
     }
     dev_no++; // 下一磁盘
      }
      channel_no++; // 下一通道
   }
   sys_free(sb_buf);

    /* 确定默认操作的分区 */
    char default_part[8] = "sdb1";
    /* 挂载分区 */
    list_traversal(&partition_list, mount_partition, (int)default_part);
    /* 将当前分区的根目录打开 */
    open_root_dir(cur_part);

    /* 初始化文件表 */
    uint32_t fd_idx = 0;
    while (fd_idx < MAX_FILE_OPEN) {
        file_table[fd_idx++].fd_inode = NULL;
    }
}

更改的地方就是在filesys_init函数最后添加几行代码

调用 open_root_dir打开根目录和初始化文件表,使文件结构中的 fd_inode 置为 NULL,表示该文件结构为空位,可分配。

 

修改/thread/thread.c的init_thread函数

将fd_table标准输入输出先空出来,其余全部置为-1。

/*线程初始化*/
/* 初始化线程基本信息 */
void init_thread(struct task_struct* pthread, char* name, int prio) {
    memset(pthread, 0, sizeof(*pthread));
    pthread->pid = allocate_pid();
    strcpy(pthread->name, name);

    if (pthread == main_thread) {
        /* 由于把main函数也封装成一个线程,并且它一直是运行的,故将其直接设为TASK_RUNNING */
        pthread->status = TASK_RUNNING;
    } else {
        pthread->status = TASK_READY;
    }

    /* self_kstack是线程自己在内核态下使用的栈顶地址 */
    pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);
    pthread->priority = prio;
    pthread->ticks = prio;
    pthread->elapsed_ticks = 0;
    pthread->pgdir = NULL;

    /* 标准输入输出先空出来 */
    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->stack_magic = 0x19870916;    // 自定义的魔数
}

 

我们修改main.c创建文件测试一下:

添加sys_open(“/file1”, O_CREAT)

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");
    sys_open("/file1", O_CREAT);
    while(1);
    return 0;
}

 

执行效果

同个目录下不能有同名文件

第一次执行

第二次执行

 

参考

郑钢著操作系统真象还原

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

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

暂无评论

发送评论 编辑评论

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