autofs - 工作原理

目的

autofs 的目標是按需掛載和無競爭地自動解除安裝各種其他檔案系統。這提供了兩個關鍵優勢

  1. 無需延遲啟動,直到所有可能需要的檔案系統都被掛載。嘗試訪問這些慢速檔案系統的程序可能會被延遲,但其他程序可以自由繼續。 這對於網路檔案系統(例如 NFS)或儲存在具有媒體更換機器人的媒體上的檔案系統尤為重要。

  2. 檔案系統的名稱和位置可以儲存在遠端資料庫中,並且可以隨時更改。訪問時資料庫中的內容將用於為訪問提供目標。 檔案系統中名稱的解釋甚至可以是程式化的而不是資料庫支援的,例如允許使用萬用字元,並且可以根據首次訪問名稱的使用者而有所不同。

上下文

“autofs”檔案系統模組只是 autofs 系統的一部分。 還需要有一個使用者空間程式來查詢名稱和掛載檔案系統。 這通常是“automount”程式,但包括“systemd”在內的其他工具也可以使用“autofs”。 本文件僅描述核心模組以及與任何使用者空間程式所需的互動。 後續文字將此稱為“automount 守護程式”或簡稱為“守護程式”。

“autofs”是一個 Linux 核心模組,它提供“autofs”檔案系統型別。 可以掛載多個“autofs”檔案系統,並且可以單獨管理每個檔案系統,或者由同一個守護程式管理所有檔案系統。

內容

autofs 檔案系統可以包含 3 種物件:目錄、符號連結和掛載陷阱。 掛載陷阱是具有額外屬性的目錄,如下一節所述。

物件只能由 automount 守護程式建立:符號連結使用常規的 symlink 系統呼叫建立,而目錄和掛載陷阱使用 mkdir 建立。 確定目錄是否應為掛載陷阱基於主對映。autofs 查詢此主對映以確定哪些目錄是掛載點。 掛載點可以是直接/間接/偏移。 在大多數系統上,預設主對映位於 /etc/auto.master

如果未給出直接偏移掛載選項(因此掛載被認為是間接的),則根目錄始終是常規目錄,否則當它為空時是掛載陷阱,而當它不為空時是常規目錄。請注意,直接偏移的處理方式相同,因此簡明扼要的總結是,僅當檔案系統直接掛載且根目錄為空時,根目錄才是掛載陷阱。

僅當檔案系統間接掛載且根目錄為空時,才會在根目錄中建立的目錄成為掛載陷阱。

樹中更深層次的目錄取決於 maxproto 掛載選項,尤其是它是否小於 5。 當 maxproto 為 5 時,樹中更深層次的目錄永遠不會成為掛載陷阱,它們始終是常規目錄。 當 maxproto 為 4(或 3)時,只有當這些目錄為空時,它們才是掛載陷阱。

因此:非空(即非葉)目錄永遠不會成為掛載陷阱。 空目錄有時是掛載陷阱,有時不是,具體取決於它們在樹中的位置(根、頂層或更低層)、maxproto 以及掛載是否為間接掛載。

掛載陷阱

autofs 實現的核心要素是 Linux VFS 提供的掛載陷阱。 檔案系統提供的任何目錄都可以指定為陷阱。 這涉及兩個獨立的功能協同工作,以使 autofs 能夠完成其工作。

DCACHE_NEED_AUTOMOUNT

如果 dentry 設定了 DCACHE_NEED_AUTOMOUNT 標誌(如果在 inode 上設定了 S_AUTOMOUNT,或者可以直接設定,則會設定該標誌),那麼它(可能)是一個掛載陷阱。 除“stat”之外的對該目錄的任何訪問都(通常)會導致呼叫 d_op->d_automount() dentry 操作。 此方法的任務是找到應掛載在該目錄上的檔案系統並返回它。 VFS 負責實際將該檔案系統的根掛載在該目錄上。

