歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

走馬觀花: Linux 系統調用 open 七日游(四)

現在,我們的“路徑行走”只剩下最後一個小問題需要處理了——符號鏈接。

【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 也就結束了,接下來就要對打開最終目標了。

Copyright © Linux教程網 All Rights Reserved