歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux中文件名解析處理源碼分析

Linux中文件名解析處理源碼分析

日期:2017/3/3 16:20:20   编辑:關於Linux

前言

Linux中對一個文件進行操作的時候,一件很重要的事情是對文件名進行解析處理,並且找到對應文件的inode對象,然後創建表示文件的file對象。在此,對文件名解析過程,並且如何找到對應inode的過程進行源碼分析。分析代碼基於Linux-3.2版本。

關鍵函數分析

不管是通過應用層的API函數還是在內核中打開一個文件,最終都需要調用filp_open函數,該函數的主要職責就是解析文件名,找到文件對應的inode對象,然後分配內存創建file對象,最後執行該文件對應的file->open函數。

filp_open的核心處理函數是path_openat,該函數分析如下:

static struct file *path_openat(int dfd, const char *pathname,  
        struct nameidata *nd, const struct open_flags *op, int flags)  
{  
    struct file *base = NULL;  
    struct file *filp;  
    struct path path;  
    int error;  
    /* 創建一個file對象 */
    filp = get_empty_filp();  
    if (!filp)  
        return ERR_PTR(-ENFILE);  
     
    filp->f_flags = op->open_flag;  
    nd->intent.open.file = filp;  
    nd->intent.open.flags = open_to_namei_flags(op->open_flag);  
    nd->intent.open.create_mode = op->mode;  
    /* 初始化檢索的起始目錄,判斷起始目錄是根目錄還是當前目錄,並且初始化nd->inode對象,為link_path_walk函數的解析處理做准備。 */
    error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base);  
    if (unlikely(error))  
        goto out_filp;  
     
    current->total_link_count = 0;  
    /* 關鍵的字符串解析處理函數,其核心思想是分級解析字符串,通過字符串對應的目錄項找到下一級目錄的inode節點。該函數的具體分析如下。 */
    error = link_path_walk(pathname, nd);  
    if (unlikely(error))  
        goto out_filp;  
    /* do_last函數創建或者獲取文件對應的inode對象,並且初始化file對象,至此一個表示打開文件的內存對象filp誕生 */
    filp = do_last(nd, &path, op, pathname);  
    while (unlikely(!filp)) { /* trailing symlink */
        struct path link = path;  
        void *cookie;  
        if (!(nd->flags & LOOKUP_FOLLOW)) {  
            path_put_conditional(&path, nd);  
            path_put(&nd->path);  
            filp = ERR_PTR(-ELOOP);  
            break;  
        }  
        nd->flags |= LOOKUP_PARENT;  
        nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);  
        error = follow_link(&link, nd, &cookie);  
        if (unlikely(error))  
            filp = ERR_PTR(error);  
        else
            filp = do_last(nd, &path, op, pathname);  
        put_link(nd, &link, cookie);  
    }  
out:  
    if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT))  
        path_put(&nd->root);  
    if (base)  
        fput(base);  
    release_open_intent(nd);  
    return filp;  
     
out_filp:  
    filp = ERR_PTR(error);  
    goto out;  
}

link_path_walk函數完成了基本的名字解析功能,是名字字符串解析處理實現的核心。該函數的實現基於分級解析處理的思想。例如,當需要解析“/dev/mapper/map0”字符串時,其首先需要判斷從何處開始解析?根目錄還是當前目錄?案例是從根目錄開始解析,那麼獲取根目錄的dentry對象並開始分析後繼字符串。以’/’字符為界按序提取字符串,首先我們可以提取”dev”字符串,並且計算該字符串的hash值,通過該hash值查找detry下的inode hash表,就可以得到/dev/目錄的inode對象。依次類推,最後解析得到”/dev/mapper/”目錄的inode對象以及文件名”map0”。至此,link_path_walk函數的使命完成,最後可以通過do_last函數獲取或者創建文件inode。link_path_walk函數分析如下:

