autofs - 工作原理 【ChatGPT】

发布时间 2023-12-10 20:34:20作者: 摩斯电码

autofs - 工作原理

目的

autofs 的目标是提供按需挂载和无竞争的自动卸载各种其他文件系统。这提供了两个关键优势:

  1. 无需延迟引导,直到所有可能需要的文件系统都被挂载。尝试访问这些慢文件系统的进程可能会延迟,但其他进程可以自由进行。这对于网络文件系统(例如 NFS)或存储在具有介质更换机器人的介质上的文件系统尤为重要。
  2. 文件系统的名称和位置可以存储在远程数据库中,并且随时可以更改。在访问时,将使用该数据中的内容来提供访问的目标。文件系统中名称的解释甚至可以是基于程序而不是基于数据库支持的,例如允许使用通配符,并且可以根据首次访问名称的用户而变化。

上下文

"autofs" 文件系统模块只是 autofs 系统的一部分。还需要一个用户空间程序来查找名称并挂载文件系统。这通常是 "automount" 程序,尽管其他工具包括 "systemd" 也可以使用 "autofs"。本文档仅描述了内核模块和与任何用户空间程序所需的交互。随后的文本将其称为 "automount 守护程序" 或简称为 "守护程序"。

"autofs" 是一个提供 "autofs" 文件系统类型的 Linux 内核模块。可以挂载多个 "autofs" 文件系统,它们可以分别进行管理,也可以由同一个守护程序进行统一管理。

内容

autofs 文件系统可以包含三种对象:目录、符号链接和挂载陷阱。挂载陷阱是具有下一节中描述的额外属性的目录。

只有 automount 守护程序可以创建对象:符号链接使用常规符号链接系统调用创建,而目录和挂载陷阱使用 mkdir 创建。确定目录是否应该是挂载陷阱是基于主映射的。autofs 通过主映射来确定哪些目录是挂载点。挂载点可以是直接的/间接的/偏移的。在大多数系统上,默认的主映射位于 /etc/auto.master。

如果既没有给出直接挂载选项也没有给出偏移挂载选项(因此将挂载视为间接挂载),那么根目录始终是一个常规目录,否则当它为空时它是一个挂载陷阱,当不为空时它是一个常规目录。请注意,直接和偏移被视为相同,因此简洁地总结是,只有当文件系统直接挂载且根目录为空时,根目录才是一个挂载陷阱。

在根目录中创建的目录只有在文件系统间接挂载且为空时才是挂载陷阱。

树中更深的目录取决于 maxproto 挂载选项,特别是它是否小于五。当 maxproto 为五时,树中更深的目录永远不是挂载陷阱,它们始终是常规目录。当 maxproto 为四(或三)时,只有当它们为空时,这些目录才是挂载陷阱。

因此:非空(即非叶子)目录永远不是挂载陷阱。空目录有时是挂载陷阱,有时不是,这取决于它们在树中的位置(根、顶层或更低)、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,它创建陷阱以便在服务器上反映客户端上的挂载点)是足够的。但对于 autofs 来说不够。由于将文件系统挂载到目录被视为 "超出 stat",automount 守护程序将无法在 '陷阱' 目录上挂载文件系统,除非有一种避免陷入陷阱的方法。为此,还有另一个标志。

DCACHE_MANAGE_TRANSIT

如果一个 dentry 设置了 DCACHE_MANAGE_TRANSIT,那么将调用两个非常不同但相关的行为,都使用 d_op->d_manage() dentry 操作。

首先,在检查目录是否有任何文件系统挂载之前,将使用 d_manage() 并将 rcu_walk 参数设置为 false。它可能返回以下三种情况之一:

  • 返回值为零表示该 dentry 没有任何特殊之处,应继续正常检查挂载和自动挂载。
    autofs 通常返回零,但首先等待任何到期(已挂载文件系统的自动卸载)完成。这避免了竞争。

  • 返回值为 -EISDIR 告诉 VFS 忽略目录上的任何挂载,并且不考虑调用 ->d_automount()。这实际上禁用了 DCACHE_NEED_AUTOMOUNT 标志,导致该目录最终不是挂载陷阱。

    autofs 如果检测到执行查找的进程是 automount 守护程序,并且已经请求了挂载但尚未完成,则返回 -EISDIR。它是如何确定这一点的将在后面讨论。这允许 automount 守护程序不陷入挂载陷阱。

    这里有一个微妙之处。可能会在第一个 autofs 文件系统下挂载第二个文件系统,并且两者都由同一个守护程序管理。为了使守护程序能够在第二个文件系统上挂载某些内容,它必须能够 "向下" 走过第一个文件系统。这意味着 d_manage 不能总是对 automount 守护程序返回 -EISDIR。它只能在已经请求了挂载但尚未完成时返回。

    d_manage 也会在 dentry 不应该是挂载陷阱时返回 -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 模式,它将使用 ->d_manage() 并将 rcu_walk 设置为 true。

