Linux_文件系统

文章目录

  • 文件操作
    • Linux的文件类型
  • fopen
    • a+
      • 接下来介绍文件的系统调用
  • chmod
  • getcwd
    • 指针前面的const
  • chdir
  • mkdir rmdir
  • 目录流
  • opendir closedir readdir
  • telldir seekdir
  • rewinddir
  • stat
    • getpwuid
    • getgrgid
    • ctime
  • tree命令的实现
    • sprintf
  • 不带用户态缓冲的文件IO
  • open close
  • read write
    • ASCII和16进制的转换
  • cp
  • 文件截断
  • truncate ftruncate
    • 文件空洞
  • mmap
  • lseek
  • 文件描述符
  • 文件描述符的复制
  • dup
  • 有名管道
    • mkfifo
      • 建立一个小型qq即时通信
  • IO多路转接 / 多路复用
  • select
    • 参数分析:
    • select的用处:
    • 流程:
    • 管道的关闭
      • 优化:超时退出
  • 管道
  • select的原理

文件操作

Linux的文件类型

  1. 普通文件
  2. 目标文件:链表,结点称为目录项
  3. 符号链接 / 软链接:另一个文件的路径
  4. 字符设备文件:IO设备、鼠标、键盘
  5. 块设备文件:磁盘、固态硬盘
  6. 管道:进程间通信
  7. socket:网络通信

接下来学习的都是遵循Linux系统调用规范的POSIX系统调用

fopen

在这里插入图片描述
后面的const char *mode中,在Linux中”r“”rb“一样。
分配在用户态空间。

打开的另一种模式:
a+:使用a+时,读从头开始读,写从尾部开始写,若文件不存在,则创建文件。

func.h的文件路径在cd /usr/include

#include 

int main(int argc,char *argv[])
{
    //./fopen file1
    ARGS_CHECK(argc,2);
    FILE *fp;
    fp = fopen(argv[1],"a+");
    ERROR_CHECK(fp,NULL,"fopen");
    char buf[7] = {0};
    fread(buf,1,sizeof(buf),fp);
    puts(buf);
    fwrite("how are you",1,11,fp);
    fclose(fp);
    return 0;
}

a+

a+模式:

  1. 从头开始读
  2. 从尾开始写

就算使用fseek(fp,0,SEEK_SET),即使移动ptr写入依然从尾开始。

接下来介绍文件的系统调用

chmod

在这里插入图片描述
在这里插入图片描述

#include 

int main(int argc,char *argv[])
{
    //.chmod 777 file1
    ARGS_CHECK(argc,3);
    mode_t mode;
    sscanf(argv[1],"%o",&mode);
    //把传进来的字符串变成八进制整数
    int ret = chmod(argv[2],mode);
    ERROR_CHECK(ret,-1,"chmod");
    return 0;
}

getcwd

Linux_文件系统_第1张图片
Linux_文件系统_第2张图片

指针前面的const

eg : char* getcwd(char *buf,size_t size);

  1. 有const ----> 则传入参数
  2. 没有const ----> 传入传出参数(不要传字符串字面值常量,容易变成指针)
    其中,作为参数传入的指针,指针指向的空间必需已经分配。

char*作为返回值的指针,所指向的空间也被已经分配。

#include 

int main(int argc,char *argv[])
{
    char buf[128] = {0};
    char *ret = getcwd(buf,sizeof(buf));
    ERROR_CHECK(ret,NULL,"getcwd");
    puts(buf);
    puts(ret);


    //getcwd可以自动申请空间,不用想上面那样手动分配空间
    printf("%s\n",getcwd(NULL,0));
    return 0;
}

chdir

改变工作目录
Linux_文件系统_第3张图片

#include

int main(int argc,char* argv[])
{
    // ./chdir dit
    ARGS_CHECK(argc,2);
    printf("before chdir = %s\n",getcwd(NULL,0));
    int ret = chdir(argv[1]);
    ERROR_CHECK(ret,-1,"chdir");
    printf("after chdir = %s\n",getcwd(NULL,0));
    return 0;
}

