2 Linux系统编程之文件系统--学习笔记

1.前置基础

默认的代码模板可以先locate snippet.c,找到位置后可以改这个文件然后得以修改代码模板。

可以把所有用的头文件放到一个文件中,然后放在/usr/include/下

iso c标准
请添加图片描述
打开文件:

​   FILE *fopen(const char *pathname,const char *mode);

​   "a"和"a+"模式,后者写时文件指针自动偏移到文件末尾

​   “r”和“r+”模式,“w”和“w+”模式

获取当前位置:

​   long ftell(FILE *stream);

​   成功返回当前位置,失败返回-1

移动指针:

​   int fseek(FILE *stream,long offset,int whence);

​   whence参数:SEEK_SET,SEEK_CUR,SEEK_END

读写操作:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);

int fputc(int c, FILE *stream);
int fputs(const char *s, FILE *stream);

int fgetc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);


2.一些文件相关的系统调用

修改权限:int chmod(const char *pathname,mode_t mode);

获取当前工作目录:char *getcwd(char *buf,size_t size);

​   若参数为:NULL 0,则返回值内存是函数所分配

改变当前工作目录:int chdir(const char *path);

​   改变当前进程的工作目录,并不是shell,而是运行程序而启动的进程。

创建目录:int mkdir(const char *pathname,mode_t mode);

​   权限受umask的影响

删除目录:int rmdir(const char *pathname);

访问目录(目录流):

  1. DIR *opendir(const char *name);

  2. int closedir(DIR *dirp);

  3. struct dirent *readdir(DIR *dirp);其中struct dirent中包括索引结点号,next指针,结构体长度,文件类型,文件名字,每次readdir后,取出ptr所指目录项数据,ptr后移。
    请添加图片描述

    ​ 可用opendir和readdir配合遍历一个目录内的目录项

  4. 调整ptr的位置:

    ​ long telldir(DIR *dirp);

    ​ void seekdir(DIR *dirp,long loc);

  5. 回到最开始:void rewinddir(DIR *dirp);

    //遍历目录举例
    int main(int argc, char *argv[])
    {
        ARGS_CHECK(argc,2);
        DIR *dirp = opendir(argv[1]);
        ERROR_CHECK(dirp,NULL,"opendir");
        struct dirent *pdirent;
        long loc;
        while((pdirent = readdir(dirp)) != NULL){
            printf("ino = %ld, reclen = %d, type = %d, name = %s\n",
                   pdirent->d_ino,
                   pdirent->d_reclen,
                   pdirent->d_type,
                   pdirent->d_name);
            if(strcmp(pdirent->d_name,"ftell.c") == 0){
                loc = telldir(dirp);    
            }
        }
        puts("\n");
        seekdir(dirp,loc);
        pdirent = readdir(dirp);
        printf("ino = %ld, reclen = %d, type = %d, name = %s\n",
               pdirent->d_ino,
               pdirent->d_reclen,
               pdirent->d_type,
               pdirent->d_name);
        closedir(dirp);
        return 0;
    }
    

  stat文件信息:int stat(const char *pathname,struct stat *statbuf);
  请添加图片描述
  请添加图片描述

从用户id到用户名:struct passwd *getpwuid(uid_t uid);
  请添加图片描述
从组id到组名:struct group *getgrgid(gid_t gid);
  请添加图片描述
日历时间显示:

​   char *ctime(const time_t *timep); 返回一个固定格式的日历字符串。

​   struct tm *localtime(const time_t *timep);
  请添加图片描述