在这种情况下,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,否则应返回零。

autofs 如果已启动或正在考虑启动文件系统的到期,则返回 -ECHILD,否则返回零。

挂载点过期

VFS(虚拟文件系统)具有自动过期未使用挂载点的机制,就像它可以从dcache中过期未使用的dentry信息一样。这由MNT_SHRINKABLE标志指导。这仅适用于由d_automount()创建并返回要挂载的文件系统的挂载点。由于autofs不返回此类文件系统,而是将挂载交给自动挂载守护程序,因此它也必须涉及自动挂载守护程序进行卸载。这也意味着autofs对过期有更多的控制权。

VFS还支持使用MNT_EXPIRE标志对挂载点进行“过期”处理,该标志用于umount系统调用。使用MNT_EXPIRE进行卸载将失败,除非先前尝试过,并且自那次尝试以来文件系统一直处于非活动和未更改状态。autofs不依赖于此,但它具有自己内部的跟踪,以确定文件系统是否最近被使用过。这允许autofs中的各个名称单独过期。

在协议的第4版中,自动挂载守护程序可以随时尝试卸载在autofs文件系统上挂载的任何文件系统,或者删除任何符号链接或空目录。如果卸载或删除成功,文件系统将恢复到挂载或创建之前的状态,因此对名称的任何访问都将触发正常的自动挂载处理。特别是,rmdir和unlink不会像正常文件系统那样在dcache中留下负条目,因此尝试访问最近删除的对象将传递给autofs进行处理。

在第5版中,除了从顶级目录卸载之外,这种操作是不安全的。由于较低级别的目录永远不会成为挂载陷阱,其他进程将在文件系统卸载后立即看到空目录。因此,通常最安全的做法是使用下面描述的autofs过期协议。

通常,守护程序只想删除一段时间未使用的条目。为此,autofs在每个目录或符号链接上维护一个“last_used”时间戳。对于符号链接,它确实记录了最后一次使用符号链接的时间,或者跟随符号链接以查找其指向的位置。对于目录,该字段的使用略有不同。该字段在挂载时进行更新,并在过期检查期间进行更新,如果发现正在使用(即打开的文件描述符或进程工作目录)和路径遍历期间。路径遍历期间进行的更新防止了频繁过期和频繁访问的自动挂载的立即挂载。但是,在GUI不断访问或应用程序频繁扫描autofs目录树的情况下,可能会积累一些实际上未被使用的挂载。为了满足这种情况,可以使用“strictexpire” autofs挂载选项,以避免在路径遍历中更新“last_used”,从而防止这种明显无法过期未真正使用的挂载。

守护程序能够询问autofs是否有任何需要过期的内容,使用稍后讨论的ioctl。对于直接挂载,autofs会考虑整个挂载树是否可以卸载。对于间接挂载,autofs会考虑顶级目录中的每个名称,以确定是否可以卸载和清理其中任何一个。

对于间接挂载,有一个选项可以考虑已挂载的每个叶子,而不是考虑顶级名称。最初,这是为了与autofs的第4版兼容而设计的,但对于Sun格式的自动挂载映射来说,应该被视为已弃用。但是,它可能再次用于amd格式的挂载映射(通常是间接映射),因为amd自动挂载程序允许为各个挂载设置过期超时。但是,在进行所需更改时存在一些困难。

当autofs考虑一个目录时,它会检查last_used时间并将其与文件系统挂载时设置的“timeout”值进行比较,尽管在某些情况下会忽略此检查。它还会检查目录或其下的任何内容是否正在使用。对于符号链接,只考虑last_used时间。

如果两者都支持过期目录或符号链接,则会执行操作。

有两种方法可以要求autofs考虑过期。第一种方法是使用AUTOFS_IOC_EXPIRE ioctl。这仅适用于间接挂载。如果它在根目录中找到要过期的内容,它将返回该内容的名称。一旦返回了名称,自动挂载守护程序需要正常卸载任何在该名称下挂载的文件系统。如上所述,在第5版autofs中,对于非顶级挂载来说,这是不安全的。因此,当前的automount(8)不使用此ioctl。

第二种机制使用AUTOFS_DEV_IOCTL_EXPIRE_CMD或AUTOFS_IOC_EXPIRE_MULTI ioctl。这对直接和间接挂载都适用。如果它选择要过期的对象,它将使用下面描述的通知机制通知守护程序。这将阻塞,直到守护程序确认过期通知。这意味着“EXPIRE”ioctl必须从处理通知的线程中发送,而不是从处理通知的线程中发送。