在这里插入图片描述

mkdir rmdir

Linux_文件系统_第4张图片
在这里插入图片描述
Linux_文件系统_第5张图片

#include

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int ret = mkdir(argv[1],0777);
    // 0 表示八进制字面值常量
    // 依然会受到umask的影响
    ERROR_CHECK(ret,-1,"mkdir");
    return 0;
}
#include

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int ret = rmdir(argv[1]);
    //只能删除空目录
    ERROR_CHECK(ret,-1,"rmdir");
    return 0;
}

目录流

由链表存储,目录流在每次读取的时候都会移动ptr

opendir closedir readdir

Linux_文件系统_第6张图片
在这里插入图片描述
其中DIR *指目录流
Linux_文件系统_第7张图片
Linux_文件系统_第8张图片
其中,struct dirent *readdir指目录项,要把目录项读出来
Linux_文件系统_第9张图片
这里面,d_ino:磁盘地址;d_off:next指针;d_reclen:本结构体长度;d_type:类型;d_name[256]:名字。

#include 

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)
    {
        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;
}

telldir seekdir

Linux_文件系统_第10张图片
Linux_文件系统_第11张图片
这两个搭配使用,可以去到目录流下一个位置。

#include 

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,"dir1") == 0)
    {
        loc = telldir(dirp);
    }

    puts("----------------------------------");
    seekdir(dirp,loc);
    pdirent = readdir(dirp);

    if(pdirent != NULL)
    {
        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;
}

rewinddir

从头开始,直接回到过去
Linux_文件系统_第12张图片

stat

显示文件的具体信息
Linux_文件系统_第13张图片
struct stat *statbuf指向的空间一定是分配好的
Linux_文件系统_第14张图片

在man配置中,struct stat结构体如下:
 
struct stat {
               dev_t     st_dev;        /* 文件设备编号 */
               ino_t     st_ino;        /* inode节点 */
               mode_t    st_mode;       /* 文件的类型和存储的权限 */
               nlink_t   st_nlink;      /* 连到该文件的硬链接数,刚建立的文件值为1 */
               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;     /* 块数 */
 
               /* 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;  /* 最后一次访问时间 */
               struct timespec st_mtim;  /* 最后一次修改时间(指文件内容) */
               struct timespec st_ctim;  /* 最后一次属性改变的时间 */
 
           #define st_atime st_atim.tv_sec  
           #define st_mtime st_mtim.tv_sec
           #define st_ctime st_ctim.tv_sec
           };

其中重要的变量,st_mode:权限和类型;st_size:文件大小
stat可以实现ls -al

getpwuid

Linux_文件系统_第15张图片
Linux_文件系统_第16张图片

getgrgid

Linux_文件系统_第17张图片
Linux_文件系统_第18张图片

ctime

在这里插入图片描述
char *字符串,自带换号,返回一个固定格式的字符串

#include 

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,2);
    DIR *dirp = opendir(argv[1]);
    ERROR_CHECK(dirp,NULL,"opendir");

    chdir(argv[1]);
    //为了文件名直接可以当路径用,一个bug

    struct dirent *pdirent ;
    struct stat statbuf ;

    while((pdirent = readdir(dirp)) != NULL)
    {
        int ret = stat(pdirent->d_name,&statbuf);
        ERROR_CHECK(ret,-1,"stat");
        printf("%6o %ld %s %s %6ld %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)));
    }
    closedir(dirp);
    return 0;
}

tree命令的实现

实现类似于tree命令,深度优先遍历、递归

  1. 如果遇到目录文件,就寻找这个目录文件的目录项
  2. 如果目录项访问完,就退出

sprintf

作用:printf函数调用的主要用途就是把一个字符串放在一个已知的字符数组里去。其实这是一个很常用的库函数,在解决某些OJ题的时候会经常用到它来帮助实现字符串的转移和储存。
在这里插入图片描述
第一个参数:这个参数就是接收字符串的字符数组。
第二个参数:这个参数就是要传的字符串了。

#include 

