[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 join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

Linux命令rm如何删除名字以“-”开头的文件

《Linux命令rm如何删除名字以“-”开头的文件》Linux中,命令的解析机制非常灵活,它会根据命令的开头字符来判断是否需要执行命令选项,对于文件操作命令(如rm、ls等),系统默认会将命令开头的某... 目录先搞懂:为啥“-”开头的文件删不掉?两种超简单的删除方法(小白也能学会)方法1:用“--”分隔命

Linux五种IO模型的使用解读

《Linux五种IO模型的使用解读》文章系统解析了Linux的五种IO模型(阻塞、非阻塞、IO复用、信号驱动、异步),重点区分同步与异步IO的本质差异,强调同步由用户发起,异步由内核触发,通过对比各模... 目录1.IO模型简介2.五种IO模型2.1 IO模型分析方法2.2 阻塞IO2.3 非阻塞IO2.4