手写操作系统(五十一)-文件路径解析和搜索

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

查找文件时先得实现路径解析

有关路径解析的代码是在fs.c中

修改/fs/fs.c

/* 将最上层路径名称解析出来 */
char* path_parse(char* pathname, char* name_store) {
    if (pathname[0] == '/') {   // 根目录不需要单独解析
        /* 路径中出现1个或多个连续的字符'/',将这些'/'跳过,如"///a/b" */
        while(*(++pathname) == '/');
    }

    /* 开始一般的路径解析 */
    while (*pathname != '/' && *pathname != 0) {
        *name_store++ = *pathname++;
    }

    if (pathname[0] == 0) {   // 若路径字符串为空则返回NULL
        return NULL;
    }
    return pathname;
}

/* 返回路径深度,比如/a/b/c,深度为3 */
int32_t path_depth_cnt(char* pathname) {
    ASSERT(pathname != NULL);
    char* p = pathname;
    char name[MAX_FILE_NAME_LEN];       // 用于path_parse的参数做路径解析
    uint32_t depth = 0;

    /* 解析路径,从中拆分出各级名称 */
    p = path_parse(p, name);
    while (name[0]) {
        depth++;
        memset(name, 0, MAX_FILE_NAME_LEN);
        if (p) {         // 如果p不等于NULL,继续分析路径
            p  = path_parse(p, name);
        }
    }
    return depth;
}

/* 搜索文件pathname,若找到则返回其inode号,否则返回-1 */
static int search_file(const char* pathname, struct path_search_record* searched_record) {
    /* 如果待查找的是根目录,为避免下面无用的查找,直接返回已知根目录信息 */
    if (!strcmp(pathname, "/") || !strcmp(pathname, "/.") || !strcmp(pathname, "/..")) {
        searched_record->parent_dir = &root_dir;
        searched_record->file_type = FT_DIRECTORY;
        searched_record->searched_path[0] = 0;     // 搜索路径置空
        return 0;
    }

    uint32_t path_len = strlen(pathname);
    /* 保证pathname至少是这样的路径/x且小于最大长度 */
    ASSERT(pathname[0] == '/' && path_len > 1 && path_len < MAX_PATH_LEN);
    char* sub_path = (char*)pathname;
    struct dir* parent_dir = &root_dir;
    struct dir_entry dir_e;

    /* 记录路径解析出来的各级名称,如路径"/a/b/c",
     * 数组name每次的值分别是"a","b","c" */
    char name[MAX_FILE_NAME_LEN] = {0};

    searched_record->parent_dir = parent_dir;
    searched_record->file_type = FT_UNKNOWN;
    uint32_t parent_inode_no = 0;  // 父目录的inode号

    sub_path = path_parse(sub_path, name);
    while (name[0]) {      // 若第一个字符就是结束符,结束循环
        /* 记录查找过的路径,但不能超过searched_path的长度512字节 */
        ASSERT(strlen(searched_record->searched_path) < 512);

        /* 记录已存在的父目录 */
        strcat(searched_record->searched_path, "/");
        strcat(searched_record->searched_path, name);

        /* 在所给的目录中查找文件 */
        if (search_dir_entry(cur_part, parent_dir, name, &dir_e)) {
            memset(name, 0, MAX_FILE_NAME_LEN);
            /* 若sub_path不等于NULL,也就是未结束时继续拆分路径 */
            if (sub_path) {
                sub_path = path_parse(sub_path, name);
            }

            if (FT_DIRECTORY == dir_e.f_type) {   // 如果被打开的是目录
                parent_inode_no = parent_dir->inode->i_no;
                dir_close(parent_dir);
                parent_dir = dir_open(cur_part, dir_e.i_no); // 更新父目录
                searched_record->parent_dir = parent_dir;
                continue;
            } else if (FT_REGULAR == dir_e.f_type) {     // 若是普通文件
                searched_record->file_type = FT_REGULAR;
                return dir_e.i_no;
            }
        } else {          //若找不到,则返回-1
            /* 找不到目录项时,要留着parent_dir不要关闭,
             * 若是创建新文件的话需要在parent_dir中创建 */
            return -1;
        }
    }

    /* 执行到此,必然是遍历了完整路径并且查找的文件或目录只有同名目录存在 */
    dir_close(searched_record->parent_dir);

    /* 保存被查找目录的直接父目录 */
    searched_record->parent_dir = dir_open(cur_part, parent_inode_no);
    searched_record->file_type = FT_DIRECTORY;
    return dir_e.i_no;
}

