【Linux】文件描述符 fd

news2024/11/25 14:51:48

目录

一、C语言文件操作

1.1 fopen和fclose

1.2 fwrite和fread

1.3 C语言中的输入输出流

二、Linux的文件系统调用

2.1 open和文件描述符

2.2 close

2.3 read

2.4 write

三、Linux内核数据结构与文件描述符


一、C语言文件操作

在C语言中我们想要打开一个文件并对其进行读取写入等各种操作,需要依赖于fopen、fread、fwrite等函数。我们先来回顾一下这些函数

1.1 fopen和fclose

其中:

  • path:目标文件的路径
  • mode:文件的打开模式

文件的打开模式中,r表示只读,w表示只写,a表示追加,还有很多模式相信对大家来说都不陌生

fopen如果打开文件成功,会返回一个FILE*指针,其中FILE结构体是C库自己封装的结构体,内部封装了文件的各种属性

一个文件被打开后,如果我们不需要使用该文件了,就需要使用fclose关闭它,向函数传入先前从fopen接收到的FILE*指针即可。

1.2 fwrite和fread

其中:

  • ptr:指向某一内存块的指针
  • size:要读取/写入的元素大小
  • nmemb:要读取/写入的元素个数
  • stream:文件指针

所以读取或写入的字节总数为size*nmemb

我们可以用fwrite对一个文件进行写入,例如:

#include <stdio.h>
#include <string.h>

int main()
{
    FILE *fp = fopen("myfile", "w");
    if(!fp)
    {
        printf("fopen error!\n");
    }
    const char *str = "hello Linux\n";
    fwrite(str, strlen(str), 1, fp); 
    fclose(fp);
    return 0;                                                                                                                                                                                                 
}            

运行代码,可以看到已经写入成功了

我们还可以用fread读取一个文件的内容,例如:

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    FILE *fp = fopen("myfile", "r");                                                                                                                                                                          
    if(!fp)
    {
        printf("fopen error!\n");
    }
    char buf[1024];
    ssize_t s = fread(buf, 1, sizeof(buf), fp);
    if(s)
    {
        buf[s] = '\0';
        printf("%s", buf);
    }
    fclose(fp);
    return 0;
}

fread如果读取成功,会返回读取的元素总数,与nmemb个数相同

运行代码,可以发现之前写入到myfile文件中的内容已经被读取出来了

除了以上的这些接口,C语言还有很多的文件操作接口,这里不是重点不作过多赘述

1.3 C语言中的输入输出流

在Linux中一切皆文件,显示器也是文件,我们也可以对其进行写入操作

如何对其进行写入呢?这里介绍C语言中的三个输入输出流

其中stdin标准输入流,对应我们的键盘stdout标准输出流stderr标准错误流,这两个流对应我们的显示器。这三个输入输出流是C语言程序在启动时默认会给我们打开的

不止是C语言,其他语言也会默认打开这三个流,不过名称不同。C++中分别是cin、cout和cerr 

进程将从标准输入流中得到输入数据,将正常输出数据输出到标准输出流,而将错误信息送到标准错误流中

可以看到,这三个流的类型都是FILE*,也就是说我们自己也可以通过fread从标准输入流中读取键盘输入的内容,通过fwrite向标准输出流或标准错误流即显示器文件写入我们的内容。

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    const char* str = "hello Linux\n";
    fwrite(str, 1, strlen(str), stdout);
    return 0;                                                                                                                                                                                                 
}

运行程序,可以看到我们已经把内容写入到显示器文件中了


二、Linux的文件系统调用

在前面了解操作系统的时候就提到,操作系统是有自己的系统调用接口的。

而语言中涉及到操作系统内核部分的函数,实际上都是封装了操作系统的系统调用接口,包括C语言的文件操作接口也是封装了操作系统的文件系统调用接口

所以我们先来了解一下Linux中的文件系统调用接口

2.1 open和文件描述符

open是Linux系统中用来打开文件的一个系统调用接口,其中:

  • pathname:目标文件的路径
  • flag:文件访问权限标志
  • mode:如果创建新文件,指定新建文件的访问权限

flag参数是文件访问权限标志位,其中:

  • O_RDONLY:以只读方式打开文件
  • O_WRONLY:以只写方式打开文件
  • O_RDWR:以可读可写方式打开文件

