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

news2024/11/24 9:35:24

文章目录

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


前言

理解文件原理和操作:

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

4b9bc1ffdc624addb785792f86abc3c1.png

 首先看一下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;
}

接下来我们运行一下:

050b5363cb0946739adca264abd63702.png

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

fdf2d420d4ca4888b00b90c5d7a6607c.png

f71a0ec33b994ff58bd9cb0ddc78d4a8.png

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

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

2b6de12b9b544b71a13fef0743c45ad8.png

普通的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;
}

ac8e727dc6814dbca0f0f0c8fdbc22b7.png

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

14126e135e0a4a7ba4270da5e628e96a.png

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

ae9c660e6b3f4b399410842b31deb5b7.png

93f26cbf0ca149c298296e0c1d2f1c2c.png 运行后我们发现直接就能显示不用再打开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的大小进行打印,然后将数组中的数据写入文件中。

2d6981692cb7420088ce6b6c2bbd068c.png

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

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


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

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

8dbfb80a3e1c49f0982e2f01fce296cb.png

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

3abe030b766049a589af46f8bb9dd6e0.png 如果成功返回一个新的文件描述符,否则返回-1,下面我们演示一下这个接口:

首先一定要包头文件:

f6f45daaff7f4253bd2d00edece224fe.png

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

71dc49ee99a047e8bdb2933b6ed74240.png 在上图中我们就利用位图传不同的flags就能打打印不同的数,并且不仅可以打印一个也可以同时打印多个:

11e7d9104cd04cacbb49edd82e6e8aad.png

 9cd8b53f8612417e96b7ca5667a85e8e.png

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

ded46c57d792467c9c22c1957b0b86bf.png 这些都是宏像我们刚刚演示的#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是只写的意思。

26eed91ff2a043a1959127329ff92c40.png

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

e82987679bca408dbfde187b3bac2f22.png

23c4470e521e47c4872fb4fcb2f98c8b.png

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

f9d24ccf8c94494d9440a3ea6dade890.png

19d6d81111b44e0bba71a773abfdaddb.png

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

54c0640266414b69b5971c6241213e45.png

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

836cf421c1c744d9966bdafe148f1655.png

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

d4a86f29b9e74edfbc2abab990c7ceb1.png

63484de293c84158850e4218d952f320.png

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

b200727d86834cedbaafafcbbe8f0703.png

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

f23d58fa140a4737872ee28e42e13343.png 8c6d6e03bf2f431aae700f7b8f38432d.png

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

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

2e5a4e10ee7b4c5e8af7a8d75fee4944.png

 向文件描述符写入一个缓冲区大小为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;
}

接下来我们运行一下:

ac5e4beaed3540afbb8a1bc69049302d.png

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

下面我们重新写一下:

7f10b275c7ef477bad949c4a9f737270.png

ed5e1f7c5d66420b99d5e6250c48eb19.png

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

1c4eb69d6df44093b2b4b60f2eead12f.png

791fa3dc96a54d3fb7fd60fd2f00e5c9.png

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

f8e39f73532d4533b43a35eb79d7fde4.png

22efbf0b454b4973b77e181f6f253aea.png

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

74ede980ace7475da6d2eafba70c8683.png

f39e110369584cb3ae869902b4a276ca.png

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

9bbef5925f90420b9ed3b558a6f643e5.png

 读取的返回值就是当前读取了多少字节,读取到文件结尾就是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;
}

c04ac4cc81d243aabc25cf2e6f80fa10.png

 我们在读取的时候为什么-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,然后写入刚刚读取到的字符,下面我们运行一下:

47b5b5784b704676aeda8962fb08b2cb.png

 通过这个小例子我们就知道了文件描述符就是从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;
}

ca683570a2d34bda840101b991def3a4.png

 我们发现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;
}

04da7ea731724a0fb444113ae6b9bd55.png

 通过运行结果我们发现,当我们将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;
}

ec82e3f6f34a42588786d6d5f60f46bf.png

 当我们将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);
}