path_parse函数接受2个参数,pathname是字符串形式的路径及文件名,name_store是主调函数提供的缓冲区,用于存储最上层路径名,此函数功能是将最上层路径名称解析出来存储到name_store中,调用结束后返回除顶层路径之外的子路径字符串的地址。每次调用path_parse只会解析一层路径名,也就是最顶层的路径名,例如要是解析路径”/a/b/c”的话,调用path_parse时,name_store的值是”a”,然后返回子路径字符串”/b/c”的地址。下次再调用path_parse时,主调函数若传给pathname的参数是”/b/c”, path_parse执行后name_store中的值变为”b”,然后返回子路径”/c”的地址。

函数开头先判断路径名第0个字符是否为”/”,也就是说类似这样的路径“/a/b/c”。首先,任何时候路径中最左边的,都表示根目录,根目录不需要单独解析,因为它是已知的,并且已经被提前打开了。其次,路径中的”/”仅表示路径分隔符,我们只关心路径分隔符之间的路径名,因此无论,表示根目录,还是路径分隔符,我们始终要跨过路径中最左边的”/”。

接下来在while(*(++pathname) == ‘/’)通过 while 循环去掉路径中连续重复的多个”/”,Linux中各层路径间的分隔符可以是多个,比如”//a/b///c”这样的路径是合法的,我们也兼容此方式,因此必须要去掉路径中连续重复的多个。

while (*pathname != ‘/’ && *pathname != 0)的while循环开始路径解析,也就是解析出参数pathname中最顶层(最左边)的路径名存入 name_store。比如若此时的pathname经过处理后已变为”a/b”的话,循环处理后,name_store 为a。

若pathname已经结束,指向末尾的结束字符”\0″ (其ASCII值为0),就返回NULL表示处理结束,否 则pathname 依然包含子路径,将其返回。

path_depth_cnt函数接受1个参数,pathname表示待分析的路径。函数功能是返回路径深度,比如路径”/a/b/c”,深度为3,不过,这里不能简单地根据路径分隔符/来统计目录深度,前面说过在Linux中”//a/b///e”这样的路径是合法的,我们也是如此。函数实现较简单,,主要是循环调用path_parse统计各级路径名,通过变量depth来累计路径层数。

修改/fs/fs.h

#ifndef __FS_FS_H
#define __FS_FS_H
#include "stdint.h"

#define MAX_FILES_PER_PART 4096     // 每个分区所支持最大创建的文件数
#define BITS_PER_SECTOR 4096        // 每扇区的位数
#define SECTOR_SIZE 512        // 扇区字节大小
#define BLOCK_SIZE SECTOR_SIZE      // 块字节大小

#define MAX_PATH_LEN 512        // 路径最大长度

/* 文件类型 */
enum file_types {
    FT_UNKNOWN,   // 不支持的文件类型
    FT_REGULAR,   // 普通文件
    FT_DIRECTORY      // 目录
};

/* 打开文件的选项 */
enum oflags {
    O_RDONLY,     // 只读
    O_WRONLY,     // 只写
    O_RDWR,   // 读写
    O_CREAT = 4   // 创建
};

/* 用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中"走过的地方" */
struct path_search_record {
    char searched_path[MAX_PATH_LEN];       // 查找过程中的父路径
    struct dir* parent_dir;        // 文件或目录所在的直接父目录
    enum file_types file_type;         // 找到的是普通文件还是目录,找不到将为未知类型(FT_UNKNOWN)
};

extern struct partition* cur_part;
char* path_parse(char* pathname, char* name_store);
void filesys_init(void);
int32_t path_depth_cnt(char* pathname);
int32_t sys_open(const char* pathname, uint8_t flags);
#endif

宏MAX_PATH_LEN表示路径名最大的长度,这里其值为512。

枚举结构enum oflags是打开文件时的选项,将来实现file_create和open函数时会用到。目前enum oflags结构中只支持4个标识,这里是按位来定义各标识的,按二进制来说,O_RDONLY的值为000b,O_WRONLY的值为001b,O_RDWR的值为010b,O_CREAT的值为100b,这样的好处是当这几个标识通过位或1叠加到一起作为复合参数时,可以通过位与运算“&”单独解析出各位以反推标识位。

struct path_search_record是路径搜索记录,此结构用来记录查找文件过程中已处理过的上级路径,也就是查找文件过程中走过的地方。用此结构的目的是想获取路径中“断链”的部分,比如查找文件”/a/b/c”,若找不到的话,我们想知道是c不存在,还是上层目录b或a就不存在,其中成员searched_path就是查找过程中不存在的路径,还是拿查找”/a/b/c”来说,若仅是c不存在,searched_path的值为”/a/b/c”,若是b就不存在,searched_path的值为”/a/b”。 成员parent_dir用于记录文件或目录所在的直接父目录,成员file_type是找到的文件类型,若找不到文件的话,该值为未知类型FT_UNKNOWN。

 

接着就是实现搜索功能了

更改/fs/fs.c