我们在使用open函数时必须包含以上三种中的一种标志位,除了这些还有其他的可选标志位,这里列出常用的三种:

  • O_CREAT:如果pathname指向的文件不存在则创建文件
  • O_TRUNC:打开文件的同时清空文件
  • O_APPEND:以追加方式对文件进行写入

标志位可以通过位运算的方式同时选中多种,例如:

像上面这样,就是以只写方式打开文件、以追加方式写入、如果文件不存在则创建

其中如果选中了O_CREAT,一般都要对mode参数进行设置,该参数用来设置新创建的文件权限

关于文件权限,我在前面的文章中有讲过

【Linux】常用指令、热键与权限管理-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Eristic0618/article/details/138795212?spm=1001.2014.3001.5501接下来我们看看open函数的返回值

可以看到,成功打开文件后open函数会返回一个file descriptor,即所谓的文件描述符(fd)

文件描述符是一个整型,从0开始向后分配,系统在给文件分配文件描述符时会分配当前没有被使用的最小的文件描述符 

关于文件描述符的本质,在后面会详细提到,目前只需要知道进程是通过文件描述符来访问文件的

2.2 close

close是Linux系统中关闭文件的系统调用接口,其参数就是一个文件描述符,传入文件对应的文件描述符即可关闭该文件

2.3 read

read函数将从fd对应的文件中读取count个字节到buf指向的内存块中

成功则返回读取到的字节数,失败则返回-1,若调用read时文件已达末尾则返回0

2.4 write

write函数将把buf指向的内存块中的内容写入count个字节到fd对应的文件中

成功则返回写入的字节数,失败则返回-1


三、Linux内核数据结构与文件描述符

到这里,相信大家已经学会如何使用Linux中的文件系统调用接口了,接下来我们深入探讨一下访问文件的本质

一个文件由内容属性两部分构成,没打开的文件存储在磁盘中,本文重点不在于未打开的文件所以不作过多探讨。而我们如果要打开一个文件,就先得将其加载到内存中,这是由冯·诺依曼体系结构决定的。

所以在操作系统内部一定存在着大量的被打开的文件,操作系统也自然要对这些文件进行管理,即先描述再组织。操作系统内部使用file结构体(与C语言的FILE结构体不同)来描述一个被打开文件的信息,其中包括该文件的基本属性、权限、大小、内核缓冲区信息等等,将这些结构体用数据结构组织起来,就可以完成对被打开文件的管理了。

而一个进程可以同时打开多个文件,所以进程和文件是一对多的关系。要把一个进程打开的所有文件管理起来,我们就要使用一个files_struct结构体

我们知道操作系统通过PCB描述进程,而在进程的PCB中就有这么一个类型为 struct files_struct* 的指针files指向了进程的files_struct结构体,其定义如下

struct files_struct {
    atomic_t count;
    struct fdtable *fdt;
    struct fdtable  fdtab;
   
    int next_fd;
    struct embedded_fd_set close_on_exec_init;
    struct embedded_fd_set open_fds_init;
    struct file * fd_array[NR_OPEN_DEFAULT];
};

其中的fd_array数组,就指向了进程打开的一个个文件的file结构体。很多人称files_struct是文件描述符表,但我认为这个fd_array数组才是更准确的文件描述符表,因为所谓的文件描述符,其实就是文件在这个表中存储位置的下标

前面提到,程序在启动时会默认打开三个标准输入输出流,所以文件描述符表中的前三个位置是默认被使用的。对应的,标准输入流的文件描述符为0,标准输出流的文件描述符为1,标准错误流的文件描述符为2

如何验证?在C语言中文件的属性被封装在FILE结构体中,结构体中的_fileno就是文件的文件描述符,我们只需要把三个标准流的文件描述符打印出来就能验证了

int main()
{
    printf("stdin:%d\n", stdin->_fileno);
    printf("stdout:%d\n", stdout->_fileno);
    printf("stderr:%d\n", stderr->_fileno);                                                                                                                                                                   
    return 0;
}

可以看到三个标准流对应的文件描述符就是0、1、2

如果此时我们在打印前close(1),会发生什么呢?

int main()
{
    close(1);
    printf("stdin:%d\n", stdin->_fileno);
    printf("stdout:%d\n", stdout->_fileno);
    printf("stderr:%d\n", stderr->_fileno);                                                                                                                                                                   
    return 0;
}

什么也不会打印,因为标准输出流即显示器文件在打印前已经被关闭了

我们可以在程序中打开多个文件,看看它们的文件描述符是什么

