Linux文件系统——文件描述符

Linux文件系统——文件描述符_第1张图片

文章目录

    • 0. 前言
    • 1. C文件接口
      • 文件打开
      • 文件写入
    • 2. 系统文件接口
      • open && write && close
      • open的返回值

本章gitee代码仓库:文件描述符

0. 前言

基础原理知识:

  1. 文件 = 内容 + 属性

  2. 文件分为:打开的文件(本章重点讲解)和没打开文件

    打开的文件,本质上是进程将其打开

    没打开的文件,存储在磁盘

  3. 要访问这个文件,根据冯诺依曼体系结构,这个文件必须先加载到内存当中,文件又是内容+属性,那么第一步肯定是要先将文件的属性先加载进来,然后根据要不要对这个文件进行读取修改,从而决定是否将内容加载。

  4. 一个进程可以打开多个文件,操作系统内部一定是存在者大量的被打开的文件,操作系统要对这些文件进行管理,肯定也是先描述,再组织。在内核当中,一个被打开的文件,都必须有自己的打开对象,这里面包含了文件的很多属性。而Linux内核是用C语言写的,所以描述这个文件,用的是struct结构体:

    struct xxx
    {
        文件属性;
        struct xxx *next;
    }
    

    这样对文件的管理就变成了对链表的增删查改。

1. C文件接口

文件打开

打开文件的接口fopen,头文件也是stdio.h

#include
#include
int main()
{
  printf("pid:%d\n",getpid());
  FILE *fp = fopen("log.txt","w");
  if(fp == NULL)
  {
    perror("fopen");
    return 1;
  }
  fclose(fp);
  sleep(1000);
  return 0;
}

如果不指定文件路径,则默认在当前工作目录创建或者访问这个文件

这个工作目录就是该进程的工作目录,指令ll /proc/进程pid,可查看进程的工作目录,cwdcurrent working directory

Linux文件系统——文件描述符_第2张图片

如果我们修改当前进程的工作目录,则这个创建的文件,则会创建到修改的工作目录里面

chdir("/home/Pyh")

Linux文件系统——文件描述符_第3张图片

文件写入

这里采用fwrite作为演示

const char *str = "hello linux\n";
fwrite(str,strlen(str),1,fp);

这里的strlen(str),不需要再加上1('\0'),因为字符串末尾加'\0'是C语言的规定,而这个和文件并没有关系,文件只关心这个内容

Linux文件系统——文件描述符_第4张图片

我们这里发现,这里写入的时候,都会将文件的内容进行清空然后再写入,而>这个重定向符号,也是会将文件进行清空再写入。

这本质上就因为"w"操作,会将原始文件的内容进行清空再写入,所以>打开文件的方式肯定是"w" 方式。

如果想要对文件不进行清空,那么可以采用"a"操作进行打开a就就是append追加,而追加重定向>>,就是以"a"方式打开文件。

Linux文件系统——文件描述符_第5张图片

C语言程序在启动的时候,会默认打开三个输入输出流(文件):

  • stdin:标准输入,键盘文件
  • stdout:标准输出,显示器文件
  • stderr:标准错误,显示器文件

Linux文件系统——文件描述符_第6张图片

那么我们可以直接向这些文件里面写入,例如:

fwrite(str,strlen(str),1,stdout);

Linux文件系统——文件描述符_第7张图片

2. 系统文件接口

文件是存储在磁盘上的,而磁盘是外设,所以访问文件就是访问硬件。而硬件是在操作系统之下,是被操作系统管理的,而我们的普通用户,是没有资格去直接访问硬件的,所以需要通过操作系统提供的接口来访问。而这就是系统调用,C语言的库函数,类似printffprintffscanffwrite这基本上都封装了系统调用。

open && write && close

在系统调用中,我们可以采用open接口打开文件

Linux文件系统——文件描述符_第8张图片

