• [Linux]----文件操作(复习C语言+文件描述符)



    前言

    今天这个小结节,我来大家来了解Linux下的文件操作。首先我们来复习一下C语言的文件操作,基于C语言的文件操作我们对Linux的学习就会方便很多了!我带大家首先来了解文件相关系统的接口和文件描述符,并且理解重定向!
    最后在基于重定向,在下一小节将我上节写的myshell完善一下!



    正文开始!

    一、基础概念

    1. 文件=文件内容+文件属性(属性也是数据,即便你创建一个空文件,也要占据磁盘空间)
    2. 文件操作=文件内容的操作+文件属性的操作(有可能再操作文件的时候,即改变内容,又改变属性)
    3. 对于文件操作,我们首先要打开文件。所谓"打开"文件,究竟在干什么?将文件的属性或者内容加载到内存中!(冯诺依曼体系结构决定!)
    4. 是不是所有的文件,都会被处于打开的状态呢?没有被打开的文件,在哪里呢?(只在磁盘上存储!)
    5. 打开的文件(内存文件)和磁盘文件
    6. 通常我们打开文件,访问文件,关闭文件,是谁在进行相关操作?fopen,fwrite,fread,fclose…->代码->程序->当我们文件程序,运行起来的时候,才会执行对应的代码,然后才是真正的对文件进行相关的操作(进程在做相关操作!!!)
    7. 进程和打开文件的关系!

    二、回顾C语言

    2.1 对文件进行写操作

    在这里插入图片描述
    当我们以w方式打开文件,准备写入的时候,其实文件已经先被清空了!

    #include
    #include
    
    int main()
    {
        FILE* fp=fopen("log.txt","w");
        if(fp==NULL)
        {
            perror("fopen");
            return 1;
        }
        const char* msg="hello rose!";
        int cnt=0;
        while(cnt<10)
        {
            fprintf(fp,"%s %d\n",msg,cnt);
            cnt++;
        }
        fclose(fp);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    默认这个"log.txt"文件会在哪里形成呢?—>当前路径

    那么什么是当前路径呢?–>进程当前的路径

    接下来带大家查看进程的信息

    #include
    #include
    
    int main()
    {
        FILE* fp=fopen("log.txt","w");
        if(fp==NULL)
        {
            perror("fopen");
            return 1;
        }
        printf("%d\n",getpid());
        while(1)
        {
            sleep(1);
        }//在这里会一直休眠下去,直到我们杀掉这个进程
        const char* msg="hello rose!";
        int cnt=0;
        while(cnt<10)
        {
            fprintf(fp,"%s %d\n",msg,cnt);
            cnt++;
        }
        fclose(fp);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    ll /proc/进程id

    在这里插入图片描述
    在这里我们就可以看到"log.txt"就在当前cwd,也就是进程所处的路径了。

    接下来我们有意识的更改当前路径,这里需要用到系统接口chdir();
    在这里插入图片描述

    #include
    #include
    
    int main()
    {
        chdir("/home/hulu");
        FILE* fp=fopen("log.txt","w");
        if(fp==NULL)
        {
            perror("fopen");
            return 1;
        }
        printf("%d\n",getpid());
        while(1)
        {
            sleep(1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    在这里插入图片描述

    2.2 追加写文件

    #include
    #include
    
    int main()
    {
        FILE* fp=fopen("log.txt","a");
        if(fp==NULL)
        {
            perror("fopen");
            return 1;
        }
        const char* msg="hello rose!";
        int cnt=0;
        while(cnt<5)
        {
            fprintf(fp,"%s %d\n",msg,cnt);
            cnt++;
        }
        fclose(fp);
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    追加写入,不断的往文件中新增内容—>追加重定向!

    在这里插入图片描述

    2.3 读文件

    在这里插入图片描述

    #include
    #include
    
    int main()
    {
        FILE* fp=fopen("log.txt","r");
        if(fp==NULL)
        {
            perror("fopen");
            return 1;
        }
        char buffer[64];
        while(fgets(buffer,sizeof(buffer),fp)!=NULL)
        {
            printf("echo: %s",buffer);
        }
        fclose(fp);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    2.4 简易cat功能

    #include
    #include
    
    //myfile filename
    int main(int argc,char* argv[])
    {
        if(argc!=2)
        {
            printf("Usage: %s filename\n",argv[0]);
            return 1;
        }
        FILE* fp=fopen(argv[1],"r");
        if(fp==NULL)
        {
            perror("fopen");
            return 1;
        }
        char buffer[64];
        while(fgets(buffer,sizeof(buffer),fp)!=NULL)
        {
            printf("%s",buffer);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    当我们向文件写入的时候,最终是不是向磁盘写入?

    因为磁盘是硬件,所以只有OS有资格向硬件写入!

    那么能绕开操作系统吗?

    答:不能!那么所有上层的访问文件的操作,都必须贯穿操作系统!

    操作系统是如何被上层使用的呢?

    因为操作系统不相信任何人,所以必须使用操作系统提供的相关系统调用!

    那么为什么要进行封装文件操作接口呢?

    • 原生系统接口,使用成本比较高!
    • 语言不具备跨平台性!

    那么封装是如何解决跨平台性的问题呢?

    • 穷举所有底层的接口+条件编译!

    C库提供的文件访问接口是来自于系统调用!
    那么就能解释不同的语言有不同的文件访问接口!!
    所以无论什么语言的底层的接口是不变的!

    所以这就要求我们必须学习文件级别的系统接口!

    总结

    stdin&stdout&stderr

    • C默认会打开三个输入输出流,分别是stdin,stdout,stderr
    • 仔细观察发现,这三个流的类型都是FILE*,fopen返回值类型,文件指针

    打开文件的方式

    在这里插入图片描述

    三、系统文件I/O

    接口介绍

    open介绍

    在这里插入图片描述

    返回值

    在这里插入图片描述

    打开文件的选项

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    对于O_RDONLY,O_WRONLY,O_RDWR,O_APPEND,O_CREAT!这些都是宏!

    系统传递标记位,使用位图结构来进行传递的!

    每一个宏标记,一般只需要有一个比特位为1,并且和其他宏对于的值不能重叠。

    代码模拟实现

    #include
    
    #define PRINT_A 0x1
    #define PRINT_B 0x2
    #define PRINT_C 0x4
    #define PRINT_D 0x8
    #define PRINT_DFL 0x0
    
    
    void Show(int flags)
    {
        if(flags&PRINT_A)
            printf("hello A\n");
        
        if(flags&PRINT_B)
        printf("hello B\n");
        
        if(flags&PRINT_C)
        printf("hello C\n");
        
        if(flags&PRINT_D)
        printf("hello D\n");
    
        if(flags==PRINT_DFL)
            printf("hello Default\n");
    
    }
    
    int main()
    {
        Show(PRINT_DFL);
        Show(PRINT_A);
        Show(PRINT_B);
        Show(PRINT_A|PRINT_B);
        Show(PRINT_C|PRINT_D);
        Show(PRINT_A|PRINT_B|PRINT_C|PRINT_D);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    我们通过传入不同的选项,打印出不同的语句。

    在这里插入图片描述

    使用open接口

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
      int fd = open("log.txt",O_WRONLY|O_CREAT);
      if(fd<0)
      {
        perror("open error");
        return 1;
      }
      printf("fd=%d\n",fd);
    
          return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述
    在这里插入图片描述
    所以我们要打开曾经不存在的文件,我们要用到第二个open函数,带有权限的设置!

    int open(const char *pathname, int flags, mode_t mode);
    int fd = open(“log.txt”,O_WRONLY|O_CREAT,0666);

    在这里插入图片描述
    可以看到我们创建文件的权限是0666,可是实际显示的是0664呢?

    这就和我们之前学的掩码umask有联系了!

    在这里插入图片描述

    不清楚的话可以去看看这篇博客权限的理解!

    close

    在这里插入图片描述

    write

    在这里插入图片描述

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
        if(fd<0)
        {
          perror("open error");
          return 1;
        }
        printf("fd=%d\n",fd);
        int cnt=0;
        const char* str="hello file!\n";
        while(cnt<5)
        {
          write(fd,str,strlen(str));
          cnt++;
        }
    
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述
    C在w方式打开文件的时候,会清空的!

    int main()
    {
        int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
        if(fd<0)
        {
          perror("open error");
          return 1;
        }
        printf("fd=%d\n",fd);
        int cnt=0;
        const char* str="aaaa";
        //const char* str="hello file!\n";
        while(cnt<5)
        {
          write(fd,str,strlen(str));
          cnt++;
        }
    
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    修改我们的代码后

    在这里插入图片描述
    我们发现此处直接覆盖曾经的数据,但是曾经的数据为什么保留呢了?

    因为我们没有带有截断选项O_TRUNC

    在这里插入图片描述

    接下来我们带上这个选项后

    int main()
    {
        int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
        if(fd<0)
        {
          perror("open error");
          return 1;
        }
        printf("fd=%d\n",fd);
        int cnt=0;
        const char* str="hello rose!\n";
        while(cnt<5)
        {
          write(fd,str,strlen(str));
          cnt++;
        }
    
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述
    现在我们就发现之前的数据被截断了!

    所以我们现在可以类比与C语言的fopen,底层的open的选项就是"O_WRONLY|O_CREAT|O_TRUNC"!!!

    接下来我们验证追加选项"O_APPEND"
    代码如下

    int main()
    {
        int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
        if(fd<0)
        {
          perror("open error");
          return 1;
        }
        printf("fd=%d\n",fd);
        int cnt=0;
        const char* str="hello hulu!\n";
        while(cnt<5)
        {
          write(fd,str,strlen(str));
          cnt++;
        }
    
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    read

    我们打开文件就默认他是存在的,不需要携带"O_CREAT"选项
    如果文件不存在会返回-1;
    在这里插入图片描述

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define NUM 1024
    int main()
    {
        int fd = open("log.txt",O_RDONLY);
        if(fd<0)
        {
          perror("open error");
          return 1;
        }
        printf("fd=%d\n",fd);
        char buffer[NUM];
        while(1)
        {
          ssize_t s=read(fd,buffer,sizeof(buffer)-1);
          if(s>0)
          {
            buffer[s]='\0';
            printf("%s",buffer);
          }
          else
            break;
        }
    
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述

    四、文件描述符

    在上面的实验中我们了解到打开文件后返回给文件描述符fd=3,这是为什么?

    接下来先来看代码,让我们去了解文件描述符

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
        int fda=open("loga.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
        int fdb=open("logb.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
        int fdc=open("logc.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
        int fdd=open("logd.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
        int fde=open("loge.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
        printf("fda=%d\n",fda);
        printf("fdb=%d\n",fdb);
        printf("fdc=%d\n",fdc);
        printf("fdd=%d\n",fdd);
        printf("fde=%d\n",fde);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    1. 为什么文件描述符默认是从3开始的呢?那么0,1,2去哪了?
      因为0,1,2被默认打开了
    • 0:标准输入,键盘

    • 1:标准输出,显示器

    • 2:标准错误,显示器
      在这里插入图片描述
      这就与我们C语言联系起来了!因为C语言封装的系统接口。

      首先我们之前在C语言中学到FILE*–>文件指针—>FILE是什么呢?—>C语言提供的结构体!–>封装了多个成员

      因为对于文件操作而言,系统接口只认识fd;(FILE内部必定封装了fd)

    1. 0,1,2,3,4…,我们之前见过什么样的数据是这个样子的呢?

      这和我们之前学习的C/C++的数组下标相似

      进程:内存文件的关系—>内存—>被打开的文件是存在内存里面的!!!

      一个进程可不可以打开多个文件?–>当然可以,所以在内核中,进程:打开的文件=1:n–>所以系统在运行中,有可能会存在大量的被打开的文件!—>OS要不要对这些被打开的文件进行管理呢??—>操作系统如何管理这些被打开的文件呢??—>答案是先描述,在组织。

      一个文件被打开,在内核中,要创建该被打开的文件的内核数据结构—先描述

      在这里插入图片描述
      在这里插入图片描述
      那么进程如何和打开的文件建立映射关系呢??

    在这里插入图片描述

    先验证0,1,2就是标准的IO

    标准输入流

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
      char buffer[1024];
      ssize_t s=read(0,buffer,sizeof(buffer)-1);
      if(s>0)
      {
        buffer[s]='\0';
        printf("echo:%s",buffer);
      }
      return 0;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    标准输出流

    int main()
    {
      const char* s="hello write!\n";
      write(1,s,strlen(s));
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    标准错误流

    int main()
    {
      const char* s="hello write!\n";
      write(2,s,strlen(s));
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    我们看出标准错误流也打印到了显示器上面。

    至于标准输出和标准错误的区别,我们稍后带大家了解。

    验证0,1,2和stdin,stdout,stderr的对应关系

    
    int main()
    {
        printf("stdin: %d\n",stdin->_fileno);
        printf("stdout: %d\n",stdout->_fileno);
        printf("stderr: %d\n",stderr->_fileno);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    由上面的实验我们可以得出,FILE结构体中的fileno就是封装了文件描述符fd!!!

    在这里插入图片描述

    0,1,2—>stdin,stdout,stderr—>键盘,显示器,显示器(这些都是硬件呀!)也用你上面的struct file来标识对应的文件吗??
    在这里插入图片描述
    如何证明呢?

    我来带大家看看LInux下的内核结构!!!
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    文件描述符的分配规则

    int main()
    {
        close(0);
        //close(1);
        //close(2);
        int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
        if(fd<0)
        {
            perror("open");
            return 1;
        }
        printf("fd=%d\n",fd);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    我们发现把1关掉后什么都没有了!

    因为printf->stdout->1虽然不在指向对应的显示器了,但是已经指向了log.txt的底层struct_file对象!

    在这里插入图片描述

    遍历fd_array[],找到最小的没有被使用的下标,分配给新的文件!!


    总结

    下小节我来给大家讲述关于重定向的本质,和缓冲区的概念,等讲完重定向后,我们再把myshell完善!
    (本章完!)

  • 相关阅读:
    DTU配置工具-F2x16工具
    C#将对象转换为Dictionary字典集合
    2022年全国职业院校技能大赛:网络系统管理项目-模块B--Windows样题7
    经营收款码收款有手续费吗
    [附源码]SSM计算机毕业设计远程在线教育平台JAVA
    在UniApp的H5项目中,生成二维码和扫描二维码的操作处理
    Redis02-高级使用
    如何设计测试用例
    【开发教程10】疯壳·开源蓝牙智能健康手表-OTA镜像制作及下载技术文档
    Springboot+Rabbitmq消费者注解详解、改序列化方式
  • 原文地址:https://blog.csdn.net/m0_61560468/article/details/127645384