文件元数据
文件元数据的获取
#include <sys/stat.h>
int stat(char const* path, struct stat* buf);int fstat(int fd, struct stat* buf);
int lstat(char const* path, struct stat* buf);
->功能:从i节点中提取文件的元数据,即文件的属性信息
->参数:path 文件路径
buf 文件元数据结构
fd 文件描述符
->返回值:成功返回0,失败返回-1
- lstat()函数与另外两个函数的区别在于它不跟踪符号链接
- ->例:
- -> abc.txt--->xyz.txt abc.txt文件是xyz.txt文件的符号链接
- -> stat( "abc.txt" ,... ); //得到xyz.txt文件的元数据
- -> lstat( "abc.txt",...); //得到abc..xt文件的元数据
文件元数据结构
文件元数据结构
stat函数族通过stat结构体,向调用者输出文件的元数据struct stat {
dev_t st_dev; //设备ID
ino_t st_ino; //i节点号
mode_t st_mode; // 文件的类型和权限
nlink_t st nlink; // 硬链接数
uid_t st_uid; // 拥有者用户ID
gid_t st_gid; // 拥有者组_D
dev_t st_rdev; // 特殊设备ID
off_t st_size; // 总字节数
blksize_t st_blksize; // l/O块字节数blkcnt_t st_blocks; // 存储块数
time_t st_atime; // 最后访问时间
time_t st_mtime; // 最后修改时间
time_t st_ctime; }// 最后状态改变时间
stat结构的st_mode成员表示文件的类型和权限,该成员在stat结构中被声明为mode_t类型,其原始类型在32位系统中被定义为unsigned int,即32位无符号整数,但到目前为止,只有其中的低16位有意义用16位二进制数(B15...B0)表示的文件类型和权限,从高到低可被分为五组
>B15-B12:文件类型
>B11-B9 :设置用户ID,设置组ID,粘滞
>B8-B6:拥有者用户的读、写和执行权限
>B5-B3:拥有者组的读、写和执行权限
>B2-B0:其它用户的读、写和执行权限
辅助分析文件类型的实用宏
>S_ISREG() 是否普通文件
>S_ISDIR() 是否目录
>S_ISSOCK() 是否本地套接字
S_ISCHR() 是否字符设备
S_ISBLK() 是否块设备
S_ISLNK() 是否符号链接
S_ISFIFO() 是否有名管道:
代码实现stat函数
//获取文件元数据
#include <stdio.h>
#include<string.h>
#include<time.h>
#include<sys/stat.h>
#include<unistd.h>
//hello.c --> stat()-->struct stat st-->st.st_mode-->mtos()-->rwxrwxrwx
char* mtos(mode_t m){
static char buf[11];
buf[10] = '\0';
/*if(S_ISDIR(m)){
strcpy(buf, "d");
} else if(S_ISLNK(m)){
strcpy(buf, "l");
}else if(S_ISBLK(m)){
strcpy(buf, "b");
}else if(S_ISCHR(m)){
strcpy(buf, "c");
}else if(S_ISFIFO(m)){
strcpy(buf, "p");
}else if(S_ISSOCK(m)){
strcpy(buf, "s");
}else{
strcpy(buf, "-");
}*/
if (S_ISDIR(m)) buf[0] = 'd';
else if (S_ISLNK(m)) buf[0] = 'l';
else if (S_ISBLK(m)) buf[0] = 'b';
else if (S_ISCHR(m)) buf[0] = 'c';
else if (S_ISFIFO(m)) buf[0] = 'p';
else if (S_ISSOCK(m)) buf[0] = 's';
else buf[0] = '-';
buf[1] = m & S_IRUSR ? 'r' : '-';
buf[2] = m & S_IWUSR ? 'w' : '-';
buf[3] = m & S_IXUSR ? 'x' : '-';
buf[4] = m & S_IRGRP ? 'r' : '-';
buf[5] = m & S_IWGRP ? 'w' : '-';
buf[6] = m & S_IXGRP ? 'x' : '-';
buf[7] = m & S_IROTH ? 'r' : '-';
buf[8] = m & S_IWOTH ? 'w' : '-';
buf[9] = m & S_IXOTH ? 'x' : '-';
/*
strcat(buf,S_IRUSR & m ? "r" : "-");
strcat(buf,S_IWUSR & m ? "w" : "-");
strcat(buf,S_IXUSR & m ? "x" : "-");
strcat(buf,S_IRGRP & m ? "r" : "-");
strcat(buf,S_IWGRP & m ? "w" : "-");
strcat(buf,S_IXGRP & m ? "x" : "-");
strcat(buf,S_IROTH & m ? "r" : "-");
strcat(buf,S_IWOTH & m ? "w" : "-");
strcat(buf,S_IXOTH & m ? "x" : "-");*/
return buf;
}
//时间转换
char* ttos(time_t t){
static char time[20];
struct tm* l = localtime(&t);
sprintf(time, "%04d-%02d-%02d %02d:%02d:%02d", l->tm_year + 1900,
l->tm_mon + 1, l->tm_mday, l->tm_hour, l->tm_min, l->tm_sec);
return time;
}
int main(int argc, char *argv[])
{
//./stat hello.c
if(argc < 2)
{
fprintf(stderr,"Usage: %s <filename>\n", argv[0]);
return -1;
}
struct stat st;
if(stat(argv[1], &st) == -1)
{
perror("stat");
return -1;
}
printf(" 设备ID:%lu\n", st.st_dev);
printf(" i节点号:%lu\n", st.st_ino);
printf(" 文件类型和权限:%s\n", mtos(st.st_mode));
printf(" 硬链接数:%lu\n", st.st_nlink);
printf(" 文件所有者ID:%u\n", st.st_uid);
printf(" 文件组ID:%u\n", st.st_gid);
printf(" 文件大小:%ld\n", st.st_size);
printf(" 特殊设备ID:%lu\n", st.st_rdev);
printf(" I/O块字节数:%ld\n", st.st_blksize);
printf(" 存储块数:%ld\n", st.st_blocks);
printf(" 最后访问时间:%s\n", ttos(st.st_atime));
printf(" 最后修改时间:%s\n", ttos(st.st_mtime));
printf(" 最后状态改变时间:%s\n", ttos(st.st_ctime));
return 0;
}
内存映射文件
#include <sys/mman.h>
void* mmap(void* start, size_t length, int prot, int flags,int fd, off_t offset);
->功能:建立虚拟内存到物理内存或磁盘文件的映射->参数:start:映射区虚拟内存的起始地址,NULL系统自动选定后返回
length:映射区字节数,自动按页取整。
prot:映射区操作权限,可放以下值:
PROT_READ)-映射区可读
PROT_WRITE-映射区可写
PROT_EXEC -映射区可执行
PROT_NONE -映射区不可访问
代码实现
//内存映射文件
#include<stdio.h>
#include<sys/mman.h>
#include<string.h>
#include<fcntl.h>
int main(){
//打开文件
int fd = open("./fmap.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
//修改文件大小
if(ftruncate(fd,4096) == -1){
perror("ftruncate");
return -1;
}
//建立内存映射文件
char *start = mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(start == MAP_FAILED){
perror("mmap");
return -1;
}
//操作内存映射文件 strcpy printf
strcpy(start,"hello world");
printf("%s\n",start);
//解除内存映射
if(munmap(start,4096) == -1){
perror("munmap");
return -1;
}
//关闭文件
close(fd);
return 0;
}
内存映射文件与read/write操作比较
在操作系统中,内核空间和用户空间是两个独立的内存区域,它们的分离是出于安全性和稳定性的考虑。内核空间是操作系统核心代码和数据所在的区域,用户空间则是应用程序运行的区域。为了防止用户进程直接访问或修改内核数据,操作系统通过系统调用(如 read
和 write
)来管理进程与内核之间的数据交换。
1. 内核空间与用户空间的分离
- 用户空间:这是应用程序运行的地方。用户进程不能直接访问内核空间的数据,只能通过系统调用与内核交互。
- 内核空间:这是操作系统内核及其管理的数据所在的地方。内核负责管理硬件资源,如内存、文件系统、网络等。
2. 传统的 read
操作的内存复制过程
当进程调用 read从文件中读取数据时,涉及到两个主要步骤的内存复制:
-
从磁盘到内核缓冲区:
- 当一个进程调用
read
系统调用时,操作系统首先会把数据从磁盘(或其他存储介质)读入到内核空间的缓冲区中。 - 这个过程涉及磁盘I/O操作,由操作系统和硬件设备驱动程序处理。数据在内核空间中临时存储,以确保安全性和系统控制。
- 当一个进程调用
-
从内核缓冲区到用户缓冲区:
- 数据被读取到内核缓冲区后,操作系统将这些数据拷贝到用户空间的缓冲区中,这就是用户进程实际要读取的数据。
- 由于用户进程不能直接访问内核空间,所以这个数据复制操作是必要的,但它增加了系统开销。
3. 传统的 write
操作的内存复制过程
类似地,当进程调用 write
将数据写入文件时,也涉及两个主要步骤的内存复制:
-
从用户缓冲区到内核缓冲区:
- 当进程调用
write
系统调用时,数据从用户空间的缓冲区复制到内核空间的缓冲区中。 - 这是因为写入操作由内核管理,数据必须经过内核才能最终写入磁盘。
- 当进程调用
-
从内核缓冲区到磁盘:
- 数据从内核缓冲区被写入到磁盘或其他存储介质上。这一步涉及磁盘I/O操作,通常由操作系统调度和硬件驱动程序执行。
4. 多次内存复制的开销
每次 read
和 write
操作,都涉及从内核空间到用户空间或从用户空间到内核空间的内存复制。内存复制操作虽然在现代计算机中是相对快速的,但仍然会带来一定的性能开销,尤其是在处理大量数据或频繁读写操作时,这种开销更加明显。
- 系统调用开销:每次内存复制通常需要系统调用,系统调用是从用户态到内核态的切换,涉及上下文切换的开销。
- CPU资源消耗:内存复制需要消耗CPU资源,尤其是在大规模数据传输时,这种开销可能导致CPU资源紧张。
5. 对比内存映射文件(Memory-Mapped File)
相比之下,内存映射文件通过将文件直接映射到进程的虚拟地址空间,允许进程直接访问文件内容,数据访问不需要多次的内存复制,极大地减少了系统开销。内存映射的读写操作实际上就是对内存的读写操作,省去了在内核缓冲区和用户缓冲区之间来回拷贝数据的步骤,因此可以显著提升性能。