天天看點

走馬觀花: 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)。沒問題的話就可以進行下一步了:

    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。揭曉來就要真正的“跟随連結”了。

    nd->last_type = LAST_BIND;

    *p = dentry->d_inode->i_op->follow_link(dentry, nd);

    error = PTR_ERR(*p);

    if (IS_ERR(*p))

    這裡主要就是啟動具體檔案系統自己的“跟随連結”機制,等它傳回後 nd 中的 saved_names[nd->depth] 就會儲存一個指向路徑字元串的指針,随後我們就用着個字元串啟動一次“全新的”路徑查找。

    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);

            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))

    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 的使命也就完成了:

    current->link_count--;

    nd->depth--;

    return res;

    在這裡就會恢複嵌套(遞歸)計數。

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

繼續閱讀