0708,文件流,目录流,MMAP内存映射

目录

目录相关操作

目录流及相关操作

文件描述符和相关操作

fopen()和open()的关系

内存映射mmap

mmap相关函数

01_chdir.c

02_mkdir.c

03_rewinddir.c

04_ftruncate.c

05_mmap.c

目录相关操作

#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

char *getcwd(char *buf, size_t size);
int chdir(const char *path);
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);  

/*
//getcwd   如果 char* buff=NULL,getcwd会调用malloc,但是不负责free
//chdir    当前工作目录是进程的属性,也就是说每一个进程都有自己的当前工作目录。且父进程创建 
           (fork)子进程的时候,子进程会继承父进程的当前工作目录
//mkdir    mode: 目录的权限位,会受文件创建掩码umask的影响,实际的权限为(mode & ~umask& 
           0777)
//rmdir    *pathname要删除的目录(空)

目录流及相关操作

#include <sys/types.h>
#include <dirent.h>

DIR* opendir(const char *name);      //可以打开一个目录,得到一个指向目录流的指针 DIR*
int closedir(DIR *dirp);
struct dirent* readdir(DIR *dirp);   //读目录流,得到指向下一个目录项的指针/NULL
void seekdir(DIR *dirp, long loc);   //移动 loc位置,是前面调用telldir函数的返回值。
long telldir(DIR *dirp);             //返回目录流中现在的位置
void rewinddir(DIR *dirp);          //重置目录流,即移动到目录流的起始位置
The  readdir()  function returns a pointer to a dirent structure representing the next directory entry in  the  directory  stream pointed  to by dirp. 

struct dirent {
    ino_t          d_ino;       /* Inode number */
    off_t          d_off;       /* Not an offset; see below */
    unsigned short d_reclen;    /* Length of this record 数组长度*/
    unsigned char  d_type;      /* Type of file; not supported 文件类型
                                  by all filesystem types */
    char           d_name[256]; /* Null-terminated filename 文件名/实现上可变数组*/
};

//d_type的可选值
              DT_BLK      This is a block device.
              DT_CHR      This is a character device.
              DT_DIR      This is a directory.
              DT_FIFO     This is a named pipe (FIFO).
              DT_LNK      This is a symbolic link.
              DT_REG      This is a regular file.
              DT_SOCK     This is a UNIX domain socket.
              DT_UNKNOWN  The file type could not be determined.

文件描述符和相关操作

//文件描述符是一种整数,用于标识被打开的文件。在操作系统层面,文件描述符是一个抽象的句柄,它指向操作系统内核中与文件相关的数据结构。

 kernel space
//process table     //open file table     //vnode table
  进程控制块PCB        进程之间共享

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);//flag==O_CREAT --> mode
int close(int fd);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);         //移动文件位置

int fsync(int fd);                    //把内核中和文件描述符 fd 相关的脏页刷新到磁盘
int ftruncate(int fd, off_t length);  //将文件截断为指定长度 length/after
int fstat(int fd, struct stat *statbuf);  //获取文件的元数据信息  stat
int dup(int oldfd);                       //copy fd,return new_fd(min)
int dup2(int oldfd, int newfd);           //会自动close(oldfd)


//open    成功:新的文件描述符(最小可用的文件描述符)
//read    返回实际读取的字节数目(0表示读到了文件的末尾),并且文件位置会向前移动这么多个字节
//write   返回实际写入的字节数目
//lseek   return 移动后文件位置   whence(SEEK_SET,SEEK_CUR,SEEK_END)
//flag 标志位

O_RDONLY  只读
O_WRONLY  只写
O_RDWR    可读/可写
O_CREAT   如果文件不存在,则创建文件
O_EXCL    和 O_CREAT 连用,如果文件存在, open() 会失败
O_TRUNC   如果文件存在,则将文件长度截断为 0 (清空文件内容)
O_APPEND  追加模式,即每次调用 write() 的时候,都会将文件位置移动到文件的末尾

其中 O_RDONLY 、 O_WRONLY 、 O_RDWR 又被称为访问模式 (access modes),我们有且只能选择其中一个。如果 flags 中有设置 O_CREAT 标志位,那么就需要设置第三个参数,用来指定创建文件的权限。
mode : 用来指定文件的权限,会受 umask 的影响,实际权限为 (mode & ~umask) 。
//statbuf