int DFSprint(char *path,int width)
{
    DIR *dirp = opendir(path);
    ERROR_CHECK(dirp,NULL,"opendir");
    struct dirent *pdirent ;
    char newPath[1024] = {0};

    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 == DT_DIR)
            //这是一个目录
        {
            sprintf(newPath,"%s%s%s",path,"/",pdirent->d_name);
            DFSprint(newPath,width+4);
        }
    }
    closedir(dirp);
    return 0;
}

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    printf("%s\n",argv[1]);
    DFSprint(argv[1],4);
    return 0;
}

不带用户态缓冲的文件IO

open close

Linux_文件系统_第19张图片
open函数是为了取代create函数而专门设立的,open若能成功运行,则会返回一个文件描述符

Linux_文件系统_第20张图片
其中,O_RDWR|O_CREAT要同时生效,再加上O_TRUNC表示创建一个文件。

Linux_文件系统_第21张图片

#include

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd;
    fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC);
    ERROR_CHECK(fd,-1,"open");
    close(fd);
    return 0;

}

Linux_文件系统_第22张图片

read write

Linux_文件系统_第23张图片
在这里插入图片描述

Linux_文件系统_第24张图片

ASCII和16进制的转换

:%!xxd -r  <==========> :%!xxd

cp

#include

int main(int argc,char *argv[])
{
    //./mycp src dest
    ARGS_CHECK(argc,3);
    int fdr = open(argv[1],O_RDONLY);//读的文件描述符
    ERROR_CHECK(fdr,-1,"fdr");
    int fdw = open(argv[2],O_RDWR|O_CREAT|O_EXCL);
    ERROR_CHECK(fdw,-1,"fdw");
    char buf[4096] = {0};
    while(1)
    {
        memset(buf,0,sizeof(buf));
        int ret = read(fdr,buf,sizeof(buf));
        if(ret == 0)
        {
            break;
        }
        ERROR_CHECK(ret,-1,"read");
        write(fdw,buf,ret);
    }
    close(fdr);
    close(fdw);
    return 0;
}

带缓冲的IO:fread / fwrite

文件截断

truncate ftruncate

Linux_文件系统_第25张图片
其中,大文件到小文件直接截断,小文件到大文件则补0。

文件空洞

文件大小大于磁盘分配的空间

mmap

建立了磁盘到内存之间的映射

步骤:

  1. 建立映射 open
  2. 自动分配映射区在堆上 mmap
  3. 回收堆空间 munmap

Linux_文件系统_第26张图片
参数分析:
void *addr:NULL,自动分配
size_t length:映射区长度要写内存页的整数倍(4096的整数倍)
int protLinux_文件系统_第27张图片
int flags在这里插入图片描述
off_t offset:0
(用法跟malloc相似)

在这里插入图片描述
参数分析:
void *addr:mmap的返回值
size_t length:每次回收的长度

#include

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[5] = '\0';
    //p[0] ~ p[4]为映射区
    p[0] = 'H';
    //修改映射区相当于修改文件
    munmap(p,5);
    close(fd);
    return 0;
}

注:使用mmap直接操作数组就可以修改文件,不用像read、write那样修改文件。

lseek

修改文件对象的偏移
Linux_文件系统_第28张图片

#include

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;
}

输出结果:
在这里插入图片描述

文件描述符

文件描述符是一个非负的索引值(一般从3开始,0、1、2已经被使用)。
文件流中有一个域,是文件描述符。
打开文件流时,间接使用了文件描述符。
Linux_文件系统_第29张图片

获取文件描述符的函数:
在这里插入图片描述

文件描述符的复制

不同数值的文件描述符指向同一个FO(内核块)。

dup

复制文件描述符
Linux_文件系统_第30张图片
第一个函数int dup(int oldfd):找到一个最小可用的fd,使其也指向这个文件对象。
第二个函数int dup2(int oldfd,int newfd):手动指定文件描述符进行复制,其实就是close(oldfd)
,让newfd指向oldfd原来指向的文件

