文件元数据,内存映射文件

文件元数据

文件元数据的获取

#include
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 
#include
#include
#include
#include


//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 \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
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
#include
#include
#include

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操作比较

在操作系统中,内核空间和用户空间是两个独立的内存区域,它们的分离是出于安全性和稳定性的考虑。内核空间是操作系统核心代码和数据所在的区域,用户空间则是应用程序运行的区域。为了防止用户进程直接访问或修改内核数据,操作系统通过系统调用(如 readwrite)来管理进程与内核之间的数据交换。

1. 内核空间与用户空间的分离

  • 用户空间:这是应用程序运行的地方。用户进程不能直接访问内核空间的数据,只能通过系统调用与内核交互。
  • 内核空间:这是操作系统内核及其管理的数据所在的地方。内核负责管理硬件资源,如内存、文件系统、网络等。

2. 传统的 read 操作的内存复制过程

当进程调用 read从文件中读取数据时,涉及到两个主要步骤的内存复制:

  1. 从磁盘到内核缓冲区

    • 当一个进程调用 read 系统调用时,操作系统首先会把数据从磁盘(或其他存储介质)读入到内核空间的缓冲区中。
    • 这个过程涉及磁盘I/O操作,由操作系统和硬件设备驱动程序处理。数据在内核空间中临时存储,以确保安全性和系统控制。
  2. 从内核缓冲区到用户缓冲区

    • 数据被读取到内核缓冲区后,操作系统将这些数据拷贝到用户空间的缓冲区中,这就是用户进程实际要读取的数据。
    • 由于用户进程不能直接访问内核空间,所以这个数据复制操作是必要的,但它增加了系统开销。

3. 传统的 write 操作的内存复制过程

类似地,当进程调用 write 将数据写入文件时,也涉及两个主要步骤的内存复制:

  1. 从用户缓冲区到内核缓冲区

    • 当进程调用 write 系统调用时,数据从用户空间的缓冲区复制到内核空间的缓冲区中。
    • 这是因为写入操作由内核管理,数据必须经过内核才能最终写入磁盘。
  2. 从内核缓冲区到磁盘

    • 数据从内核缓冲区被写入到磁盘或其他存储介质上。这一步涉及磁盘I/O操作,通常由操作系统调度和硬件驱动程序执行。

4. 多次内存复制的开销

每次 readwrite 操作,都涉及从内核空间到用户空间或从用户空间到内核空间的内存复制。内存复制操作虽然在现代计算机中是相对快速的,但仍然会带来一定的性能开销,尤其是在处理大量数据或频繁读写操作时,这种开销更加明显。

  • 系统调用开销:每次内存复制通常需要系统调用,系统调用是从用户态到内核态的切换,涉及上下文切换的开销。
  • CPU资源消耗:内存复制需要消耗CPU资源,尤其是在大规模数据传输时,这种开销可能导致CPU资源紧张。

5. 对比内存映射文件(Memory-Mapped File)

相比之下,内存映射文件通过将文件直接映射到进程的虚拟地址空间,允许进程直接访问文件内容,数据访问不需要多次的内存复制,极大地减少了系统开销。内存映射的读写操作实际上就是对内存的读写操作,省去了在内核缓冲区和用户缓冲区之间来回拷贝数据的步骤,因此可以显著提升性能。

 

你可能感兴趣的:(c语言,vim,linux,ubuntu,bash)