int main()
{
    int fd1 = open("file1", O_WRONLY|O_TRUNC|O_CREAT, 0666);
    int fd2 = open("file2", O_WRONLY|O_TRUNC|O_CREAT, 0666);
    int fd3 = open("file3", O_WRONLY|O_TRUNC|O_CREAT, 0666);
    int fd4 = open("file4", O_WRONLY|O_TRUNC|O_CREAT, 0666);
    printf("fd1:%d\n", fd1);
    printf("fd2:%d\n", fd2);
    printf("fd3:%d\n", fd3);
    printf("fd4:%d\n", fd4);                                                                                                                                                                                  
    return 0;
}

可以看到,系统会给文件分配当前未被使用的最小的文件描述符

一个文件可以被多个进程打开,那么是不是一个进程将该文件关闭了,该文件对应的资源就要被系统回收呢?

不是的,在file结构体中有该文件的引用计数,用来计算该文件被多少个进程打开了。如果一个进程将该文件对应的fd关闭则减少对应的引用计数,只有当引用计数为0时文件才会被真正的关闭

完.

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

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

相关文章

精进日常:每日练习与明智取舍的艺术

目录 题目1.对于非运行时异常&#xff0c;程序中一般可不做处理&#xff0c;由java虚拟机自动进行处理。2.下面哪个关键字可以用于Java的构造方法上&#xff1f;3.以下代码执行的结果显示是多少&#xff08; &#xff09;&#xff1f;注解总结 题目 选自牛客网 1.对于非运行时…

GCC编译:静态链接库与动态链接库

&#xff01; GCC 编译 C(C)静态链接库&#xff08;gcc -L、gcc -l&#xff09;和动态链接库&#xff08;gcc -fPIC -shared&#xff09;的创建和使用_fpic shared-CSDN博客https://blog.csdn.net/wohu1104/article/details/110789570静态链接库&#xff1a; 在 Linux 发行版…

JDFrame 一款比 Java 8 Stream 更灵活的数据处理工具

一、JDFrame 介绍 在大数据处理领域&#xff0c;Apache Spark以其强大的分布式计算能力和丰富的数据处理API而广受好评。然而&#xff0c;在许多日常的软件开发场景中&#xff0c;我们面临的数据量可能并不需要Spark这样的分布式系统来处理。相反&#xff0c;我们更希望有一种…

聊聊ChatGLM2-6B模型的微调

概述 GLM、ChatGLM的相关基础知识说明&#xff1a; GLM模型底层还是基于Transformer&#xff0c;因此其设计、优化都是围绕Transformer的各个组件的。从注意力层的掩码、位置编码等方面优化与设计。ChatGLM3/ChatGLM2的源码中&#xff0c;比如finetune、trainer等代码&#x…

看不见的硝烟:中国网络安全三十年沉浮史

昆仑侠 锦缎 2022 年 05 月 20 日 本文系基于公开资料撰写&#xff0c;仅作为信息交流之用&#xff0c;不构成任何投资建议。 2022 年 5 月 16 日&#xff0c;俄罗斯黑客组织 KillNet 向包括美国、英国、德国在内 10 个国家的政府正式 “宣战”。 2022 年 4 月 28 日&#xf…

vue如何在组件中监听路由参数的变化

使用 watch 监听 $route 对象 的变化&#xff0c;从而捕捉路由参数的变化 beforeRouteUpdate 导航守卫 当前组件路由更新时调用 beforeRouteUpdate 钩子只在组件被复用时调用&#xff0c;即当组件实例仍然存在时。如果组件是完全重新创建的&#xff0c;那么应该使用 beforeR…

GD 32 滤波算法

GD32硬件滤波算法 程序代码&#xff1a; #include <stdint.h> #include <stdio.h> #include "gd32f30x.h" #include "delay.h"static void GpioInit(void) {rcu_periph_clock_enable(RCU_GPIOC);gpio_init(GPIOC, GPIO_MODE_AIN, GPIO_OSPEED_…

log4j2远程执行代码CVE-2021-44228复现