struct stat {
	dev_t st_dev; /* ID of device containing file */
	ino_t st_ino; /* Inode number */
	mode_t st_mode; /* File type and mode */
	nlink_t st_nlink; /* Number of hard links */
	uid_t st_uid; /* User ID of owner */
	gid_t st_gid; /* Group ID of owner */
	dev_t st_rdev; /* Device ID (if special file) */
	off_t st_size; /* Total size, in bytes */
	blksize_t st_blksize; /* Block size for filesystem I/O */
	blkcnt_t st_blocks; /* Number of 512B blocks allocated */
    
	/* Since Linux 2.6, the kernel supports nanosecond
	precision for the following timestamp fields.
	For the details before Linux 2.6, see NOTES. */
    
	struct timespec st_atim; /* Time of last access */
	struct timespec st_mtim; /* Time of last modification */
	struct timespec st_ctim; /* Time of last status change */
    
	#define st_atime st_atim.tv_sec /* Backward compatibility */
	#define st_mtime st_mtim.tv_sec
	#define st_ctime st_ctim.tv_sec
};

fopen()和open()的关系

open() 是系统调用, fopen() 是库函数。库函数是系统调用的封装。 fopen() 的底层是调用open() 实现的,它们之间有一个很好的对应关系。
    
fopen()mode         open() flags
r                   O_RDONLY
w                   O_WRONLY | O_CREAT | O_TRUNC
a                   O_WRONLY | O_CREAT | O_APPEND
r+                  O_RDWR
w+                  O_RDWR | O_CREAT | O_TRUNC
a+                  O_RDWR | O_CREAT | O_APPEND
    
