linux檔案系統呼叫 open 七日遊(四)
阿新 • • 發佈:2018-11-08
現在,我們的“路徑行走”只剩下最後一個小問題需要處理了——符號連結。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink
回到我們的旅程,這裡先檢查巢狀層數,一共有兩個地方來對巢狀進行控制:一個是程序(1581);另一個是當前的“路徑行走”(1586)。沒問題的話就可以進行下一步了:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink
現在就來驗證我們的猜測:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
舉個例子,比如現在有一個符號連結“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
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk
點選(此處)摺疊或開啟
...
- if (err) {
- err
- 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
- {
- 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;
...
- }
舉個例子,比如現在有一個符號連結“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 也就結束了,接下來就要對開啟最終目標了。
轉自:http://blog.chinaunix.net/uid-20522771-id-4423034.html