【linux】:linux下文件的使用以及文件描述符

这篇具有很好参考价值的文章主要介绍了【linux】:linux下文件的使用以及文件描述符。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

文章目录

  • 前言
  • 一、linux系统中的文件操作以及文件接口
  • 二、文件描述符
    • 1.文件描述符的分配规则
    • 2.重定向
  • 总结

前言

理解文件原理和操作:

我们先快速回忆下一C语言的文件操作:

【linux】:linux下文件的使用以及文件描述符

 首先看一下fopen函数的使用,然后我们写一段简单的C语言打开文件的代码如下图:

#include <stdio.h>
#define LOG "log.txt"
int main()
{
  //默认写方式打开文件,如果文件不存在,就删除它
  FILE* fp = fopen(LOG,"w");
  if (fp==NULL)
  {
    perror("fopen:");
    return 1;
  }
  //正常进行文件操作
  const char* msg = "hello friends\n";
  int cnt = 1;
  while (cnt)
  {
    fputs(msg,fp);
    --cnt;
  }
  fclose(fp);
  return 0;
}

接下来我们运行一下:

【linux】:linux下文件的使用以及文件描述符

 通过上图我们可以看到成功将我们的字符串写入文件中,那么这次我们什么都不写再看看:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符

 为什么原先log里的字符串没了呢?因为我们打开的方式是w,文件内容会先被清空再写入。

下面我们再介绍一个接口snprintf:

【linux】:linux下文件的使用以及文件描述符

普通的printf是默认向显示器打印,fprintf是指定文件流向指定文件进行打印,下面我们演示一下:

首先我们修改一下代码,将fputs替换为fprintf函数

int main()
{
  //默认写方式打开文件,如果文件不存在,就删除它
  FILE* fp = fopen(LOG,"w");
  if (fp==NULL)
  {
    perror("fopen:");
    return 1;
  }
 //正常进行文件操作
  const char* msg = "hello friends\n";
  int cnt = 5;
  while (cnt)
  {
   // fputs(msg,fp);
    fprintf(fp,"%s:%d:sxy\n",msg,cnt);
    --cnt;
  }
  fclose(fp);
  return 0;
}

【linux】:linux下文件的使用以及文件描述符

 运行结果如上图所示,并且我们不仅可以像文件中打印,也可以在输出流中打印:

【linux】:linux下文件的使用以及文件描述符

 我们都知道,一个程序会默认打开以上三个输入输出流,下面我们像stdout打印一下:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符 运行后我们发现直接就能显示不用再打开log.txt查看了,下面我们再来演示一下snprintf函数:

int main()
{
  //默认写方式打开文件,如果文件不存在,就删除它
  FILE* fp = fopen(LOG,"w");
  if (fp==NULL)
  {
    perror("fopen:");
    return 1;
  }
 //正常进行文件操作
  const char* msg = "hello friends\n";
  int cnt = 5;
  while (cnt)
  {
    char buffer[256];
    snprintf(buffer,sizeof(buffer),"%s:%d:sxy\n",msg,cnt);
    fputs(buffer,fp);
    --cnt;
  }
  fclose(fp);
  return 0;
}

 snprintf就是将msg格式化到buffer数组里并且以buffer的大小进行打印,然后将数组中的数据写入文件中。

【linux】:linux下文件的使用以及文件描述符

 后面还有a追加写入等的方式我们就不在演示,这些都是我们C语言的时候学过的。

以上都是语言进行文件的操作,下面我们进入今天的正题,在系统层面使用文件操作。


一、linux系统中的文件操作以及文件接口

首先我们看一下系统中文件的接口open:

【linux】:linux下文件的使用以及文件描述符

然后我们再看一下这个接口的返回值是什么:

【linux】:linux下文件的使用以及文件描述符 如果成功返回一个新的文件描述符,否则返回-1,下面我们演示一下这个接口:

首先一定要包头文件:

【linux】:linux下文件的使用以及文件描述符

这个函数的第一个参数与刚刚一样都是文件名,第二个参数其实是用位图实现的,就是用一个标志位充当不同的行为,如下图所示:

【linux】:linux下文件的使用以及文件描述符 在上图中我们就利用位图传不同的flags就能打打印不同的数,并且不仅可以打印一个也可以同时打印多个:

【linux】:linux下文件的使用以及文件描述符

 【linux】:linux下文件的使用以及文件描述符

而第二个参数的使用方式与上面演示的一样,都有下面这几种选项:

【linux】:linux下文件的使用以及文件描述符 这些都是宏像我们刚刚演示的#define的那样。下面我们演示这个接口:

//系统层面
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define LOG "log.txt"
int main()
{
  int fd = open(LOG,O_WRONLY);
  printf("fd:%d\n",fd);
  close(fd);
  return 0;
}

 0_CREAT是如果没有这个文件就给我们创建一个文件,O_WRONLY是只写的意思。

【linux】:linux下文件的使用以及文件描述符

 我们先删除原先的log文件,运行后发现返回值为-1,也就是说出错了,那是什么原因呢?我们修改一下代码如果出错就打印一下出错原因:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符

因为我们要写这个文件,但是这个文件并不存在,所以我们没有的话应该先创建一个文件:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符

 这次我们发现文件创建成功了,错误码为0。我们这里用的是root所以文件的权限没有问题,如果这里是普通用户创建文件的话文件权限是乱码有问题的,如下图:

【linux】:linux下文件的使用以及文件描述符

 所以我们一般创建文件用的是三个接口的那个函数,我们演示的这个函数没有提供文件权限的接口。

【linux】:linux下文件的使用以及文件描述符

 下面我们用三个接口的open创建一下文件:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符

 我们先删掉之前的文件,然后重新运行一下发现这次文件的权限没有问题是正确的(不是666是因为受umask影响)。所以,一个文件被创建默认权限受umask影响,那么如果想不受umask影响呢?

【linux】:linux下文件的使用以及文件描述符

我们可以直接将所有的权限掩码设置为0:

【linux】:linux下文件的使用以及文件描述符 【linux】:linux下文件的使用以及文件描述符

 我们发现这次就不受umask影响了。接下来我们写入文件试试:

关于写入的文件接口的函数为write:

【linux】:linux下文件的使用以及文件描述符

 向文件描述符写入一个缓冲区大小为count,返回值是实际写入多少字节,写入失败返回-1.

int main()
{
  umask(0);
  int fd = open(LOG,O_WRONLY | O_CREAT,0666);
  if (fd==-1)
  {
    printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno));
  }
  else printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno));
  printf("fd:%d\n",fd);
  const char* msg = "hello friend";
  int cnt = 5;
  while (cnt)
  {
    char line[128];
    snprintf(line,sizeof(line),"%s,%d\n",msg,cnt);
    write(fd,line,strlen(line));
    --cnt;
  }
  close(fd);
  return 0;
}

接下来我们运行一下:

【linux】:linux下文件的使用以及文件描述符

 通过上图我们发现写入成功,其实在我们刚刚strlen的时候发现我们并没有带\0,为什么没有\0也能成功写入呢,因为\0是C语言的标准不是系统的规定,并且\0在系统中写入会变成乱码。

下面我们重新写一下:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符

 在我们重新运行后发现文件中保留了上一次的数据,也就是说O_CREAT | O_WRONLY是不会清空原来的文件再写入的,这个时候我们加上O_TRUNC选项就可以每次写入前先清空文件了:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符

 搞定了写入那么饿追加就不是问题了,追加只需要注意将写入替换为追加,并且我们不希望每次打开文件先清空文件,所以不需要O_TRUNC选项:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符

 为什么我们上面的运行结果不正确呢?因为0_APPEND只是追加不是追加写,所以才没有写入。下面我们修改一下代码:

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符

 这样我们就成功的追加了字符串,那么如果是只读呢?其实只读就很简单了,只读只需要调用两个接口的open函数即可,因为只读默认认为是有文件的。

【linux】:linux下文件的使用以及文件描述符

 读取的返回值就是当前读取了多少字节,读取到文件结尾就是0,失败就是-1。

int main()
{
  umask(0);
  int fd = open(LOG,O_RDONLY);
  if (fd==-1)
  {
    printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno));
  }
  else printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno));
  char buffer[1024];
  ssize_t n = read(fd,buffer,sizeof(buffer)-1); //使用系统接口来进行IO的时候,一定要注意\0问题。
  if (n>0)
  {
    buffer[n] = '\0';
    printf("%s\n",buffer);
  }
  close(fd);
  return 0;
}

【linux】:linux下文件的使用以及文件描述符

 我们在读取的时候为什么-1呢?因为我们不考虑\0的问题,只有当读取的时候返回值大于0我们将\0放到n的位置以字符串的形式打印的时候才需要\0,所以最后需要将\0放到n的位置再打印。

以上就是系统级的文件接口,而入C语言等要进行文件操作都必须调用系统接口。

二、文件描述符

1.0 & 1 & 2

通过对open函数的学习,我们知道了文件描述符就是一个小整数。linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。0 1 2对应的物理设备一般是:键盘,显示器,显示器,所以输出还可以有以下方式:

下面我们用文件的读写完成简单的输入输出:

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

