📒 内核源码:NFS¶
NFS 标准规范¶
Tip
在阅读 NFS RFC 前,最好对 XDR(RFC 4506)和 RPC(RFC 5531)有所了解。
内核源码¶
mount¶
mount 工具属于 util-linux,让我们来看看它是如何挂载文件系统的。
mount.c 中的 main() 函数:
struct libmnt_context *cxt;
mnt_context_set_source(cxt, argv[0]);
mnt_context_set_target(cxt, argv[1]);
rc = mnt_context_mount(cxt);
通过各种方法获取挂载信息 cxt,然后调用 mnt_context_mount() 函数进行挂载。
结构体 struct libmnt_context 定义在 libmount/src/mountP.h 中,包含各种挂载所需的信息,如:
/*
 * Mount context -- high-level API
 */
struct libmnt_context
{
 int action;  /* MNT_ACT_{MOUNT,UMOUNT} */
 int restricted; /* root or not? */
 char *fstype_pattern; /* for mnt_match_fstype() */
 char *optstr_pattern; /* for mnt_match_options() */
 struct libmnt_fs *fs;  /* filesystem description (type, mountpoint, device, ...) */
// ...
};
嵌套在其中的 struct libmnt_fs 结构体包含重要信息,定义在同一个文件中:
/*
 * This struct represents one entry in a fstab/mountinfo file.
 * (note that fstab[1] means the first column from fstab, and so on...)
 */
struct libmnt_fs {
 struct list_head ents;
 struct libmnt_table *tab;
 int  refcount; /* reference counter */
 unsigned int opts_age; /* to sync with optlist */
 struct libmnt_optlist *optlist;
 int  id;  /* mountinfo[1]: ID */
 int  parent;  /* mountinfo[2]: parent */
 dev_t  devno;  /* mountinfo[3]: st_dev */
 char  *bindsrc; /* utab, full path from fstab[1] for bind mounts */
 char  *source; /* fstab[1], mountinfo[10], swaps[1]:
                                         * source dev, file, dir or TAG */
 char  *tagname; /* fstab[1]: tag name - "LABEL", "UUID", ..*/
 char  *tagval; /*           tag value */
//...
};
来到 mnt_context_mount() 函数:
/**
 * mnt_context_mount:
 * @cxt: mount context
 *
 * High-level, mounts the filesystem by mount(2) or fork()+exec(/sbin/mount.type).
 * ...
 */
注释已经解释了这个函数的作用。从这里开始,函数调用链为:
mnt_context_do_mount():决定具体使用哪种方式挂载。do_mount_by_types()、do_mount_by_pattern()或do_mount():根据cxt中的信息采用不同的方式进行挂载。前两者会用不同的逻辑尝试文件系统,最终将挂载任务转交到do_mount()完成。exec_helper():fork()并构造参数,用execv()执行mount.type(由mnt_context_prepare_helper()从PATH中找到)。
mount.nfs¶
mount.nfs 是 NFS 的挂载工具,它是 nfs-utils 的一部分。早期 mount.nfs4 是独立的,但现在 mount.nfs 可以挂载任何版本的 NFS。
nfs-utils/utils/mount/mount.c 是 mount.nfs 的源码,它的 main() 函数与 mount 类似,但只负责 NFS 相关的逻辑。其中 try_mount() 根据 string 和 fs_type 决定具体采用哪个挂载函数,有可能为以下三种之一:
nfsmount_string()- 当 
string被设置时使用该方式。从目前的源码来看,Linux 2.6.3 以上都不会使用该方式。 
- 当 
 nfsmount()nfs4mount()- 只有在程序名为 
mount.nfs4时才会被调用。也就是说,一般情况下(包括使用mount命令挂载 NFSv4)都不是nfs4mount()。 
- 只有在程序名为 
 
nfs4mount.c 中的内容
嗯,我想我大概明白了……mount.nfs4 可能是当年变化太大临时弄出来兼容的,后来 mount.nfs 把 NFSv4.x 都实现了,但 mount.nfs4 还保留着。可以看到 mount.nfs 的内容比 mount.nfs4 丰富很多。
读都读了,还是记一下吧:
if (parse_devname(hostdir, &hostname, &dirname))
goto fail;
if (fill_ipv4_sockaddr(hostname, &server_addr))
goto fail;
if (get_my_ipv4addr(ip_addr, sizeof(ip_addr)))
goto fail;
fill_ipv4_sockaddr() 将 NFS Server 的 IP 地址填入 sockaddr_in 结构体。如果提供的不是 IP 地址,那么会调用库函数 gethostbyname() 函数获取 IP 地址。我找到了一例该库函数的实现,它将调用库函数 res_search() 进行 DNS 查询。
get_my_ipv4addr() 使用 gethostname() 获取本机主机名,然后将该主机名转交给 fill_ipv4_sockaddr() 获取本机 IP 地址。
接下来我们看 nfs-utils/utils/mount/nfsmount.c 中的 nfsmount() 函数,从这里开始就涉及很多网络栈的内容了。