一.访问网址 发现 /solr/admin/cores?action参数 可以上传 如下图 步骤二 在dnslog平台上来监控我们注入的效果 上传参数 solr/admin/cores?action${jndi:ldap://${sys:java.version}.jxmxiy.dnslog.cn 获得java版本号 查看他的回显 开始准备反弹shell 下载JDNI&#xff0c…

编程语言 | C | 代码整理 | 4月

八月拍了拍你&#xff0c;并对你说&#xff1a;“好运就要开始了”&#xff01; 目录 编程语言 | C | 代码整理 | 4月2019/4/12019/4/22019/4/22019/4/32019/4/42019/4/52019/4/62019/4/72019/4/82019/4/92019/4/102019/4/112019/4/122019/4/132019/4/142019/4/152019/4/162019…

【简历】武汉某985大学:前端简历指导,拿offer可能性低

注&#xff1a;为保证用户信息安全&#xff0c;姓名和学校等信息已经进行同层次变更&#xff0c;内容部分细节也进行了部分隐藏 简历说明 这是一份985武汉某大学25届的前端简历&#xff0c;那么985面向的肯定是大厂的层次&#xff0c;但是作为前端简历&#xff0c;学校部分&a…

Jenkins的安装方式

一、Jenkins是什么 Jenkins是一款开源CI&CD软件&#xff0c;用于自动化构建、测试和部署软件等各种任务&#xff0c;以实现持续集成。 Jenkins支持各种运行方式&#xff0c;可通过系统包、Docker或者通过一个独立的Java程序。 二、安装方式 2.1禅道智能应用平台一键安装…

区间预测 | 光伏出力的区间预测(Matlab)

区间预测 | 光伏出力的区间预测&#xff08;Matlab&#xff09; 目录 区间预测 | 光伏出力的区间预测&#xff08;Matlab&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.适用于matlab2020及以上。可任意选择置信区间&#xff0c;区间覆盖率picp、区间平均…

入门Pandas必练习100题基础到进阶|阶级教程2

作者:郭震 51. How to get the row number of the nth largest value in a column? Find the row position of the 5th largest value of column a in df. # input df pd.DataFrame(np.random.randint(1, 30, 30).reshape(10,-1), columnslist(abc)) df# Solution 1# argsort…

Modelsim仿真Vivado IP核报错

问题 VIVADO版本为2017.2&#xff0c;Modelsim版本为10.7c 在vivado中调用modelsim仿真&#xff0c;出现报错“Module GND is not defined.”&#xff0c;“Module LUT4 is not defined.”等等一大堆&#xff0c;所有的IP核都是这样的报错。 由于问题已经解决了所以我没有报…

基于Flask的出租车、GPS轨迹数据分析可视化系统

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍技术路线内容介绍数据预处理系统界面可视化展示每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 针对杭州市出租车GPS数据的分析&…

万虹商城电影售票系统设计与实现

1 项目介绍 1.1 摘要 在如今高速发展的社会&#xff0c;电影产业蓬勃发展&#xff0c;人们对电影的需求日益增加&#xff0c;导致电影售票系统需要更加高效、便捷地满足观众的购票需求。传统的电影售票方式伴随着一系列的问题&#xff0c;排队购票现象是最为突出和普遍的现象…

拥抱简约之美,浦东TOP5装修公司打造现代风尚空间

在浦东地区&#xff0c;现代简约风格因其清新、实用的特点而深受居民喜爱。以下是五大装修公司&#xff0c;它们专注于打造现代风尚的居住空间&#xff1a; 1&#xff0e;即住空间装饰 设计理念&#xff1a;即住空间装饰以"高效、省心、精工"为核心理念&#xff0c…

【LeetCode每日一题】长度最小的子数组

【LeetCode每日一题】长度最小的子数组 标签: 二分&#xff0c;前缀和&#xff0c;滑动窗口&#xff0c;双指针 前缀和&二分 target要大于等于子数组nums[l]到nums[r]这段连续区间的和&#xff0c;因此想到用前缀和sums[r]-sums[l-1]可以快速求得区间和。 如何寻找targ…

1.DM8下载与安装

下载地址&#xff1a;产品下载 | 达梦数据库 DM8的安装与卸载&#xff0c;请参考课件资料中的《DM8 Install.pdf》。 2.DM8管理工具 更多的DM8管理工具请查看官方文档。 2.1.DM服务查看器 DM 服务查看器&#xff0c;顾名思义是对数据库服务进行查看管理的工具。通过服务查看…

JNDI-反序列化

参考博客&#xff1a; JNDI注入与动态类加载 分析版本 jdk8u201 流程分析 在前面JNDI-ldap绕过分析中提到&#xff0c;存在ldap原生反序列化利用点。 再回顾一下&#xff0c;在deserializeObject private static Object deserializeObject(byte[] var0, ClassLoader var…