/* 搜索文件pathname,若找到则返回其inode号,否则返回-1 */
static int search_file(const char* pathname, struct path_search_record* searched_record) {
    /* 如果待查找的是根目录,为避免下面无用的查找,直接返回已知根目录信息 */
    if (!strcmp(pathname, "/") || !strcmp(pathname, "/.") || !strcmp(pathname, "/..")) {
        searched_record->parent_dir = &root_dir;
        searched_record->file_type = FT_DIRECTORY;
        searched_record->searched_path[0] = 0;     // 搜索路径置空
        return 0;
    }

    uint32_t path_len = strlen(pathname);
    /* 保证pathname至少是这样的路径/x且小于最大长度 */
    ASSERT(pathname[0] == '/' && path_len > 1 && path_len < MAX_PATH_LEN);
    char* sub_path = (char*)pathname;
    struct dir* parent_dir = &root_dir;
    struct dir_entry dir_e;

    /* 记录路径解析出来的各级名称,如路径"/a/b/c",
     * 数组name每次的值分别是"a","b","c" */
    char name[MAX_FILE_NAME_LEN] = {0};

    searched_record->parent_dir = parent_dir;
    searched_record->file_type = FT_UNKNOWN;
    uint32_t parent_inode_no = 0;  // 父目录的inode号

    sub_path = path_parse(sub_path, name);
    while (name[0]) {      // 若第一个字符就是结束符,结束循环
        /* 记录查找过的路径,但不能超过searched_path的长度512字节 */
        ASSERT(strlen(searched_record->searched_path) < 512);

        /* 记录已存在的父目录 */
        strcat(searched_record->searched_path, "/");
        strcat(searched_record->searched_path, name);

        /* 在所给的目录中查找文件 */
        if (search_dir_entry(cur_part, parent_dir, name, &dir_e)) {
            memset(name, 0, MAX_FILE_NAME_LEN);
            /* 若sub_path不等于NULL,也就是未结束时继续拆分路径 */
            if (sub_path) {
                sub_path = path_parse(sub_path, name);
            }

            if (FT_DIRECTORY == dir_e.f_type) {   // 如果被打开的是目录
                parent_inode_no = parent_dir->inode->i_no;
                dir_close(parent_dir);
                parent_dir = dir_open(cur_part, dir_e.i_no); // 更新父目录
                searched_record->parent_dir = parent_dir;
                continue;
            } else if (FT_REGULAR == dir_e.f_type) {     // 若是普通文件
                searched_record->file_type = FT_REGULAR;
                return dir_e.i_no;
            }
        } else {          //若找不到,则返回-1
            /* 找不到目录项时,要留着parent_dir不要关闭,
             * 若是创建新文件的话需要在parent_dir中创建 */
            return -1;
        }
    }

    /* 执行到此,必然是遍历了完整路径并且查找的文件或目录只有同名目录存在 */
    dir_close(searched_record->parent_dir);

    /* 保存被查找目录的直接父目录 */
    searched_record->parent_dir = dir_open(cur_part, parent_inode_no);
    searched_record->file_type = FT_DIRECTORY;
    return dir_e.i_no;
}

search_file函数接受2个参数,被检索的文件pathname和路径搜索记录指针searched_record,功能是 搜索文件 pathname,若找到则返回其 inode 号,否则返回-1。其中参数 pathname 是全路径,即从根目录开始的路径。参数searched_record由主调函数提供,主调函数只关注该结构中的信息,并不关注搜索过程,该结构中的数据由函数search_file填充,其中的成员parent_dir记录的是待查找目标(文件或目录)的直接父目录,原因是主调函数通常需要获取目标的父目录作为操作对象,比如将来创建文件时,需要知道在哪个目录中创建文件,因此所有调用search_file的主调函数记得释放目录searched_record->parent_dir,避 免内存泄漏。下面开始介绍函数实现。

有时候用户输入的路径可能仅是根目录”/”,不包括子路径,比如执行命令”ls/”就是这样的情况。因此在代码if (!strcmp(pathname, “/”) || !strcmp(pathname, “/.”) || !strcmp(pathname, “/..”))判断,如果待查找的是根目录,为避免后续无用的查找工作,直接在searched_record中写入根目录信息后返回。

然后用指针 sub_path 指向路径名 pathname,我们后面的处理主要用sub_path 变量,声明目录指针 parent_dir 指向根目录,我们要从根目录开始往下查找文件,然后声明了目录项 dir_e。char name[MAX_FILE_NAME_LEN] = {0}声明了数组name[MAX_FILE_NAME_LEN],它用来存储路径解析中的各层路径名,name必然会作为函数path_parse的参数。

parent_inode_no是父目录inode编号,它的作用是备份各层解析出来的路径的父目录的 inode编号。我们从根目录开始解析路径,因此初始化parent_inode_no为根目录的inode编号0。

