回到 path_openat:
【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat
點選(此處)折疊或打開
...
error = do_last(nd, &path, file, op, &opened, pathname);
while (unlikely(error > 0)) { /* trailing symlink */
struct path link = path;
void *cookie;
if (!(nd->flags & LOOKUP_FOLLOW)) {
path_put_conditional(&path, nd);
path_put(&nd->path);
error = -ELOOP;
break;
}
error = may_follow_link(&link, nd);
if (unlikely(error))
nd->flags |= LOOKUP_PARENT;
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
error = follow_link(&link, nd, &cookie);
error = do_last(nd, &path, file, op, &opened, pathname);
put_link(nd, &link, cookie);
}
其實到現在為止我們的旅程才進行了一半,還記得嗎,我們現在隻是順着路徑走到了最終目标所在的目錄,對最終目标還沒有進行任何處理,甚至連這個最終目标到底存不存在我們都不知道。這一切都會交給 do_last 來完成,如果一切順利它會填充 file 結構并傳回 0;如果傳回值大于 0(其實還是 1,為什麼是“還是”?回頭看看 walk_component 你就明白了),那就表明這個最終目标是一個符号連結且需要跟随這個符号連結,那麼就要進入 3194 行這個循環來順着符号連結找到真正目标(還有一種情況是雖然最終目标是一個符号連結但我們隻想得到這個符号連結本身,也不會進入這個循環)。符号連結的處理我們應該很熟悉了,隻不過因為是最終目标是以增加了一些對标志位的判斷和處理,大家了解這段代碼應該不會有什麼問題。
接下來我們就要進入 do_last 了,首先快速浏覽一遍這個函數,它有 228 行,而且遍地是 if,到處是 goto,誰第一次看到它都會發懵,根本就無從下手。但是隻要我們抓住本質,一切事物都有規律可循,比如這個 do_last,可以說它是既複雜又簡單。說它複雜是因為這裡面進行了大量的标志位的檢查,這些标志位是使用者傳進來訓示 Kernel 需要打開什麼樣的檔案、打開這些檔案的模式以及怎麼打開這些檔案的,這些标志位總共有二十幾個而且還互相影響互相制約,盤根錯節的能不複雜嗎?說它簡單,那是和 link_path_walk 相比,在邏輯上和結構上相對簡單,雖然這裡也會遇到諸如跟随“..”、跟随符号連結、跟随挂載點,但這都是我們熟悉的老朋友了,沒有什麼新奇的東西。
事情是這樣的話我們的 do_last 之行就不能像之前一樣沿着代碼一步一步邊走邊看了,如果那樣的話我們很快就會迷失在 flag 的海洋之中,而且也會使我們本來愉快的旅行變得枯燥乏味。現在讓我們用一種全新的方式遊覽 do_last 吧,我稱之為“情景模式”。在情景模式中我們假設了幾個 open 應用的場景,然後在我們想像的場景之中看看都有哪些标志位起到了怎樣的作用。
【場景一】open(pathname, O_RDONLY)
使用隻讀方式打開一個檔案,這應該是最簡單的 open 應用了。我們先來看看 build_open_flags 是怎麼包裝 O_RDONLY 的:
【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)
{
op->mode = 0;
acc_mode = MAY_OPEN | ACC_MODE(flags);
op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;
}
經過我們的簡化,build_open_flags 和 O_RDONLY 相關的就剩這麼三行了。我記得第一天說過,mode 代表檔案權限隻有在建立檔案的時候才會用到,咱們目前的情景顯然不是建立檔案,是以先将 mode 置零(847)。接下來設定通路模式 acc_mode 這個東西主要用來進行權限檢查,把宏 ACC_MODE 展開後這一行其實就是這樣:acc_mode = MAY_OPEN | MAY_READ,其意思也很明白,那就是對于目标檔案我們至少需要打開和讀取的權限。最後是判斷标志位中 O_PATH 有沒有被設定,如果沒有就将 intent 加上 LOOKUP_OPEN,intent 用來标記我們對最終目标想要做什麼操作(intent 字面意思就是“意圖”),是以在以後我們會看到這裡暫存的 op->intent 會在 do_last 裡重出江湖。
build_open_flags 之後我們直接進入 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)
nd->flags &= ~LOOKUP_PARENT;
nd->flags |= op->intent;
if (!(open_flag & O_CREAT)) {
error = lookup_fast(nd, path, &inode);
if (likely(!error))
goto finish_lookup;
retry_lookup:
LOOKUP_PARENT 實在 patn_init 的時候設定的,當時我們的目标是找到最終檔案的父目錄,但現在我們要找的就是最終檔案,是以需要将這個标志位清除(2888),緊接着 intent 重制江湖(2889)。很明顯我們目前的情況不是建立檔案,是以會進入 2898 這個 if,這裡有個熟面孔 lookup_fast,看到老朋友就是高興。還記得 lookup_fast 執行結構有幾種情況麼?如果傳回 0,則表示成功找到;傳回 1,表示記憶體中沒有,需要 lookup_real;傳回小于 0,則表示需要從目前 rcu-walk 轉到 ref-walk。那現在我們先看看傳回 1 的情況:
mutex_lock(&dir->d_inode->i_mutex);
error = lookup_open(nd, path, file, op, got_write, opened);
mutex_unlock(&dir->d_inode->i_mutex);
error = follow_managed(path, nd->flags);
inode = path->dentry->d_inode;
lookup_fast 傳回 1 的話程式會直接走到标号 retry_lookup 處。現在的程式已經肯定不在 rcu-walk 模式裡了(為什麼?),是以可以使用各種有可能引起程序阻塞的鎖來占有相應的資源了(2941)。接下來是一個新朋友 lookup_open,說是新朋友其實是新瓶裝舊酒,因為它和 lookup_slow 很像,都是先使用 lookup_dcache 在記憶體中找,如果不行就啟動 lookup_real 在具體檔案系統裡面去找,當它成功傳回時會将 path 指向找到的目标。接下來是 follow_managed 它也算是老朋友吧,之前我們簡單介紹過的。再往下走,我們來到了 finish_lookup:
finish_lookup:
if ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path->mnt) {
path_to_nameidata(path, nd);
} else {
save_parent.dentry = nd->path.dentry;
save_parent.mnt = mntget(path->mnt);
nd->path.dentry = path->dentry;
nd->inode = inode;
這裡是 lookup_fast 傳回 1 和傳回 0 的交彙點。這時就需要更新 nd 了,但這個交彙點有兩個來源也就是說現在有可能還在 rcu-walk 模式當中,是以還需要分情況處理一下(3014)。請注意這個 if 的第二個條件“nd->path.mnt != path->mnt”,什麼情況下會出現這兩個 mnt 不相等呢?還記得 nd 的脾氣嗎,當遇到挂載點的時候 nd 會原地踏步,隻有 path 才大無畏的向前走。既然兩個 mnt 不一樣了,那麼更新 nd 前也許要放棄原先占有的結構,這就是 path_to_nameidata 所做。接下來就要徹底告别 rcu-walk 了:
finish_open:
error = complete_walk(nd);
我們到 complete_walk 裡面看看 rcu-walk 告别儀式:
【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat > do_last > complete_walk
static int complete_walk(struct nameidata *nd)
struct dentry *dentry = nd->path.dentry;
int status;
if (nd->flags & LOOKUP_RCU) {
nd->flags &= ~LOOKUP_RCU;
rcu_read_unlock();
if (likely(!(nd->flags & LOOKUP_JUMPED)))
return 0;
告别 rcu-walk 其實很簡單,最主要的就是 605 和 624 這兩行,rcu_read_unlock 之後就會重新啟動程序搶占,本程序就有可能被切換出去,這樣的話目前 CPU 就會經過 quiescent state,當所有 CPU 都經過了自己的 quiescent state 之後,在 rcu-walk 期間的變更才會被更新。接着如果目前不是 LOOKUP_JUMPED,就會直接傳回。這個 LOOKUP_JUMPED 在哪裡會被設定呢?其實我們之前遇到了很多次設定 LOOKUP_JUMPED 的地方,比方說遇到“..”的時候、遇到挂載點的時候、符号連結是絕對路徑的時候,它們的共同點就是有可能會跨越(jump)檔案系統。如果的确跨越了檔案系統也很簡單,檢查一下目前 dentry 需不需要驗證(DCACHE_OP_WEAK_REVALIDATE),大部分情況是不需要,是以這段代碼咱們也省略了,有興趣的同學請查閱 Kernel 源代碼。
接下來終于要真正“打開”這個檔案了:
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);
if (error) {
if (error == -EOPENSTALE)
goto stale_open;
opened:
out:
if (got_write)
mnt_drop_write(nd->path.mnt);
path_put(&save_parent);
terminate_walk(nd);
return error;
may_open 就是權限和标志位的檢查,咱們就懶得進去看了。finish_open 會真正打開這個我們期待已久的目标檔案,裡面主要是利用該檔案系統自己的 file_operations.open 來填充 file 結構。如果一切順利 finish_open 傳回 0,然後釋放占用的資源之後就大功告成可以傳回了。
這樣,我們的情景一就結束了,休息一下,咱們再看看稍微複雜一點的情景。