int main()
{
    umask(0);	//修改权限掩码
    int fd = open("log.txt",O_WRONLY|O_CREAT,0666);	//读取文件|创建,0666设置权限
    if(fd < 0)	//创建失败返回-1
    {
    perror("open file error\n");
    return 1;
    }
    return 0;
}

因为涉及到创建文件,所以权限是必须要告诉系统的,不然会出现乱码。

Linux文件系统——文件描述符_第9张图片

这里我们还修改了权限掩码,具体知识,之前在此篇文章讲到过,有兴趣可查看——Linux权限

  • O_RONLY:只读打开
  • O_WRONLY:只写打开
  • O_RDWR:读写打开

上面这三个必须指定一个且只能指定一个

  • O_CREAT:若文件不存在则创建,但必须要使用mode选项,指定文件访问的权限
  • O_APPEND:追加写入
  • O_TRUNC:清空文件内容

有了这些组合,我们就能推断出C语言库函数中的这些"w""a"封装的是哪些

Linux文件系统——文件描述符_第10张图片

#include
#include
#include
#include
#include
#include

int main()
{
  umask(0);
  //int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  if(fd < 0)
  {
    perror("open file error\n");
    return 1;
  }
  const char *str = "abc";
  write(fd,str,strlen(str));
  close(fd);                                                                          
  return 0;                          
} 

Linux文件系统——文件描述符_第11张图片

open的返回值

在操作系统内部描述一个被打开的文件,会直接或者间接包含这些信息:

  • 在磁盘的位置
  • 文件的基本属性
  • 文件的内核缓冲区信息
  • 文件结构体指针struct file *next

在进程的task_struct结构体中,里面文件结构体指针struct files_struct *files,它指向了一个文件描述符表struct files_struct,这个表里面包含一个指针数组struct file *fd_array[],它里面存放的就是struct file *

当一个文件打开的时候,系统会创建一个文件对象,进程指向的文件描述表里面,就会给它分配一个没有占用的下标来指向这个对象。

所以这个open的返回值,其实就是返回这个数组的下标。

Linux文件系统——文件描述符_第12张图片

#include
#include
#include
#include
#include

int main()
{
  umask(0);
  //int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  int fd1 = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  int fd2 = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  int fd3 = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  int fd4 = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  if(fd1 < 0)
  {
    perror("open file error\n");
    return 1;
  }
  const char *str = "abc";
  write(fd1,str,strlen(str));
  printf("fd1:%d\n",fd1);
  printf("fd2:%d\n",fd2);
  printf("fd3:%d\n",fd3);
  printf("fd4:%d\n",fd4);
  close(fd1);
  return 0;
}

我们发现这个open返回的就是这个文件描述符对应数组的下标

Linux文件系统——文件描述符_第13张图片

但是这里是从3开始的,这就是以为C语言程序会默认打开三个输入输出流文件,这里我们也可以验证一下

C语言FILE这个结构体就包含了这个文件描述符

printf("stdin->%d\n",stdin->_fileno);
printf("stdout->%d\n",stdout->_fileno);
printf("stderr->%d\n",stderr->_fileno);

Linux文件系统——文件描述符_第14张图片

我们发现,这三个输入输出流的文件描述符分别是0、1、2

这里程序启动打开三个输入输出流,并不单是C语言的特性,而是操作系统的特性

因为在计算机开机的时候,我们的屏幕和键盘就被默认打开了,所以我们启动进程的时候,只需要将它们的文件对象地址填到进程的pcb里面

我们再来看一个现象:

close(1);
int n = printf("hello linux\n");
fprintf(stderr,"printf %d\n",n);

Linux文件系统——文件描述符_第15张图片

我们这里已经关闭的了标准输出,可是这里显示器上还是输出了信息,这是因为每个文件结构体对象中都包含了一个count引用计数,标准输出和标准错误都是指向的显示器文件,这里的计数就是2,所以我们close(1)其实是将该文件计数减一,然后再将1号位置置空,如果这个这个count计数不为零,则不管它,如果为零了,则系统回收这个文件对象。

你可能感兴趣的:(原创,Linux,linux,jquery,运维)