autofs 不會自己查詢檔案系統,而是向 automount 守護程式傳送訊息,要求它查詢和掛載檔案系統。 然後,autofs d_automount 方法等待守護程式報告一切已準備就緒。 然後它將返回“NULL”,指示掛載已經發生。 VFS 不會嘗試掛載任何內容,而是沿著已經存在的掛載向下進行。

此功能對於某些掛載陷阱的使用者(例如 NFS)來說已經足夠了,NFS 建立陷阱以便伺服器上的掛載點可以反映在客戶端上。 但是,對於 autofs 來說還不夠。 由於掛載到目錄被認為是“超出 stat”,因此 automount 守護程式將無法在“陷阱”目錄上掛載檔案系統,而沒有任何避免陷入陷阱的方法。 為此,還有另一個標誌。

DCACHE_MANAGE_TRANSIT

如果 dentry 設定了 DCACHE_MANAGE_TRANSIT,那麼將呼叫兩種非常不同但相關的行為,它們都使用 d_op->d_manage() dentry 操作。

首先,在檢查目錄上是否已掛載任何檔案系統之前,將使用設定為 falsercu_walk 引數呼叫 d_manage()。 它可能會返回以下三件事之一

  • 返回值為零表示此 dentry 沒有任何特殊之處,應進行掛載和自動掛載的正常檢查。

    autofs 通常返回零,但首先等待任何到期(自動解除安裝已掛載的檔案系統)完成。 這避免了競爭。

  • 返回值為 -EISDIR 告訴 VFS 忽略目錄上的任何掛載,並且不要考慮呼叫 ->d_automount()。 這有效地停用了 DCACHE_NEED_AUTOMOUNT 標誌,導致該目錄畢竟不是掛載陷阱。

    如果 autofs 檢測到執行查詢的程序是 automount 守護程式,並且已請求掛載但尚未完成,則 autofs 會返回此值。 如何確定這一點將在後面討論。 這允許 automount 守護程式不會陷入掛載陷阱。

    這裡有一個微妙之處。 可能在第一個 autofs 檔案系統下掛載第二個 autofs 檔案系統,並且它們都由同一個守護程式管理。 為了使守護程式能夠在第二個檔案系統上掛載某些內容,它必須能夠“向下走過”第一個檔案系統。 這意味著 d_manage 不能總是為 automount 守護程式返回 -EISDIR。 只有在已請求掛載但尚未完成時,它才必須返回此值。

    如果 dentry 不應該是掛載陷阱,則 d_manage 也會返回 -EISDIR,無論是由於它是符號連結還是由於它不為空。

  • 任何其他負值都將被視為錯誤並返回給呼叫者。

    autofs 可以返回

    • -ENOENT,如果 automount 守護程式未能掛載任何內容,

    • -ENOMEM,如果它耗盡了記憶體,

    • -EINTR,如果在等待到期完成時收到訊號

    • 或 automount 守護程式傳送下來的任何其他錯誤。

第二個用例僅在“RCU-walk”期間發生,因此將設定 rcu_walk

RCU-walk 是一個快速而輕量級的過程,用於向下走檔名路徑(即它就像踮著腳走路一樣)。 RCU-walk 無法應對所有情況,因此當它遇到困難時,它會回退到“REF-walk”,後者速度較慢但更可靠。

RCU-walk 永遠不會呼叫 ->d_automount;檔案系統必須已經掛載,否則 RCU-walk 無法處理該路徑。 為了確定掛載陷阱是否對於 RCU-walk 模式是安全的,它會呼叫設定為 true->d_manage()rcu_walk

在這種情況下,d_manage() 必須避免阻塞,並且應儘可能避免獲取自旋鎖。 它唯一目的是確定是否可以安全地向下進入任何已掛載的目錄,並且它可能不安全的原因是掛載的到期正在進行中。

rcu_walk 情況下,d_manage() 無法返回 -EISDIR 來告訴 VFS 這是一個不需要 d_automount 的目錄。 如果 rcu_walk 看到一個設定了 DCACHE_NEED_AUTOMOUNT 但沒有任何內容掛載的 dentry,它回退到 REF-walk。 d_manage() 無法使 VFS 保持在 RCU-walk 模式,但只能透過返回 -ECHILD 來告訴它退出 RCU-walk 模式。

