【Linux操作系统】文件描述符fd

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️林 子
      🛰️博客专栏:✈️ Linux之路
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

目录

  • 系统文件I/O函数介绍
  • open函数返回值
  • 文件描述符fd
  • 为什么文件描述符从0开始?
  • 重定向的实现原理
    • 不同的文件,怎么输入输出到不同的设备?
  • 文件描述符的验证
  • 文件描述符的继承

系统文件I/O函数介绍

我们C语言有fopen,fwrite,fread等接口来进行文件访问。其根本原因还是进行了一层系统调用,使用了系统提供的接口。所以我们可以直接使用系统的接口来进行文件的访问操作。

再此之前需要先介绍俩个函数。

open

在这里插入图片描述

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

其中 pathname,代表要写入的字符串,flags,就是要对文件进行的操作,mode_t mode 是一个八进制的权限值。如果文件打开失败会返回-1,打开成功则返回文件的fd值

write

在这里插入图片描述

ssize_t write(int fd, const void *buf, size_t count);

fd就是文件的fd值,buf就是要写入的字符串。count就是要写入的字符数量。而返回值是实际写入的数量。

所以我们可以写这样一份代码:

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


int main() {   int fd = open("logo",O_WRONLY | O_CREAT,0664);//O_WRONLY写,O_CREAT如果文件不存在就创建   if(fd < 0) //打开文件失败,open会返回-1   {
    perror("fail\n");
    return 1;   }
     int count = 5;   const char* str = "hello linux\n";   int len = strlen(str);   while(count--)
    write(fd,str,len); // 往fd的位置写文件


  close(fd);

  return 0; }

如何运行看看结果。

在这里插入图片描述

我们可以发现,再运行这个程序之后。我们就会文件里写入程序中指定的内容。

那么再为大家介绍一个系统接口。

read

在这里插入图片描述

使用方法和write一样,只不过write是写,read是读。

代码:

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


int main()
{
  int fd = open("logo",O_RDONLY);
  if(fd < 0) //打开文件失败,open会返回-1
  {
    perror("fail\n");
    return 1;
  }
  
  int count = 5;
  const char* str = "hello linux\n";
  int len = strlen(str);
  char buff[1024] = {0};
  while(read(fd,buff,len) > 0) //把文件的数据读到buff中
  {
    printf("%s",buff);//打印读取的数据
  }
  close(fd);

  return 0;
}

而close,就是关闭文件的意思。open打开文件,close关闭文件。


open函数返回值

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数。

  1. 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
  2. 而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

在这里插入图片描述

系统调用接口和库函数的关系,一目了然。

所以,可以认为,f系列的函数,都是对系统调用的封装,方便二次开发。


文件描述符fd

通过对open函数的学习,我们知道了文件描述符就是一个小整数

open打开文件时会返回这个文件描述符,那么我们来看看这个文件描述符是多少呢?

那我们就用下面这段代码来观察每个打开文件的文件描述符。

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

int main()
{
  int fd1 = open("./log.txt",O_WRONLY | O_CREAT);
  int fd2 = open("./log1.txt",O_WRONLY | O_CREAT);
  int fd3 = open("./log2.txt",O_WRONLY | O_CREAT);
  int fd4 = open("./log3.txt",O_WRONLY | O_CREAT);
  int fd5 = open("./log4.txt",O_WRONLY | O_CREAT);
  printf("fd1 : %d\n", fd1);
  printf("fd2 : %d\n", fd2);
  printf("fd3 : %d\n", fd3);
  printf("fd4 : %d\n", fd4);
  printf("fd5 : %d\n", fd5);
  return 0;
}

编译后我们发现对应的fd(文件描述符)值是 3 4 5 6 7

在这里插入图片描述

我们可以看到是一个连续的整数数列。那么这下就有点好奇了,为什么文件描述符从3开始? 而不是从0 或者1 开始。

