Linux 操作二:文件映射与文件状态查询
文件映射
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写数据到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。
mmap函数:
-
头文件
c#include <sys/mman.h>
-
函数原型
cvoid *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); //解除映射 int munmap(void *addr, size_t length);
-
参数说明
-
addr:映射的地址空间首地址,NULL 表示让系统决定;
-
length:地址空间大小
-
prot:映射的地址空间访问方式,必须和文件打开方式匹配
-
flags: 映射的地址空间的访问标记常见的标志位:
MAP_SHARED
这个选项表示映射的内存区域与文件共享,即对映射内存区域的修改会直接反映到原始文件中。通常用于多个进程共享同一文件的情况。- 例如:进程 A 和进程 B 都映射了同一个文件,进程 A 修改了映射区域中的内容,进程 B 可以立即看到这些修改。
MAP_PRIVATE
这个选项表示映射的内存区域与文件私有,修改映射区域的内容不会写回到原文件。此类修改仅在当前进程的内存中有效,其他进程不可见。- 例如:进程 A 映射了一个文件并修改了映射区域的内容,但文件本身保持不变。
MAP_ANONYMOUS
这个选项表示创建一个匿名内存映射,即该映射不与任何文件相关联。fd
参数通常为-1
。它用于分配一块内存,而不是映射一个文件。对于不需要读取或写入文件的场景,匿名映射特别有用。- 例如:创建一个内存区域供程序使用(如内存池、数据结构等),而不需要任何文件作为后端。
MAP_FIXED
这个选项表示指定的addr
地址是映射区域的起始地址。它要求映射区域严格地在该地址上开始。如果该地址已有其他映射或冲突,则会直接替换现有的映射区域。- 注意 :如果指定的地址无法映射(例如,无法与当前地址空间兼容),
mmap
将返回MAP_FAILED
。
- 注意 :如果指定的地址无法映射(例如,无法与当前地址空间兼容),
MAP_FILE
这个选项指定映射的是文件,通常默认情况下,mmap
会将文件映射到内存。该选项几乎与MAP_SHARED
等价,但出于兼容性考虑,MAP_FILE
仍然可以使用。MAP_HUGETLB
这个选项表示使用大页面(huge pages)。它要求映射使用大页内存,而不是系统默认的标准页面。这个选项通常需要超级用户权限,并且在支持大页的操作系统中才有效。- 例如:在内存密集型应用(如数据库、虚拟机管理程序)中,使用大页面可以提高内存管理的效率。
MAP_LOCKED
这个选项表示映射的内存区域在物理内存中将被锁定,操作系统不会将其交换到交换空间(swap)中。这对于实时应用或对内存访问有严格要求的应用非常有用。MAP_NORESERVE
这个选项表示在创建映射时不保留交换空间(swap),即使映射的内存区域并没有实际分配内存。这通常用于创建一个大区域的虚拟内存映射,期望它不会被使用。- 该选项一般在大数据结构的内存分配中使用,以避免过多的交换空间消耗。
MAP_POPULATE
这个选项会在映射时立即预取(prefetch)所有的页面,即使这些页面还没有被访问过。它可以减少后续对内存的访问延迟,适用于希望提前加载数据的应用。MAP_STACK
这个选项用来为程序的线程栈创建映射,表示该映射是为了线程的栈而创建的。通常由系统自动处理。
-
fd: 需映射的文件描述符
-
offset: 文件存储空间的偏移量
-
-
返回值
- 成功:munmap返回 0 ,mmap 返回映射后的地址;
- 出错:返回 MMAP_FAILED,并将错误码存入 errno 中
-
案例
c#include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> int main() { const char *filename = "testfile.txt"; int fd = open(filename, O_RDWR); // 打开文件,读取/写入权限 if (fd == -1) { perror("open"); return 1; } // 获取文件的大小 off_t file_size = lseek(fd, 0, SEEK_END); // 使用 mmap 映射文件到内存 char *mapped = (char *)mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mapped == MAP_FAILED) { perror("mmap"); close(fd); return 1; } // 修改文件内容 mapped[0] = 'H'; // 将文件的第一个字符改为 'H' // 同步映射区的内容到文件 if (msync(mapped, file_size, MS_SYNC) == -1) { perror("msync"); } // 解除映射 if (munmap(mapped, file_size) == -1) { perror("munmap"); } close(fd); return 0; }
-
文件映射与系统i/o的区别
-
抽象层次
-
文件映射(Memory-Mapped Files):文件映射是一种将文件内容映射到进程的虚拟内存中的技术。通过文件映射,程序可以像访问内存一样访问文件的内容,而不需要显式地进行文件读取或写入操作。文件映射通过操作系统的虚拟内存管理机制来管理文件的加载和访问。
-
系统I/O(System I/O) :系统I/O是通过操作系统提供的系统调用(如
read
,write
,open
,close
等)直接进行文件操作。程序需要显式地读取或写入数据到文件中,通常依赖于内存缓冲区。
-
-
性能和效率
-
文件映射:文件映射能够提供更高效的文件操作,尤其是在处理大文件或频繁访问文件的场景下。操作系统会自动将文件的数据加载到内存中,并根据需要进行页面交换。文件映射的优势在于它通过内存管理机制减少了多次I/O操作,提高了性能。内存映射文件可以直接在内存中访问,从而避免了频繁的磁盘I/O。
-
系统I/O:系统I/O操作通常需要显式地读取或写入文件,每次操作都可能涉及磁盘I/O,尤其是在没有有效缓存时。系统I/O的性能受到磁盘I/O和缓冲策略的影响,通常比文件映射较慢,特别是在频繁读取大文件时。
-
-
访问方式
-
文件映射:文件映射将文件的一部分或整个文件映射到进程的虚拟地址空间,使程序可以通过指针直接访问文件内容。这种访问方式类似于访问普通内存,程序员可以像操作内存一样对文件进行读写。
-
系统I/O :系统I/O需要使用操作系统提供的接口(如
read
,write
等)来显式地读取或写入文件内容,数据需要先从文件中读取到内存中的缓冲区,然后进行处理。文件的访问方式是通过系统调用来完成的。
-
-
数据同步
-
文件映射 :文件映射的更大优点之一是,操作系统负责管理数据的同步。数据可以在内存中直接操作,操作系统会定期将内存中更改的数据写回磁盘。为了保证数据一致性,通常会使用
msync
或munmap
等函数来手动同步数据。 -
系统I/O :系统I/O通常依赖于缓冲区来处理数据,程序员需要显式地调用
flush
等函数来确保缓冲区的内容被写回磁盘,或者使用文件关闭操作来触发数据的同步。
-
-
内存使用
-
文件映射:文件映射会直接将文件的一部分或整个文件映射到进程的虚拟内存空间。操作系统会为映射区域分配虚拟内存,并在实际访问时将数据从磁盘加载到内存中。对于大型文件,文件映射只会在需要时加载文件的部分数据,而不是将整个文件加载到内存中。
-
系统I/O:系统I/O操作通常需要将文件的数据读入到应用程序提供的缓冲区中。缓冲区的大小由程序员控制,且每次I/O操作都可能导致较高的内存使用,尤其是在读取大型文件时。
-
-
总结:
- 文件映射:通过将文件映射到内存中,提供了一种高效的方式来处理文件,可以像操作内存一样访问文件内容,适用于需要频繁或大规模访问文件的场景。
- 系统I/O:是传统的文件操作方式,需要显式进行文件读取或写入操作,适用于一般的文件访问,但性能和灵活性相对较低。
文件状态查询
-
头文件
c#include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h>
-
函数原型:
cint stat(const char *path, struct stat* buf) int fstat(int fd, struct stat *buf);
-
参数说明
-
path:要操作的文件名或路径;
-
buf:指向stat 结构体的指针,用来获取文件状态信息:
cstruct stat { dev_t st_dev; //文件设备编号 ino_t st_ino; //文件inode节点号 mode_t st_mode; //文件类型,访问权限等 nlink_t st_nlink; //文件的连接数 uid_t st_uid; //文件所有者的用户ID gid_t st_gid; //文件所有者对应的组ID dev_t st_rdev; //若文件为设备文件,则表示设备编号 off_t st_size; //文件大小,对应的文件字节数 blksize_t st_blksize; //文件系统的 I/O 缓冲区大小 blkcnt_t st_blocks; //占用文件区块数量,每一区块512 字节 time_t st_atime; //文件最后一次被访问的时间 time_t st_mtime; //文件内容最后一次被修改的时间 time_t st_ctime; //最后一次改变时间(属性改变) };
-
fd:文件描述符
-
st_mode
st_mode 常见取值: (八进制,文件类型主要取决第3字节)
S_IFSOCK 0140000 socket 套接字文件 S_IFLNK 0120000 链接文件 S_IFREG 0100000 一般文件 S_IFBLK 0060000 块设备文件 S_IFDIR 0040000 目录 S_IFCHR 0020000 字符设备文件 S_IFIFO 0010000 管道文件 上述的文件类型在POSIX中定义了检查这些类型的宏定义:
S_ISLNK (st_mode) 判断是否为链接文件 S_ISREG (st_mode) 是否为一般文件 S_ISDIR (st_mode) 是否为目录 S_ISCHR (st_mode) 是否为字符设备文件 S_ISBLK (st_mode) 是否为块设备文件 S_ISSOCK (st_mode) 是否为socket套接字文件 S_ISFIFO (st_mode) 是否为管道文件 st_mode 其他常见取值:
S_IRWXU 00700 自己拥有所有权限 S_IRUSR 00400 自己拥有读权限 S_IWUSR 00200 自己拥有写权限 S_IXUSR 00100 自己拥有执行权限 S_IRWXG 00070 自己组拥有所有权限 S_IRGRP 00040 自己组拥有写权限 S_IWGRP 00020 自己组拥有执行权限 S_IXGRP 00010 自己组拥有执行权限 S_IRWXO 00007 其他组用户拥有所有权限 S_IROTH 00004 其他组用户拥有读权限 S_IWOTH 00002 其他组用户拥有写权限 S_IXOTH 00001 其他组用户拥有执行权限
-
利用用户ID获取用户信息,
-
头文件
c#include <sys/types.h> #include <pwd.h>
-
函数原型
cstruct passwd* getpwuid(uid_t uid);
-
参数说明
uid:用户id;
利用用户组ID获取用户组信息
-
头文件
c#include <sys/types.h> #include <grp.h>
-
函数原型
cstruct group *getgrgid(gid_t gid)
-
参数说明
gid:用户组id;
-
返回值
成功后返回下列结构体:
c
struct passwd {
char* pw_name; /* user name */
char* pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char* pw_gecos; /* user information */
char* pw_dir; /* home directory */
char* pw_shell; /* shell program */
};
struct group {
char* gr_name; /* group name */
char* gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char** gr_mem; /* group members */
};
-
案例:
c#include <stdio.h> #include <stdlib.h> #include <grp.h> #include <sys/types.h> int main() { gid_t gid = 1000; // 替换为一个有效的组 ID struct group *grp = getgrgid(gid); if (grp != NULL) { printf("组名: %s\n", grp->gr_name); printf("组 ID: %d\n", grp->gr_gid); printf("组成员: "); for (char **member = grp->gr_mem; *member != NULL; member++) { printf("%s ", *member); } printf("\n"); } else { perror("getgrgid"); } struct passwd *pw = getpwuid(uid); if (pw != NULL) { printf("用户名: %s\n", pw->pw_name); printf("用户 ID: %d\n", pw->pw_uid); printf("主组 ID: %d\n", pw->pw_gid); printf("主目录: %s\n", pw->pw_dir); printf("登录 Shell: %s\n", pw->pw_shell); } else { perror("getpwnam"); } return 0; }