因此,當設定了 rcu_walk 時,d_manage() 應在有任何理由認為進入已掛載的檔案系統不安全時返回 -ECHILD,否則應返回 0。

如果已啟動或正在考慮檔案系統的到期,則 autofs 將返回 -ECHILD,否則它將返回 0。

掛載點到期

VFS 具有自動使未使用的掛載過期的機制,就像它可以使 dcache 中任何未使用的 dentry 資訊過期一樣。 這受 MNT_SHRINKABLE 標誌的指導。 這僅適用於透過 d_automount() 返回要掛載的檔案系統建立的掛載。 由於 autofs 不返回這樣的檔案系統,而是將掛載留給 automount 守護程式,因此它也必須讓 automount 守護程式參與解除安裝。 這也意味著 autofs 對到期有更多的控制權。

VFS 還支援使用 MNT_EXPIRE 標誌對 umount 系統呼叫“到期”掛載。 除非之前已經嘗試過,並且自上次嘗試以來檔案系統一直處於非活動狀態且未被觸及,否則使用 MNT_EXPIRE 解除安裝將失敗。 autofs 不依賴於此,而是擁有自己的內部跟蹤檔案系統最近是否使用過。 這允許 autofs 目錄中的各個名稱單獨過期。

對於協議的第 4 版,automount 守護程式可以嘗試解除安裝掛載在 autofs 檔案系統上的任何檔案系統,或隨時刪除任何符號連結或空目錄。 如果解除安裝或刪除成功,檔案系統將返回到掛載或建立之前的狀態,因此對名稱的任何訪問都將觸發正常的自動掛載處理。 特別是,rmdirunlink 不會在 dcache 中留下負條目,就像普通檔案系統一樣,因此嘗試訪問最近刪除的物件會傳遞給 autofs 進行處理。

對於第 5 版,除了從頂層目錄解除安裝外,這不安全。 由於較低級別的目錄永遠不是掛載陷阱,因此一旦檔案系統解除安裝,其他程序將看到一個空目錄。 因此,通常最安全的方法是使用下面描述的 autofs 到期協議。

通常,守護程式只想刪除一段時間未使用的條目。 為此,autofs 在每個目錄或符號連結上維護一個“last_used”時間戳。 對於符號連結,它確實會記錄上次“使用”或遵循符號連結以查詢它指向的位置的時間。 對於目錄,該欄位的使用方式略有不同。 該欄位在掛載時以及在到期檢查期間(如果發現它正在使用中(即開啟檔案描述符或程序工作目錄)以及在路徑遍歷期間)進行更新。 在路徑遍歷期間完成的更新可防止頻繁到期和頻繁訪問的自動掛載的立即掛載。 但是,如果 GUI 不斷訪問或應用程式頻繁掃描 autofs 目錄樹,則可能會累積實際未使用的掛載。 為了滿足這種情況,可以使用“strictexpire”autofs 掛載選項來避免路徑遍歷時“last_used”更新,從而防止這種明顯無法使實際未使用的掛載過期的情況。

守護程式可以使用 ioctl 詢問 autofs 是否有任何內容即將到期,如稍後所述。 對於直接掛載,autofs 會考慮是否可以解除安裝整個掛載樹。 對於間接掛載,autofs 會考慮頂層目錄中的每個名稱,以確定是否可以解除安裝和清理其中的任何一個。

間接掛載有一個選項,可以考慮已掛載的每個葉子,而不是考慮頂層名稱。 這最初是為了與 autofs 的第 4 版相容,應被視為 Sun Format 自動掛載對映的已棄用選項。 但是,它可以再次用於 amd 格式的掛載對映(通常是間接對映),因為 amd 自動掛載器允許為單個掛載設定到期超時。 但是,在進行所需的更改方面存在一些困難。

當 autofs 考慮一個目錄時,它會檢查 last_used 時間,並將其與掛載檔案系統時設定的“超時”值進行比較,儘管在某些情況下會忽略此檢查。 它還會檢查目錄或其下方的任何內容是否正在使用中。 對於符號連結,僅考慮 last_used 時間。