答案是,每个进程执行时,会默认打开三个文件。 这三个文件就是 stdin(标准输入) , stdout(标准输出) , stderr(标准错误) 。 而它们三个对应的fd值分别就是 0 , 1 , 2。所以后续打开的文件会从3开始。

所以我们还可以这样子输入输出:

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

int main()
{
  const char* print = "print\n";
  char buff[1024]; 
  write(1,print,strlen(print)); //把 print写到显示器

  read(0,buff,1024); //把 键盘的字符串 读到buff里
  buff[strlen(buff)-1] = '\0';  //因为输入会录入回车。把回车换成\0
  printf("input:%s\n",buff);

  return 0;
}

运行结果:

在这里插入图片描述

为什么文件描述符从0开始?

现在知道,文件描述符就是从0开始的小整数。为什么从0 开始? 我们不妨思考一下,从0开始的,连续的整数。像什么? 是不是很像数组的下标? 没错,fd值对应的就是数组下标!!

在每个进程控制块(PCB) 中, 都有一个指针*files指针指向一张表files_struct 。这是因为当进程打开文件时,操作系统需要创建相应的数据结构来描述这些文件。 而在 files_stuct表中,有一个最重要的部分,那就是一个指针数组!这个数组中的每一个元素都是一个指向当前进程已经打开了的文件的指针!所以本质上,文件描述符就是这个数组的下标,只要拿着文件描述符,就可以找到对应的已打开的文件

在这里插入图片描述

fd的本质是内核中是进程和文件关联的数组的下标。

而我们一个进程可以打开多个文件。所以 **进程:文件 = 1 : n **。进程和文件是一对多的关系。

所以我们也可以直接往 1(标准输出)里面写 ,一样可以在显示器打印结果。

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

int main()
{
  char buff[] = "hello file\n";
  write(1,buff,sizeof(buff)-1);
  return 0;
}

我们编译运行一下。

在这里插入图片描述

那我们再来玩一个好玩的,我们把标准输入关掉。然后在打开文件,文件描述符还是3 吗?

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

int main()
{
  close(0);
  int fd1 = open("./log.txt",O_WRONLY | O_CREAT);
  printf("fd1 = %d\n",fd1);
  return 0;
}

我们会发现新打开的文件,它的fd值是0。 也就是我们把 标准输入关掉之后, 0 这个下标的位置空出来了,而这个时候打开文件那么就会往最前面的空位找。

在这里插入图片描述

结论:文件描述符的分配规则是从头开始,找下第一个空位分配。

重定向的实现原理

那再玩一个好玩的,把文件描述符1关掉呢?会发生什么?

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

int main()
{
  close(1);
  int fd1 = open("./log.txt",O_WRONLY | O_CREAT);
  printf("fd1 = %d\n",fd1);
  return 0;
}

我们编译运行

在这里插入图片描述

我们发现运行没有打印任何信息,但是! 我们在log.txt 发现了 fd1 = 1这个信息。这本是我们要打印在屏幕上的信息,为什么输出到了文件中呢?

这是因为printf函数不管你是标准输出,还是你自己打开的文件。它只负责往文件描述符为1的文件里写入。当我们关掉了标准输出,打开了新的文件log.txt时,printf函数还是理所当然的往fd为1的文件写入。然而此时文件描述符为1的文件早已不是我们的标准输出了,而是我们新打开的文件,所以就写入了我们新打开的文件。

在这里插入图片描述

而重定向的原理也是如此。

不同的文件,怎么输入输出到不同的设备?

我们都知道每个 file文件都是write和read接口。那么它怎么知道我要从键盘读,它怎么知道我要从键盘读取?

这就要用到多态的原理了。首先我们得有个虚函数表,存放的是对应的read/write函数的函数指针。这样文件就可以通过函数指针找到对应的外设进行该外设的读写操作。

在这里插入图片描述

文件描述符的验证

