Linux系统编程之文件系统的挂载
概述
在Linux系统中,所有的存储设备(比如:U盘、硬盘等)都不是直接可用的。为了使这些设备上的数据能够被操作系统读取和写入,我们必须将它们“挂载”到文件系统的某个位置。这就好比是在树上添加一个新的分支,挂载点就是这个新分支的位置,而所挂载的文件系统则成为了该位置下的子树。
挂载的好处在于:它可以让我们将多个不同的物理设备整合成一个统一的文件层次结构,简化了对多个存储设备的管理和使用。此外,它还允许我们对不同类型的文件系统进行灵活的配置和访问控制。
挂载操作
我们可以直接使用mount函数来对文件系统进行挂载操作。这个函数提供了一种更底层、更灵活的方式来与操作系统交互,适用于那些需要在程序中动态管理文件系统的场景。其函数原型如下。
int mount(const char *source, const char *target, const char *filesystemtype,
unsigned long mountflags, const void *data);
mount函数各个参数和返回值的含义如下。
source:指向源设备或远程文件系统地址的字符串指针。对于块设备来说,通常是设备节点的路径(比如:/dev/sdb1)。对于网络文件系统,则是远程主机和路径。
target:指向目标挂载点的字符串指针,即文件系统将被附加到现有目录结构中的位置。
filesystemtype:指定要挂载的文件系统类型(比如:"ext4"、"ntfs"、"vfat"等)。如果设置为NULL或者空字符串,内核会尝试自动检测文件系统类型。
mountflags:定义了挂载行为的一组标志位,这些标志可以通过按位或运算组合起来使用。常用的标志位如下。
(1)MS_RDONLY:用于以只读方式挂载文件系统。这意味着,任何尝试对挂载点进行写入的操作,都将被拒绝,包括:创建新文件、修改现有文件或删除文件。
(2)MS_NOSUID:用于禁用挂载点下的SUID(Set User ID)和SGID(Set Group ID)位。这意味着,即使文件设置了这些特殊权限位,在该挂载点下运行这些文件,也不会改变执行者的用户或组身份。
(3)MS_NODEV:用于禁止解释挂载点下的特殊设备文件(即字符设备或块设备)。这意味着,即使挂载点内存在表示硬件设备的文件(比如:/dev/sda1),这些文件也不会被识别为实际的设备节点,从而防止用户通过该挂载点访问底层硬件资源。
(4)MS_NOEXEC:用于禁止执行挂载点下的任何二进制文件。这可以有效阻止恶意代码的运行,尤其是在挂载不可信来源的文件系统时。
(5)MS_REMOUNT:重新挂载一个已经挂载的文件系统。这允许我们在不卸载的情况下,更改现有的挂载选项。比如:可以将一个原本以只读方式挂载的文件系统改为读写模式。
(6)MS_BIND:用于创建绑定挂载。这意味着,我们可以在文件系统的不同位置创建一个新的视图,而不会影响原始挂载点。绑定挂载后,两个挂载点共享相同的文件和目录结构。
data:指向特定于文件系统的选项数据的指针。对于大多数文件系统来说,这里可以传入NULL。但对于某些特殊类型的文件系统(比如:NFS等),则可能需要传递额外的配置信息。
卸载操作
卸载操作通常通过umount或umount2函数来完成,这两个函数用于卸载之前已经挂载的文件系统。umount2函数提供了比umount更多的灵活性,因为它允许我们指定额外的标志来控制卸载行为。这两个函数的原型如下。
int umount(const char *target);
int umount2(const char *target, int flags);
这两个函数各个参数和返回值的含义如下。
target:指向要卸载的挂载点路径的字符串指针。
flags:定义了卸载行为的一组标志位,可使用的标志如下。
(1)MNT_FORCE:强制卸载,即使有打开的文件或活动进程在使用该挂载点。请注意:这可能会导致数据丢失或损坏。
(2)MNT_DETACH:懒惰卸载,使挂载点立即不可访问,但延迟实际卸载直到所有活动进程都结束对它的使用。这对于网络文件系统特别有用,因为它们可能需要时间来完成正在进行的操作。
返回值:成功时,返回0。失败时,返回-1,并设置errno来指示具体的错误原因。errno常见的取值为:EBUSY(目标挂载点正忙)、EINVAL(无效参数)、EPERM(权限不足)、ENOENT(指定的目标挂载点不存在)。
在下面的示例代码中,我们首先尝试以只读方式将指定的设备节点挂载到指定的目录,并在挂载失败时输出错误信息。成功挂载后,我们可以插入其他操作逻辑,比如:访问挂载点中的文件和目录。最后,我们尝试卸载该文件系统。
#include <stdio.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <string.h>
#include <errno.h>
int main()
{
// 设备节点路径
const char *pszSource = "/dev/sdb1";
// 挂载点
const char *pszTarget = "/mnt/mydisk";
// 只读挂载
if (mount(pszSource, pszTarget, "ext4", MS_RDONLY, NULL) == -1)
{
printf("mount failed: %s\n", strerror(errno));
return -1;
}
printf("mount to %s successfully\n", pszTarget);
// 挂载后的其他操作
// ... ...
// 卸载文件系统
if (umount(pszTarget) == -1)
{
printf("umount failed: %s\n", strerror(errno));
return -1;
}
printf("umount ok\n");
return 0;
}
注意事项
可以看到,文件系统的挂载与卸载并不复杂。但实际使用时,还是有不少注意事项需要考虑的。
1、权限要求。挂载与卸载通常需要超级用户权限才能执行。
2、挂载点必须存在。在挂载之前,请确保目标挂载点是一个存在的空目录。如果该目录不存在,则需要先创建它。如果有其他内容,则可能会导致意外行为。
3、检查现有挂载。在尝试挂载新的文件系统之前,最好先确认目标挂载点当前没有被占用。此时,我们可以使用mount命令或/proc/mounts文件来查看所有已挂载的文件系统。
4、避免重复挂载。同一个设备不应该多次挂载到不同的位置,除非明确地使用了MS_BIND等特殊标志。否则,可能导致数据的混乱访问。
5、清理资源。当不再需要访问挂载的文件系统时,务必及时卸载它。这不仅可以释放系统资源,还可以防止潜在的安全风险。
6、卸载确保没有活动进程。在尝试卸载文件系统之前,请确保没有进程正在使用该挂载点。此时,可以使用lsof +D命令来检查是否有打开的文件。
7、处理繁忙的挂载点。如果遇到EBUSY错误,表明挂载点正在被使用。此时,可以考虑使用MNT_DETACH标志进行懒惰卸载,或者查找并终止相关进程后再尝试卸载。
8、避免强制卸载。除非绝对必要,否则不要轻易使用MNT_FORCE标志,因为它可能导致数据丢失,甚至文件系统损坏。