如果兩者都似乎支援使目錄或符號連結過期,則會採取措施。

有兩種方法可以要求 autofs 考慮到期。 第一種是使用 AUTOFS_IOC_EXPIRE ioctl。 這僅適用於間接掛載。 如果它在根目錄中找到要過期的內容,它將返回該內容的名稱。 一旦返回一個名稱,automount 守護程式就需要正常解除安裝掛載在該名稱下的任何檔案系統。 如上所述,這對於版本 5 autofs 中的非頂層掛載是不安全的。 因此,當前的 automount(8) 不使用此 ioctl。

第二種機制使用 AUTOFS_DEV_IOCTL_EXPIRE_CMDAUTOFS_IOC_EXPIRE_MULTI ioctl。 這適用於直接掛載和間接掛載。 如果它選擇要過期的物件,它將使用下面描述的通知機制通知守護程式。 這將阻塞,直到守護程式確認到期通知。 這意味著“EXPIRE”ioctl 必須從與處理通知的執行緒不同的執行緒傳送。

在 ioctl 阻塞時,該條目被標記為“過期”,並且 d_manage 將阻塞,直到守護程式確認解除安裝已完成(以及刪除任何可能必要的目錄),或者已中止。

與 autofs 通訊:檢測守護程式

automount 守護程式和檔案系統之間存在幾種形式的通訊。 正如我們已經看到的,守護程式可以使用普通檔案系統操作建立和刪除目錄和符號連結。 autofs 根據程序的程序組 ID 號(參見 getpgid(1))來知道請求某些操作的程序是否為守護程式。

除非給出“pgrp=”選項,否則掛載 autofs 檔案系統時會記錄掛載程序的 pgid,在這種情況下會記錄該數字。 來自該程序組中的程序的任何請求都被認為來自守護程式。 如果守護程式必須停止並重新啟動,則可以透過 ioctl 提供新的 pgid,如下所述。

與 autofs 通訊:事件管道

掛載 autofs 檔案系統時,必須使用“fd=”掛載選項傳遞管道的“寫入”端。 autofs 會將通知訊息寫入此管道,供守護程式響應。 對於版本 5,訊息的格式為

struct autofs_v5_packet {
        struct autofs_packet_hdr hdr;
        autofs_wqt_t wait_queue_token;
        __u32 dev;
        __u64 ino;
        __u32 uid;
        __u32 gid;
        __u32 pid;
        __u32 tgid;
        __u32 len;
        char name[NAME_MAX+1];
};

標頭的格式為

struct autofs_packet_hdr {
        int proto_version;              /* Protocol version */
        int type;                       /* Type of packet */
};

其中型別是

autofs_ptype_missing_indirect
autofs_ptype_expire_indirect
autofs_ptype_missing_direct
autofs_ptype_expire_direct

因此,訊息可以指示名稱缺失(有人嘗試訪問它但它不存在)或已被選中用於到期。

管道將被設定為“資料包模式”(等效於傳遞 O_DIRECT)到 _pipe2(2)_,以便從管道讀取最多返回一個數據包,並且將丟棄資料包的任何未讀取部分。

wait_queue_token 是一個唯一數字,可以標識要確認的特定請求。 透過管道傳送訊息時,受影響的 dentry 將被標記為“活動”或“過期”,並且對它的其他訪問將阻塞,直到使用具有相關 wait_queue_token 的以下 ioctl 之一確認訊息。

與 autofs 通訊:根目錄 ioctl

autofs 檔案系統的根目錄將響應多個 ioctl。 發出 ioctl 的程序必須具有 CAP_SYS_ADMIN 功能,或者必須是 automount 守護程式。

