当前位置: 首页 > news >正文

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

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

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 内核中用于挂载文件系统的系统调用,负责将存储设备(如硬盘分区)挂载到指定的目录位置

代码详细分析

变量声明部分

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: 用于存储从用户空间复制的数据的内核页面指针

复制文件系统类型参数

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

获取挂载目录路径

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

复制设备名称参数

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

复制挂载选项数据

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

执行挂载操作

	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): 立即释放数据页面,因为不再需要

错误处理与资源清理

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

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 详细分析

函数声明和变量定义

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

初始化和空指针检查

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

内存分配

	if (!(page = __get_free_page(GFP_KERNEL)))return -ENOMEM;
  • __get_free_page(GFP_KERNEL): 分配一个完整的内存页(通常4KB)
  • GFP_KERNEL: 标准内核内存分配标志,允许睡眠
  • 错误处理: 如果分配失败,返回 -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;
  • copy_from_user 不能跨越 TASK_SIZE 边界

  • 边界计算:

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

执行复制操作

	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(错误的地址)

清零剩余空间和返回成功

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

exact_copy_from_user 详细分析

函数声明和变量定义

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: 临时存储单个字符

地址有效性检查

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

逐字节复制循环

	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--: 递减剩余计数
  • 返回值: 返回未能成功复制的剩余字节数

关键设计特点

安全复制策略

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

边界保护

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

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

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);elseretval = do_new_mount(&nd, type_page, flags, mnt_flags,dev_name, data_page);
dput_out:path_release(&nd);return retval;
}

函数功能概述

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

代码详细分析

函数声明和变量定义

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: 转换后的挂载点标志

魔数标志处理

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

基本完整性检查

	/* 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(无效参数)

数据页终止符确保

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

挂载点路径查找

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

安全模块检查

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

挂载类型分发

	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);elseretval = do_new_mount(&nd, type_page, flags, mnt_flags,dev_name, data_page);

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

重新挂载 (MS_REMOUNT)

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

绑定挂载 (MS_BIND)

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

移动挂载 (MS_MOVE)

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

新建挂载 (默认情况)

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

清理和返回

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

关键设计特点

统一入口点

// 所有挂载操作都通过这个函数处理
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
http://www.dtcms.com/a/533043.html

相关文章:

  • 邢台网站定制做网站找那些公司
  • 天津做网站建设的公司沧州网站建设联系电话
  • 使用VisualVM进行java性能瓶颈定位 1.无需像JProfiler那样必须加启动参数???
  • 五金 东莞网站建设wordpress注册简化
  • 好用的苏州GEO多渠道优化推广哪个公司好
  • Arduino无人机操控系统开发实战指南
  • 第九章:架构篇 - 设计可插拔的语音助手内核
  • 上海嘉定网站建设公司发布培训的免费网站模板
  • 深圳做网站哪家便宜注册网店怎么注册流程
  • jQuery 简介
  • 网站建设个人简历表达查询一个网站是用什么系统做的
  • 记git status不显示已追踪文件文件的更改
  • Agent Laboratory: 利用 LLM Agent 作为研究助手
  • 网站登录不上怎么回事在线咨询免费
  • 网站怎么快速做收录如何拥有一个免费的企业邮箱
  • 免费网站建设模块优化设计一年级下册数学答案
  • CCF-GESP 等级考试 2024年9月认证C++四级真题解析
  • Gorm(六)错误处理 RowsAffected
  • 湘乡网站建设wordpress常用数组
  • Python如何做形状相似性判断
  • 糖尿病视网膜病变图像分类数据集
  • 免费网站制作平台下载怎么在电脑找到wordpress模板代码
  • MySQL数据库,DDL,DML,查询,权限,主从复制
  • 北京优化词网站昭通做网站
  • 个人网站的版权怎么写广州网站开发系统
  • OpenCV Vec3b类型用法
  • 记一次 Spring Boot 项目中 Redis 工具类的重构实践
  • vue适合什么样的网站开发做全景的h5网站
  • 重庆网站模板制作网络游戏挣钱的有哪些
  • 杭州有哪些做网站的公司有区域名和主机怎么做网站