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

news2024/11/26 2:27:59

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️林 子
      🛰️博客专栏:✈️ 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.coloradmin.cn/o/872837.html

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

相关文章

easyx图形库基础:1.基本概念的介绍+图形的绘制。

基本概念的介绍图形的绘制 一.基本概念的介绍。1.为什么要使用easyx图形库2.安装easyx图形库。3.语法相关 二.图形绘制1.窗体创建和坐标的概念。1.基本窗体的创建。2.坐标概念3.改变逻辑坐标。 2.设置图形颜色1.设置描边颜色和描边样式。2.设置图形填充颜色和填充样式3.绘制图形…

【第二阶段】kotlin的函数类型作为返回类型

fun main() {//调用,返回的是一个匿名类型&#xff0c;所以info就是一个匿名函数val infoshow("",0)//info接受的返回值为匿名类型&#xff0c;此时info就是一个匿名函数println(info("kotlin",20)) }//返回类型为一个匿名函数的返回类型fun show(name:Str…

编程工具合集

须知&#xff1a; 本博文只是为了记录程序员在编程中所用到的编程工具以及效率软件&#xff0c;如有侵权&#xff0c;请告知&#xff01; VSCode 英文官网&#xff1a;https://code.visualstudio.com/ 中文官网&#xff1a;http://vscode.p2hp.com/ win下载链接&#xff1a;VS…

ModaHub魔搭社区:AI时代连接硬件和上层应用的中间层基础设施。

从类比的角度理解AI Infra:AI时代连接硬件和上层应用的中间层基础设施。传统本地部署时代,三大基础软件(数据库、操作系统、中间件)实现控制硬件交互、存储管理数据、网络通信调度等共性功能,抽象并隔绝底层硬件系统的复杂性,让上层应用开发者能够专注于业务逻辑和应用功…

Kotlin入门:变量和函数——02

目录 一、Kotlin 基本数据类型 ​编辑 二、变量 val 关键字&#xff1a; var 关键字: 类型推断: 可空类型: 三、函数 基本函数语法&#xff1a; 单表达式函数&#xff1a; 默认参数值&#xff1a; 命名参数&#xff1a; 一、Kotlin 基本数据类型 Kotlin 的基本数…

CCLINK IE 转MODBUS-RTU网关modbusrtu与485区别

远创智控YC-CCLKIE-RTU。这款产品的主要功能是将各种MODBUS-RTU、RS485、RS232设备接入到CCLINK IE FIELD BASIC网络中。 那么&#xff0c;这款通讯网关又有哪些特点呢&#xff1f;首先&#xff0c;它能够连接到CCLINK IE FIELD BASIC总线中作为从站使用&#xff0c;同时也能连…

sqlyog下载和卸载的最新详细过程,超多图快速安装或者卸载

目录 1.SQLyog的介绍2.sqlyog的下载和安装3.sqlyog的卸载 ✨ 原创不易&#xff0c;还希望各位大佬支持一下&#xff01; &#x1f44d; 点赞&#xff0c;你的认可是我创作的动力&#xff01; ⭐️ 收藏&#xff0c;你的青睐是我努力的方向&#xff01; ✏️ 评论&#xff0c…

7.5.tensorRT高级(2)-RAII接口模式下的生产者消费者多batch实现

目录 前言1. RAII接口模式封装生产者消费者2. 问答环节总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-RAI…

Netty核心源码解析(一)

Netty是什么&#xff1f; Netty是Jboss提供的一个Java 开源框架&#xff0c;主要针对TCP协议的高并发场景Netty本质是对Java NIO做了封装的网络通信框架&#xff0c;主要作用是Java NIO基本接口的封装&#xff0c;提供了网络通信中线程同步&#xff0c;编解码&#xff0c;粘包拆…

小龟带你敲排序之冒泡排序

冒泡排序 一. 定义二.题目三. 思路分析&#xff08;图文结合&#xff09;四. 代码演示 一. 定义 冒泡排序&#xff08;Bubble Sort&#xff0c;台湾译为&#xff1a;泡沫排序或气泡排序&#xff09;是一种简单的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元…

Kettle系列(一)下载安装与基础配置

Kettle系列&#xff08;一&#xff09;下载安装与基础配置 说明一、下载二、目录结构三、基础配置&#xff08;1&#xff09;环境变量&#xff08;2&#xff09;kettle配置 四、连接mysql8五、连接其他数据库六、总结 说明 更新时间&#xff1a;2023/08/13 17:47 本文记录了wi…

openGauss学习笔记-38 openGauss 高级数据管理-游标

文章目录 openGauss学习笔记-38 openGauss 高级数据管理-游标38.1 语法格式38.2 参数说明38.3 示例 openGauss学习笔记-38 openGauss 高级数据管理-游标 为了处理SQL语句&#xff0c;存储过程进程分配一段内存区域来保存上下文联系。游标是指向上下文区域的句柄或指针。借助游…

【Pytroch】基于决策树算法的数据分类预测(Excel可直接替换数据)

【Pytroch】基于决策树算法的数据分类预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理2.数学公式3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 决策树是一种常用的机器学习算法&#xff0c;用于分类和回归任务。它通过树状结构表示数据的决策…

Opencv4基于C++的 实时人脸检测

文章目录: 一&#xff1a;环境配置搭建(VS2015Opencv4.6) 二&#xff1a;下资源文件 第一种&#xff1a;本地生成 第二种 直接下载 三&#xff1a;代码展示 窗口布局 main.cpp test.h test.cpp 效果图◕‿◕✌✌✌&#xff1a;opencv人脸识别效果图(请叫我真爱粉) 一&…

运算器组成实验

1.实验目的及要求 实验目的 1、熟悉双端口通用寄存器组的读写操作。 2、熟悉运算器的数据传送通路。 3、验证运算器74LS181的算术逻辑功能。 4、按给定数据&#xff0c;完成指定的算术、逻辑运算。 实验要求 1、做好实验预习。掌握运算器的数据传送通路和ALU的功能特性&…

7.3.tensorRT高级(2)-future、promise、condition_variable

目录 前言1. 生产者消费者模式2. 问答环节总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-future、promise…

【算法——双指针】LeetCode 1089 复写零

千万不要被这道题标注着“简单”迷惑了&#xff0c;实际上需要注意的细节很多。 题目描述&#xff1a; 解题思路&#xff1a; 正序遍历&#xff0c;确定结果数组的最后一个元素所在的位置&#xff1b;知道最后一个元素的位置后倒序进行填充。 先找到最后一个需要复写的数 先…

C++ 泛型编程:函数模板

文章目录 前言一、什么是泛型编程二、函数模板三、函数模板的使用四、多参数函数模板五&#xff0c;示例代码&#xff1a;总结 前言 当需要编写通用的代码以处理不同类型的数据时&#xff0c;C 中的函数模板是一个很有用的工具。函数模板允许我们编写一个通用的函数定义&#…

php从静态资源到动态内容

1、从HTML到PHP demo.php:后缀由html直接改为php,实际上当前页面已经变成了动态的php应用程序脚本 demo.php: 允许通过<?php ... ?>标签,添加php代码到当前脚本中 php标签内部代码由php.exe解释, php标签之外的代码原样输出,仍由web服务器解析 <!DOCTYPE html>…

Qt中将信号封装在一个继承类中的方法

QLabel标签类对应的信号如下&#xff1a; Qt中标签是没有双击&#xff08;double Click&#xff09;这个信号的&#xff1b; 需求一&#xff1a;若想双击标签使其能够改变标签中文字的内容&#xff0c;那么就需要自定义一个“双击”信号&#xff0c;并将其封装在QLabel类的派生…