下面开始搜索文件。搜索文件的原理是路径解析,也就是把路径按照分隔符”/”拆分,每解析出一层路径名就去目录中确认相应的目录项,与目录项中的filename比对,找到后继续路径解析,直到路径解析完成或找不到某个中间目录就返回。”sub_path=path_parse(sub_path, name)”开始路径解析,path_parse返回后,最上层的路径名会存储在name中,返回值存入sub_path,此时的sub_path已经剥去了最上层的路径。while (name[0])使用 while循环处理各层路径,其判断条件是 name[0],只要name[0]不等于字符串结束符”\0“(其 ASCII 码值为 0),就表示 name 不是空字符串,路径解析尚未结束。

每次解析过的路径都会追加到searched_record->searched_path中,searched_path用于记录已解析的路径,由于是先调用path_parse解析路径,再调用search_dir_entry去验证路径是否存在,因此searched_record->searched_path中的最后一级目录未必存在,其前的所有路径都是存在 的。strcat(searched_record->searched_path, “/”)代码在第一次执行时所添加的”/”表示根目录,后续循环中添加的是路径分隔符。

接着在if (search_dir_entry(cur_part, parent_dir, name, &dir_e))调用search_dir_entry判断解析出来的上层路径name是否在父目录parent_dir中存在,如果存在,也就是被找到了,dir_e中会被录入目录项的信息。接着调用memset将name清0,因为后面我们要继续用name来存储新的路径。if (sub_path)判断sub_path是否为null,它是由函数path_parse赋值的,该函数总是返回最上层路径之外的子路径。若sub_path不为null,这说明路径解析未完成,还有子路径未拆出来,再次执行”sub_path= path_parse(sub_path, name)”进行下一步的路径解析。

经过前面的 search_dir_entry 调用, dir_e 中已经是目录项的信息了,在if (FT_DIRECTORY == dir_e.f_type)来判断解析出的最上层路径name是否为目录,若是目录,就将父目录的 inode编号赋值给变量parent_inode_no,此变量用于备份父目录的inode编号,它会在最后一级路径为目录的情况下用到,也就是searched_record->parent_dir = dir_open(cur_part, parent_inode_no)代码。接着在dir_close(parent_dir)关闭父目录parent_dir,打开的目录记得关闭, 否则造成内存泄漏。注意,根目录是不可被关闭的,我们在dir_close中有特殊处理。

接下来把目录name打开,重新为parent_dir赋值,此处对应的代码是parent_dir = dir_open(cur_part, dir_e.i_no)同时通过searched_record->parent_dir = parent_dir更新搜索记录中的父目录。如果name 为普通文件的话,在正常情况下,也就是路径输入正确的情况下,这说明已经把路径从左到右处理完了,因此将搜索记录中的file_type更新为FT_REGULAR,随后返回普通文件name的inode编号,即dir_e.i_no。

search_file只负责在文件系统上检索文件,它不负责侦错处理,它是由主调函数调用的,具体的处理方法是由主调函数安排的。search_file已经把路径处理信息保存在路径搜索记录path_search_record中了,每处理一层路径,都会将其追加到path_search_record 中的searched_path中,因此主调函数可以根据searched_path来判断pathname是否正确,是否处理完了。

若if (search_dir_entry(cur_part, parent_dir, name, &dir_e))的search_dir_entry返回false,也就是未找到name,程序跳到else直接返回-1。注意,未找到name时,parent_dir也不能关闭,因为有可能主调函数会在parent_dir目录中创建文件name,也就是说,主调函数需要知道在哪个目录中创建文件,此时searched_record->parent_dir指向父目录,主调函数负责关闭该目录。

程序若能执行到dir_close(searched_record->parent_dir),说明路径pathname已经被完整地解析过了,pathname的最后一层路径不是普通文件,而是目录。

结论是待查找的目标是目录,如”/a/b/c”, c 是目录,不是普通文件。此时 searched_record-> parent_dir 是路径pathname中的最后一级目录c,并不是倒数第二级的父目录b,我们在任何时候都应该使 searched_record->parent_dir是被查找目标的直接父目录,也就是说,无论目标是普通文件,还是目录, searched_record->parent_dir中记录的都应该是目录b。因此我们需要把searched_record->parent_dir 重新更新为 父目录b。在重新打开父目录之前,为避免内存溢出,先调用dir_close关闭目录searched_record->parent_dir。 接下来是重新打开父目录,我们之前已经将父目录的inode编号保存在了变量parent_inode_no中,此时是它派上用场的时候,在 searched_record->parent_dir = dir_open(cur_part, parent_inode_no)打开父目录并为searched_record->parent_dir 赋值。然后在下一行更新成员 file_type 为FT_DIRECTORY,最后返回目录的 inode编号。

 

参考

郑钢著操作系统真象还原

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

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

暂无评论

发送评论 编辑评论

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