前面(見 http://www.linuxidc.com/Linux/2012-02/53694.htm )對linux虛擬文件系統的架構以及設計到的數據結構有了一個整體的認識,這裡看看linux內核怎麼根據給定的文件路徑名在內存中找到和建立代表著目標文件或目錄的dentry結構和inode結構。文件路徑的搜索是文件系統中最基本也是最重要的一部分之一,後面我們會看到,文件的打開、關閉等等操作都將涉及到文件路徑的搜索。下面我們看看linux內核中時怎麼實現的。
一、搜索中所用數據結構
[cpp]
- /*這個數據結構是臨時的,只在路徑搜索的過程中返回搜索的結果。
- */
- struct nameidata {
- struct path path;/*將目錄結構和mount結構封裝在path結構中*/
- struct qstr last;
- struct path root;
- unsigned int flags;/*對應搜索的標志*/
- int last_type;
- unsigned depth;
- char *saved_names[MAX_NESTED_LINKS + 1];
-
- /* Intent data */
- union {
- struct open_intent open;
- } intent;
- };
[cpp]
- /*用來存放路徑名中當前節點的雜湊值以及節點名的長度*/
- struct qstr {
- unsigned int hash;
- unsigned int len;
- const unsigned char *name;
- };
二、搜索
[cpp]
- /*name指向在用戶空間的路徑名;
- flag為一些標志位,nd為搜索返回值
- */
- int path_lookup(const char *name, unsigned int flags,
- struct nameidata *nd)
- {
- return do_path_lookup(AT_FDCWD, name, flags, nd);
- }
實際工作都是由上面的do_path_lookup()函數實現的,在這裡我們就他進行分析。
[cpp]
- /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
- static int do_path_lookup(int dfd, const char *name,
- unsigned int flags, struct nameidata *nd)
- { /*找到搜索的起點,保存在nd中*/
- int retval = path_init(dfd, name, flags, nd);
- if (!retval)
- /*一旦找到了搜索的起點,從起點開始路徑的搜索
- 其中nd用來返回搜索結果*/
- retval = path_walk(name, nd);
- if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&
- nd->path.dentry->d_inode))
- audit_inode(name, nd->path.dentry);
- if (nd->root.mnt) {
- path_put(&nd->root);
- nd->root.mnt = NULL;
- }
- return retval;
- }
2.1 初始化階段
初始化階段是由函數path_init()函數實現
[cpp]
- /*path_init主要是初始化查詢,設置nd結構指向查詢開始處的文件,這裡分兩種情況:
- a,絕對路徑(以/開始),獲得根目錄的dentry。它存儲在task_struct中fs指向的fs_struct結構中。
- b,相對路徑,直接從當前進程task_struct結構中的獲得指針fs,它指向的一個fs_struct,
- fs_struct中有一個指向“當前工作目錄”的dentry。
- */
- static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
- {
- int retval = 0;
- int fput_needed;
- struct file *file;
- /*在搜索的過程中,這個字段的值會隨著路徑名當前搜索結果而變;
- 例如,如果成功找到目標文件,那麼這個字段的值就變成了LAST_NORM
- 而如果最後停留在了一個.上,則變成LAST_DOT(*/
- nd->last_type = LAST_ROOT; /* if there are only slashes... */
- nd->flags = flags;
- nd->depth = 0;
- nd->root.mnt = NULL;
-
- if (*name=='/') {/*路徑名以'/'開頭*/
- set_root(nd);/*設置nd的root為當前進程fs的root*/
- nd->path = nd->root;/*保存根目錄*/
- path_get(&nd->root);/*遞增引用計數*/
- } else if (dfd == AT_FDCWD) {/*相對路徑*/
- struct fs_struct *fs = current->fs;
- read_lock(&fs->lock);
- nd->path = fs->pwd;/*保存當前路徑*/
- path_get(&fs->pwd);/*遞增引用計數*/
- read_unlock(&fs->lock);
- } else {/*???*/
- struct dentry *dentry;
- /*fget_light在當前進程的struct files_struct中根據所謂的用戶空間
- 文件描述符fd來獲取文件描述符。另外,根據當前fs_struct
- 是否被多各進程共享來判斷是否需要對文件描述符進行加
- 鎖,並將加鎖結果存到一個int中返回
- */
- file = fget_light(dfd, &fput_needed);
- retval = -EBADF;
- if (!file)
- goto out_fail;
-
- dentry = file->f_path.dentry;
-
- retval = -ENOTDIR;
- if (!S_ISDIR(dentry->d_inode->i_mode))
- goto fput_fail;
- /*權限檢查*/
- retval = file_permission(file, MAY_EXEC);
- if (retval)
- goto fput_fail;
- /*獲得path*/
- nd->path = file->f_path;
- path_get(&file->f_path);
- /*解鎖*/
- fput_light(file, fput_needed);
- }
- return 0;
-
- fput_fail:
- fput_light(file, fput_needed);
- out_fail:
- return retval;
- }
2.2 實際搜索操作
[cpp]
- static int path_walk(const char *name, struct nameidata *nd)
- {
- current->total_link_count = 0;
- return link_path_walk(name, nd);
- }
[cpp]
- /*
- * Wrapper to retry pathname resolution whenever the underlying
- * file system returns an ESTALE.
- *
- * Retry the whole path once, forcing real lookup requests
- * instead of relying on the dcache.
- */
- static __always_inline int link_path_walk(const char *name, struct nameidata *nd)
- {
- struct path save = nd->path;
- int result;
-
- /* make sure the stuff we saved doesn't go away */
- path_get(&save);/*遞增path的引用計數*/
- /*實際的工作*/
- result = __link_path_walk(name, nd);
- if (result == -ESTALE) {
- /* nd->path had been dropped */
- nd->path = save;
- path_get(&nd->path);
- nd->flags |= LOOKUP_REVAL;
- result = __link_path_walk(name, nd);
- }
-
- path_put(&save);
-
- return result;
- }
[cpp]
- /*
- * Name resolution.
- * This is the basic name resolution function, turning a pathname into
- * the final dentry. We expect 'base' to be positive and a directory.
- *
- * Returns 0 and nd will have valid dentry and mnt on success.
- * Returns error and drops reference to input namei data on failure.
- */
- static int __link_path_walk(const char *name, struct nameidata *nd)
- {
- struct path next;
- struct inode *inode;
- int err;
- unsigned int lookup_flags = nd->flags;
- /*如果路徑名以'/'開頭,就把他跳過去,因為在這種情況下nd中
- path已經指向本進程的根目錄了,注意,這裡多個連續的'/'與一個
- ‘/’是等價的,如果路徑名中僅僅包含有'/'字符的話,那麼其
- 目標就是根目錄,所以任務完成,不然需要繼續搜索*/
- while (*name=='/')
- name++;
- if (!*name)
- goto return_reval;
- /*作為path_walk起點的節點必定是一個目錄,一定有相應的索引節點
- 存在,所以指針inode一定是有效的,而不可能是空指針*/
- inode = nd->path.dentry->d_inode;
- /*進程的task_struct結構中有個計數器link_count.在搜索過程中有可能
- 碰到一個節點(目錄項)只是指向另一個節點的鏈接,此時就用這個計數器來對
- 鏈的長度進行計數,這樣,當鏈的長度達到某一個值時就可以終止搜索而失敗
- 返回,以防陷入循環。另一方面,當順著符號鏈接進入另一個設備上的文件系統
- 時,有可能會遞歸地調用path_walk。所以,進入path_walk後,如果發現這個
- 計數器值非0,就表示正在順著符號鏈接遞歸調用path_walk往前搜索過程中,
- 此時不管怎樣都把LOOKUP_FOLLOW標志位設成1.*/
- if (nd->depth)
- lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);
-
- /* At this point we know we have a real path component. */
- for(;;) {
- unsigned long hash;
-
- struct qstr this;
- unsigned int c;
-
- nd->flags |= LOOKUP_CONTINUE;
- /*檢查當前進程對當前節點的訪問權限,這裡所檢查的是相對路徑中
- 的各層目錄(而不是目標文件)的訪問權限。注意,對於中間節點所需
- 的權限為執行權,即MAY_EXEC*/
- err = exec_permission_lite(inode);
- if (err)
- break;
-
- this.name = name;
- c = *(const unsigned char *)name;
-
- hash = init_name_hash();
- do {
- name++;
- hash = partial_name_hash(c, hash);
- c = *(const unsigned char *)name;
- } while (c && (c != '/'));/*路徑名中的節點定以‘/’字符分開的,*/
- this.len = name - (const char *) this.name;
- this.hash = end_name_hash(hash);
-
- /* remove trailing slashes? */
- if (!c)/*最後一個字符為'\0',就是說當前節點已經是路徑名中的最後一節*/
- goto last_component;/*跳轉*/
- /*循環跳過'/'*/
- while (*++name == '/');
- /*當前節點實際上已經是路徑名的最後一個節點,只不過在此後面又多添加了
- 若干個'/'字符,這種情況常常發生在用戶界面上,特別是在shell的命令中
- 當然這種情況要求最後的節點必須是個目錄*/
- if (!*name)
- goto last_with_slashes;/*跳轉*/
-
- /*運行到這裡,表示當前節點為中間節點,所以'/'字符後面還有其他字符*/
- /*
- * "." and ".." are special - ".." especially so because it has
- * to be able to know about the current root directory and
- * parent relationships.
- */
- /*以'.'開頭表示這是個隱藏的文件,而對於代表著目錄的節點則只有在兩種
- ���況下才是允許的。一種是節點名為'.',表示當前目錄,另一種是'..',表示
- 當前目錄的父目錄*/
- if (this.name[0] == '.') switch (this.len) {
- default:
- break;
- case 2:
- if (this.name[1] != '.')
- break;
- follow_dotdot(nd);/*為'..',到父目錄中去*/
- inode = nd->path.dentry->d_inode;
- /* fallthrough */
- /*2中沒有break語句,也就是所繼續執行1中的語句,
- 將會跳到for語句的開頭處理路徑中的下一個節點*/
- case 1:
- continue;
- }
- /*
- * See if the low-level filesystem might want
- * to use its own hash..
- */
- /*特定文件系統提供他自己專用的雜湊函數,所以在這種情況下就通過這個
- 函數再計算一遍當前節點的雜湊值*/
- if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) {
- err = nd->path.dentry->d_op->d_hash(nd->path.dentry,
- &this);
- if (err < 0)
- break;
- }
- /* This does the actual lookups.. */
- /*實際的搜索工作*/
- err = do_lookup(nd, &this, &next);
- if (err)
- break;
-
- err = -ENOENT;
- inode = next.dentry->d_inode;
- if (!inode)
- goto out_dput;
- /*涉及到具體文件系統的相關操作*/
- if (inode->i_op->follow_link) {
- err = do_follow_link(&next, nd);
- if (err)
- goto return_err;
- err = -ENOENT;
- inode = nd->path.dentry->d_inode;
- if (!inode)
- break;
- } else/*將path中的相關內容轉化到nd中*/
- path_to_nameidata(&next, nd);
- err = -ENOTDIR;
- if (!inode->i_op->lookup)
- break;
- continue;
- /* here ends the main loop */
-
- last_with_slashes:
- lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
- last_component:
- /* Clear LOOKUP_CONTINUE iff it was previously unset */
- nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
- if (lookup_flags & LOOKUP_PARENT)/*要尋找的不是路徑終點,而是他的上一層*/
- goto lookup_parent;
- if (this.name[0] == '.') switch (this.len) {
- default:
- break;
- case 2:
- if (this.name[1] != '.')
- break;
- follow_dotdot(nd);/*向上層移動*/
- inode = nd->path.dentry->d_inode;
- /* fallthrough */
- case 1:
- goto return_reval;
- }
- /*具體文件系統的操作*/
- if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) {
- err = nd->path.dentry->d_op->d_hash(nd->path.dentry,
- &this);
- if (err < 0)
- break;
- }/*順次查找路徑節點,下一個存放在next中*/
- err = do_lookup(nd, &this, &next);
- if (err)
- break;
- inode = next.dentry->d_inode;
- if ((lookup_flags & LOOKUP_FOLLOW)/*當終點為符號鏈接時*/
- && inode && inode->i_op->follow_link) {
- err = do_follow_link(&next, nd);
- if (err)
- goto return_err;
- inode = nd->path.dentry->d_inode;
- } else
- /*path轉化為nd*/
- path_to_nameidata(&next, nd);
- err = -ENOENT;
- if (!inode)
- break;
- if (lookup_flags & LOOKUP_DIRECTORY) {
- err = -ENOTDIR;
- if (!inode->i_op->lookup)
- break;
- }
- goto return_base;
- lookup_parent:
- nd->last = this;
- nd->last_type = LAST_NORM;/*根據終點節點名設置*/
- if (this.name[0] != '.')
- goto return_base;
- if (this.len == 1)
- nd->last_type = LAST_DOT;
- else if (this.len == 2 && this.name[1] == '.')
- nd->last_type = LAST_DOTDOT;
- else
- goto return_base;
- return_reval:
- /*
- * We bypassed the ordinary revalidation routines.
- * We may need to check the cached dentry for staleness.
- */
- if (nd->path.dentry && nd->path.dentry->d_sb &&
- (nd->path.dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {
- err = -ESTALE;
- /* Note: we do not d_invalidate() */
- if (!nd->path.dentry->d_op->d_revalidate(
- nd->path.dentry, nd))
- break;
- }
- return_base:
- return 0;
- out_dput:
- path_put_conditional(&next, nd);
- break;
- }
- path_put(&nd->path);
- return_err:
- return err;
- }