C语言中的FILE* 结构体一定包含了文件描述符, 为什么这么说呢? 因为任何语言,你想要使用外设设备。都必须要经过操作系统的同意!而你的标准输入输出会访问键盘和显示器, 所以也必须经过操作系统的同意! 因为操作系统不信任任何人,只相信自己,所以操作系统会提供一层系统调用接口。 通过调用系统调用接口,由操作系统去访问外设。 所以 C语言中的FILE* 文件结构体一定包含了文件描述符。怎么验证呢?我们可以访问结构体成员**_fileno** 来获取文件描述符,因为这个成员存取的就是文件描述符。

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

int main()
{
  FILE* f = fopen("./log.txt","w");
  printf("fd1 = %d\n",f->_fileno);
  return 0;
}

随后编译输出

在这里插入图片描述

输出的新打开文件的文件描述符是3。

文件描述符的继承

如果当父进程fork出了一个子进程之后,那么子进程会继承父进程的文件描述符吗?

答案是,一定会! 因为子进程是以父进程的模板创建的。子进程会单独复制一份file_struct 出来! 而不是和父进程共享。

所以,我们也可以解释一下,为什么每个进程执行的时候都会默认打开标准输入,标准输出,标准错误三个文件? 这一切都是因为这些进程继承了父进程的file_struct。而每个执行的进程的父进程都是bash,bash是命令行解析器。输入命令时,我们需要标准输入流,而标准输出用来返回输入命令的结果。标准错误用来返回报错。

一个进程可以打开多个文件,一个文件也可以被多个进程打开。在这里会用到一个引用计数的计数。文件每被一个进程打开,就会计数一次。被一个进程关闭,就会减少一次。直到为0时操作系统才会真正关闭这个文件。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用logback异步打印日志

文章目录 一、介绍二、运行环境三、演示项目1. 接口2. 日志配置文件3. 效果演示4. 异步输出验证 四、异步输出原理五、其他参数配置六、源码分析1. 同步输出2. 异步输出 七、总结 一、介绍 对于每一个开发人员来说&#xff0c;在业务代码中添加日志是至关重要的&#xff0c;尤…

docker基本使用方法

docker使用 1. Docker 介绍 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。Docker 使您能够将应用程序与基础架构分开&#xff0c;从而可以快速交付软件。通过利用 …

Oracle 开发篇+Java通过DRCP访问Oracle数据库

标签&#xff1a;DRCP、Database Resident Connection Pooling、数据库驻留连接池释义&#xff1a;DRCP&#xff08;全称Database Resident Connection Pooling&#xff09;数据库驻留连接池&#xff08;Oracle自己的数据库连接池技术&#xff09; ★ Oracle开启并配置DRCP sq…

神码ai伪原创文章生成器软件【php源码】

大家好&#xff0c;本文将围绕python二级用哪个版本的软件展开说明&#xff0c;二级python 值不值得考是一个很多人都想弄明白的事情&#xff0c;想搞清楚python二级用什么软件需要先了解以下几个事情。 火车头采集ai伪原创插件截图&#xff1a; 问题一&#xff1a;安装python…

java+springboot+mysql智能社区管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的社区住户综合管理系统&#xff0c;系统包含超级管理员、管理员、住户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理&#xff1b;住户管理&#xff1b;房屋管理&#xff08;楼栋、房屋&#xff…

代码详解——Transformer

文章目录 整体架构Modules.pyScaledDotProductAttention SubLayers.pyMultiHeadAttentionPositionwiseFeedForward Layers.pyEncoderLayerDecoderLayer Models.pyget_pad_maskget_subsequent_maskPositionalEncodingEncoderDecoderTransformer 整体架构 源码地址&#xff08;py…

电商3D产品渲染简明教程

3D 渲染让动作电影看起来更酷&#xff0c;让建筑设计变得栩栩如生&#xff0c;现在还可以帮助营销人员推广他们的产品。 从最新的《阿凡达》电影到 Spotify 的上一次营销活动&#xff0c;3D 的应用让一切变得更加美好。 在营销领域&#xff0c;3D 产品渲染可帮助品牌创建产品的…