ca0bd90ac21443b88a3f895cf31fd55c.png

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

e03cac692e2e4c45a1bdec2d05d94bea.png

 当然对于重定向来说还有一个系统调用接口,这个函数叫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);
}

a3c84da75e0748109c7702f3690d4404.png

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

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

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

5dedd5dd9bdf43f08646b979766a759e.png

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

269261c8d26a4f9d8e665a2273f42e1c.png

8cced6275c8a430d81155683837ca885.png我们发现运行结果和刚刚是一样的,难道没有什么用吗?我们接下来重定向到文件中:

88fdddb8526441c58b93c3215debcda3.png 这个时候我们发现了,居然会多打印一个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中的指针数组的下标,文件描述符的分配规则就是优先从最小的下标开始分配

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/426292.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用conda来管理虚拟环境

百度miniconda 下载windows版&#xff0c;安装的时候添加环境变量Conda -info -e 可以看到只有一个虚拟环境​ ​​​​​​3.Conda命令 &#xff08;1&#x…

高效学习方法分享和学习工具推荐

方法不对&#xff0c;努力白费&#xff1b;方法对了&#xff0c;事半功倍&#xff01; 口号喊起来&#xff0c;气氛拉满&#xff01; 我现在是吉林大学计算机科学与技术大四在读&#xff08;2023春季学期&#xff09;&#xff0c;刚好趁这个活动回顾一下我的大学生活。上过大学…

Android: 在wsl中使用libevent实现http服务器,并编写Android app 实现文件下载

在wsl中使用libevent实现http服务器&#xff0c;并编写Android app 实现文件下载 PC端&#xff1a; 在WSL中编译libevent-2.1.8-stable sample中的http-server.c&#xff0c;实现http测试用服务器 Makefile如下&#xff1a; #指定源文件为*.c src $(wildcard *.c) #通过src…

houjie-cpp面向对象

houjie 面向对象 面向对象&#xff08;上&#xff09; const 在一个函数后面放const&#xff0c;这个只能修饰成员函数&#xff0c;告诉编译器这个成员函数不会改数据 const还是属于函数签名的一部分。 引用计数&#xff1a;涉及到共享的东东&#xff0c;然后当某个修改的时候&…

Java的时代依然还在,合格的Java工程师成为紧缺人才

Java的时代依然还在&#xff0c;合格的Java工程师成为紧缺人才编程语言的世界变化莫测&#xff0c;在其中浮浮沉沉28年的Java&#xff0c;也经历见证了很多语言的兴起和衰败。在最新的编程语言排行榜中&#xff0c;Java依旧位居前三&#xff0c;可见Java的发展后劲有多强&#…

BlockCanary 卡顿监测

作者&#xff1a;海象 前言 最近在处理项目中的拍摄视频后上传界面卡顿的问题,找到 BlockCanary 这个工具来定位,由于不支持高版本 Android,当时在定位卡顿时先将项目的 targetSdk 版本降下来,当然这不是个长久的办法,打算花一点时间适配下高版本,先过一遍源码流程 网上很多博…

ESP32 分区表

ESP32 分区表 1. 分区表概述 ESP32 针对 flash 进行划分&#xff0c;划分为不同的区域用作不同的功能&#xff0c;并在flash的 0x8000 位置处烧写了一张分区表用来描述分区信息。 分区表可以根据自己的需要进行配置&#xff0c;每一个分区都有其特定的作用&#xff0c;可根据…

有学生问我,重构是什么?我应该如何回答?

重构到底是什么&#xff1f;只是代码的推倒重新编码&#xff1f;还是有规则、有方法可寻&#xff1f;当然&#xff0c;结论肯定是有的&#xff0c;本文&#xff0c;我们通过一个简单的实例&#xff0c;来理解一下重构。 1.借助一个实例需求 这是一个影片出租店用的程序&#…

管理笔记2职级管理