#include 

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd = open(argv[1],O_RDWR);
    ERROR_CHECK(fd,-1,"open");
    printf("fd = %d\n",fd);
    int fd1 = dup(fd);
    ERROR_CHECK(fd1,-1,"dup");
    printf("fd1 = %d\n",fd1);
    write(fd,"hello",5);
    write(fd1,"world",5);
    close(fd);
    close(fd1);
    return 0;
}

文件对象是引用计数管理。

其实printf() <=========> write(1,......);

Q:关闭1号文件描述符,再让文件描述符联系文件,打印三句话,第一句话在屏幕上,第二句话在文件内,第三句话在屏幕上?

思路:

  1. dup2(1,5);
  2. dup2(3,1);->自动close(1)
  3. dup2(5,1);
#include 

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);
    
    dup2(STDOUT_FILENO,5);
    //关闭STDOUT_FILENO,让5号文件描述符指向屏幕
    dup2(fd1,STDOUT_FILENO);
    printf("you cannot see me!\n");

    dup2(5,STDOUT_FILENO);
    printf("you can see me!\n");

    close(fd1);
    close(5);
    return 0;
}

有名管道

named pipe / FIFO
是用于进程间通信的一种文件,在文件系统中的一种映射,方便使用open函数。

mkfifo

创建管道,只用于通信,不存储数据
在这里插入图片描述
并且在建立好管道读写端之前,进程是阻塞的。
open函数中,读端用O_RDONLY,写端用O_WRONLY

全双工通信时,建立读写端要有顺序要求,否则会造成死锁。
读端:

int fdr = open(argv[1],O_RDONLY);
int fdw = open(argv[2],O_WRONLY);

写端:

int fdr = open(argv[2],O_RDONLY);
int fdw = open(argv[1],O_WRONLY);

建立一个小型qq即时通信

chat1

#include 

int main(int argc,char *argv[])
{
   ARGS_CHECK(argc,3);
   int fdr = open(argv[1],O_RDONLY);
   int fdw = open(argv[2],O_WRONLY);
   puts("chat1\n");
   char buf[128] = {0};

   while(1)
   {
       memset(buf,0,sizeof(buf));
       read(fdr,buf,sizeof(buf));
       puts(buf);
       read(STDIN_FILENO,buf,sizeof(buf));
       write(fdw,buf,strlen(buf));
   }
   close(fdw);
   close(fdr);
   return 0;
}

chat2

#include 

int main(int argc,char *argv[])
{
   ARGS_CHECK(argc,3);
   int fdw = open(argv[1],O_WRONLY);
   int fdr = open(argv[2],O_RDONLY);
   puts("chat2\n");
   char buf[128] = {0};

   while(1)
   {
       read(STDIN_FILENO,buf,sizeof(buf));
       write(fdw,buf,strlen(buf));
       memset(buf,0,sizeof(buf));
       read(fdr,buf,sizeof(buf));
       puts(buf);
   }
   close(fdw);
   close(fdr);
   return 0;
}

IO多路转接 / 多路复用

select

Linux_文件系统_第31张图片

参数分析:

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)
其中:
int nfds:指监听的文件描述符,一般是最大值+1。
第二、三、四参数:读、写、错误事件
struct timeval *timeout:为超时时间,其值为超时剩余时间,一般是NULL,无限等待。

FD_ZERO(fd_set* fdset): 将fd_set变量的所有位初始化为0。
FD_SET(int fd, fd_set* fdset):在参数fd_set指向的变量中注册文件描述符fd的信息。
FD_CLR(int fd, fd_set* fdset):参数fd_set指向的变量中清除文件描述符fd的信息。
FD_ISSET(int fd, fd_set* fdset):若参数fd_set指向的变量中包含文件描述符fd的信息,则返回真

select的用处:

等待一个集合,集合监听了若干FD,只要集合中任意一个FD就绪,就解除阻塞。

流程:

  1. 创建集合 fd_set

  2. 集合初始化 FD_ZERO(&set)

  3. 注册待监听的FD在集合中 FD_SET
    (以上三步是创建集合)

  4. 调用select函数,进程阻塞,任意FD就绪,就接触阻塞

  5. 遍历所有FD,检查是否就绪 FD_ISSET

  6. 读取就绪FD中的数据

