Linux中系统调用sys_mount函数的实现

挂载文件系统的系统调用sys_mount

c 复制代码
asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
			  char __user * type, unsigned long flags,
			  void __user * data)
{
	int retval;
	unsigned long data_page;
	unsigned long type_page;
	unsigned long dev_page;
	char *dir_page;

	retval = copy_mount_options (type, &type_page);
	if (retval < 0)
		return retval;

	dir_page = getname(dir_name);
	retval = PTR_ERR(dir_page);
	if (IS_ERR(dir_page))
		goto out1;

	retval = copy_mount_options (dev_name, &dev_page);
	if (retval < 0)
		goto out2;

	retval = copy_mount_options (data, &data_page);
	if (retval < 0)
		goto out3;

	lock_kernel();
	retval = do_mount((char*)dev_page, dir_page, (char*)type_page,
			  flags, (void*)data_page);
	unlock_kernel();
	free_page(data_page);

out3:
	free_page(dev_page);
out2:
	putname(dir_page);
out1:
	free_page(type_page);
	return retval;
}

函数功能概述

sys_mount 是 Linux 内核中用于挂载文件系统的系统调用,负责将存储设备(如硬盘分区)挂载到指定的目录位置

代码详细分析

变量声明部分

c 复制代码
asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
			  char __user * type, unsigned long flags,
			  void __user * data)
{
	int retval;
	unsigned long data_page;
	unsigned long type_page;
	unsigned long dev_page;
	char *dir_page;
  • asmlinkage: 告诉编译器参数通过栈传递,这是系统调用的标准调用约定
  • 参数说明 :
    • dev_name: 设备文件名(如 "/dev/sda1"),来自用户空间
    • dir_name: 挂载目标目录(如 "/mnt"),来自用户空间
    • type: 文件系统类型(如 "ext4"、"devfs"),来自用户空间
    • flags: 挂载标志位,控制挂载行为
    • data: 文件系统特定的选项数据
  • 局部变量 :
    • retval: 存储函数执行结果
    • *_page: 用于存储从用户空间复制的数据的内核页面指针

复制文件系统类型参数

c 复制代码
	retval = copy_mount_options (type, &type_page);
	if (retval < 0)
		return retval;
  • copy_mount_options(): 将用户空间的字符串数据安全地复制到内核空间
  • 如果复制失败(返回负值),立即返回错误码
  • type_page 现在包含内核空间中的文件系统类型字符串

获取挂载目录路径

c 复制代码
	dir_page = getname(dir_name);
	retval = PTR_ERR(dir_page);
	if (IS_ERR(dir_page))
		goto out1;
  • getname(): 专门用于获取路径名的函数,处理用户空间到内核空间的路径复制
  • IS_ERR(): 检查指针是否包含错误码
  • 如果出错,跳转到 out1 标签进行清理

复制设备名称参数

c 复制代码
	retval = copy_mount_options (dev_name, &dev_page);
	if (retval < 0)
		goto out2;
  • 同样使用 copy_mount_options 复制设备名称
  • 如果失败,跳转到 out2 清理之前分配的资源

复制挂载选项数据

c 复制代码
	retval = copy_mount_options (data, &data_page);
	if (retval < 0)
		goto out3;
  • 复制文件系统特定的挂载选项数据
  • 如果失败,跳转到 out3 进行资源清理

执行挂载操作

c 复制代码
	lock_kernel();
	retval = do_mount((char*)dev_page, dir_page, (char*)type_page,
			  flags, (void*)data_page);
	unlock_kernel();
	free_page(data_page);
  • lock_kernel(): 获取大内核锁,确保操作原子性
  • do_mount(): 执行实际的挂载操作的核心函数
  • unlock_kernel(): 释放内核锁
  • free_page(data_page): 立即释放数据页面,因为不再需要

错误处理与资源清理

c 复制代码
out3:
	free_page(dev_page);
out2:
	putname(dir_page);
out1:
	free_page(type_page);
	return retval;
}
  • 分层清理机制 :
    • out3: 释放设备名称页面
    • out2: 释放目录路径名称 (putname 专门用于路径名清理)
    • out1: 释放文件系统类型页面
  • 最终返回操作结果 retval

关键设计特点

  1. 分层错误处理 : 使用 goto 实现清晰的资源清理路径
  2. 安全复制: 所有用户空间数据都通过安全函数复制到内核空间
  3. 原子性保护: 使用内核锁保护核心挂载操作
  4. 资源管理: 及时释放不再需要的资源,避免内存泄漏
  5. 错误传播: 保持错误码的传递,确保调用方能了解失败原因