可用的 ioctl 命令是

  • AUTOFS_IOC_READY:

    已處理通知。 ioctl 命令的引數是與正在確認的通知相對應的“wait_queue_token”數字。

  • AUTOFS_IOC_FAIL:

    類似於上面,但指示錯誤程式碼 ENOENT 失敗。

  • AUTOFS_IOC_CATATONIC:

    導致 autofs 進入“緊張”模式,這意味著它停止向守護程式傳送通知。 如果寫入管道失敗,也會進入此模式。

  • AUTOFS_IOC_PROTOVER:

    這將返回正在使用的協議版本。

  • AUTOFS_IOC_PROTOSUBVER:

    返回協議子版本,它實際上是實現的版本號。

  • AUTOFS_IOC_SETTIMEOUT:

    這會傳遞指向一個無符號長的指標。 該值用於設定到期超時,並且當前超時值將透過指標儲存回去。

  • AUTOFS_IOC_ASKUMOUNT:

    如果在指向的 int 中,檔案系統可以被解除安裝,則返回 1。 這只是一個提示,因為情況可能隨時發生變化。 此呼叫可用於避免更昂貴的完整解除安裝嘗試。

  • AUTOFS_IOC_EXPIRE:

    如上所述,這會詢問是否有什麼適合到期。 需要指向資料包的指標

    struct autofs_packet_expire_multi {
            struct autofs_packet_hdr hdr;
            autofs_wqt_t wait_queue_token;
            int len;
            char name[NAME_MAX+1];
    };
    

    用可以解除安裝或刪除的物件的名稱填充它。 如果沒有任何內容可以過期,則 errno 設定為 EAGAIN。 即使結構中存在 wait_queue_token,也不會建立“等待佇列”,也不需要確認。

  • AUTOFS_IOC_EXPIRE_MULTI:

    這類似於 AUTOFS_IOC_EXPIRE,只是它導致通知被髮送到守護程式,並且它會阻塞,直到守護程式確認。 該引數是一個整數,可以包含兩個不同的標誌。

    AUTOFS_EXP_IMMEDIATE 導致忽略 last_used 時間,並且如果物件未使用,則會使其過期。

    AUTOFS_EXP_FORCED 導致忽略正在使用狀態,並且即使物件正在使用中,也會使其過期。 這假定守護程式已請求這樣做,因為它能夠執行解除安裝。

    AUTOFS_EXP_LEAVES 將選擇一個葉子而不是頂層名稱使其過期。 只有當 maxproto 為 4 時,這才是安全的。

與 autofs 通訊:字元裝置 ioctl

並非總是可以開啟 autofs 檔案系統的根目錄,特別是直接掛載的檔案系統。 如果 automount 守護程式重新啟動,則無法使用上述任何通訊渠道重新獲得對現有掛載的控制權。 為了解決這一需求,有一個“雜項”字元裝置(主裝置號 10,次裝置號 235),可用於直接與 autofs 檔案系統通訊。 它需要 CAP_SYS_ADMIN 才能訪問。

可以在此裝置上使用的“ioctl”在單獨的文件 autofs 核心模組的雜項裝置控制操作 中進行了描述,並在此處進行了簡要總結。 每個 ioctl 都傳遞一個指向 autofs_dev_ioctl 結構的指標

struct autofs_dev_ioctl {
        __u32 ver_major;
        __u32 ver_minor;
        __u32 size;             /* total size of data passed in
                                 * including this struct */
        __s32 ioctlfd;          /* automount command fd */

        /* Command parameters */
        union {
                struct args_protover            protover;
                struct args_protosubver         protosubver;
                struct args_openmount           openmount;
                struct args_ready               ready;
                struct args_fail                fail;
                struct args_setpipefd           setpipefd;
                struct args_timeout             timeout;
                struct args_requester           requester;
                struct args_expire              expire;
                struct args_askumount           askumount;
                struct args_ismountpoint        ismountpoint;
        };

        char path[];
};

對於 OPEN_MOUNTIS_MOUNTPOINT 命令,目標檔案系統由 path 標識。 所有其他命令都透過 ioctlfd 標識檔案系統,ioctlfd 是在根目錄上開啟的檔案描述符,可以透過 OPEN_MOUNT 返回。

ver_majorver_minor 是輸入/輸出引數,用於檢查是否支援請求的版本,並報告核心模組可以支援的最大版本。