static int link_path_walk(const char *name, struct nameidata *nd)  
{  
    struct path next;  
    int err;  
    /* 移除’/’字符 */
    while (*name=='/')  
        name++;  
    /* 如果解析已經完成,直接返回 */
    if (!*name)  
        return 0;  
     
    /* At this point we know we have a real path component. */
    for(;;) {  
        unsigned long hash;  
        struct qstr this;  
        unsigned int c;  
        int type;  
        /* inode訪問的permission檢查 */
        err = may_lookup(nd);  
        if (err)  
            break;  
     
        this.name = name;  
        c = *(const unsigned char *)name;  
        /* 初始化hash值 */
        hash = init_name_hash();  
        do {  
            name++;  
            /* 累計計算名字字符串的hash值 */
            hash = partial_name_hash(c, hash);  
            c = *(const unsigned char *)name;  
        /* 如果遇到’/’字符,結束一次hash計算統計 */
        } while (c && (c != '/'));  
        /* 得到字符串長度和hash結果 */
        this.len = name - (const char *) this.name;  
        this.hash = end_name_hash(hash);  
     
        type = LAST_NORM;  
        /* LAST_DOT和LAST_DOTDOT情形判斷 */
        if (this.name[0] == '.') switch (this.len) {  
            case 2:  /* LAST_DOTDOT是上級目錄 */
                if (this.name[1] == '.') {  
                    type = LAST_DOTDOT;  
                    nd->flags |= LOOKUP_JUMPED;  
                }  
                break;  
            case 1: /* LAST_DOT是當前目錄 */
                type = LAST_DOT;  
        }  
        if (likely(type == LAST_NORM)) {  
            /* LAST_NORM標記說明是需要通過本地目錄進行字符串解析 */
            struct dentry *parent = nd->path.dentry;  
            nd->flags &= ~LOOKUP_JUMPED;  
            if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {  
                /* 如果該標記有效,需要重新計算hash值 */
                err = parent->d_op->d_hash(parent, nd->inode,  
                               &this);  
                if (err < 0)  
                    break;  
            }  
        }  
        /* 如果字符串已經解析完畢,直接跳轉到last_component */
        /* remove trailing slashes? */
        if (!c)  
            goto last_component;  
        while (*++name == '/');  
        if (!*name)  
            goto last_component;  
        /* 通過walk_component函數找到解析字符串對應的inode,並且將nd->inode改稱最新inode,准備繼續解析後面的字符串信息。因為目錄項所管理的inode在系統中通過hash表進行維護,因此,通過hash值可以很容易的找到inode。如果內存中還不存在inode對象,對於ext3文件系統會通過ext3_lookup函數從磁盤上獲取inode的元數據信息,並且構造目錄項中所有的inode對象。 */
        err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);  
        if (err < 0)  
            return err;  
     
        if (err) {  
            err = nested_symlink(&next, nd);  
            if (err)  
                return err;  
        }  
        if (can_lookup(nd->inode))  
            continue;  
        /* 字符串還沒有解析完畢,但是當前的inode已經繼續不允許解析處理了,所以,返回錯誤碼 */
        err = -ENOTDIR;   
        break;  
        /* here ends the main loop */
     
last_component:  
        /* 最後一個字符串不需要解析處理,需要由do_last函數來處理,此處結束解析,正確返回 */
        nd->last = this;  
        nd->last_type = type;  
        return 0;  
    }  
    terminate_walk(nd);  
    return err;  
}

小結

文件名解析處理是文件系統的必備功能,通過文件名的解析索引到表示文件的inode內存對象,並且創建文件對象file。在文件名解析的過程中,首先需要確定的是檢索起始點,然後通過hash table查找目錄項以及檢索文件。在查找的過程中,需要考慮文件訪問的權限以及符號連接等問題。總體來說這些代碼難度不是很大,但是需要有一個整體的思路,就可以更好的理解分析代碼了,這裡只是對名字解析過程中的幾個關鍵函數進行拋磚引玉式的分析。不正之處,敬請指出。

Copyright © Linux教程網 All Rights Reserved