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
关键设计特点
- 分层错误处理: 使用
goto实现清晰的资源清理路径 - 安全复制: 所有用户空间数据都通过安全函数复制到内核空间
- 原子性保护: 使用内核锁保护核心挂载操作
- 资源管理: 及时释放不再需要的资源,避免内存泄漏
- 错误传播: 保持错误码的传递,确保调用方能了解失败原因
从用户空间安全地复制挂载选项数据到内核空间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_NOSUID→MNT_NOSUID: 忽略suid权限MS_NODEV→MNT_NODEV: 禁止访问设备文件MS_NOEXEC→MNT_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)的挂载安全检查- 模块化安全: 允许
SELinux、AppArmor等安全模块实施策略 - 错误处理: 如果安全检查失败,跳转到清理代码
挂载类型分发
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 |