//实现类ls命令
int main(int argc, char *argv[])
{
    ARGS_CHECK(argc,2);
    DIR *dirp = opendir(argv[1]);
    ERROR_CHECK(dirp,NULL,"opendir");
    struct dirent *pdirent;
    while((pdirent = readdir(dirp)) != NULL){
        struct stat statbuf;
        char buf[512] = {0};
        sprintf(buf,"%s%s%s",argv[1],"/",pdirent->d_name);
        int ret = stat(buf,&statbuf);
        ERROR_CHECK(ret,-1,"stat");
        //类型 权限 硬连接数 用户名 组名 大小 修改时间 文件名字
        printf("%8o %ld %s %s %10ld %15s %s",
               statbuf.st_mode,
               statbuf.st_nlink,
               getpwuid(statbuf.st_uid)->pw_name,
               getgrgid(statbuf.st_gid)->gr_name,
               statbuf.st_size,
               pdirent->d_name,
               ctime(&statbuf.st_mtime)
               );
    }
    return 0;
}
//tree命令实现
#include <func.h>
int DFSprint(char *path,int width);
int main(int argc, char *argv[])
{
    // ./myTree dir
    ARGS_CHECK(argc,2);
    puts(argv[1]);
    DFSprint(argv[1],4);
    return 0;
}
int DFSprint(char *path,int width){
    DIR* dirp = opendir(path);
    char newPath[512] = {0};
    ERROR_CHECK(dirp,NULL,"opendir");
    struct dirent *pdirent;
    while((pdirent = readdir(dirp)) != NULL){
        if(strcmp(pdirent->d_name,".") == 0 ||strcmp(pdirent->d_name,"..") == 0){
            continue;
        }
        printf("%*s%s\n",width,"",pdirent->d_name);
        if(pdirent->d_type == 4){
            sprintf(newPath,"%s%s%s",path,"/",pdirent->d_name);
            DFSprint(newPath,width+4);
        }
    }
    closedir(dirp);
    return 0;
}


3.不带缓冲区的文件IO

是不带用户态缓冲区的IO

int open(const char *pathname,int flags);

  flags:O_RDONLY|O_WRONLY|O_RDWR|O_CREAT|O_TRUNC|O_APPEND|O_EXCL(如果文件存在就报错)等等

int open(const char *pathname,int flags,mode_t mode);

  成功返回文件描述符fd(指针数组的下标,该数组中内容所对应的地址是打开文件表的对应项/文件对象/内核文件缓冲区的地址),失败返回-1

ssize_t read(int fd,void *buf,size_t count);

ssize_t write(int fd,const void *buf,size_t count);

:%!xxd 在vim中将文件转化为16进制,加-r返回原来格式

//cp命令实现
#include <func.h>
int main(int argc, char *argv[])
{
   // ./myCp srcs dest
   ARGS_CHECK(argc,3);
   int fdr = open(argv[1],O_RDONLY);
   ERROR_CHECK(fdr,-1,"open fdr");
   int fdw = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);
   ERROR_CHECK(fdw,-1,"open fdw");
   char buf[4096] = {0};
   while(1){
       memset(buf,0,sizeof(buf));
       int ret = read(fdr,buf,sizeof(buf));
       if(ret == 0){
           break;
       }
       write(fdw,buf,ret);
   }
   close(fdr);
   close(fdw);
   return 0;
}

fopen VS open: fopen多了一次拷贝,先拷贝到用户态文件对象,再拷贝一次到内核态缓冲区,open直接拷贝到内核态缓冲区。当拷贝的内容大于4096时,open效率高,当小于4096时,fopen效率高,因为陷入内核态的次数小。



4.一些其他的文件操作

int ftruncate(int fd,off_t length);

​   使文件长度为指定长度:可实现文件空洞,成功返回0,失败-1

#include <func.h>
int main(int argc, char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd = open(argv[1], O_RDWR);
    ERROR_CHECK(fd,-1,"open");
    int ret = ftruncate(fd,40960);
    ERROR_CHECK(ret,-1,"ftruncate");
    close(fd);
    return 0;
}

内核态文件到用户态缓冲区的映射(内核态文件与磁盘通过DMA方式来进行自动信息传递),使操作内核态文件跟操作数组一样:

void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset);

​   prot参数:建立的映射的权限:PROT_EXEC(执行),PROT_READ,PROT_WRITE

​   flags参数:映射是否对其他进程可见:MAP_SHARED,MAP_PRIVATE

int munmap(void *addr,size_t length);

