# linux-mount-01-mountflags传参

# mount

mount2 介绍了mount调用的定义

NAME         top
       mount - mount filesystem
SYNOPSIS         top
       #include <sys/mount.h>

       int mount(const char *source, const char *target,
                 const char *filesystemtype, unsigned long mountflags,
                 const void *data);

关键参数有source,target,filesystemtype,mountflags,data

假设有个分区 /dev/sdb1, 如果我们要把它挂载到/mnt/test,并且传参nodev,noquotanosuid,则可执行如下命令:

mount /dev/sdb1 /mnt/test -o nodev,noquota,nosuid

看下mount结果,可以看到nodev,noquota和nosuid都生效了:

#mount | grep test
/dev/sdb1 on /mnt/test type xfs (rw,nosuid,nodev,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)

重新mount,通过starce来跟踪看下

# umount /mnt/test
# strace mount /dev/sdb1 /mnt/test -o nodev,noquota,nosuid 2>&1 | grep '/mnt/test'
execve("/usr/bin/mount", ["mount", "/dev/sdb1", "/mnt/test", "-o", "nodev,noquota,nosuid"], 0x7ffc9b571e00 /* 19 vars */) = 0
lstat("/mnt/test", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
mount("/dev/sdb1", "/mnt/test", "xfs", MS_NOSUID|MS_NODEV, "noquota") = 0

可以看到在调用mount方法时,传参为:

mount("/dev/sdb1", "/mnt/test", "xfs", MS_NOSUID|MS_NODEV, "noquota") = 0

这里的MS_NOSUID|MS_NODEV就是mount参数里的mountflags,使用了位运算的方式来传参,挺有意思,我们就来看看mountflags的传参吧。

# 位运算

在开始之前复习下位运算

运算符 描述 例子
& 与运算,两值同为1,结果为1,否则为0 A=0,B=1, A&B=0
| 或运算,只要有一个值为1,结果为1 A=0,B=1, A|B=1
^ 异或运算,如果a、b两个值相同,结果为0。如果a、b两个值不同,结果为1 A=0,B=1, A^B=1
~ 取反 A=1, ~A=-1
<< 左移, 向左移动 A=1, A<<2=4
>> 右移, 向右移动 A=3, A>>1=1

举个例子,假设有两个数p=23和q=15,对应的二进制为:

p = 10111
q = 1111

p | q 为:

10111 | 01111

每一位一一对应,做|运算,得到

11111

结果为31

我们用代码验证下

>>> p = 23
>>> q = 15
>>> p | q
31

可以看到结果相等。

# mountflags

有了位运算的基础,我们就可以来看下mountflags的传参了,并体会到这样传参的好处。

/usr/include/linux/mount.h里可以看到mountflags的定义

/*
 * These are the fs-independent mount-flags: up to 32 flags are supported
 *
 * Usage of these is restricted within the kernel to core mount(2) code and
 * callers of sys_mount() only.  Filesystems should be using the SB_*
 * equivalent instead.
 */
#define MS_RDONLY	 1	/* Mount read-only */
#define MS_NOSUID	 2	/* Ignore suid and sgid bits */
#define MS_NODEV	 4	/* Disallow access to device special files */
#define MS_NOEXEC	 8	/* Disallow program execution */
#define MS_SYNCHRONOUS	16	/* Writes are synced at once */
#define MS_REMOUNT	32	/* Alter flags of a mounted FS */
#define MS_MANDLOCK	64	/* Allow mandatory locks on an FS */
#define MS_DIRSYNC	128	/* Directory modifications are synchronous */
#define MS_NOATIME	1024	/* Do not update access times. */
#define MS_NODIRATIME	2048	/* Do not update directory access times */
#define MS_BIND		4096
#define MS_MOVE		8192
#define MS_REC		16384
#define MS_VERBOSE	32768	/* War is peace. Verbosity is silence.
				   MS_VERBOSE is deprecated. */
#define MS_SILENT	32768
#define MS_POSIXACL	(1<<16)	/* VFS does not apply the umask */
#define MS_UNBINDABLE	(1<<17)	/* change to unbindable */
#define MS_PRIVATE	(1<<18)	/* change to private */
#define MS_SLAVE	(1<<19)	/* change to slave */
#define MS_SHARED	(1<<20)	/* change to shared */
#define MS_RELATIME	(1<<21)	/* Update atime relative to mtime/ctime. */
#define MS_KERNMOUNT	(1<<22) /* this is a kern_mount call */
#define MS_I_VERSION	(1<<23) /* Update inode I_version field */
#define MS_STRICTATIME	(1<<24) /* Always perform atime updates */
#define MS_LAZYTIME	(1<<25) /* Update the on-disk [acm]times lazily */

/* These sb flags are internal to the kernel */
#define MS_SUBMOUNT     (1<<26)
#define MS_NOREMOTELOCK	(1<<27)
#define MS_NOSEC	(1<<28)
#define MS_BORN		(1<<29)
#define MS_ACTIVE	(1<<30)
#define MS_NOUSER	(1<<31)

/*
 * Superblock flags that can be altered by MS_REMOUNT
 */
#define MS_RMT_MASK	(MS_RDONLY|MS_SYNCHRONOUS|MS_MANDLOCK|MS_I_VERSION|\
			 MS_LAZYTIME)

