天天看点

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

文章目录

  • 前言
  • 一、基础概念
  • 二、回顾C语言
  • 2.1 对文件进行写操作
  • 2.2 追加写文件
  • 2.3 读文件
  • 2.4 简易cat功能
  • 总结
  • stdin&stdout&stderr
  • 打开文件的方式
  • 三、系统文件I/O
  • 接口介绍
  • open介绍
  • 使用open接口
  • close
  • write
  • read
  • 四、文件描述符
  • 先验证0,1,2就是标准的IO
  • 标准输入流
  • 标准输出流
  • 标准错误流
  • 验证0,1,2和stdin,stdout,stderr的对应关系
  • 文件描述符的分配规则
  • 总结

前言

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

最后在基于重定向,在下一小节将我上节写的myshell完善一下!

​正文开始!​

一、基础概念

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

二、回顾C语言

2.1 对文件进行写操作

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

当我们以w方式打开文件,准备写入的时候,其实文件已经先被清空了!

#include<stdio.h>
#include<unistd.h>

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;
}      
[Linux]----文件操作(复习C语言+文件描述符)

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

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

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

#include<stdio.h>
#include<unistd.h>

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;
}      
ll /proc/进程id
[Linux]----文件操作(复习C语言+文件描述符)

在这里我们就可以看到"log.txt"就在当前cwd,也就是进程所处的路径了。接下来我们有意识的更改当前路径,这里需要用到系统接口chdir();

[Linux]----文件操作(复习C语言+文件描述符)
#include<stdio.h>
#include<unistd.h>

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);
    }
}      
[Linux]----文件操作(复习C语言+文件描述符)
[Linux]----文件操作(复习C语言+文件描述符)

2.2 追加写文件

#include<stdio.h>
#include<unistd.h>

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;
}      
[Linux]----文件操作(复习C语言+文件描述符)

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

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

2.3 读文件

[Linux]----文件操作(复习C语言+文件描述符)
#include<stdio.h>
#include<unistd.h>

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;
}      
[Linux]----文件操作(复习C语言+文件描述符)

2.4 简易cat功能

#include<stdio.h>
#include<unistd.h>

//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);
    }
}      
[Linux]----文件操作(复习C语言+文件描述符)
[Linux]----文件操作(复习C语言+文件描述符)

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

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

那么能绕开操作系统吗?

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

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

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

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

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

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

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

C库提供的文件访问接口是来自于系统调用!

那么就能解释不同的语言有不同的文件访问接口!!

所以无论什么语言的底层的接口是不变的!

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

总结

stdin&stdout&stderr

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

打开文件的方式

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

三、系统文件I/O

接口介绍

open介绍

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

返回值

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

打开文件的选项

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

对于O_RDONLY,O_WRONLY,O_RDWR,O_APPEND,O_CREAT!这些都是宏!

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

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

代码模拟实现

#include<stdio.h>

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

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

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

使用open接口

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

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;
}      
[Linux]----文件操作(复习C语言+文件描述符)
[Linux]----文件操作(复习C语言+文件描述符)

所以我们要打开曾经不存在的文件,我们要用到第二个open函数,带有权限的设置!

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

int fd = open(“log.txt”,O_WRONLY|O_CREAT,0666);

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

可以看到我们创建文件的权限是0666,可是实际显示的是0664呢?

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

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

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

close

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

write

[Linux]----文件操作(复习C语言+文件描述符)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

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;
}      
[Linux]----文件操作(复习C语言+文件描述符)

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

修改我们的代码后

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

我们发现此处直接覆盖曾经的数据,但是曾经的数据为什么保留呢了?

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

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

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

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;
}      
[Linux]----文件操作(复习C语言+文件描述符)

现在我们就发现之前的数据被截断了!

所以我们现在可以类比与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;
}      
[Linux]----文件操作(复习C语言+文件描述符)

read

我们打开文件就默认他是存在的,不需要携带"O_CREAT"选项

如果文件不存在会返回-1;

[Linux]----文件操作(复习C语言+文件描述符)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#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;
}      
[Linux]----文件操作(复习C语言+文件描述符)

四、文件描述符

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

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
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;
}      
[Linux]----文件操作(复习C语言+文件描述符)
  1. 为什么文件描述符默认是从3开始的呢?那么0,1,2去哪了?

    因为0,1,2被默认打开了

  • 0:标准输入,键盘
  • 1:标准输出,显示器
  • 2:标准错误,显示器
  • [Linux]----文件操作(复习C语言+文件描述符)
  • 这就与我们C语言联系起来了!因为C语言封装的系统接口。

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

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

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

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

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

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

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

    那么进程如何和打开的文件建立映射关系呢??

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

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

标准输入流

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

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;
}      
[Linux]----文件操作(复习C语言+文件描述符)

标准输出流

int main()
{
  const char* s="hello write!\n";
  write(1,s,strlen(s));
}      
[Linux]----文件操作(复习C语言+文件描述符)

标准错误流

int main()
{
  const char* s="hello write!\n";
  write(2,s,strlen(s));
}      
[Linux]----文件操作(复习C语言+文件描述符)

我们看出标准错误流也打印到了显示器上面。

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

验证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);
}      
[Linux]----文件操作(复习C语言+文件描述符)

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

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

0,1,2—>stdin,stdout,stderr—>键盘,显示器,显示器(这些都是硬件呀!)也用你上面的struct file来标识对应的文件吗??

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

如何证明呢?我来带大家看看LInux下的内核结构!!!

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

文件描述符的分配规则

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);
}      
[Linux]----文件操作(复习C语言+文件描述符)

我们发现把1关掉后什么都没有了!

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

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

总结