int main()
{
  char buf[1024];
  ssize_t s = read(0,buf,sizeof(buf));
  if (s>0)
  {
    buf[s] = 0;
    write(1,buf,strlen(buf));
    write(2,buf,strlen(buf));
  }
  return 0;
}

 1和2代表标准输出和标准错误,我们先去读一下缓冲区,当返回值是缓冲区的最后一个字符,我们在这个位置放入\0,然后写入刚刚读取到的字符,下面我们运行一下:

【linux】:linux下文件的使用以及文件描述符

 通过这个小例子我们就知道了文件描述符就是从0开始的小整数,当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了file结构体,表示一个已经打开的文件对象,而进程执行open系统调用,所以必须让进程和文件关联起来,每个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

文件描述符的分配规则

我们写一个只读的代码来获取文件的fd:

int main()
{
  int fd = open("my.txt",O_RDONLY);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  printf("fd:%d\n",fd);
  close(fd);
  return 0;
}

【linux】:linux下文件的使用以及文件描述符

 我们发现fd是3,那么这个时候我们把0关闭了再看看是什么结果:

int main()
{
  close(0);
  int fd = open("my.txt",O_RDONLY);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  printf("fd:%d\n",fd);
  close(fd);
  return 0;
}

【linux】:linux下文件的使用以及文件描述符

 通过运行结果我们发现,当我们将0关闭后文件描述符立马变成了0,我们再看看把2关闭了如何:

int main()
{
  //close(0);
  close(2);
  int fd = open("my.txt",O_RDONLY);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  printf("fd:%d\n",fd);
  close(fd);
  return 0;
}

【linux】:linux下文件的使用以及文件描述符

 当我们将2关闭后文件描述符又变成了2,这就说明文件描述符的分配规则是:在file_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

2.重定向

还是刚刚的代码,如果我们将1关闭了会出现什么情况呢?

int main()
{
  //close(0);
  close(1);
  int fd = open("my.txt",O_WRONLY | O_CREAT,00644);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  printf("fd:%d\n",fd);
  fflush(stdout);
  close(fd);
  exit(0);
}

【linux】:linux下文件的使用以及文件描述符

 通过运行结果我们可以发现,本来应该输出到显示器上的内容,输出到了文件中,其中fd=1,这种现象叫做输出重定向,常见的重定向有> >> <。那么重定向的本质是什么呢?我们看下图:

【linux】:linux下文件的使用以及文件描述符

 当然对于重定向来说还有一个系统调用接口,这个函数叫dup2:

#include <unistd.h>
int dup2(int oldfd, int newfd);

对于这个函数的参数1来说是被换的那个描述符,参数2是像0,1,2,这样的默认描述符,接下来我们使用这个函数完成以下重定向:

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

int main()
{
  int fd = open("my.txt",O_WRONLY | O_CREAT,00644);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  dup2(fd,1);
  printf("fd:%d\n",fd);
  close(fd);
}

【linux】:linux下文件的使用以及文件描述符

 通过上图我们可以看到成功的重定向到文件中。下面我们看看文件缓冲区的概念:

我们先编写一段代码看看现象:

int main()
{
  //c库中的
  fprintf(stdout,"hello fprintf\n");
  //系统调用
  const char* msg = "hello write\n";
  write(1,msg,strlen(msg));
  //fork();
  return 0;
}

【linux】:linux下文件的使用以及文件描述符

 可以正常打印,那么我们调用一下子进程会变成什么呢?

【linux】:linux下文件的使用以及文件描述符

【linux】:linux下文件的使用以及文件描述符我们发现运行结果和刚刚是一样的,难道没有什么用吗?我们接下来重定向到文件中:

【linux】:linux下文件的使用以及文件描述符 这个时候我们发现了,居然会多打印一个fprintf,为什么会出现这样的现象呢?

一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据
的缓冲方式由行缓冲变成了全缓冲。
而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
但是进程退出之后,会统一刷新,写入文件当中。
但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的 一份数据,随即产生两份数据。
write 没有变化,说明没有所谓的缓冲

所以:

printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。 那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。
行缓冲:碰到\0就刷新缓冲区
全缓冲:缓冲区满了才刷新
显示器采用的刷新策略:行缓冲。普通文件采用的刷新策略:全缓冲
那么缓冲区在哪里呢?在你进行fopen打开文件的时候,你会得到FILE结构体,缓冲区就在这个FILE结构体中。

总结

对于linux下的文件操作而言,C语言等对于文件操作的函数都是经过linux系统文件接口来封装的,我们在用C语言文件操作的时候看着很简单的一句代码在系统调用中会有很多的操作,对于文件描述符实际上就是file_struct中的指针数组的下标,文件描述符的分配规则就是优先从最小的下标开始分配文章来源地址https://www.toymoban.com/news/detail-412178.html