可以看到里面的参数比较多,如果按照普通的传参方式,我们可能会这样做:

// 定义个函数,并定义几个参数
function mount(MS_RDONLY,MS_NOSUID,MS_NODEV,MS_NOEXEC){
    // 逻辑代码里一个个去判断,做计算
    if(MS_RDONLY){
        // ...
    }
    if(MS_NOSUID){
        // ...
    }
    if(MS_NODEV){
        // ...
    }
    if(MS_NOEXEC){
        // ...
    }
}

初看好像没啥问题,但如果参数变多了,且调用函数的地方多了之后,这样的方式就会变得难以维护。 通过看Linux内核代码,我们会发现使用标志位操作就是解决这个问题的一个很好的方式。 看下Linux内核(5.17)里path_mount

/*
 * Flags is a 32-bit value that allows up to 31 non-fs dependent flags to
 * be given to the mount() call (ie: read-only, no-dev, no-suid etc).
 *
 * data is a (void *) that can point to any structure up to
 * PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent
 * information (or be NULL).
 *
 * Pre-0.97 versions of mount() didn't have a flags word.
 * When the flags word was introduced its top half was required
 * to have the magic value 0xC0ED, and this remained so until 2.4.0-test9.
 * Therefore, if this magic number is present, it carries no information
 * and must be discarded.
 */
int path_mount(const char *dev_name, struct path *path,
		const char *type_page, unsigned long flags, void *data_page)
{
	unsigned int mnt_flags = 0, sb_flags;
	int ret;

	/* Discard magic */
	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
		flags &= ~MS_MGC_MSK;

	/* Basic sanity checks */
	if (data_page)
		((char *)data_page)[PAGE_SIZE - 1] = 0;

	if (flags & MS_NOUSER)
		return -EINVAL;

	ret = security_sb_mount(dev_name, path, type_page, flags, data_page);
	if (ret)
		return ret;
	if (!may_mount())
		return -EPERM;
	if (flags & SB_MANDLOCK)
		warn_mandlock();

	/* Default to relatime unless overriden */
	if (!(flags & MS_NOATIME))
		mnt_flags |= MNT_RELATIME;

	/* Separate the per-mountpoint flags */
	if (flags & MS_NOSUID)
		mnt_flags |= MNT_NOSUID;
	if (flags & MS_NODEV)
		mnt_flags |= MNT_NODEV;
	if (flags & MS_NOEXEC)
		mnt_flags |= MNT_NOEXEC;
	if (flags & MS_NOATIME)
		mnt_flags |= MNT_NOATIME;
	if (flags & MS_NODIRATIME)
		mnt_flags |= MNT_NODIRATIME;
	if (flags & MS_STRICTATIME)
		mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
	if (flags & MS_RDONLY)
		mnt_flags |= MNT_READONLY;
	if (flags & MS_NOSYMFOLLOW)
		mnt_flags |= MNT_NOSYMFOLLOW;

	/* The default atime for remount is preservation */
	if ((flags & MS_REMOUNT) &&
	    ((flags & (MS_NOATIME | MS_NODIRATIME | MS_RELATIME |
		       MS_STRICTATIME)) == 0)) {
		mnt_flags &= ~MNT_ATIME_MASK;
		mnt_flags |= path->mnt->mnt_flags & MNT_ATIME_MASK;
	}

	sb_flags = flags & (SB_RDONLY |
			    SB_SYNCHRONOUS |
			    SB_MANDLOCK |
			    SB_DIRSYNC |
			    SB_SILENT |
			    SB_POSIXACL |
			    SB_LAZYTIME |
			    SB_I_VERSION);

	if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND))
		return do_reconfigure_mnt(path, mnt_flags);
	if (flags & MS_REMOUNT)
		return do_remount(path, flags, sb_flags, mnt_flags, data_page);
	if (flags & MS_BIND)
		return do_loopback(path, dev_name, flags & MS_REC);
	if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
		return do_change_type(path, flags);
	if (flags & MS_MOVE)
		return do_move_mount_old(path, dev_name);

	return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name,
			    data_page);
}

可以看到里面有大量的位运算来判断传参,比如判断MS_NOSUID参数:

if (flags & MS_NOSUID)
		mnt_flags |= MNT_NOSUID;

传参时我们传的是MS_NOSUID|MS_NODEV,所以这里判断MS_NOSUID时实际上是:

(MS_NOSUID|MS_NODEV) & MS_NOSUID

可以说是非常的优雅了。

今天的介绍就到这里了,感谢阅读。