現在,我們的“路徑行走”只剩下最後一個小問題需要處理了——符號鏈接。
【fs/namei.c】sys_open
> do_sys_open >
do_filp_open >
path_openat >
link_path_walk
點擊(此處)折疊或打開
...
if (err)
{
err
= nested_symlink(&next, nd);
if
(err)
return err;
}
...
nested_symlink 就是用來處理符號鏈接的,現在 nd 還“站”在原來的目錄上,next 才指向了當前這個符號鏈接。咱們進去看看:
【fs/namei.c】sys_open
> do_sys_open >
do_filp_open >
path_openat >
link_path_walk > nested_symlink
點擊(此處)折疊或打開
static inline int nested_symlink(struct path
*path, struct nameidata
*nd)
{
int res;
if (unlikely(current->link_count
>= MAX_NESTED_LINKS))
{
path_put_conditional(path, nd);
path_put(&nd->path);
return -ELOOP;
}
BUG_ON(nd->depth
>= MAX_NESTED_LINKS);
nd->depth++;
current->link_count++;
...
從本質上來講符號鏈接就是一個路徑字符串,這和硬連接有著本質上的區別(請參考【dentry-inode 結構圖】)。而且對於創建目錄的鏈接,硬連接有著嚴格的限制(比如普通用戶就不允許創建目錄的硬連接),但是符號鏈接就沒有那麼多的限制,用戶可以隨意創建目錄的鏈接,甚至可以創建一個鏈接的死循環(比如:a->b;b->c;c->a)。既然不限制創建各式各樣的符號鏈接,那麼在讀取的時候就需要格外小心了,Kernel 設置了兩個限制位,這裡我們就遇到了第一個:MAX_NESTED_LINKS,它的值是 8,它限制了符號鏈接的嵌套(遞歸)層數,那麼什麼時候會發生嵌套(遞歸)呢,別著急,等我們游覽完符號鏈接的時候自然就清楚了,這裡先賣個關子;還有一個是鏈接長度,這個比較好理解,馬上我們就會遇到。
回到我們的旅程,這裡先檢查嵌套層數,一共有兩個地方來對嵌套進行控制:一個是進程(1581);另一個是當前的“路徑行走”(1586)。沒問題的話就可以進行下一步了:
【fs/namei.c】sys_open
> do_sys_open >
do_filp_open >
path_openat >
link_path_walk > nested_symlink
點擊(此處)折疊或打開
...
do {
struct path link
= *path;
void *cookie;
res = follow_link(&link, nd,
&cookie);
if (res)
break;
res = walk_component(nd, path, LOOKUP_FOLLOW);
put_link(nd,
&link, cookie);
} while
(res > 0);
...
符號鏈接的目標很有可能也是一個符號鏈接,所以應該用一個循環來跟蹤它。仔細觀察這個循環體,我們發現了一個老朋友 walk_component,還記得嗎,當它返回正值的時候(其實就是 1)就表明當前目標是一個符號鏈接(我們就是這麼進來的,當然應該記得了),這時就需要再一次循環(1600)。既然這樣的話那我們就大膽的猜測 follow_link 也應該和 link_path_walk 差不多,返回時 nd 也應該是“站在”最終目標所在的目錄上,然後在 walk_component 的幫助下“站上”最終目標。
現在就來驗證我們的猜測:
【fs/namei.c】sys_open
> do_sys_open >
do_filp_open >
path_openat >
link_path_walk > nested_symlink > follow_link
點擊(此處)折疊或打開
static __always_inline
int
follow_link(struct path
*link, struct nameidata
*nd, void
**p)
{
...
error =
-ELOOP;
if (unlikely(current->total_link_count
>= 40))
goto out_put_nd_path;
cond_resched();
current->total_link_count++;
touch_atime(link);
nd_set_link(nd,
NULL);
...
在這裡就是 Kernel 給符號鏈接設置的第二個限制:鏈接長度——簡單地說就是在一個路徑中符號鏈接的個數——不能超過 40 個。因為我門剛剛從 rcu-walk 模式切換過來,而 rcu-walk 是不允許搶占的,所以現在需要看看剛才有沒有想要搶占但又搶占失敗的進程,如果有的話就讓人家先運行,畢竟我們現在是 ref-walk 模式,不是那麼著急的,這就是 cond_resched 所做的(838)。接下來就是更新當前這個符號鏈接的訪問時間(841),然後 nd_set_link 先把當前遞歸深度相應的路徑字符串指針初始化成
NULL。揭曉來就要真正的“跟隨鏈接”了。
【fs/namei.c】sys_open
> do_sys_open >
do_filp_open >
path_openat >
link_path_walk > nested_symlink > follow_link
點擊(此處)折疊或打開
...
nd->last_type
= LAST_BIND;
*p = dentry->d_inode->i_op->follow_link(dentry,
nd);
error = PTR_ERR(*p);
if (IS_ERR(*p))
goto out_put_nd_path;
...
這裡主要就是啟動具體文件系統自己的“跟隨鏈接”機制,等它返回後 nd 中的 saved_names[nd->depth] 就會保存一個指向路徑字符串的指針,隨後我們就用著個字符串啟動一次“全新的”路徑查找。
【fs/namei.c】sys_open
> do_sys_open >
do_filp_open >
path_openat >
link_path_walk > nested_symlink > follow_link
點擊(此處)折疊或打開
...
error = 0;
s = nd_get_link(nd);
if (s)
{
if (unlikely(IS_ERR(s)))
{
path_put(&nd->path);
put_link(nd,
link,
*p);
return PTR_ERR(s);
}
if (*s
==
'/')
{
set_root(nd);
path_put(&nd->path);
nd->path
= nd->root;
path_get(&nd->root);
nd->flags
|= LOOKUP_JUMPED;
}
nd->inode
= nd->path.dentry->d_inode;
error
= link_path_walk(s, nd);
if (unlikely(error))
put_link(nd,
link,
*p);
}
return error;
...
}
s 就是上面提到的那個路徑字符串,看看這個 if (s) 裡的代碼,是不是似曾相似?其中第一個 if(857)是錯誤處理我們不用關心。請看看第二個,如果路徑以“/”開頭就設置一下預設根目錄和當前路徑,是不是很像當年那個 path_init?如果不是“/”開頭呢,那就以當前路徑為起點,而這時 nd 就是“站在”當前路徑上的,所以不用做啥處理直接就可以使用。接下來又是一個老朋友 link_path_walk,其實都不能說是老朋友,因為我們現在還在 link_path_walk 的肚子裡呢。但現在還不能算是符號鏈接的嵌套(遞歸),還記得這個嵌套(遞歸)計數在哪裡麼?沒錯是在
nested_symlink,也就是說只有再次進入 nested_symlink 才能算是遞歸一次。想想我們是怎麼進來的,別忘了 link_path_walk 並不處理路徑中的最終目標,所以要想遞歸就必須在子路徑中加入符號鏈接。簡而言之,符號鏈接的嵌套(遞歸)是發生在該符號鏈接所指目標的路徑中存在另一個符號鏈接。
舉個例子,比如現在有一個符號鏈接“a”它指向一個目錄“/tmp/dir/”,就像這樣“a -> /tmp/dir/”。這個目錄下還有一個文件“/tmp/dir/file”,這時有另一個符號鏈接“b”,我們把它指向“a/file”,就像這樣“b -> a/file”。如果這時訪問“b”,就會遞歸一次,因為“b”所指的路徑中“a/”就是一個符號鏈接且是一個子路徑。
通過這樣的解釋,大家應該了解在什麼情況下會發生嵌套(遞歸)了吧。從 link_path_walk 返回之後,nd 應該已經“站在”最終目錄等待著對最終目標的處理了。這正好應證了剛開始我們的猜測,接下來隊最終目標的處理就會交給 walk_component,如果這個最終目標也是一個符號鏈接那麼 walk_component 就會返回 1 並再一次 nested_symlink 裡的 do-while 循環。
當最終目標不是符號鏈接的時候,nested_symlink 的使命也就完成了:
【fs/namei.c】sys_open
> do_sys_open >
do_filp_open >
path_openat >
link_path_walk > nested_symlink
點擊(此處)折疊或打開
...
current->link_count--;
nd->depth--;
return res;
}
在這裡就會恢復嵌套(遞歸)計數。
現在符號鏈接我們也參觀完畢了,那麼 link_path_walk 也就結束了,接下來就要對打開最終目標了。