到了这里,关于【linux】:linux下文件的使用以及文件描述符的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《Git入门实践教程》前言+目录

    版本控制系统(VCS)在项目开发中异常重要,但和在校大学生的交流中知道,这个重要方向并未受到重视。具备这一技能,既是项目开发能力的体现,也可为各种面试加码。在学习体验后知道,Git多样化平台、多种操作方式、丰富的资源为业内人士提供了方便的同时,也造成

    2024年02月10日
    浏览(72)
  • 在Linux中使用rsync进行备份时如何排除文件和目录?

    在Linux系统中,rsync是一种强大的工具,用于文件和目录的备份和同步。然而,在进行备份时,我们可能希望排除某些文件或目录,例如临时文件、日志文件或其他不需要备份的内容。本文将介绍在Linux中使用rsync进行备份时如何排除文件和目录的方法。 方法一:使用–exclud

    2024年02月07日
    浏览(37)
  • FPGA学习实践之旅——前言及目录

    很早就有在博客中记录技术细节,分享一些自己体会的想法,拖着拖着也就到了现在。毕业至今已经半年有余,随着项目越来越深入,感觉可以慢慢进行总结工作了。趁着2024伊始,就先开个头吧,这篇博客暂时作为汇总篇,记录在这几个月以及之后从FPGA初学者到也算有一定

    2024年02月03日
    浏览(58)
  • 关于Linux文件和目录的777、755、644权限,chmod -R 777 xxx,chmod -R 755 xxx,以及chmod -R 644 xxx 命令意义

    哈喽 大家好啊 之前相对linux的某个文件夹下的文件执行读写操作权限的时候 直接会 chmod -R 777 xxx 路径  赋值某个文件夹读写权限 在linux中,文件或者目录权限分为3种: r:4 读 w:2写 x:1 执行 -:对应的数值0 rwx= 4+2+1 表示读写执行权限 rw= 4+2=6 表示可读写,但是不可以执行 rx =

    2024年02月02日
    浏览(54)
  • 【Shell 命令集合 磁盘管理 】Linux 列出目录中的文件和子目录 ls命令使用教程

    Shell 命令专栏:Linux Shell 命令全解析 ls命令是Linux系统中常用的一个命令,用于列出目录中的文件和子目录。它的作用是显示当前工作目录中的文件和目录列表。 当我们在终端输入ls命令时,系统会将当前目录中的文件和子目录的名称以及相关信息显示出来。这些信息包括文

    2024年02月08日
    浏览(109)
  • Linux:使用for+find查找文件并cp到其他目录,文件名带有空格

    在终端窗口中,用 shell 命令,批量拷贝文件到指定目录。 我是在 Windows 系统上,通过 git bash 终端来执行 shell 命令的。 结果: 于是把 find 命令单独拿出来执行: 结果: 发现文件路径和文件名存在空格。 查找解决办法 复制时,如何处理带空格的文件路径或文件名。 查找结

    2024年01月22日
    浏览(110)
  • 详解—[Linux 文件描述符]

    一、文件描述符的概念  文件描述符 是Linux系统中用于访问文件的一种机制,它是一个 非负整数 ,用于指代 被打开的文件 。 在Linux中,所有执行I/O操作的系统调用都是通过文件描述符完成的。 文件描述符是一个简单的非负整数,用来表明每一个被进程打开的文件。 在程序

    2024年02月04日
    浏览(57)
  • 【Linux】文件描述符 (上篇)

    本章开始,我们将进入Linux文件相关的学习与操作,从复习回顾C语言的文件操作接口,再从操作系统角度出发,学习系统调用接口。再了解虚拟文件系统,内核管理文件的数据结构。最后通过学习的操作文件的系统接口来模拟实现C语言的文件操作接口,最后对之前实现的sh

    2024年02月12日
    浏览(49)
  • 【Linux】文件描述符 - fd

    使用 man open 指令查看手册: open 函数具体使用哪个,和具体应用场景有关。如:目标文件不存在,需要 open 创建,则第三个参数表示创建文件的默认权限;否则使用两个参数的 open。 write read close lseek ,类比 C 文件相关接口。 操作文件,除了使用 C 语言的接口【Linux】回顾

    2024年03月23日
    浏览(40)
  • (20)Linux初始文件描述符

    前言:本章我们介绍 O_WRONLY, O_TRUNC, O_APPEND 和 O_RDONLY。之后我们开始讲解文件描述符。 C 语言在 w 模式打开文件时,文件内容是会被清空的,但是 O_WRONLY 好像并非如此? 代码演示: 当前我们的 log.txt 内有 5 行数据,现在我们执行下面的代码:  运行结果: 我们以前在 C 语

    2024年02月02日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包