Linux_系统文件IOopen、write、read、close、文件描述符(磁盘文件和内存文件)、files_struct结构体、文件描述符分配规则、重定向、FILE*与文件描述符的关系、缓冲区)

1.C语言文件IO

*当前路径的概念

在C语言文件操作时调用fopen函数以写的方式打开文件,会自动在当前路径下创建文件

#include<stdio.h>

int main()
{
    
    
  FILE*fp=fopen("Test","w");
  fclose(fp);
  return 0;
}

注意:当前路径并不是指可执行程序的位置,而是当前的工作目录
eg:
在这里插入图片描述

标准输入、标准输出、标准错误

stdin:标准输入- - >键盘
stdout:标准输出- - >显示器
stderr:标准错误- - >显示器

每个进程在打开时都会默认打开这三个输入输出流

2.Linux系统文件IO

打开文件open(sys/types.h - sys/stat.h - fcntl.h)

在这里插入图片描述
两种初始化方式
参数解释:
pathname:要打开的文件名

flags:打开文件选项

  • O_APPEND:追加的方式打开文件
  • O_RDWR:读写方式打开文件
  • O_CREAT:当文件不存在时自动创建文件

mode:打开文件默认权限
注意:这个权限收到系统umask值的影响,umask在Linux-文件权限中介绍不在赘述

返回值:
成功返回文件描述符(file descriptor),失败返回-1

关闭文件close(unistd.h)

在这里插入图片描述
参数解释:
fd:文件描述符
返回值:成功返回0,失败返回-1并设置错误码

*标志位(方便函数传参)

一个整数有32位,每一个比特位代表一种标志,每一个标志通过 | 运算联系起来可以一起传入函数中

eg:
#define X 0x1;
00000000…1
#define Y 0x2;
0000000…10
X|Y就把
0000000…11传入,相当于把X和Y的信息一起传入函数中
在函数内部判断某一个比特位是否为1就代表是否传入这个信息

eg:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
    
    
  umask(0);//将umask值设为0
  int fd=open("Test.txt",O_WRONLY|O_CREAT,0666);
  printf("%d\n",fd);
  close(fd);
  return 0;
}

在这里插入图片描述

从文件中读取文件read(unistd.h)

在这里插入图片描述
参数解释
fd:从那个文件描述符中读数据
buf:读到的数据放到那个缓冲区
count:每次要读几个字节数据
返回值
返回实际读到的字节个数
返回值<=count

向文件写入数据write(unistd.h)

在这里插入图片描述
参数解释
fd:写到那个文件描述符中。
buf:写那个缓冲区中的数据
count:要写多大的字节数

返回值:实际上写了几个字节。

eg:

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

int main()
{
    
    
  umask(0);//将umask值设为0
  int fd=open("Test.txt",O_RDWR|O_CREAT,0666);
  if(fd<0)
  {
    
    
    return -1;
  }
  char ch=0;
  while(1)
  {
    
    
    ssize_t size=read(fd,&ch,1);//每次读取一格字符
    if(size<=0){
    
    
      break;
    }
    write(1,&ch,1);//1文件描述符是标准输出(显示器)的文件描述符
  }
  close(fd);
  return 0;
}

在这里插入图片描述

3.文件描述符(数组下标)

在Linux中系统,默认一个进程会打开3个文件描述符
0,1,2、分别代表标准输入,标准输出,标准错误,对应C语言的(stdin,stdout,stderr)
这个数字本质是一个数组下标

内存文件VS磁盘文件

struct file结构体(内存文件)

Linux系统为了管理保存进程打开的文件,用struct file来描述每一个打开的文件,多个文件之间选择双链表的形式组织起来。这张双链表保存在内存中。
这种文件称为内存文件

文件的构成(磁盘文件)

一个文件不仅仅由文件的内容,还包括修改时间,文件大小等信息。这些信息统称为文件属性

所以:文件=文件内容+文件属性

这个文件称为磁盘文件

文件被打开时有两份,一份是加载到内存的内存文件,一份是磁盘文件。
内存文件:将磁盘文件中的属性信息加载到内存中,形成struct file数据结构。延后式加载数据(当进行文件操作时才加载数据)。
每一个struct file结构体代表一个打开的文件。

struct files_struct结构体

每一个进程的task_struct这个结构体。
结构体中有结构struct file* fd_array[32]数组每个元素都是一个指向内存文件的指针

在这里插入图片描述
不同的进程有不同的files_struct但都指向同一张struct file双链表。文件描述符就是files_struct结构中fd_array数组的数组下标。

4.文件描述符的分配规则

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

int main()
{
    
    
  umask(0);
  int fd=open("a.txt",O_RDWR,0666);
  printf("fd=%d\n",fd);
  close(fd);
  close(0);//关闭标准输出
  fd=open("a.txt",O_RDWR,0666);
  printf("New fd=%d\n",fd);
  close(fd);
  return 0;
}