从用户空间安全地复制挂载选项数据到内核空间copy_mount_options

c 复制代码
int copy_mount_options(const void __user *data, unsigned long *where)
{
	int i;
	unsigned long page;
	unsigned long size;
	
	*where = 0;
	if (!data)
		return 0;

	if (!(page = __get_free_page(GFP_KERNEL)))
		return -ENOMEM;

	/* We only care that *some* data at the address the user
	 * gave us is valid.  Just in case, we'll zero
	 * the remainder of the page.
	 */
	/* copy_from_user cannot cross TASK_SIZE ! */
	size = TASK_SIZE - (unsigned long)data;
	if (size > PAGE_SIZE)
		size = PAGE_SIZE;

	i = size - exact_copy_from_user((void *)page, data, size);
	if (!i) {
		free_page(page); 
		return -EFAULT;
	}
	if (i != PAGE_SIZE)
		memset((char *)page + i, 0, PAGE_SIZE - i);
	*where = page;
	return 0;
}
static long
exact_copy_from_user(void *to, const void __user *from, unsigned long n)
{
	char *t = to;
	const char __user *f = from;
	char c;

	if (!access_ok(VERIFY_READ, from, n))
		return n;

	while (n) {
		if (__get_user(c, f)) {
			memset(t, 0, n);
			break;
		}
		*t++ = c;
		f++;
		n--;
	}
	return n;
}

函数功能概述

copy_mount_options 用于从用户空间安全地复制挂载选项数据到内核空间,是 sys_mount 系统调用的关键辅助函数

copy_mount_options 详细分析

函数声明和变量定义

c 复制代码
int copy_mount_options(const void __user *data, unsigned long *where)
{
	int i;
	unsigned long page;
	unsigned long size;
  • 参数 :
    • data: 用户空间的挂载选项数据指针
    • where: 输出参数,存储分配的内核页面地址
  • 局部变量 :
    • i: 实际成功复制的字节数
    • page: 分配的内核页面地址
    • size: 要复制的数据大小

初始化和空指针检查

c 复制代码
	*where = 0;
	if (!data)
		return 0;
  • 初始化输出 : *where = 0 确保调用方总能获得有效值
  • 空指针检查: 如果用户没有提供数据,直接返回成功(0)
  • 设计考虑: 挂载选项是可选的,允许为空

内存分配

c 复制代码
	if (!(page = __get_free_page(GFP_KERNEL)))
		return -ENOMEM;
  • __get_free_page(GFP_KERNEL): 分配一个完整的内存页(通常4KB)
  • GFP_KERNEL: 标准内核内存分配标志,允许睡眠
  • 错误处理 : 如果分配失败,返回 -ENOMEM(内存不足)

计算复制大小

c 复制代码
	/* We only care that *some* data at the address the user
	 * gave us is valid.  Just in case, we'll zero
	 * the remainder of the page.
	 */
	/* copy_from_user cannot cross TASK_SIZE ! */
	size = TASK_SIZE - (unsigned long)data;
	if (size > PAGE_SIZE)
		size = PAGE_SIZE;
  • copy_from_user 不能跨越 TASK_SIZE 边界

  • 边界计算:

    • size = TASK_SIZE - (unsigned long)data: 计算从当前地址到用户空间末尾的距离
    • if (size > PAGE_SIZE) size = PAGE_SIZE: 限制最大复制大小为1页

执行复制操作

c 复制代码
	i = size - exact_copy_from_user((void *)page, data, size);
	if (!i) {
		free_page(page); 
		return -EFAULT;
	}
  • 复制调用 : exact_copy_from_user((void *)page, data, size)
    • 返回未能成功复制的剩余字节数
    • i = size - 剩余字节数 = 实际成功复制的字节数
  • 错误检查 :
    • 如果 i == 0(没有复制任何数据)
    • 释放分配的页面
    • 返回 -EFAULT(错误的地址)

清零剩余空间和返回成功

c 复制代码
	if (i != PAGE_SIZE)
		memset((char *)page + i, 0, PAGE_SIZE - i);
	*where = page;
	return 0;
}
  • 清零操作: 如果复制的数据不满一页,将剩余部分清零
  • 设置输出 : *where = page 将分配的页面地址返回给调用方
  • 成功返回: 返回 0 表示成功

exact_copy_from_user 详细分析

函数声明和变量定义