Docker+rancher部署SkyWalking8.5并应用在springboot服务中

1.Skywalking介绍 Skywalking是一个国产的开源框架&#xff0c;2015年有吴晟个人开源&#xff0c;2017年加入Apache孵化器&#xff0c;国人开源的产品&#xff0c;主要开发人员来自于华为&#xff0c;2019年4月17日Apache董事会批准SkyWalking成为顶级项目&#xff0c;支持Jav…

Kafka消息队列学习(一)

文章目录 概述核心概念生产者示例同步 / 异步发送消息生产者参数配置ack-确认机制retries - 重试次数compression_type - 消息压缩类型 分区机制分区策略 消费者消息有序性提交和偏移量偏移量提交方式手动提交 高可用设计 SpringBoot集成Kafka基本使用传递对象消息 概述 核心概…

2023企业微信0day漏洞复现以及处理意见

2023企业微信0day漏洞复现以及处理意见 一、 漏洞概述二、 影响版本三、 漏洞复现小龙POC检测脚本: 四、 整改意见 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#x…

Arduino+esp32学习笔记

学习目标&#xff1a; 使用Arduino配置好蓝牙或者wifi模块 学习使用python配置好蓝牙或者wifi模块 学习内容&#xff08;笔记&#xff09;&#xff1a; 一、 Arduino语法基础 Arduino语法是基于C的语法,C又是c基础上增加了面向对象思想等进阶语言。那就只记录没见过的。 单多…

Mysql in 查询的奇怪方向

Mysql in 查询的奇怪方向 关于表字段存储的数据为 num1,num2,num3时, 还要通过多个num1,num2入参针对该字段进行查询 建表语句 CREATE TABLE test (test_ids varchar(100) DEFAULT NULL COMMENT 保存ids 以逗号分隔 ) ENGINEInnoDB;数据项 查询语句 SELECT test_ids FROM t…

Android之版本号、版本别名、API等级对应关系(全)(一百六十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

数据结构:选择排序

简单选择排序 选择排序是一种简单直观的排序算法。首先在未排序序列中找到最大&#xff08;最小&#xff09;的元素&#xff0c;存放到排序学列的其实位置&#xff0c;然后在剩余的未排序的元素中寻找最小&#xff08;最大&#xff09;元素&#xff0c;存放在已排序序列的后面…

NLP文本分类

NLP文本分类 落地实战五大利器&#xff01;_kaiyuan_sjtu的博客-CSDN博客https://zhuanlan.zhihu.com/p/432619164 https://github.com/alibaba/EasyNLP/blob/master/README.cn.md

【5G 核心网】5G 多PDU会话锚点技术介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

Docker部署rabbitmq遇到的问题 Stats in management UI are disabled on this node

1. Stats in management UI are disabled on this node #进入rabbitmq容器 docker exec -it {rabbitmq容器名称或者id} /bin/bash#进入容器后&#xff0c;cd到以下路径 cd /etc/rabbitmq/conf.d/#修改 management_agent.disable_metrics_collector false echo management_age…

什么是gRPC?

1. GRPC是google开源的rpc框架 2. 核心是一个.proto的服务描述文件 3. 添加依赖的grpc相关的包&#xff0c;配置IDEA的grpc插件&#xff0c;就可以很方便的生成调用代码 4. 通过在IDEA的protobuf插件上分别执行以下两个服务&#xff0c;就可以生成需要的调用代码 1&#xff…

2023深圳杯A题完整代码模型

已更新深圳杯A题全部版本&#xff0c;文末获取&#xff01; 摘要 现代社会&#xff0c;随着生活方式的变化和工作压力的增大&#xff0c;慢性非传染性疾病日益成为威胁公众健康的主要问题。心脑血管疾病、糖尿病、恶性肿瘤及慢性阻塞性肺病等慢性病的发病率呈现出上升趋势。为…

通过将信号频谱与噪声频谱进行比较,自动检测适当的带通滤波器转折频率研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…