[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

相关文章

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

linux hostname设置全过程

《linuxhostname设置全过程》:本文主要介绍linuxhostname设置全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录查询hostname设置步骤其它相关点hostid/etc/hostsEDChina编程A工具license破解注意事项总结以RHE

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

在Linux终端中统计非二进制文件行数的实现方法

《在Linux终端中统计非二进制文件行数的实现方法》在Linux系统中,有时需要统计非二进制文件(如CSV、TXT文件)的行数,而不希望手动打开文件进行查看,例如,在处理大型日志文件、数据文件时,了解... 目录在linux终端中统计非二进制文件的行数技术背景实现步骤1. 使用wc命令2. 使用grep命令

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

linux重启命令有哪些? 7个实用的Linux系统重启命令汇总

《linux重启命令有哪些?7个实用的Linux系统重启命令汇总》Linux系统提供了多种重启命令,常用的包括shutdown-r、reboot、init6等,不同命令适用于不同场景,本文将详细... 在管理和维护 linux 服务器时,完成系统更新、故障排查或日常维护后,重启系统往往是必不可少的步骤。本文

基于Linux的ffmpeg python的关键帧抽取

《基于Linux的ffmpegpython的关键帧抽取》本文主要介绍了基于Linux的ffmpegpython的关键帧抽取,实现以按帧或时间间隔抽取关键帧,文中通过示例代码介绍的非常详细,对大家的学... 目录1.FFmpeg的环境配置1) 创建一个虚拟环境envjavascript2) ffmpeg-py

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与