命令是

  • AUTOFS_DEV_IOCTL_VERSION_CMD:

    除了驗證和設定版本號之外,什麼也不做。

  • AUTOFS_DEV_IOCTL_OPENMOUNT_CMD:

    返回 autofs 檔案系統根目錄上的開啟的檔案描述符。 檔案系統按名稱和裝置號標識,該名稱和裝置號儲存在 openmount.devid 中。 現有檔案系統的裝置號可以在 /proc/self/mountinfo 中找到。

  • AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD:

    close(ioctlfd) 相同。

  • AUTOFS_DEV_IOCTL_SETPIPEFD_CMD:

    如果檔案系統處於緊張模式,這可以在 setpipefd.pipefd 中提供新管道的寫入端,以重新建立與守護程式的通訊。 呼叫程序的程序組用於標識守護程式。

  • AUTOFS_DEV_IOCTL_REQUESTER_CMD:

    path 應該是檔案系統中的一個名稱,該檔案系統已自動掛載。 成功返回後,requester.uidrequester.gid 將是觸發該掛載的程序的 UID 和 GID。

  • AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD:

    檢查路徑是否為特定型別的掛載點 - 有關詳細資訊,請參見單獨的文件。

  • AUTOFS_DEV_IOCTL_PROTOVER_CMD

  • AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD

  • AUTOFS_DEV_IOCTL_READY_CMD

  • AUTOFS_DEV_IOCTL_FAIL_CMD

  • AUTOFS_DEV_IOCTL_CATATONIC_CMD

  • AUTOFS_DEV_IOCTL_TIMEOUT_CMD

  • AUTOFS_DEV_IOCTL_EXPIRE_CMD

  • AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD

這些都具有與類似命名的 AUTOFS_IOC ioctl 相同的功能,只是 FAIL 可以在 fail.status 中給出顯式錯誤號,而不是假設 ENOENT,並且此 EXPIRE 命令對應於 AUTOFS_IOC_EXPIRE_MULTI

緊張模式

如前所述,autofs 掛載可以進入“緊張”模式。 如果寫入通知管道失敗,或者如果透過 ioctl 顯式請求,則會發生這種情況。

進入緊張模式時,管道會關閉,並且任何掛起的通知都會用錯誤 ENOENT 確認。

一旦進入緊張模式,嘗試訪問不存在的名稱將導致 ENOENT,而嘗試訪問現有目錄的處理方式與它們來自守護程式的方式相同,因此掛載陷阱不會觸發。

掛載檔案系統時,可以給出 _uid_ 和 _gid_,它們設定目錄和符號連結的所有權。 當檔案系統處於緊張模式時,任何具有匹配 UID 的程序都可以在根目錄中建立目錄或符號連結,但不能在其他目錄中建立。

只能透過 /dev/autofs 上的 AUTOFS_DEV_IOCTL_OPENMOUNT_CMD ioctl 離開緊張模式。

“忽略”掛載選項

可以使用“忽略”掛載選項嚮應用程式提供一個通用指示器,表明在顯示掛載資訊時應忽略掛載條目。

在提供 autofs 並且基於核心掛載列表向用戶空間提供掛載列表的其他作業系統中,允許使用空操作掛載選項(“忽略”是大多數常見作業系統上使用的選項),以便 autofs 檔案系統使用者可以選擇使用它。

這旨在供使用者空間程式使用,以便在讀取掛載列表時排除 autofs 掛載的考慮。

autofs、名稱空間和共享掛載

使用繫結掛載和名稱空間,autofs 檔案系統可能會出現在一個或多個檔案系統名稱空間中的多個位置。 為了使此操作合理地工作,應始終“共享”掛載 autofs 檔案系統。 例如

mount --make-shared /autofs/mount/point

automount 守護程式只能管理 autofs 檔案系統的單個掛載位置,並且如果該掛載不是“共享”的,則其他位置的行為將不如預期。 特別是,訪問那些其他位置可能會導致 ELOOP 錯誤

Too many levels of symbolic links