【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';  //因为输入会录入回车。把回车换成\0printf("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.mzph.cn/news/35307.shtml

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

相关文章

python单元测试框架(测试固件、批量执行)

python测试框架 在Python语言中应用最广泛的单元测试框架是unittest和pytest,unittest属于标准库&#xff0c;只要安装了Python解释器后就可以直接导入使用了,pytest是第三方的库&#xff0c;需要单独的安装。 1.白盒测试原理 在软件架构的层面来说&#xff0c;测试最核心的…

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

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

关于selenium 元素定位的浅度解析

一、By类单一属性定位 元素名称 描述 Webdriver API id id属性 driver.find_element(By.ID, "id属性值") name name属性 driver.find_element(By.NAME, "name属性值") class_name class属性 driver.find_element(By.CLASS_NAME, "class_na…

MAUI+Blazor:隐藏标题栏和问题

文章目录 前言相关文章代码问题有必要解决吗&#xff1f; 前言 最近在研究MAUIBlazor开发&#xff0c;发现一个问题&#xff0c;原生的的标题栏实在是太丑了。 相关文章 MAUI桌面端标题栏设置和窗口调整 MAUI Windows How to completely hide the TitleBar? #15142 MAUI …

Chrome开发者工具介绍

Chrome开发者工具介绍 前言1 打开DevTools2 命令菜单3 Elements面板ConsoleJavaScript调试Network 前言 Chrome开发者工具是谷歌浏览器自带的一款开发者工具&#xff0c;它可以给开发者带来很大的便利。常用的开发者工具面板主要包含Elements面板、Console面板、Sources面板、…

数据结构——时间复杂度和空间复杂度

1.算法效率 2.时间复杂度 3.空间复杂度 4. 常见时间复杂度以及复杂度oj练习 1.算法效率 1.1 如何衡量一个算法的好坏 如何衡量一个算法的好坏呢&#xff1f;比如对于以下斐波那契数的计算 long long Fib(int N) { if(N < 3) return 1; return Fib(N-1) Fib(N-2); }我们看到…

2023 互联网大厂薪资大比拼

最近整理了33家互联网大厂的薪资情况。可以看出来&#xff0c;大部分互联网大厂薪资还是很不错的&#xff0c;腾讯、阿里、美团、百度等大厂平均月薪超过30k&#xff0c;其他互联网大厂平均月薪也都在25k以上。01020304050607080910111213141516171819202122232425262728293031…

yo!这里是STL::list类简单模拟实现

目录 前言 重要接口实现 框架 默认成员函数 迭代器&#xff08;重点&#xff09; 1.引言 2.list迭代器类实现 3.list类中调用实现 增删查改 后记 前言 我们知道&#xff0c;stl中的vector对应数据结构中的顺序表&#xff0c;string类对应字符串&#xff0c;而今天要…

Unity C# 之 Http 获取网页的 html 数据,并去掉 html 格式等相关信息

Unity C# 之 Http 获取网页的 html 数据&#xff0c;并去掉 html 格式等相关信息 目录 Unity C# 之 Http 获取网页的 html 数据&#xff0c;并去掉 html 格式等相关信息 一、简单介绍 二、实现原理 三、注意事项 四、效果预览 五、关键代码 一、简单介绍 Unity中的一些知…

Linux网络基础(中)

目录&#xff1a; 再谈“协议” HTTP协议 认识URL&#xff1a; urlnecode和urldecode HTTP协议格式&#xff1a; HTTP的方法&#xff1a; 简易HTTP服务器&#xff1a; 传输层 再谈端口号&#xff1a; 端口号范围划分&#xff1a; netstat&#xff1a; pidof&…

Ubantu安装Docker(完整详细)

先在官网上查看对应的版本:官网 然后根据官方文档一步一步跟着操作即可 必要准备 要成功安装Docker Desktop&#xff0c;必须&#xff1a; 满足系统要求 拥有64位版本的Ubuntu Jammy Jellyfish 22.04&#xff08;LTS&#xff09;或Ubuntu Impish Indri 21.10。 Docker Deskto…

Redis基础命令大全

这里写目录标题 第一章、Redis 命令大全1.1&#xff09;通用命令语法&#xff1a;ping语法&#xff1a;dbsize语法&#xff1a;select db语法&#xff1a;flushdb语法&#xff1a;exit 或 quit语法&#xff1a;redis-cli 1.2&#xff09;Redis 的 Key 的操作命令语法&#xff1…

Elasticsearch之kibana相关命令

1.中文分词器相关命令 2.拼音分词器相关命令

服务器之LNMP

lnmp的构成 L&#xff1a;linux系统,操作系统。 N&#xff1a;nginx网站服务&#xff0c;前端,提供前端的静态页面服务。同时具有代理,转发的作用。 转发&#xff1a;主要是转发后端请求。转发到PHP。nginx没有处理动态资源的功能,他有可以支持转发动态请求的模块。 M&…

REDIS主从配置

目录 前言 一、概述 二、作用 三、缺点 四、redis主从复制的流程 五、搭建redis主从复制 总结 前言 Redis的主从配置是指在Redis集群中&#xff0c;将一个Redis节点配置为主节点&#xff08;master&#xff09;&#xff0c;其他节点配置为从节点&#xff08;slave&#xff09;…

【数据结构•堆】堆排序(理论基础)

堆的定义  • 堆是一个完全二叉树   –所有叶子在同一层或者两个连续层   –最后一层的结点占据尽量左的位置  • 堆性质   –为空, 或者最小元素在根上   –两棵子树也是堆 存储方式  • 最小堆的元素保存在heap[1..hs]内   – 根在heap[1]   –K的左儿子是2k,…

细胞——求细胞数量 C++详解

细胞——求细胞数量 C详解 求细胞数量题目描述输入格式输出格式样例样例输入样例输出 提示数据规模与约定 解法代码 求细胞数量 题目描述 一矩形阵列由数字 0 0 0 到 9 9 9 组成&#xff0c;数字 1 1 1 到 9 9 9 代表细胞&#xff0c;细胞的定义为沿细胞数字上下左右若还…

vue3中使用component动态组件常见问题

一. 在vue3中使用动态组件问题警告处理 1. 代码如下 <template><div v-for"(item, index) in navItems" :key"index"><component :is"item.component" :key"item.gameId"></component></div> </te…

Leetcode-每日一题【剑指 Offer 26. 树的子结构】

题目 输入两棵二叉树A和B&#xff0c;判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构&#xff0c; 即 A中有出现和B相同的结构和节点值。 例如: 给定的树 A: 3 / \ 4 5 / \ 1 2 给定的树 B&#xff1a; 4 / 1 返回 true&#xff0…

Vue + MapBox快速搭建

一、说明&#xff1a; 1.mapbox-gl自2.0版本开始不再开源&#xff0c;需要用户在官网申请key使用。 2.maplibre GL JS是一个开源库&#xff0c;它起源于 mapbox-gl-js 的开源分支。该库的初始版本&#xff08;1.x&#xff09;旨在替代Mapbox的OSS版本。简单来说maplibre是mapb…