1. 程式人生 > >linux檔案系統呼叫 open 七日遊(四)

linux檔案系統呼叫 open 七日遊(四)

現在,我們的“路徑行走”只剩下最後一個小問題需要處理了——符號連結。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk

點選(此處)摺疊或開啟

  ...

  1.         if (err) {
  2.             err
     = nested_symlink(&next, nd);
  3.             if (err)
  4.                 return err;
  5.         }

  ...

    nested_symlink 就是用來處理符號連結的,現在 nd 還“站”在原來的目錄上,next 才指向了當前這個符號連結。咱們進去看看:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink

點選(此處)摺疊或開啟

  1. static inline int
     nested_symlink(struct path *path, struct nameidata *nd)
  2. {
  3.     int res;

  4.     if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
  5.         path_put_conditional(path, nd);
  6.         path_put(&nd->path);
  7.         return -ELOOP;
  8.     }
  9.     BUG_ON(nd->depth >= MAX_NESTED_LINKS);

  10.     nd->depth++;
  11.     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

點選(此處)摺疊或開啟

  ...

  1.     do {
  2.         struct path link = *path;
  3.         void *cookie;

  4.         res = follow_link(&link, nd, &cookie);
  5.         if (res)
  6.             break;
  7.         res = walk_component(nd, path, LOOKUP_FOLLOW);
  8.         put_link(nd, &link, cookie);
  9.     } 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

點選(此處)摺疊或開啟

  1. static __always_inline int
  2. follow_link(struct path *link, struct nameidata *nd, void **p)
  3. {

  ...

  1.     error = -ELOOP;
  2.     if (unlikely(current->total_link_count >= 40))
  3.         goto out_put_nd_path;

  4.     cond_resched();
  5.     current->total_link_count++;

  6.     touch_atime(link);
  7.     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

點選(此處)摺疊或開啟

  ...

  1.     nd->last_type = LAST_BIND;
  2.     *= dentry->d_inode->i_op->follow_link(dentry, nd);
  3.     error = PTR_ERR(*p);
  4.     if (IS_ERR(*p))
  5.         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

點選(此處)摺疊或開啟

  ...

  1.     error = 0;
  2.     s = nd_get_link(nd);
  3.     if (s) {
  4.         if (unlikely(IS_ERR(s))) {
  5.             path_put(&nd->path);
  6.             put_link(nd, link, *p);
  7.             return PTR_ERR(s);
  8.         }
  9.         if (*== '/') {
  10.             set_root(nd);
  11.             path_put(&nd->path);
  12.             nd->path = nd->root;
  13.             path_get(&nd->root);
  14.             nd->flags |= LOOKUP_JUMPED;
  15.         }
  16.         nd->inode = nd->path.dentry->d_inode;
  17.         error = link_path_walk(s, nd);
  18.         if (unlikely(error))
  19.             put_link(nd, link, *p);
  20.     }

  21.     return error;

  ...

  1. }
    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

點選(此處)摺疊或開啟

  ...

  1.     current->link_count--;
  2.     nd->depth--;
  3.     return res;
  4. }
    在這裡就會恢復巢狀(遞迴)計數。

    現在符號連結我們也參觀完畢了,那麼 link_path_walk 也就結束了,接下來就要對開啟最終目標了。

轉自:http://blog.chinaunix.net/uid-20522771-id-4423034.html