c 复制代码
static long
exact_copy_from_user(void *to, const void __user *from, unsigned long n)
{
	char *t = to;
	const char __user *f = from;
	char c;
  • 参数 :
    • to: 目标内核缓冲区
    • from: 源用户空间地址
    • n: 要复制的字节数
  • 局部变量 :
    • t: 指向目标缓冲区的字符指针
    • f: 指向源地址的字符指针
    • c: 临时存储单个字符

地址有效性检查

c 复制代码
	if (!access_ok(VERIFY_READ, from, n))
		return n;
  • access_ok(VERIFY_READ, from, n) : 检查用户地址是否可读
    • 用户地址是否溢出
    • 用户地址是否超出用户空间地址限制
  • 错误处理 : 如果地址无效,返回 n(表示所有字节都未能复制)

逐字节复制循环

c 复制代码
	while (n) {
		if (__get_user(c, f)) {
			memset(t, 0, n);
			break;
		}
		*t++ = c;
		f++;
		n--;
	}
	return n;
}
  • 循环条件 : while (n) 直到复制完所有字节
  • __get_user(c, f) :
    • 安全地从用户空间读取一个字节
    • 如果读取失败,返回非零值
    • 这里内核通过一个局部变量c来接收用户空间地址的内容,确保不会因为内核刚刚分配的页面复制时发生缺页错误而导致从用户空间复制失败
  • 错误处理 :
    • 如果读取失败,将剩余的目标缓冲区清零
    • 跳出循环
    • 用户空间地址可能已经回收等
  • 正常复制 :
    • *t++ = c: 将读取的字节存储到内核缓冲区
    • f++: 移动到下一个源地址
    • n--: 递减剩余计数
  • 返回值: 返回未能成功复制的剩余字节数

关键设计特点

安全复制策略

c 复制代码
// 逐字节复制,遇到错误立即停止
while (n) {
    if (__get_user(c, f)) {
        memset(t, 0, n);  // 清零已部分复制的数据
        break;
    }
    // ... 复制逻辑
}

边界保护

c 复制代码
// 防止跨越用户空间边界
size = TASK_SIZE - (unsigned long)data;
if (size > PAGE_SIZE)
    size = PAGE_SIZE;

挂载文件系统的核心实现do_mount

c 复制代码
long do_mount(char * dev_name, char * dir_name, char *type_page,
		  unsigned long flags, void *data_page)
{
	struct nameidata nd;
	int retval = 0;
	int mnt_flags = 0;

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

	/* Basic sanity checks */

	if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
		return -EINVAL;
	if (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
		return -EINVAL;

	if (data_page)
		((char *)data_page)[PAGE_SIZE - 1] = 0;

	/* 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;
	flags &= ~(MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_ACTIVE);

	/* ... and get the mountpoint */
	retval = path_lookup(dir_name, LOOKUP_FOLLOW, &nd);
	if (retval)
		return retval;

	retval = security_sb_mount(dev_name, &nd, type_page, flags, data_page);
	if (retval)
		goto dput_out;

	if (flags & MS_REMOUNT)
		retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags,
				    data_page);
	else if (flags & MS_BIND)
		retval = do_loopback(&nd, dev_name, flags & MS_REC);
	else if (flags & MS_MOVE)
		retval = do_move_mount(&nd, dev_name);
	else
		retval = do_new_mount(&nd, type_page, flags, mnt_flags,
				      dev_name, data_page);
dput_out:
	path_release(&nd);
	return retval;
}

函数功能概述

do_mount 是挂载文件系统的核心实现函数,处理所有类型的挂载操作(新建挂载、重新挂载、绑定挂载、移动挂载等)

代码详细分析

函数声明和变量定义

