Linux学习之路 -- 文件 -- 文件描述符

news/2024/5/20 8:27:27

前面介绍了与文件相关的各种操作,其中的各个接口都离不开一个整数,那就是文件描述符,本文将介绍文件描述符的一些相关知识。

目录

<1>现象

<2>原理

文件fd的分配规则和利用规则实现重定向


<1>现象

我们可以先通过printf把文件描述符打印出来,我们可以多打开几个文件,看看文件描述符有什么特别,再解释原理

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>#define filename "file.txt"
int main()
{int fd1 = open("log1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);int fd2 = open("log2.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);int fd3 = open("log3.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);int fd4 = open("log4.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);int fd5 = open("log5.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);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;
}

运行结果

可以看见,文件的描述符从3开始增加,在3之前的0 1 2 其实已经被用掉了。看到这些数字,其实我们可以联想到数组的下标(因为文件描述符不能小于0),那么我们就可以假设一个文件描述符就代表该文件在某一数组中的位置(至于这个数组是什么,后面详谈),那是谁用了0 1 2 呢?其实是标准输入、输出和错误三个文件。

<2>原理

前面介绍过文件的几种系统调用的接口,我们不难发现,这些接口其实和C语言库中的文件操作函数非常相似,实际上C语言的文件操作函数就是封装系统调用接口。既然C语言底层是系统调用接口,那我们在使用C语言的文件函数,这些系统调用接口所需要文件描述符在哪里呢? 当然是在FILE 这个结构体当中,这个结构体是C标准库自己封装的一个结构体。 

既然FILE这个结构体中含有文件描述符,那我们就可以通FILE来查看标准输入、输出和错误的文件的文件描述符是多少。下面演示一下代码

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>#define filename "file.txt"
int main()
{printf("%d\n",stdin->_fileno); // _fileno是FILE结构体中文件描述符的成员名printf("%d\n",stdout->_fileno);printf("%d\n",stderr->_fileno);
}

运行结果

  这就印证了0 1 2 这三个文件描述符是一开始就被标准输入、输出和错误占用了。

文件描述符的具体含义

前面说过,打开文件的进程,所以研究文件的各种操作,就是研究进程与磁盘数据的关系。而我们在日常运行进程时,我们可能要打开很多的文件,对于这些文件,系统自然是需要管理的。而在linux中,OS就通过创建了一个file的结构体(别的系统也有可能是其他的数据结构)来描述一个被打开的文件,file这个结构体中包含了很多内容,这里比较重要的是文件属性、方法集和缓冲区。而这些文件的file又可以通过指针链接起来。进程PCB中有一个结构体指针,该结构体指针指向的结构体是用于描述该进程打开的所有文件,它也称为文件描述表,其中就包含了每个文件的file指针。

所以当我们需要打开一个新文件时,先在磁盘中找到对应的位置,然后生成新的struct file,初始化struct file中的数据,并将其指针填入struct file* fd_array[] 中,最后再将对应fd_array的下标返回给上层,我们就可以使用返回的fd进行文件操作。而我们如果要关闭文件时,就释放对应的struct file。

题外话

在linux中我们把所有的东西都看成文件,底层的硬件设施也可以看成文件。对于这些硬件肯定有读方法和写方法,这些硬件的读写方式肯定是不一样的。但我们可以用一个struct file来描述这些读写方法,并将这些不同的读写方法重名成相同的函数名,这样我们在调用硬件的一些函数时,就可以忽略底层的差异,直接使用上层的接口

文件fd的分配规则和利用规则实现重定向

直接先说规则:最小的没有被使用的数组下标,会分配给最新打开的文件。我们用一段代码演示一下上述的规则。

从运行结果上来看,我们关闭了文件描述符为0的文件,然后重新创建了一个log1.txt文件(该文件之前不存在)。这个新建文件的文件描述符为零,不是3,说明上述的分配规则是正确的。

输出重定向

我们先用一段代码实现一下输出重定向,再解释一下原理

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>#define filename "file.txt"
int main()
{close(1);int fd = open("log1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);printf("fd:%d\n",fd);return 0;
}

运行结果

这里我们可以发现,运行代码时,显示器上并没有显示出对应的文件描述符。而log1.txt文件里面出现了对应的文件描述符。这就是我们所熟悉的输出重定向,因为printf这个函数底层只认fileno (文件描述符在struct file里面的成员名)。而这里我们把标准输出文件关闭了,给打开的log1.txt分配的文件描述符就是1,printf只会朝文件描述符为1的文件里面打印。所以才会出现上述现象。以此类推,我们也可以写出输入重定向,下面演示一下

先创建一个long.txt文件,向里面写入一些内容(这里为了方便演示,我只输入了一段数字),然后关闭stdin(也就是标准输入),然后系统给long.txt分配文件描述符为0,使用scanf读取数据,而scanf只认文件描述符为0的文件,所以scanf会从long.txt中读取数据。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>#define filename "file.txt"
int main()
{close(0);int fd = open("long.txt",O_RDONLY);int a = 0;scanf("%d",&a);printf("%d\n",a);return 0;
}

运行结果

同理,如果我们需要实现追加重定向,我们只需要在原来输出重定向的基础上,把打开文件的方式中的O_TRUNC修改成O_APPEND即可,其余的均不变。、

dup2函数

像上述实现重定向的方式颇为繁琐,有没有更好的方式呢?当然是有的。这里文件描述符代表的数组里面存的是struct file的指针,如果我们要实现重定向,只要将数组对应下标的内容进行交换即可。而这里由于stdout可以不被使用,所以我们可以把新打开文件对应的指针,直接拷到stdout对应的数组下标中即可。

这里有专门的系统调用来帮我们实现这一功能,这个接口就是dup2(另外两个暂时不学)。

这个函数会把对应数组下标的内容拷贝到另外一个数组下标的对应内容上,而在这里就是把oldfd对应的数组下标内容拷贝到newfd对应数组下标的内容里面,所以最后只剩下oldfd对应数组下标内容。

如果要实现重定向操作,我们就不需要再关闭文件描述符,只需要使用dup2这个接口即可。

以上就是全部内容,如果文中有不对之处,还望各位大佬指正,谢谢!!!


http://www.mrgr.cn/p/01637148

相关文章

STATA 核密度图

. clear . set obs 625 . gen x = (mod((_n-mod(_n-1,25)-1)/25,25)-12)/12 . gen y = (mod(_n-1,25)-12)/12 . gen z = normalden(x,0,2)*normalden(y,0,2) //二维乘积核 . twoway contour z x y, level(15)

全栈开发之路——前端篇(6)生命周期和自定义hooks

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 第五篇 : 组件…

彻底解决python的pip install xxx报错(文末附所有依赖文件)

今天安装pip install django又报错了&#xff1a; C:\Users\Administrator>pip install django WARNING: Ignoring invalid distribution -ip (d:\soft\python\python38\lib\site-pac kages) Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting djan…

构造照亮世界——快速沃尔什变换 (FWT)

之前学习 FFT 的时候,我们知道 FFT 是用来快速求两个多项式乘积的,而 FWT 解决的多项式的位运算……博客园 我的博客快速沃尔什变换解决的卷积问题 快速沃尔什变换(FWT)是解决这样一类卷积问题: \[c_i=\sum_{i=j\odot k}a_jb_k \]其中,\(\odot\) 是位运算的一种。举个例子…

【webrtc】RemoteAudioSource的创建线程

m98 代码&#xff1a;I:\webrtc m98_yjf\src\pc\rtp_transmission_manager.cc RtpTransmissionManager::CreateReceiver 在信令线程创建receiver receiver 是&#xff1a; rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>receiver;其实际…

Nessus 部署实验

一、下载安装https://www.tenable.com/downloads/nessus 安装好之后&#xff0c;Nessus会自动打开浏览器&#xff0c;进入到初始化选择安装界面&#xff0c;这里我们要选择 Managed Scanner 点击继续&#xff0c;下一步选择Tenable.sc 点击继续&#xff0c;设置用户名和密码 等…

软件测试之linux学习day2

今天学习linux中的shell编程

虚拟机文件夹共享操作(本地访问)

新建一个文件夹 右击文件夹点击属性 找到共享 点击共享 选择本地用户共享就可以了 本地winr 输入我们图片中的格式&#xff08;IP前加 “\\” &#xff09; 会弹一个窗口&#xff0c;输入虚拟机的入户名和密码就可以共享了&#xff08;一般默认用户名都是administrator&am…

SwiftUI ScrollView 滚动视图

代码 // // ContentView.swift // SwiftUIScrollView // // Created by CHEN Hao on 2024/5/7. //import SwiftUIstruct ContentView: View {var body: some View {VStack(alignment: .leading) {VStack(alignment: .leading) {Text("monday, aug 20".uppercased(…

wpf字符图标

1、网页使用: font face=webdings size="32">i</font 2、WPF使用: TextBlock Text="1" FontFamily="Wingdings" FontSize="32"></TextBlock

虚拟机安装

学习视频:点这里 学习文字:点这里 下载安装包和密钥文件后得到: 创建虚拟机的安装目录: 安装中需要注意的地方:

欧拉回路(leetcode 重新安排行程)

先学习一下欧拉回路是怎么一回事。 对于图中这七个节点&#xff0c;从节点1出发&#xff0c;最终要到达节点1&#xff0c;并且每条路只能走一次&#xff0c;且每条路都得走过一次。 使用dfs&#xff0c;如果算法按照字典序的排列方式选择下一个节点。 第一部分&#xff1a;那…

Linux系统管理-系统概述

1、Linux系统管理-系统概述一、系统概述 3.系统安装 1.系统安装 1.选择第一项install centos7,并且设置语言,推荐使用中文,简体中文2.字符界面安装选择最小或基本网页服务器即可,接下来就是进行磁盘分区,也可以自己分配或者系统自动分配3.手动分区里面也可以让系统自动创建…

推荐几款AI写作工具:智能在线一键文章创作、代码编写、论文等

在五一节的休假期结束之后,广大员工常常体验到了显著的疲劳,对立即恢复工作状态感到抗拒。虽然这种心态在职场中并不罕见,但工作的责任与需求仍需要我们去面对。 在这种背景下,如何能够既保证工作效率与质量,又能为自己争取到宝贵的调整时间,成为了许多人士所关心的问题。…

C++设计模式-创建型设计模式

设计模式 设计模式是什么 设计模式是指在软件开发中&#xff0c;经过验证的&#xff0c;用于解决在特定环境下&#xff0c;重复出现的&#xff0c;特定问题的解决方案&#xff1b;其实就是解决问题的固定套路。但是要慎用设计模式&#xff0c;有一定的工程代码量之后用它比较…

多目标灰狼算法(MOGWO):原理讲解与代码实现 Matlab代码免费获取

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 原理简介 一、Pareto最优概念 二、单目标G…

09-初始setup-axios-promise

监听属性 需要使用ref和ractive包一下才可以正常监听。 // 使用相关属性需要导入 import { reactive, ref, watch } from "vue";// 监听单个 let name = ref("阿珂") watch(name, (newValue, oldValue) =>{console.log("老名字", oldValue);c…

《挑战100个产品拆解:抖音》

抖音&#xff0c;作为当今社交媒体领域的明星产品&#xff0c;其背后的产品思维一直备受关注。在这篇文章中&#xff0c;我们将深入拆解抖音的产品思维&#xff0c;揭示其成功的秘密。 产品定位 1.产品是什么样的用户&#xff1a; 年轻人和青少年是抖音的主要用户群体。抖音…

整理文件IO的有关知识点及笔试题

文件IO 1.FAT32和NTFS的区别 1)FAT32 优点是稳定性和兼容性好,能充分兼容Win 9X及以前版本,且维护方便。 缺点是安全性差,单个文件也只能支持最大4GB。 2)NTFS 优点是安全性及可靠性高,除兼容性之外,它远远优于FAT32。它不但可以支持2TB大小的分区,而且支持对分区、文件夹…

Leetcode --- 203. 移除链表元素

题目描述 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。 示例 1: 示例输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5] 输入:head = [7,7,7,7], val = 7 输出:[]参考实现 方式1、使用递归实现…