天天看點

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

【場景三】open(pathname, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)

    在這個場景中我們希望建立一個新檔案(O_CREAT),并賦予該檔案使用者可讀(S_IRUSR)和使用者可寫(S_IWUSR)的權限,然後以隻寫(O_WRONLY)的方式打開這個檔案。O_EXCL 在這裡保證該檔案必須被建立,如果該檔案已經存在則失敗傳回。

    這些标志位的作用已經解釋得很清楚了,現在來看看 build_open_flags:

【fs/open.c】sys_open > do_sys_open > build_open_flags

點選(此處)折疊或打開

static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)

{

  ...

    if (flags & (O_CREAT | __O_TMPFILE))

        op->mode = (mode & S_IALLUGO) | S_IFREG;

        acc_mode = MAY_OPEN | ACC_MODE(flags);

    if (flags & O_CREAT) {

        op->intent |= LOOKUP_CREATE;

        if (flags & O_EXCL)

            op->intent |= LOOKUP_EXCL;

    }

}

    由于是建立一個新檔案,是以 mode 就不能丢棄了(845),在這裡 mode 就是“S_IRUSR | S_IWUSR”。然後在我們的情境中,875 行 acc_mode 将被設定成“MAY_OPEN | MAY_WRITE”。最後 intent 也被設定成了相應的查詢模式(894、896)。接着還是 do_last:

【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat > do_last

static int do_last(struct nameidata *nd, struct path *path,

         struct file *file, const struct open_flags *op,

         int *opened, struct filename *name)

    if (!(open_flag & O_CREAT)) {

    } else {

        error = complete_walk(nd);

        if (error)

            return error;

        audit_inode(name, dir, LOOKUP_PARENT);

        error = -EISDIR;

        /* trailing slashes? */

        if (nd->last.name[nd->last.len])

            goto out;

retry_lookup:

    if (op->open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {

        error = mnt_want_write(nd->path.mnt);

        if (!error)

            got_write = true;

        /*

         * do _not_ fail yet - we might not need that or fail with

         * a different error; let lookup_open() decide; we'll be

         * dropping this one anyway.

         */

    mutex_lock(&dir->d_inode->i_mutex);

    error = lookup_open(nd, path, file, op, got_write, opened);

    mutex_unlock(&dir->d_inode->i_mutex);

    首先會調用 complete_walk 告别 rcu-walk 模式,然後會判斷這個最終目标是不是以“/”結尾,如果是的話就表示最終目标是一個目錄那就傳回并報錯“Is a directory”(2926)。随後,如果本次操作是有可能“寫入”的(2931),那就需要取得目前檔案系統的寫權限,但是注釋上寫的很明白,就算現在擷取寫權限失敗也不要急着傳回,因為首先現在隻是“有可能”會“寫入”,其次我們可能會因為别的原因失敗,是以現在先不理會這次的 fail,讓 lookup_open 來決定這一切吧。

    接着就是 lookup_open,我們進去看看:

【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat > do_last > lookup_open

static int lookup_open(struct nameidata *nd, struct path *path,

            struct file *file,

            const struct open_flags *op,

            bool got_write, int *opened)

    *opened &= ~FILE_CREATED;

    dentry = lookup_dcache(&nd->last, dir, nd->flags, &need_lookup);

        dentry = lookup_real(dir_inode, dentry, nd->flags);

    if (!dentry->d_inode && (op->open_flag & O_CREAT)) {

        if (!got_write) {

            error = -EROFS;

            goto out_dput;

        }

        *opened |= FILE_CREATED;

        error = vfs_create(dir->d_inode, dentry, mode,

                 nd->flags & LOOKUP_EXCL);

    這裡我們需要關注一個局部變量:opened,首先會清除改變量的 FILE_CREATED 位(2813),如果該檔案的确不存在的話會被重新賦上 FILE_CREATED,表示這個檔案是這次建立的(2851)。最後,vfs_create 會調用具體檔案系統的 inode_operations.create 函數真正建立這個檔案。

    好了,回到 do_last:

    if (*opened & FILE_CREATED) {

        /* Don't check for write permission, don't truncate */

        open_flag &= ~O_TRUNC;

        will_truncate = false;

        acc_mode = MAY_OPEN;

        path_to_nameidata(path, nd);

        goto finish_open_created;

    error = -EEXIST;

    if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))

        goto exit_dput;

finish_open_created:

    error = may_open(&nd->path, acc_mode, open_flag);

    if (error)

        goto out;

    file->f_path.mnt = nd->path.mnt;

    error = finish_open(file, nd->path.dentry, NULL, opened);

    這裡有兩個地方用到了局部變量 opened,首先如果這個檔案就是本次建立的,那麼就要清除 O_TRUNC 位(2959)。O_TRUNC 用來将打開的檔案長度“截斷”為零,其實就是将檔案清空,因為我們是建立的檔案是以這個标志位顯然是沒用的了。既然已經成功的建立了新檔案,那麼寫權限也沒必要檢查了(2961),然後直接跳轉到标号 finish_open_created 處完成打開。如果這個檔案本來就存在呢?這時會檢查 O_EXCL 和 O_CREAT 兩個标志位,因為 O_EXCL 标志位要求必須建立成功,是以這裡就會傳回“File exists”錯誤(2983)。

    最後 may_open 檢查相應的權限,然後 finish_open 完成打開操作,這兩個函數我們已經看過了,這裡就不深入了。

    現在,我們終于到了說再見的時候了,希望這七天的旅行能讓你有所收獲。

    本來隻是在看完代碼後想給自己留下點什麼,要不然以後忘了就白看了,但是等寫的時候才發現要想寫得明白光馬馬虎虎看完遠遠不夠,于是就越寫越多。大家可以發現在這幾篇部落格中有好多自問自答,這都是我在看代碼時的疑問,有些回答可能并不是那麼準确,還請海涵。最後推薦兩本參考書籍:

    《Linux 核心源代碼情景分析》,可以說這本書寫得相當精彩,雖然它核心版本是 2.4.0 相對來說有點老了,但是并不影響我們探索核心腳步,萬變不離其宗,有很多東西并沒有本質上的變化。

    《深度探索 Linux 作業系統》,這本書将從另一個角度帶我們領略 Linux 的美麗和神秘,在一步一步建立并編譯自己的核心的同時,也深刻的揭示了編譯、連結、加載的原理,同樣是一本精彩的 Linux 讀物。

繼續閱讀