[Linux]文件/文件描述符fd

2024-05-26 19:04
文章标签 linux fd 描述符

本文主要是介绍[Linux]文件/文件描述符fd,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、关于文件

文件=内容+属性

  • 那么所有对文件的操作,就是对内容/属性操作。
  • 内容是数据,属性也是数据,那么存储文件,就必须既存储内容数据,又存储属性数据。默认就是在磁盘中的文件。
  • 当进程访问一个文件时,都需要先把文件打开。对于普通的磁盘文件,“打开”就是将文件加载到内存。
  • 一个进程可以通过操作系统(操作系统提供系统调用接口)打开多个文件,多个进程可以通过操作系统打开多个文件。加载到内存中被打开的文件可能会存在多个。(进程:打开的文件 = 1 : n
  • 加载磁盘上的文件,一定会涉及访问磁盘设备,是由操作系统来做的。

操作系统在运行时,可能会打开很多文件,那么,操作系统该如何对文件进行管理呢?

一个文件要被打开,一定要在内核中先形成被打开的文件对象,例如:

struct file
{//文件的属性struct file* next;...
}

文件的属性与文件内容一样,在磁盘中同样有保存。

我们使用链表对这些对象进行管理。那么,对于文件的管理,就转换成了对链表的增删查改。

被打开的文件在内存中,未被打开的文件在磁盘中。 所有的文件操作,都是在内存中操作的。

因此,研究文件操作的本质就是研究进程和被打开文件的关系。

二、常见文件接口(C语言)

1、fopen

我们可以在Linux中使用man来查看fopen的手册(man fopen):

打开的模式:

以“w”方式打开为例:

当文件 以“w”方式打开时,若该文件不存在,则自动创建文件。

打开文件时,会先清空文件内容。

#include<stdio.h>//以"w"打开,若文件不存在则自动创建该文件
int main()
{FILE* fp = fopen("log.txt","w");if(NULL == fp){perror("fopen");return 1;}const char* s = "hello world\n";int i = 0;for(i = 0; i < 10; i++){fputs(s,fp);}fclose(fp);return 0;
}

在执行该程序之前,当前目录下只有两个文件。 

make会产生一个myfile.c的可执行文件myfile。

执行该程序后,当前目录下出现了一个新文件:log.txt。

我们可以看到此时该log.txt文件中,就写入了我们程序中设定的内容。

我们对该C程序进行修改:

#include<stdio.h>//以"w"打开,若文件不存在则自动创建该文件
int main()
{FILE* fp = fopen("log.txt","w");if(NULL == fp){perror("fopen");return 1;}fputs("aaa\n",fp);fclose(fp);return 0;
}

重复执行上述内容(注意:我们的log.txt文件并未删除,仍旧存在),我们再次查看log.txt中的内容:

可以看出,之前写入的内容已经被清空。

注:我们使用“> log.txt”也可以实现对该文件进行清空。“>”是重定向,向文件写入。要向文件写入,就一定要打开文件。此时文件内容就会被清空。

 

以"a"方式打开为例:

append:追加。

“a”方式也是写入,只不过是从文件结尾进行写入。

我们将myfile.c文件进行修改:

#include<stdio.h>int main()
{FILE* fp = fopen("log.txt","a");if(NULL == fp){perror("fopen");return 1;}fputs("bbbbbbbbb\n",fp);fclose(fp);return 0;
}

运行该程序,我们可以看出,原先log.txt文件中的内容并未变化,我们是在此基础上追加了内容。

我们可以不断执行,不断追加。

 注:我们使用“echo “cc” >> log.txt”也可以实现追加。

2、

三、系统调用接口

由于打开文件的操作只能由操作系统来完成,所以我们C语言中打开文件的接口,底层一定是封装了系统调用接口的。

1、open

第一个参数是文件名称(不带路径默认当前路径下)。

第二个参数是flags:

第三个参数表示我们要以什么权限来创建新文件。

返回值:

 file descriptor文件描述符:fd

模拟fopen("filename","w"):

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>int main()
{//umask(0);//权限掩码改为0 //O_TRUNC:将打开的文件的长度清零(清空)int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd < 0){perror("open");return 1;}const char* s = "abc\n";write(fd, s, strlen(s));close(fd);return 0;
}

模拟fopen("filename","a")  :

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>int main()
{//O_APPEND:在文件末尾进行写入(追加)int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);if(fd < 0){perror("open");return 1;}const char* s = "abc\n";write(fd, s, strlen(s));close(fd);return 0;
}

四、fd:文件描述符

 返回值类型为int,那么fd究竟是什么?

我们可以输出fd观察:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>int main()
{int fd1 = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fd2 = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fd3 = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fd4 = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);printf("fd1:%d\nfd2:%d\nfd3:%d\nfd4:%d\n",fd1,fd2,fd3,fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0;
}

可以看到,fd是连续的整数。 

当进程打开文件时,操作系统会给每一个文件创建文件对象(被打开文件的描述结构体对象),并对它们使用链表进行维护。

操作系统需要对进程(task_struct)和打开的文件(struct file)之间的对应关系进行维护,就产生了struct files_struct。而task_struct中包含:struct files_struct* files,该指针就指向struct files_struct

struct files_struct这个结构体中,包含了一个数组:struct file* fd_array[]。该数组中,存放struct file的地址。

因此,当进程打开文件时,操作系统会创建struct file对象,并在该数组:struct file* fd_array[]中查询可使用的下标,将struct file对象的地址填入到该下标的位置。然后将该数组的下标返回,我们将该数字称为文件描述符。我们将struct file* fd_array[]称为:进程文件描述符表。

因此,文件描述符fd本质就是数组的下标。

那么,为什么我们显示打印的fd是从3开始的呢?

这是因为进程在运行的时候,默认打开:

标准输入:键盘                stdin                   0

标准输出:显示器             stdout                1

标准错误:显示器             stderr                 2

 stdin/stdout/stderr都是FILE*类型。FILE是一个结构体,其中封装了文件描述符。

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>int main()
{printf("stdin->fd:%d\n",stdin->_fileno);printf("stdout->fd:%d\n",stdout->_fileno);printf("stderr->fd:%d\n",stderr->_fileno);return 0;
}

 那么,我们为什么要把stdin/stdout/stderr打开呢?

这是为了能够让我们默认进行输入输出代码编写。

这篇关于[Linux]文件/文件描述符fd的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1005320

相关文章

windows和Linux安装Jmeter与简单使用方式

《windows和Linux安装Jmeter与简单使用方式》:本文主要介绍windows和Linux安装Jmeter与简单使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Windows和linux安装Jmeter与简单使用一、下载安装包二、JDK安装1.windows设

Kali Linux安装实现教程(亲测有效)

《KaliLinux安装实现教程(亲测有效)》:本文主要介绍KaliLinux安装实现教程(亲测有效),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、下载二、安装总结一、下载1、点http://www.chinasem.cn击链接 Get Kali | Kal

linux服务之NIS账户管理服务方式

《linux服务之NIS账户管理服务方式》:本文主要介绍linux服务之NIS账户管理服务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、所需要的软件二、服务器配置1、安装 NIS 服务2、设定 NIS 的域名 (NIS domain name)3、修改主

Linux实现简易版Shell的代码详解

《Linux实现简易版Shell的代码详解》本篇文章,我们将一起踏上一段有趣的旅程,仿照CentOS–Bash的工作流程,实现一个功能虽然简单,但足以让你深刻理解Shell工作原理的迷你Sh... 目录一、程序流程分析二、代码实现1. 打印命令行提示符2. 获取用户输入的命令行3. 命令行解析4. 执行命令

ubuntu16.04如何部署dify? 在Linux上安装部署Dify的技巧

《ubuntu16.04如何部署dify?在Linux上安装部署Dify的技巧》随着云计算和容器技术的快速发展,Docker已经成为现代软件开发和部署的重要工具之一,Dify作为一款优秀的云原生应用... Dify 是一个基于 docker 的工作流管理工具,旨在简化机器学习和数据科学领域的多步骤工作流。它

Linux高并发场景下的网络参数调优实战指南

《Linux高并发场景下的网络参数调优实战指南》在高并发网络服务场景中,Linux内核的默认网络参数往往无法满足需求,导致性能瓶颈、连接超时甚至服务崩溃,本文基于真实案例分析,从参数解读、问题诊断到优... 目录一、问题背景:当并发连接遇上性能瓶颈1.1 案例环境1.2 初始参数分析二、深度诊断:连接状态与

Linux系统调试之ltrace工具使用与调试过程

《Linux系统调试之ltrace工具使用与调试过程》:本文主要介绍Linux系统调试之ltrace工具使用与调试过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、ltrace 定义与作用二、ltrace 工作原理1. 劫持进程的 PLT/GOT 表2. 重定

Linux区分SSD和机械硬盘的方法总结

《Linux区分SSD和机械硬盘的方法总结》在Linux系统管理中,了解存储设备的类型和特性是至关重要的,不同的存储介质(如固态硬盘SSD和机械硬盘HDD)在性能、可靠性和适用场景上有着显著差异,本文... 目录一、lsblk 命令简介基本用法二、识别磁盘类型的关键参数:ROTA查询 ROTA 参数ROTA

嵌入式Linux之使用设备树驱动GPIO的实现方式

《嵌入式Linux之使用设备树驱动GPIO的实现方式》:本文主要介绍嵌入式Linux之使用设备树驱动GPIO的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、设备树配置1.1 添加 pinctrl 节点1.2 添加 LED 设备节点二、编写驱动程序2.1

嵌入式Linux驱动中的异步通知机制详解

《嵌入式Linux驱动中的异步通知机制详解》:本文主要介绍嵌入式Linux驱动中的异步通知机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、异步通知的核心概念1. 什么是异步通知2. 异步通知的关键组件二、异步通知的实现原理三、代码示例分析1. 设备结构