mount 是一个核心的系统调用,它允许一个进程将一个文件系统(通常是存储设备,如硬盘分区、U盘、光盘或网络文件系统)附加到文件系统层次结构中的一个指定目录上,这个目录被称为“挂载点”(mount point),挂载成功后,你就可以通过访问这个挂载点目录来访问被挂载文件系统中的所有文件和目录。

核心概念
- 文件系统: 操作系统中用于管理文件、目录和数据的逻辑结构。
ext4,xfs,ntfs,vfat,nfs等。 - 块设备: 代表物理存储设备或其分区的设备文件,通常位于
/dev/目录下,如/dev/sda1,/dev/sdb2。 - 挂载点: 文件系统树中的一个普通目录,当文件系统被挂载到这个目录时,这个目录的原有内容会被暂时“隐藏”,直到文件系统被卸载(
umount)。 - 选项: 在挂载时可以指定的参数,用于控制文件系统的行为,
ro(只读),rw(读写),noexec(不允许执行文件) 等。
mount 系统调用
mount 系统调用的原型在 <sys/mount.h> 中定义。
#include <sys/mount.h>
int mount(const char *source, const char *target,
const char *filesystemtype,
unsigned long mountflags, const void *data);
参数详解:
-
const char *source:- 来源: 指定要挂载的设备或文件系统。
- 对于块设备: 通常是设备文件的路径,
/dev/sda1。 - 对于虚拟文件系统: 可能是特殊标识符,
none或proc。 - 对于网络文件系统: 通常是服务器上的路径,
server:/export/path。
-
const char *target:- 挂载点: 指定一个已经存在的、空的目录路径。
/mnt/mydisk或/media/usb。
- 挂载点: 指定一个已经存在的、空的目录路径。
-
const char *filesystemtype:
(图片来源网络,侵删)- 文件系统类型: 指定被挂载文件系统的类型。
ext4,vfat,ntfs-3g,nfs,proc。 - 如果设置为
NULL,内核会尝试自动检测文件系统类型(不推荐用于生产环境,因为它可能不安全或不可靠)。
- 文件系统类型: 指定被挂载文件系统的类型。
-
unsigned long mountflags:- 挂载标志: 控制挂载行为的位掩码,常用宏定义在
<sys/mount.h>中。 MS_RDONLY: 以只读方式挂载。MS_NOSUID: 禁用 set-user-identifier 和 set-group-identifier 位。MS_NOEXEC: 禁止在挂载的文件系统上执行文件。MS_NODEV: 禁止访问设备文件。MS_REMOUNT: 重新挂载一个已经挂载的文件系统,通常用于更改其标志(例如从rw改为ro)。MS_BIND: 创建一个挂载点的“绑定挂载”,允许你在文件系统树的不同位置拥有同一个文件的多个副本。MS_NOATIME: 禁止更新文件的访问时间,可以提高性能。- 注意:
0表示默认行为(通常是读写、允许执行、允许访问设备等)。
- 挂载标志: 控制挂载行为的位掩码,常用宏定义在
-
const void *data:- 文件系统特定数据: 一个指向参数字符串的指针,这些参数是特定于文件系统类型的。
- 例如:
- 对于
vfat(FAT32): 可以指定utf8=1,uid=1000,gid=1000来设置默认所有者和启用 UTF-8 编码。 - 对于
ext4: 可以指定errors=remount-ro表示遇到错误时重新挂载为只读。 - 对于
nfs: 可以指定tcp,nfsvers=4。
- 对于
- 如果不需要传递特定参数,可以设置为
NULL。
返回值:
- 成功时返回
0。 - 失败时返回
-1,并设置errno来指示具体的错误原因。
代码示例
下面是一个完整的 C 语言示例,演示如何挂载一个 U 盘(假设为 /dev/sdb1,类型为 vfat)到 /mnt/usb 目录。
示例代码 (mount_example.c)
#define _GNU_SOURCE // 为了使用 MS_BIND 等标志
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <errno.h>
// 检查目录是否存在,如果不存在则创建
int ensure_directory_exists(const char *path) {
struct stat st = {0};
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
printf("挂载点 %s 不存在,正在创建...\n", path);
if (mkdir(path, 0755) == -1) {
perror("mkdir 失败");
return -1;
}
printf("挂载点 %s 创建成功,\n", path);
} else {
perror("stat 检查挂载点失败");
return -1;
}
} else if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "错误: %s 已存在但不是一个目录,\n", path);
return -1;
}
return 0;
}
int main() {
const char *source = "/dev/sdb1"; // 要挂载的设备
const char *target = "/mnt/usb"; // 挂载点
const char *fs_type = "vfat"; // 文件系统类型
const char *data = "utf8=1,uid=1000,gid=1000"; // 文件系统特定参数
// 1. 确保挂载点存在
if (ensure_directory_exists(target) != 0) {
fprintf(stderr, "无法准备挂载点,程序退出,\n");
return EXIT_FAILURE;
}
printf("准备挂载 %s 到 %s...\n", source, target);
// 2. 执行 mount 系统调用
// 注意:通常需要 root 权限才能执行 mount
if (mount(source, target, fs_type, 0, data) == -1) {
// 如果失败,打印错误信息
perror("mount 失败");
fprintf(stderr, "错误详情: %s\n", strerror(errno));
fprintf(stderr, "请检查:\n");
fprintf(stderr, " 1. 您是否以 root 权限运行此程序 (sudo),\n");
fprintf(stderr, " 2. 设备 %s 是否存在且可读,\n", source);
fprintf(stderr, " 3. 文件系统类型 '%s' 是否正确,\n", fs_type);
fprintf(stderr, " 4. 挂载点 %s 是否为空目录,\n", target);
return EXIT_FAILURE;
}
printf("挂载成功!\n");
printf("您现在可以访问 %s 来查看 U 盘内容,\n", target);
// 3. (可选) 执行一些操作...
sleep(5); // 模拟操作
// 4. 卸载
printf("准备卸载 %s...\n", target);
if (umount2(target, MNT_DETACH) == -1) {
perror("umount 失败");
return EXIT_FAILURE;
}
printf("卸载成功!\n");
return EXIT_SUCCESS;
}
编译和运行
-
保存代码: 将上述代码保存为
mount_example.c。
(图片来源网络,侵删) -
编译: 你需要使用
gcc进行编译。-Wall选项会显示所有警告。gcc -Wall mount_example.c -o mount_example
-
运行:
mount系统调用需要超级用户(root)权限,你必须使用sudo来运行它。# 确保你有一个 U 盘插入,并且它被识别为 /dev/sdb1 (或类似名称) # 你可以使用 'lsblk' 或 'sudo fdisk -l' 命令来查看 # 运行程序 sudo ./mount_example
可能的输出
成功的情况下,输出会是这样:
挂载点 /mnt/usb 不存在,正在创建...
挂载点 /mnt/usb 创建成功。
准备挂载 /dev/sdb1 到 /mnt/usb...
挂载成功!
您现在可以访问 /mnt/usb 来查看 U 盘内容。
准备卸载 /mnt/usb...
卸载成功!
如果失败(没有 root 权限),输出会是这样:
挂载点 /mnt/usb 不存在,正在创建...
挂载点 /mnt/usb 创建成功。
准备挂载 /dev/sdb1 到 /mnt/usb...
mount 失败: Operation not permitted
错误详情: Operation not permitted
请检查:
1. 您是否以 root 权限运行此程序 (sudo)。
2. 设备 /dev/sdb1 是否存在且可读。
3. 文件系统类型 'vfat' 是否正确。
4. 挂载点 /mnt/usb 是否为空目录。
错误处理 (errno)
当 mount 失败时,errno 会被设置为一个有意义的值,常见的 errno 包括:
EPERM: 操作被拒绝,通常是因为没有超级用户权限。ENOENT: 源文件 (source) 或挂载点 (target) 不存在。ENOTBLK:source不是一个块设备(对于需要设备的文件系统类型)。EBUSY:source或target已经被挂载。EINVAL: 无效的参数,文件系统类型不支持,或者mountflags参数无效。EACCES: 权限不足(尝试挂载到你没有写入权限的目录)。
使用 perror() 或 strerror(errno) 可以方便地打印出可读的错误信息。
umount 系统调用
挂载完成后,当不再需要访问该文件系统时,应该使用 umount 将其卸载。
有两个相关的系统调用:
-
umount(): 通过挂载点路径卸载。#include <sys/mount.h> int umount(const char *target);
-
umount2(): 功能与umount()相同,但提供了一个额外的标志。int umount2(const char *target, int flags);
常用标志:
MNT_DETACH(或0x2): 执行“分离”挂载,如果文件系统正被使用,它会从命名空间中分离,但实际的卸载操作会延迟,直到不再有任何进程使用它,这对于卸载可能被进程使用的文件系统非常有用。MNT_EXPIRE: 标记挂载点为“过期”,用于实现自动卸载。
示例:
// 使用 umount2 和 MNT_DETACH 是更安全的方式
if (umount2(target, MNT_DETACH) == -1) {
perror("umount2 失败");
}
| 任务 | C 系统调用 | 头文件 | 关键点 |
|---|---|---|---|
| 挂载 | mount() |
<sys/mount.h> |
需要 root 权限;source, target, fs_type 是核心;data 用于特定文件系统参数。 |
| 卸载 | umount2() |
<sys/mount.h> |
需要 root 权限;推荐使用 umount2() 并带上 MNT_DETACH 标志。 |
在编写生产级代码时,除了上述基本功能,你还需要考虑更复杂的错误处理、日志记录以及如何安全地管理挂载点和设备,对于大多数用户空间应用,直接调用 mount 是比较少见的,通常由系统管理员通过命令行或通过桌面环境的自动挂载机制来完成,但如果你是在编写系统工具、容器技术或需要精细控制文件系统的应用程序,那么掌握 mount 系统调用是必不可少的。