#include <func.h>
int main(int argc, char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd = open(argv[1], O_RDWR);
    ERROR_CHECK(fd,-1,"open");
    
    int ret = ftruncate(fd,5);
    ERROR_CHECK(ret,-1,"ftruncate");
    
    char *p = (char *)mmap(NULL,5,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    ERROR_CHECK(p,MAP_FAILED,"mmap");
    
    //p[0] ~ p[4]是属于映射区的
    p[5] = '\0';
    puts(p);//读文件
    p[0] = 'H';//写文件
    munmap(p,5);
    
    close(fd);
    return 0;
}

off_t lseek(int fd,off_t offset,int whence);使文件读写偏移,可实现文件空洞

​   成功返回偏移量,失败返回-1

#include <func.h>
int main(int argc, char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd = open(argv[1], O_RDWR);
    ERROR_CHECK(fd,-1,"open");
    lseek(fd,40960,SEEK_SET);
    write(fd,"1",1);
    close(fd);
    return 0;
}

文件流底层用了文件描述符:int fileno(FILE *stream);实现文件流到文件描述符

重定向:

​   int dup(int oldfd);选择一个最小可用的fd,指向oldfd所指的fo(共享文件指针);

​   int dup2(int oldfd,int newfd);把oldfd所指的文件对象,复制到newfd所指。

#include <func.h>
int main(int argc, char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd1 = open(argv[1],O_RDWR);
    ERROR_CHECK(fd1,-1,"open");
    printf("fd1 = %d\n", fd1);
    
    int fd2 = 10;
    dup2(STDOUT_FILENO,fd2);
    dup2(fd1,STDOUT_FILENO);
    
    printf("You can't see me!\n");
    
    dup2(fd2,STDOUT_FILENO);
    printf("You can see me!\n");
    close(fd1);
    close(fd2);
    return 0;
}


5.有名管道

有名管道(进程间的通信方式)

创建有名管道的命令:mkfifo 管道名(*.pipe)

使用系统调用访问管道:

​   可用open打开管道(参数flag按照读写选择)

​   读端:O_RDONLY

​   写端:O_WRONLY

​   使用write、read来进行读写;

//简陋的及时聊天
//chat1.c
#include <func.h>
int main(int argc, char *argv[])
{
    // ./chat1 1.pipe 2.pipe
    ARGS_CHECK(argc,3);
    int fdr = open(argv[1], O_RDONLY);
    int fdw = open(argv[2], O_WRONLY);
    puts("chat1");
    char buf[4096] = {0};
    while(1){
        memset(buf,0,sizeof(buf));
        read(STDIN_FILENO,buf,sizeof(buf));
        write(fdw,buf,strlen(buf));
        memset(buf,0,sizeof(buf));
        read(fdr,buf,sizeof(buf));
        puts(buf);
    }
    return 0;
}

//chat2.c
#include <func.h>
int main(int argc, char *argv[])
{
    // ./chat2 1.pipe 2.pipe
    ARGS_CHECK(argc,3);
    int fdw = open(argv[1], O_WRONLY);
    int fdr = open(argv[2], O_RDONLY);
    puts("chat2");
    char buf[4096] = {0};
    while(1){
        memset(buf,0,sizeof(buf));
        read(fdr,buf,sizeof(buf));
        puts(buf);
        memset(buf,0,sizeof(buf));
        read(STDIN_FILENO,buf,sizeof(buf));
        write(fdw,buf,strlen(buf));
    }
    return 0;
}


6.I/O多路复用

1.创建集合,集合元素时fd,把要等待的文件描述符放到集合中监听

fd_set set;  //创建集合,将要等待的fd放入集合。

void FD_ZERO(fd_set *set); //集合初始化

void FD_SET(int fd,fd_set *set); //将要等待监听的文件描述符放到集合中

2.用select系统调用,进程阻塞,当集合中有任一个fd就绪,解除阻塞。

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

​   nfds传监听的最大文件描述符数字+1,readfds读集合,writefds写集合

​   exceptfds额外信息集合,timeout时间结构体,超时返回0

​   struct timeval{ long  tv_sec;  long  tv_nsec; };

3.用FD_ISSET()检查是否就绪,不就绪跳过,就绪获取资源。

​    int FD_ISSET(int fd,fd_set *set);

即时聊天写端关闭:

​   1.写端关闭,读端会一直读到文件终止符,造成死循环。

​   2.读端关闭,写端继续写,会触发一个SIGPIPE信号,导致程序崩溃。

//aqiang.c
#include <func.h>
int main(int argc, char *argv[])
{
    // ./chat2 1.pipe 2.pipe
    ARGS_CHECK(argc,3);
    int fdw = open(argv[1], O_WRONLY);
    int fdr = open(argv[2], O_RDONLY);
    puts("chat2");
    char buf[4096] = {0};
    fd_set rdset;
    while(1){
        FD_ZERO(&rdset);
        FD_SET(fdr,&rdset);
        FD_SET(STDIN_FILENO, &rdset);
        struct timeval timeout;
        timeout.tv_sec = 2;
        timeout.tv_usec = 500000;
        int nret = select(fdr+1,&rdset,NULL,NULL,&timeout);
        if(nret == 0){
            puts("timeout");
        }
        if(FD_ISSET(fdr,&rdset)){
            puts("msg from pipe");
            memset(buf,0,sizeof(buf));
            int ret = read(fdr,buf,sizeof(buf));
            if(ret == 0){
                puts("byebye");
                break;
            }
            puts(buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            puts("msg from stdin");
            memset(buf,0,sizeof(buf));
            int ret = read(STDIN_FILENO,buf,sizeof(buf));
            if(ret == 0){
                puts("I quit");
                write(fdw,"nishigehaoren",13);
                break;
            }
            write(fdw,buf,strlen(buf));
        }
    }
    return 0;
}

//azhen.c
#include <func.h>
int main(int argc, char *argv[])
{
    // ./chat1 1.pipe 2.pipe
    ARGS_CHECK(argc,3);
    int fdr = open(argv[1], O_RDONLY);
    int fdw = open(argv[2], O_WRONLY);
    puts("chat1");
    char buf[4096] = {0};
    fd_set rdset;
    while(1){
        FD_ZERO(&rdset);
        FD_SET(fdr,&rdset);
        FD_SET(STDIN_FILENO, &rdset);
        select(fdr+1,&rdset,NULL,NULL,NULL);
        if(FD_ISSET(fdr,&rdset)){
            puts("msg from pipe");
            memset(buf,0,sizeof(buf));
            int ret = read(fdr,buf,sizeof(buf));
            if(ret == 0){
                puts("byebye");
                break;
            }
            puts(buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            puts("msg from stdin");
            memset(buf,0,sizeof(buf));
            int ret = read(STDIN_FILENO,buf,sizeof(buf));
            if(ret == 0){
                puts("I quit");
                write(fdw,"nishigehaoren",13);
                break;
            }
            write(fdw,buf,strlen(buf));
        }
    }
    return 0;
}

select原理:fd_set是个位图

​   1.把fd_set拷贝到内核中

​   2.遍历位图中从0 - ndfs-1

​   3.如果某个设备文件就绪,就不改变对应fd_set中的位,如果未就绪,就置为0,故每次循环结束都要把要监听的文件描述符重新加入集合

​   问题:效率低,O(N),同时打开设备有上限,1024

​      用epoll,红黑树,文件无上限

超时机制参数:struct timeval *timeout;
  请添加图片描述

写阻塞问题:

​   1.读得慢写的快

​   2.写的时候要写的数据大于管道最大容量(4096),会阻塞在写的时候,比如要写4097个字节的数据,前4096个写满管道了,然后写最后一个一直写不进去,然后就阻塞在这了。(可以通过ulimit -a来查看管道最大容量)

​   3.一个文件同时读写。

#include <func.h>
int main(int argc, char *argv[])
{
    ARGS_CHECK(argc,2);
    int fdr = open(argv[1],O_RDWR);//非阻塞方式建立管道读端
    int fdw = open(argv[1],O_RDWR);
    puts("established");
    fd_set rdset;
    fd_set wrset;
    char buf[4096] = {0};
    int cnt1 = 0;
    int cnt2 = 0;
    while(1){
        FD_ZERO(&rdset);
        FD_ZERO(&wrset);
        FD_SET(fdr,&rdset);
        FD_SET(fdw,&wrset);
        select(fdw+1,&rdset,&wrset,NULL,NULL);
        if(FD_ISSET(fdr,&rdset)){
            printf("I can read cnt = %d\n",cnt1++);
            read(fdr,buf,2048);
        }
        //第一次进入循环无可读先写
        if(FD_ISSET(fdw,&wrset)){
            printf("I can write cnt = %d\n",cnt2++);
            write(fdw,buf,4096);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值