一职级目的&#xff1a; 1 员工发展的职业通道 2 招聘、晋升的公平性 二 能力模型 类似职级 三 晋升 当前级别的事做到了&#xff0c;已经做到下一个级别的事 考虑点&#xff1a;1考虑当前阶段时间&#xff0c;不会频繁晋升&#xff0c;2考虑绩效。 不断成长、主动做事&a…

认证授权SpringSecurity

如何引入SpringSecurity作为项目的权限认证服务 1.引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency><groupId>org.…

客快物流大数据项目(一百一十三):注册中心 Spring Cloud Eureka

文章目录 注册中心 Spring Cloud Eureka 一、Eureka 简介 二、架构图

不得不了解的linux网络配置

目录一、查看及测试网络1.1查看网络配置1.1.1ifconfig命令—查看网络接口地址1.1.2route命令—查看路由条目1.1.3hostname命令—查看主机名称1.1.4netstat命令—查看网络连接情况1.1.5 ss命令/lsof 命令— 获取socket(套接字)统计信息1.2测试网络连接1.2.1ping命令—测试网络连…

1 Nginx跨域配置

跨域问题在之前的单体架构开发中&#xff0c;其实是比较少见的问题&#xff0c;除非是需要接入第三方SDK时&#xff0c;才需要处理此问题。但随着现在前后端分离、分布式架构的流行&#xff0c;跨域问题也成为了每个Java开发必须要懂得解决的一个问题。 跨域问题产生的原因 产…

项目管理职业发展前景怎么样?

项目管理职业发展前景怎么样&#xff1f;我们可以从这三个问题出发—— 第一个问题&#xff1a;在中国「项目管理」的专业性的体现/认同度如何&#xff1f;缺少专业知识对未来工作选择的限制程度如何&#xff1f; 由于现在有不少跨专业从业者&#xff0c;他们通过自学考证等途…

计算广告(十二)

FFM模型 FFM&#xff08;Field-aware Factorization Machine&#xff0c;领域感知因子分解机&#xff09;是一种广泛应用于推荐系统和点击率预测&#xff08;CTR&#xff09;等任务的机器学习模型。 它是基于FM&#xff08;Factorization Machine&#xff0c;因子分解机&…

nuscenes instance 调研笔记

nuscenes instance 调研笔记&#xff1a; 前言 nuscenes这个自动驾驶数据集挺大&#xff0c;官方devkit还提供了各种操作用于提高科研人员的效率&#xff0c;但是吧&#xff0c;东西多了学起来就乱七八糟的&#xff0c;本文仅以提取每个instance关联的所有images为例子&#…

USRP具有MIMO系统如何同步

MIMO系统要求 时间和频率同步&#xff0c;并且每个通道必须满足下面两个要求&#xff1a; 1&#xff09;时钟必须同步对齐 2&#xff09;对DSP操作时间也要对齐&#xff0c;来自同一的时钟边缘 波束成形和测向要求 除了采样时间和采样时钟对准外&#xff0c;系统还必须在每个…

华为OD机试 - 相同数字组成图形的周长(Java JS Python)

题目描述 有一个6464的矩阵,每个元素的默认值为0,现在向里面填充数字,相同的数字组成一个实心图形,如下图所示是矩阵的局部(空白表示填充0): 数字1组成了蓝色边框的实心图形,数字2组成了红色边框的实心图形。 单元格的边长规定为1个单位。 请根据输入,计算每个非0值…

java 数组和字符串操作

目录一、前言二、数组操作1.charAt操作2.getBytes操作3.toCharArray操作4.String.valueOf5.substring&#xff0c;toUpperCase&#xff0c;toLowerCase&#xff0c;concat6.indexOf7.Arrays使用一、前言 本文章主要讲解数组的一些基本操作&#xff0c;让我们写代码更加方便&am…

1.docker-安装及使用

1.安装步骤 Install Docker Engine on CentOS 1. 确定CenOS7及以上版本 cat /etc/redhat-release2.卸载旧版本 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine3.yum安…