前言
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查找目錄項以及檢索文件。在查找的過程中,需要考慮文件訪問的權限以及符號連接等問題。總體來說這些代碼難度不是很大,但是需要有一個整體的思路,就可以更好的理解分析代碼了,這裡只是對名字解析過程中的幾個關鍵函數進行拋磚引玉式的分析。不正之處,敬請指出。