/*
文件流        用户空间(buff + 用户态缓冲区)  -->  内核空间  -->  磁盘
文件描述符    用户空间(buff)                -->  内核空间  -->  磁盘
//文件流:多复制一次,用户态缓冲区读写数据不需要切换上下文(适合处理文本数据,字符/行
//描述符:少复制一次,读写数据需要切换上下文(适合复制文件

内存映射mmap

//内存映射可以将文件内容直接映射到进程的虚拟地址空间。当进程访问映射区域时,操作系统会负责将相应的文件部分加载到内存中。这种机制利用了操作系统的页面管理技术,可以高效地管理内存和文件I/O

传统的文件 I/O 操作通常涉及多个步骤:

用户空间请求数据
内核从磁盘读取数据到内核空间
数据再从内核空间复制到用户空间

//kernal --> argv,environ --> stack --> unallocated memory --> heap  --> ```
                                         内存映射区(部分)
//MAP_PRIVATE:
逻辑上,每个进程都有自己的一份映射 (copy-on-write, 写时复制)。对映射区域的修改,其它进程是不可见的,修改不会同步到底层文件中
用途是用一个文件的内容来初始化一块内存区域。常见的例子有:加载可执行文件或共享库文件的相应部分来初始化一个进程的代码段和数据段
    
//MAP_SHARED:
多个进程共享同一个映射区域。一个进程对映射区域的修改,其它进程是可见的,修改会同步到底层的文件中
用途:1. 它允许内存映射 I/O,这是一种零拷贝技术,适用于处理大文件。2. 因为多个进程共享同一片内存区域,所以它也可以用于进程间通信(Interprocess Communication, IPC)

mmap相关函数

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
void* memcpy(void* dest, const void* src, size_t n); //在内存中复制指定数量的字节

/*mmap
void* ->映射区域起始地址
起始地址NULL/OR  
//大小(向上取整下一个页大小的整倍数   //保护位prot/PROT_NONE  offset偏移量/页大小整数倍
PROT_NONE  映射区域不能被访问
PROT_READ  映射区域可读
PROT_WRITE 映射区域可写
PROT_EXEC  映射区域可执行

01_chdir.c

#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <sys/stat.h>

#define SIZE(A) (sizeof(A)/sizeof(0))

int main(int argc, char* argv[]) {
    // ./chdir dir
    if(argc!=2){
        error(1,errno,"Usage:%s dir mode",argv[0]);
    }

    char buf[256];
    getcwd(buf, SIZE(buf)); // 获取当前工作目录
    puts(buf); // 打印当前工作目录

    int ret=chdir(argv[1]);
    if(ret==-1){
        error(1,errno,"mkdir %s ",argv[1]);
    }

    getcwd(buf, SIZE(buf));
    puts(buf); // 再一次打印当前工作目录,观察一下是否发生了变化


    return 0;
}

02_mkdir.c

#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <sys/stat.h>

int main(int argc,char *argv[])
{
    // ./t_mkfir dir mode
    if(argc!=3){
        error(1,errno,"Usage:%s dir mode",argv[0]);
    }
    mode_t mode;
    sscanf(argv[2],"%o",&mode);
    //标准输入输出,char* --> mode_t
    
    if(mkdir(argv[1],mode)==-1){
        error(1,errno,"mkdir %s ",argv[1]);
    }

    return 0;
}

03_rewinddir.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <string.h>

int main(int argc,char** argv)
{
    DIR* pdir=opendir(".");
    if(!pdir){error(1,errno,"opendir");}

    long loc=telldir(pdir); //返回目录流中现在的位置
    struct dirent* pdirent;
    errno=0;

    while(1){
        long tmploc=telldir(pdir);
        pdirent=readdir(pdir);
        if(!pdirent){break;}
        //如果读取失败(没有下一个目录项)跳出循环

        printf("%s ",pdirent->d_name);
        if(strcmp(pdirent->d_name,"03")==0){
            loc=tmploc;
        }
        printf("\n");

        if(errno!=0){error(1,errno,"readdir");}

    }
    
    printf("----------------------------\n");
    seekdir(pdir,loc);//移动loc位置
    pdirent=readdir(pdir);//如果不移动,此时pdirent是NULLL
    puts(pdirent->d_name);
    printf("----------------------------\n");
    rewinddir(pdir);
    pdirent=readdir(pdir);
    puts(pdirent->d_name);

    closedir(pdir);

    return 0;
}

04_ftruncate.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <fcntl.h>

int main(int argc,char** argv)
{
    //./04 file length
    if(argc!=3){
        error(1,errno,"Usage:%s file length",argv[0]);
    }

    off_t length;
    sscanf(argv[2],"%ld",&length);

    int fd=open(argv[1],O_WRONLY);
    if(fd==-1){error(1,errno,argv[1]);}

    if(ftruncate(fd,length)==-1){
        error(1,errno,"ftruncate %d",ftruncate);
    }
    return 0;
}

05_mmap.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <fcntl.h>

#define MMAP_SIZE (4096*10)

int main(int argc,char** argv)
{

    //./05 file1 file2
    if(argc!=3){
        error(1,0,"Usage:%s file1 file2",argv[0]);
    }

    int file1=open(argv[1],O_RDONLY);
    if(file1==-1){error(1,errno,"open file1");}

    int file2=open(argv[2],O_RDWR,O_CREAT,O_TRUNC,0666);
    if(file2==-1){error(1,errno,"open file2");}
    //读写打开,截断为0

    struct stat sb;
    fstat(file1,&sb);//获取文件的元数据信息
    off_t fsize=sb.st_size;
    ftruncate(file2,fsize);//file2 size

    off_t offset=0;
    while(offset<fsize){
        //计算映射区长度(分页映射)
        off_t length;
        if(fsize-offset>=MMAP_SIZE){
            length=MMAP_SIZE;
        }else{
            length=fsize-offset;
        }
        
        printf("%ld\n",offset);
        void* addr1=mmap(NULL,length,PROT_READ,MAP_SHARED,
                         file1,offset);
        if(addr1==MAP_FAILED){error(1,errno,"mmap %s",argv[1]);}

        void* addr2=mmap(NULL,length,PROT_READ|PROT_WRITE,
                        MAP_SHARED,file2,offset);
        if(addr2==MAP_FAILED){error(1,errno,"mmap %s",argv[2]);}

        memcpy(addr2,addr1,length);
        offset+=length;

        int err=munmap(addr1,length);
        if(err){error(1,errno,"munmap");}
        err=munmap(addr2,length);
        if(err){error(1,errno,"munmap2");}

        close(file1);
        close(file2);

    }



    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值