在这里插入图片描述
在进行文件描述符分配时:从上到下扫描,最小的但是没有被使用的位置开始分配

5.输出重定向原理

将要输出到显示器上的信息重定向到文件中

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

int main()
{
    
    
  umask(0);
  close(1);//关闭标准输出,fd的值为1
  int fd=open("a.txt",O_RDWR|O_CREAT,0666);
  write(1,"1\n",2);
  write(1,"2\n",2);
  close(fd);
  return 0;
}

在这里插入图片描述
只要是往显示器上打印的数据都会写到文件中

重定向的本质:
修改文件描述符fd下标对应的struct file*所指向的内容

C语言FILE*与文件描述符的关系

C语言中FILE是个结构体,内部封装了文件描述符
在这里插入图片描述
C语言中的stdout:
stdout在C语言中可以看作FILE*指针,指向的FILE结构体中的包含的文件描述符为1

stdin与stderr也类似

所以:

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

int main()
{
    
    
  umask(0);
  close(1);//关闭标准输出,此时fd的值为1
  int fd=open("a.txt",O_RDWR|O_CREAT,0666);
  fprintf(stdout,"Hello Linux\n");//因为关闭了1号文件描述符,所以改向文件中写入

  fflush(stdout);//刷新缓冲区
  close(fd);
  return 0;
}

此时stdout中fd=1指向的是新文件,达到了重定向
在这里插入图片描述
根据上述可知:
调用C语言的fopen函数时

  • 给调用用户申请FILE结构体,并且返回FILE*(结构体地址)
  • 在底层调用open函数打开文件,并将open函数的返回值(文件描述符)填充到FILE结构体中的fd上。

类似的输入重定向原理与输出重定向类似。

dup2函数调用实现重定向(fcntl.h / unistd.h)

在这里插入图片描述
在这里插入图片描述
将oldfd下标对应的fd_array数组里函数指针拷贝到newfd下标对应的fd_array数组里函数指针。
也就是说最后fd_array[newfd]=fd_array[oldfd]
eg:dup2(fd,1);
将1号文件描述符所对应的函数指针变成fd号文件描述符所对应的函数指针。
向标准输出写入的数据会被重定向到文件中。

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

int main()
{
    
    
  umask(0);
  int fd=open("a.txt",O_RDWR|O_CREAT,0666);
  if(fd<0){
    
    
    return -1;
  }
  dup2(fd,1);
  printf("Hello Linux\n");
  fflush(stdout);
  close(fd);
  return 0;
}

在这里插入图片描述

6.缓冲区

缓冲的分类

  • 无缓冲

  • 行缓冲:常见对显示器进行刷新数据。如C语言中的printf函数中的\n刷新

  • 全缓冲:常见对文件读写时采用全缓冲。当把缓冲区内容写满才刷新缓冲区

eg:

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

int main()
{
    
    
  //C语言
  printf("Hello printf\n");
  fprintf(stdout,"Hello fprintf\n");

  //系统
  const char* mes="Hello write\n";
  write(1,mes,strlen(mes));
  fork();
  return 0;
}

在这里插入图片描述
上述现象总结:

  1. 打印到显示器上是行缓冲,重定向到文件中是全缓冲。
  2. C语言接口在重定向到文件时打印了两次,系统接口重定向时打印了一次

原因:

  1. 当在显示器上打印时是行刷新,每次打印时\n会刷新缓冲区,所以fork创建子进程时缓冲区里面没有打印的数据
  2. 重定向到文件时变成全缓冲,所以fork创建子进程时之前没有刷新缓冲区,子进程缓冲区里里面有打印的数据。程序退出时,进程具有独立性,父子进程刷新缓冲区,导致打印了两次。
  3. 系统调用没有缓冲区,只有printf和fprintf存在缓冲区,所以系统调用只打印了一次。

C库函数是对系统调用的封装。所以缓冲区是语言提供的
这个缓冲区在内存中,C语言中FILE结构体中不仅有文件描述符,还包括缓冲区(用户缓冲区)

FILE中的缓冲区在用户区,刷新时先把用户区的缓冲区数据拷贝到内核缓冲区上,在刷新到显示器或磁盘上

fflush(stdout)的原因

再次来分析代码:

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

int main()
{
    
    
  umask(0);
  close(1);//关闭标准输出,此时fd的值为1
  int fd=open("a.txt",O_RDWR|O_CREAT,0666);
  fprintf(stdout,"Hello Linux\n");

  fflush(stdout);//刷新缓冲区
  close(fd);
  return 0;
}

之所以要fflush(stdout)的原因:
重定向文件后,语言缓冲模式变为全缓冲。如果最后不强制刷新的话,close函数将文件关闭,程序结束后,缓冲区里面的数据将没有办法刷新到文件中了。所以一定要强制刷新缓冲区

但如果用C语言的接口fclose时会自动刷新缓冲区,就不需要手动强制刷新缓冲区

猜你喜欢

转载自blog.csdn.net/dodamce/article/details/122173300