在ioctl阻塞时,条目被标记为“过期”,并且d_manage将阻塞,直到守护程序确认卸载已完成(同时删除可能需要的任何目录)或已中止。

与autofs通信:检测守护程序

自动挂载守护程序和文件系统之间有几种通信方式。正如我们已经看到的,守护程序可以使用正常的文件系统操作创建和删除目录和符号链接。根据进程组ID号(参见getpgid(1)),autofs可以确定请求某个操作的进程是否为守护程序。

当挂载autofs文件系统时,记录挂载进程的pgid,除非给出了“pgrp=”选项,在这种情况下,将记录该数字。来自该进程组的任何请求都被视为来自守护程序。如果必须停止并重新启动守护程序,可以通过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;              /* 协议版本 */
        int type;                       /* 数据包类型 */
};

其中类型是以下之一:

  • autofs_ptype_missing_indirect
  • autofs_ptype_expire_indirect
  • autofs_ptype_missing_direct
  • autofs_ptype_expire_direct

因此,消息可以指示名称丢失(尝试访问但不存在)或已选择过期。

管道将设置为“数据包模式”(相当于传递O_DIRECT)到_pipe2(2)_,以便从管道中读取最多一个数据包,并且将丢弃任何未读部分的数据包。

wait_queue_token是一个唯一的数字,可以标识特定的请求以进行确认。当通过管道发送消息时,受影响的dentry将被标记为“活动”或“即将过期”,并且对其的其他访问将被阻塞,直到使用以下其中一个ioctl与相关的wait_queue_token确认消息。

与autofs通信:根目录ioctls

autofs文件系统的根目录将响应多个ioctls。发出ioctl的进程必须具有CAP_SYS_ADMIN权限,或者必须是automount守护程序。

可用的ioctl命令包括:

  • AUTOFS_IOC_READY:已处理通知。ioctl命令的参数是与被确认的通知对应的“wait_queue_token”号码。

  • AUTOFS_IOC_FAIL:类似于上述,但指示失败,并带有错误代码ENOENT

  • AUTOFS_IOC_CATATONIC:导致autofs进入“catatonic”模式,意味着它停止向守护程序发送通知。如果写入管道失败,也会进入此模式。

  • 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通信:字符设备ioctls

并非总是可以打开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;             /* 传递的数据的总大小,包括此结构在内 */
        __s32 ioctlfd;          /* automount命令fd */

        /* 命令参数 */
        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命令,目标文件系统由路径标识。所有其他命令通过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:如果文件系统处于“catatonic”模式,则可以提供新管道的写端到setpipefd.pipefd,以重新与守护程序建立通信。调用进程的进程组用于标识守护程序。
  • AUTOFS_DEV_IOCTL_REQUESTER_CMD:路径应该是已自动挂载的文件系统中的名称。成功返回时,requester.uidrequester.gid将是触发该挂载的进程的UID和GID。
  • AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD:检查路径是否是特定类型的挂载点 - 有关详细信息,请参阅单独的文档。

Catatonic模式

如前所述,autofs挂载可以进入“catatonic”模式。如果写入通知管道失败,或者通过ioctl明确请求,就会发生这种情况。

进入catatonic模式时,管道将关闭,并且任何待处理的通知都将以错误ENOENT进行确认。

一旦进入catatonic模式,尝试访问不存在的名称将导致ENOENT,而尝试访问现有目录将被视为来自守护程序的访问,因此挂载陷阱将不会触发。

当挂载文件系统时,可以指定uidgid,以设置目录和符号链接的所有权。当文件系统处于catatonic模式时,具有匹配UID的任何进程都可以在根目录中创建目录或符号链接,但不能在其他目录中创建。

只能通过/dev/autofs上的AUTOFS_DEV_IOCTL_OPENMOUNT_CMDioctl离开catatonic模式。

“ignore”挂载选项

“ignore”挂载选项可用于向应用程序提供通用指示,指示在显示挂载信息时应忽略该挂载条目。

在提供autofs并基于内核挂载列表向用户空间提供挂载列表的其他操作系统中,允许使用无操作挂载选项(“ignore”是最常见的操作系统之一),以便autofs文件系统用户可以选择使用它。

这旨在供用户空间程序在读取挂载列表时排除autofs挂载使用。

autofs、名称空间和共享挂载

通过绑定挂载和名称空间,autofs文件系统可以在一个或多个文件系统名称空间中的多个位置出现。为了使其正常工作,autofs文件系统应始终以“共享”方式挂载。例如:

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

automount守护程序只能管理autofs文件系统的单个挂载位置,如果在该位置上的挂载不是“共享”的,其他位置将无法按预期方式工作。特别是对这些其他位置的访问可能会导致ELOOP错误

	太多的符号链接级别。