管道的关闭

  1. 写端先关闭,则read会读出一个EOF(返回0)
  2. 读端先关闭,会触发SIGPIPE信号

chat1

#include 

int main(int argc,char *argv[])
{
   ARGS_CHECK(argc,3);
   int fdr = open(argv[1],O_RDONLY);
   int fdw = open(argv[2],O_WRONLY);
   puts("chat1\n");
   char buf[128] = {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))
       {
           printf("msg from pipe\n");
           memset(buf,0,sizeof(buf));
           int ret = read(fdr,buf,sizeof(buf));
           if(ret == 0)
           {
               printf("chat is end\n");
               break;
           }
           puts(buf);
       }
       if(FD_ISSET(STDIN_FILENO,&rdset))
       {
           printf("msg from stdin\n");
           memset(buf,0,sizeof(buf));
           int ret = read(STDIN_FILENO,buf,sizeof(buf));
           if(ret == 0)
           {
               puts("I quit!\n");
               write(fdw,"byebye",6);
               break;
           }
           write(fdw,buf,strlen(buf));
       }
   }
   close(fdw);
   close(fdr);
   return 0;
}

chat2

#include 

int main(int argc,char *argv[])
{
   ARGS_CHECK(argc,3);
   int fdw = open(argv[1],O_WRONLY);
   int fdr = open(argv[2],O_RDONLY);
   puts("chat1\n");
   char buf[128] = {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))
       {
           printf("msg from pipe\n");
           memset(buf,0,sizeof(buf));
           int ret = read(fdr,buf,sizeof(buf));
           if(ret == 0)
           {
               printf("chat is end\n");
               break;
           }
           puts(buf);
       }
       if(FD_ISSET(STDIN_FILENO,&rdset))
       {
           printf("msg from stdin\n");
           memset(buf,0,sizeof(buf));
           int ret = read(STDIN_FILENO,buf,sizeof(buf));
           if(ret == 0)
           {
               puts("I quit!\n");
               write(fdw,"byebye",6);
               break;
           }
           write(fdw,buf,strlen(buf));
       }
   }
   close(fdw);
   close(fdr);
   return 0;
}

优化:超时退出

Linux_文件系统_第32张图片
struct timeval有两个成员,一个是秒,一个是微秒, 所以最高精确度是微秒。

Linux_文件系统_第33张图片

struct timeval timeout;
       timeout.tv_sec = 1;
       timeout.tv_usec = 500000;
       //精确度
       FD_ZERO(&rdset);
       FD_SET(fdr,&rdset);
       FD_SET(STDIN_FILENO,&rdset);
       int tret = select(fdr+1,&rdset,NULL,NULL,&timeout);
       if(tret == 0)
       {
           printf("time out!\n");
       }

管道

当管道数据区满了,写入就会发生阻塞

#include 

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int fdr = open(argv[1],O_RDWR);
    int fdw = open(argv[1],O_RDWR);
    //非标准写法,用O_RDWR
    ERROR_CHECK(fdr,-1,"open");
    ERROR_CHECK(fdw,-1,"open");
    char buf[128] = {0};
    int cnt = 0;
    fd_set rdset;
    fd_set wrset;
    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("cnt = %d, read\n",cnt);
             ++cnt;
             read(fdr,buf,2048);
             sleep(1);
        }
        if(FD_ISSET(fdw,&wrset))
        {
             printf("cnt = %d, write\n",cnt);
             ++cnt;
             write(fdw,buf,4097);
             //write函数如果大于4096,进程最终会阻塞
        }
    }

}

select的原理

Linux_文件系统_第34张图片
fd_set的本质:就是位图,每一位存储的是文件描述符

使用select时,会将fd_set拷贝到内核态空间,select的底层原理其实就是poll。
轮询polling:

  1. fd的上限是有限的
  2. 时间复杂度为O(n)

对比epoll

  1. 由红黑树组织起来
  2. 支持很大的数量,可以使并发量迅速提升

你可能感兴趣的:(Linux,linux)