1. 程式人生 > >Linux 系統呼叫 open 七日遊(七)

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

點選(此處

)摺疊或開啟

  1. static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)
  2. {

  ...

  1.     if (flags & (O_CREAT | __O_TMPFILE))
  2.         op-
    >mode = (mode & S_IALLUGO) | S_IFREG;

  ...

  1.         acc_mode = MAY_OPEN | ACC_MODE(flags);

  ...

  1.     if (flags & O_CREAT)
     {
  2.         op->intent |= LOOKUP_CREATE;
  3.         if (flags & O_EXCL)
  4.             op->intent |= LOOKUP_EXCL;
  5.     }

  ...

  1. }
    由於是建立一個新檔案,所以 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

點選(此處)摺疊或開啟

  1. static int do_last(struct nameidata *nd, struct path *path,
  2.          struct file *file, const struct open_flags *op,
  3.          int *opened, struct filename *name)
  4. {

  ...

  1.     if (!(open_flag & O_CREAT)) {

  ...

  1.     } else {

  ...

  1.         error = complete_walk(nd);
  2.         if (error)
  3.             return error;

  4.         audit_inode(name, dir, LOOKUP_PARENT);
  5.         error = -EISDIR;
  6.         /* trailing slashes? */
  7.         if (nd->last.name[nd->last.len])
  8.             goto out;
  9.     }

  10. retry_lookup:
  11.     if (op->open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {
  12.         error = mnt_want_write(nd->path.mnt);
  13.         if (!error)
  14.             got_write = true;
  15.         /*
  16.          * do _not_ fail yet - we might not need that or fail with
  17.          * a different error; let lookup_open() decide; we'll be
  18.          * dropping this one anyway.
  19.          */
  20.     }
  21.     mutex_lock(&dir->d_inode->i_mutex);
  22.     error = lookup_open(nd, path, file, op, got_write, opened);
  23.     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

點選(此處)摺疊或開啟

  1. static int lookup_open(struct nameidata *nd, struct path *path,
  2.             struct file *file,
  3.             const struct open_flags *op,
  4.             bool got_write, int *opened)
  5. {

  ...

  1.     *opened &= ~FILE_CREATED;
  2.     dentry = lookup_dcache(&nd->last, dir, nd->flags, &need_lookup);

  ...

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

  ...

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

  ...

  1.         if (!got_write) {
  2.             error = -EROFS;
  3.             goto out_dput;
  4.         }
  5.         *opened |= FILE_CREATED;

  ...

  1.         error = vfs_create(dir->d_inode, dentry, mode,
  2.                  nd->flags & LOOKUP_EXCL);
  3.         if (error)
  4.             goto out_dput;
  5.     }

  ...

  1. }
    這裡我們需要關注一個區域性變數:opened,首先會清除改變數的 FILE_CREATED 位(2813),如果該檔案的確不存在的話會被重新賦上 FILE_CREATED,表示這個檔案是這次建立的(2851)。最後,vfs_create 會呼叫具體檔案系統的 inode_operations.create 函式真正建立這個檔案。
    好了,回到 do_last:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > do_last

點選(此處)摺疊或開啟

  ...

  1.     if (*opened & FILE_CREATED) {
  2.         /* Don't check for write permission, don't truncate */
  3.         open_flag &= ~O_TRUNC;
  4.         will_truncate = false;
  5.         acc_mode = MAY_OPEN;
  6.         path_to_nameidata(path, nd);
  7.         goto finish_open_created;
  8.     }

  ...

  1.     error = -EEXIST;
  2.     if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))
  3.         goto exit_dput;

  ...

  1. finish_open_created:
  2.     error = may_open(&nd->path, acc_mode, open_flag);
  3.     if (error)
  4.         goto out;
  5.     file->f_path.mnt = nd->path.mnt;
  6.     error = finish_open(file, nd->path.dentry, NULL, opened);

  ...

  1. }
    這裡有兩個地方用到了區域性變數 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 讀物。
轉自:http://blog.chinaunix.net/uid-20522771-id-4426883.html