c 复制代码
long do_mount(char * dev_name, char * dir_name, char *type_page,
		  unsigned long flags, void *data_page)
{
	struct nameidata nd;
	int retval = 0;
	int mnt_flags = 0;
  • 参数 :
    • dev_name: 设备名(如 "/dev/sda1")
    • dir_name: 挂载目标目录
    • type_page: 文件系统类型(如 "ext4")
    • flags: 挂载标志位
    • data_page: 文件系统特定的选项数据
  • 局部变量 :
    • nd: 名称查找数据结构,用于解析路径名
    • retval: 操作返回值
    • mnt_flags: 转换后的挂载点标志

魔数标志处理

c 复制代码
	/* Discard magic */
	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
		flags &= ~MS_MGC_MSK;
  • 历史遗留 : MS_MGC_VAL 是旧的魔数值,用于标识挂载调用
  • 清除操作: 如果设置了魔数标志,将其从 flags 中移除
  • 向后兼容: 保持与旧版本用户空间的兼容性

基本完整性检查

c 复制代码
	/* Basic sanity checks */

	if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
		return -EINVAL;
	if (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
		return -EINVAL;
  • 目录名检查 :
    • !dir_name: 指针非空
    • !*dir_name: 字符串非空(第一个字符不是 '\0')
    • !memchr(dir_name, 0, PAGE_SIZE): 在 PAGE_SIZE 范围内必须包含终止符 '\0'
  • 设备名检查: 如果提供了设备名,同样检查终止符
  • 错误返回 : 如果检查失败,返回 -EINVAL(无效参数)

数据页终止符确保

c 复制代码
	if (data_page)
		((char *)data_page)[PAGE_SIZE - 1] = 0;
  • 安全措施: 确保数据页的最后一个字节是终止符
  • 防止越界: 即使数据没有正确终止,也强制添加终止符
  • 防御性编程: 防止字符串操作越界

挂载点标志分离

c 复制代码
	/* 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;
	flags &= ~(MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_ACTIVE);
  • 标志转换 : 将用户空间的挂载标志转换为内核的挂载点标志
    • MS_NOSUIDMNT_NOSUID: 忽略 suid 权限
    • MS_NODEVMNT_NODEV: 禁止访问设备文件
    • MS_NOEXECMNT_NOEXEC: 禁止执行程序
  • 清除标志: 从原始 flags 中移除这些已处理的标志
  • 设计目的: 分离不同层次的标志,便于后续处理

挂载点路径查找

c 复制代码
	/* ... and get the mountpoint */
	retval = path_lookup(dir_name, LOOKUP_FOLLOW, &nd);
	if (retval)
		return retval;
  • path_lookup: 解析路径名到内核的内部表示
  • LOOKUP_FOLLOW: 如果路径是符号链接,则跟踪它
  • 错误处理: 如果路径查找失败,直接返回错误码
  • 结果存储 : 成功时,nd 包含挂载点的目录项和挂载信息

安全模块检查

c 复制代码
	retval = security_sb_mount(dev_name, &nd, type_page, flags, data_page);
	if (retval)
		goto dput_out;
  • security_sb_mount: Linux 安全模块(LSM)的挂载安全检查
  • 模块化安全 : 允许 SELinuxAppArmor 等安全模块实施策略
  • 错误处理: 如果安全检查失败,跳转到清理代码

挂载类型分发

c 复制代码
	if (flags & MS_REMOUNT)
		retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags,
				    data_page);
	else if (flags & MS_BIND)
		retval = do_loopback(&nd, dev_name, flags & MS_REC);
	else if (flags & MS_MOVE)
		retval = do_move_mount(&nd, dev_name);
	else
		retval = do_new_mount(&nd, type_page, flags, mnt_flags,
				      dev_name, data_page);

这是函数的核心分发逻辑:

重新挂载 (MS_REMOUNT)

c 复制代码
retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags, data_page);
  • 修改已挂载文件系统的选项
  • 清除 MS_REMOUNT 标志后传递给具体实现

绑定挂载 (MS_BIND)

c 复制代码
retval = do_loopback(&nd, dev_name, flags & MS_REC);
  • 创建一个目录的镜像挂载
  • MS_REC 表示递归绑定挂载

移动挂载 (MS_MOVE)

c 复制代码
retval = do_move_mount(&nd, dev_name);
  • 将现有挂载点移动到新位置

新建挂载 (默认情况)

c 复制代码
retval = do_new_mount(&nd, type_page, flags, mnt_flags, dev_name, data_page);
  • 创建全新的文件系统挂载

清理和返回

c 复制代码
dput_out:
	path_release(&nd);
	return retval;
}
  • 标签 : dput_out 用于错误处理的跳转目标
  • path_release: 释放路径查找时获取的引用计数
  • 返回结果: 返回具体挂载操作的结果

关键设计特点

统一入口点

c 复制代码
// 所有挂载操作都通过这个函数处理
if (flags & MS_REMOUNT)
    // 重新挂载
else if (flags & MS_BIND) 
    // 绑定挂载
else if (flags & MS_MOVE)
    // 移动挂载
else
    // 新建挂载

标志处理策略

  • 转换: 用户标志 → 内核内部标志
  • 分离: 不同层次的标志分别处理
  • 清理: 已处理的标志从原始值中移除

挂载类型详解

挂载类型 标志 功能 使用场景
新建挂载 无特殊标志 挂载新文件系统 mount /dev/sda1 /mnt
重新挂载 MS_REMOUNT 修改挂载选项 mount -o remount,ro /
绑定挂载 MS_BIND 目录镜像 mount --bind /old /new
移动挂载 MS_MOVE 移动挂载点 mount --move /old /new
相关推荐
A小辣椒20 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言