Linux操作系统~系统文件IO,什么是文件描述符fd?什么是vfs虚拟文件系统

news2024/11/16 7:33:29

目录

1.open()

(1).第二个参数flags—通过比特位传多组标记

2.文件描述符fd(open函数的返回值)

(1).fd的本质

(2).vfs-虚拟文件系统(一切皆文件)

(3).调用read方法执行流程

 3.文件描述符的分配规则

输出重定向(追加重定向,输入重定向)

printf为什么会向标准输入中输入

4.dup2系统调用

Q:执行exec*程序替换的时候,会不会影响我们曾经打开的所有的文件! !

Q:子进程创建的时候,file_struct中的数据会被拷贝过来吗,指针指向的文件呢?

5.缓冲区

(1).看一个问题(刷新策略)

用户->OS :刷新策略:

(2).write是系统调用,不会用C语言缓冲区


tips:为什么要学习使用系统调用接口?

我们的输入输出最终都是访问硬件,OS是操作系统的管理者

我们使用的都是语言层面上的接口,所以的“语言”上的操作,都必须贯穿OS。

然而操作系统不相信任何人,所以访问操作系统都是需要通过系统调用的接口的。

        因此几乎所有的语言fopen,fclose,fread,fwrite,fgets,fputs,fgetc,fputc等底层一定需要使用OS提供的系统调用

上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。

而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

系统调用接口和库函数的关系,一目了然。

所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。


1.open()

1.实际上C语言的fopen函数底层调用的就是系统的open接口

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  • pathname: 要打开或创建的目标文件
  • flags参数:

flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

 O_RDONLY: 只读打开

 O_WRONLY: 只写打开(默认是覆盖写)

 O_RDWR : 读,写打开

 这三个常量,必须指定一个且只能指定一个

 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限

 O_APPEND: 追加写

  • 返回值:

 成功:新打开的文件描述符

 失败:-1

mode:控制创建文件的权限,以8进制的方式传入,这里我们传入的是0644

int main()
{
    //fopen("./log.txt", "w")
    
    int fd = open("./log.txt", O_WRONLY | O_CREAT, 0644);

    if(fd < 0){
        printf("open error\n");
    }

    printf("fd: %d\n", fd);
    close(fd);
}

(1).第二个参数flags—通过比特位传多组标记

        我们能想到的是通过传123456,来分别对应不同的标志位,但是操作系统这个地方的参数是按位传递的,每一个bit代表一个标志像O_RDONLY,O_WRONLY都是只有一个比特位是1的数,所以它们可以按位|在一起,传入系统调用以后,操作系统可以将传入的flag的值和对应的O_CREAT,O_WRONLY按位&,这样就可以判断当前的文件的打开方式是否是只写,如果文件不存在是否要创建

if(O_WRONLY & flag)
{
    //如果与一下结果为真,则表示flag传入的标志位里面有O_WRONLY,执行对应操作
}

2.文件描述符fd(open函数的返回值)

        文件不打开之前,存放在磁盘中打开文件后被加载到内存。一个进程可以打开多个文件,也就是说,进程被打开后,操作系统需要管理比进程数量更多的文件,这个时候就需要先描述再组织。

struct file 
{
    //包含了打开文件的相关属性信息
    //文件操作指针集合
}

        打开的时候,就是把文件的属性加载到struct file中,所以文件 = 内容+属性。不只是内容,属性也是文件的数据。在文件没有被打开之前,文件的内容和属性都放在磁盘里面。

(1).fd的本质

        fd:本质是操作系统内核中一个指针数组的下标,用于关联进程及其对应的文件的一个指针数组的下标。

        而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一个结构体files_struct,该结构体最重要的部分就是包涵一个指针数组每个元素都是一个指向打开文件的指针(这个文件file所指向的也是一个结构体file,里面有文件的属性等信息)!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

        file结构如下所示,通过f_inode可以找到文件对应的inode结构体(在我之后的文章中会讲到,里面存放了文件的属性信息)。除此之外,该结构中还有f_op,通过它我们可以访问到一个结构体,里面有对文件进行读写操作的接口。write和read函数会调用到这里面的读写接口,从而继续调用统一磁盘驱动中的读写接口,完成对磁盘的读写。

(2).vfs-虚拟文件系统(一切皆文件)

        我们的外设,都可以调用read和write函数,只是像键盘这样的外设,提供的写方法可能是空,然后我们在硬件层的上方有一个vfs,实现了类似多态的功能。

        操作系统中一切皆文件,所以把所有的外设都看作是文件,文件需要用struct file来组织,每个struct file里面有两个函数指针,分别指向不同文件的读方法和写方法。从而在上层看来,我要读就调用文件struct file里面的读方法,写就调用写方法,而不需要关心你这个文件是什么。上层可以将所有文件都看成是struct file类型。


(3).调用read方法执行流程

结合前面的图

        整体流程:调用read方法,进程打开文件以后,现在进程的PCB里面找到一个files struct的结构体指针,找到这个files struct,这个结构体中有一个指针数组,其下标就是文件描述符,对应的元素都是一个个file类型的指针,对应进程打开的文件(前三个下标对应的分别是标准输入,输出,以及标准错误,对应的外设是键盘,显示器,显示器,操作系统默认会为进程打开这三个文件,在操作系统中,一切皆文件),file也是一个结构体,里面存放的是文件的属性等信息,还有对文件进行read和write的函数指针(放在一个函数表里面),调用read方法对文件进行读操作。 


 3.文件描述符的分配规则

在files_struct的指针数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

        我们打开用open打开一个文件,文件的信息就会被加载到内存中,会产生一个file对象(保存文件的信息),此时就需要在一个file_struct的指针数组中分配一个空间存放指向这个文件对象,这个位置对应的下标就是文件描述符,0,1,2分别被标准输入,输出,错误给占用了,所以打开的第一个文件的文件标识符是3。

        如果我们close关闭文件,管理文件用的file也就会被回收,所以其对应的文件描述符也就空出来可以给别的文件使用了。

  • 我们这里关闭0或者2,也就是标准输入或者标准错误的话,0和2会被分配给我们新打开的这个文件

输出重定向(追加重定向,输入重定向)

  • 如果我们这里关闭1,也就是关闭掉标准输入的话,此时再打开一个文件,文件标识符1就会被配给这个文件,此时这个文件就相当于是这个进程的标准输出,所有原本标准输出的内容(比如printf)都会输出都这个文件中,这就叫做输出重定向。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
    close(1);
    int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    fflush(stdout);
    close(fd);
    exit(0);
}

        echo”hello” > log.txt输出重定向的原理就是把echo进程的1关掉,然后把log.txt这个文件打开,文件表示符1也就会被分配给log.txt,此时log.txt就作为标准输出了。

追加重定向的原理

close(1);
int fd = open("./log.txt",O_CREAT | O_WRONLY | O_APPEND, 0644);

输入重定向的原理:

close(0);
int fd = open("./log.txt", O_RDONLY);
// fd == 0
printf("fd: %d\n", fd);
char line[128];
while (fgets(line, sizeof(line) - 1, stdin))
{ // stdin -> FILE * -> FILE 是一个结构体 -> fd == 0
    printf("%s", line);
}

printf为什么会向标准输入中输入

        printf会向stdout里面打印,而stdout是一个File类型的指针,对应的File是一个结构体,存放当前文件的信息,其中肯定会存有文件描述符fd(printf是向标准输出输出内容的,所以这里的fd是1),给到操作系统,操作系统根据fd找到对应要写入的文件,也就是显示器。

  • stdout是C语言层面上的,其中包含文件描述符fd = 1,对应的文件是显示器
  • stdin包含文件描述符fd = 0,对应的文件是键盘
  • stderr包含文件描述符fd = 2,对应的文件是显示器

        C语言上层的这些对文件的操作中,一定要通过系统层来实现,所以C语言层面的File结构体中一定会包含有对应要写入或者读取文件的fd,给到操作系统,操作系统根据这个fd再去找到这个文件进行对应的读写

这些操作最终一定是通过fd文件操作符来找到对应文件并完成操作的。

甚至可以打印出来看看

printf("stdin -> %d\n", stdin->_fileno);
printf("stdout -> %d\n", stdout->_fileno);
printf("stderr -> %d\n", stderr->_fileno);

4.dup2系统调用

        实际上,重定向只需要将fd对应的指针进行拷贝覆盖就可以(因为fd表示的是指针数组的下标,我们之前关闭1,实际上就是把1下标对应的指针置空,然后在创建文件,也就是让1下标的指针指向这个文件)。比如,要让fd为3的文件,输出需要重定向到这个文件,我们只需要把fd = 3对应的指针,拷贝到fd = 1的位置,这样就完成了输出重定向。

#include <unistd.h>

int dup2(int oldfd, int newfd);

read函数从标准输入中读取,然后输出重定向到.log文件

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
    int fd = open("./log", O_CREAT | O_RDWR);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    close(1);  //关不关都行,反正标准输出用不到了
    dup2(fd, 1);
    for (;;)
    {
        char buf[1024] = {0};
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if (read_size < 0)
        {
            perror("read");
            break;
        }
        printf("%s", buf);
        fflush(stdout);
    }
    return 0;
}

Q:执行exec*程序替换的时候,会不会影响我们曾经打开的所有的文件! !

不会,因为exec进行程序替换只会替换代码或者数据,不会影响打开的文件

Q:子进程创建的时候,file_struct中的数据会被拷贝过来吗,指针指向的文件呢?

        会的,以文件描述符为下标的那个数组都会被拷贝一份。子进程创建的时候,task_struct,file_struct都是要重新创建一个的。

        但是文件的部分并不会被拷贝一份,所以会出现父进程和子进程中文件描述符对应的指针可能会指向同一个文件(这里的说的文件实际上是一个FILE类型的对象)。

        父进程打开的标准输入,标准错误和标准输出都会默认被打开,子进程都会继承(因为文件描述符对应的指针都被子进程继承了),这也就是为什么所有进程的标准输入,输出,错误都默认被打开,因为bash打开了,其他的进程都是bash的子进程。


5.缓冲区

(1).看一个问题(刷新策略)

如果我们最后关闭了fd,为什么printf的内容没有输出到文件中?不关闭就会输出到文件中。

        printf实际上是把字符串写到了C语言的缓冲区中,要把C语言缓冲区中的内容刷新到对应文件的内核缓冲区中,必须需要文件描述符fd。因为我们关闭了1,所以这里的fd实际上是1。

        本来我们是向显示器上打印,刷新策略是行缓冲,每隔一个printf都有\n,会将C语言中缓冲区的内容刷新到对应文件的内核缓冲区中,这样最后就能输出到对应的文件中。

        现在是将输出重定向到文件中,刷新策略就变成了全缓冲(此时有\n也没用了,刷新策略已经不是行缓冲了)。此时如果我们最后不关闭fd,那在程序退出的时候,C语言中缓冲区的内容会被刷新到对应文件的内核缓冲区中,最后也能成功输出到文件中。但是如果我们关闭了fd,此时程序退出时,数据被遗留在C语言中的缓冲区中,所以也就无法正常输出到文件中。(因为想把C语言缓冲区的内容刷新到对应文件的内核缓冲区中,必须要文件描述符fd

用户->OS :刷新策略:

  1. 立即刷新(不缓冲)
  2. 行刷新(行缓冲\n),比如,显示器打印
  3. 缓冲区满了,才刷新(全缓冲),比如,往磁盘文件(普通文件)中写入

FILE类里面有与C语言缓冲区相关的内容

解决方法:在最后close前面加上fflush(stdout),在fd被关闭之前,根据fd把C语言缓冲区中的内容刷新到内核缓冲区。


(2).write是系统调用,不会用C语言缓冲区

        write是系统调用,写消息的时候是直接往文件的内核缓冲区里面写的,不会使用C语言的缓冲区,所以最后的close对write的内容没有影响。但是printf是要用到C语言缓冲区的,重定向以后导致刷新策略发生变化,close1以后无法根据文件描述符将C语言缓冲区中的内容刷新到文件内核缓冲区中。

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

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

相关文章

MySQL Binlog 简介

MySQL Binlog Binlog 记录了所有的 DDL 和 DML(除了数据查询语句)语句&#xff0c;以事件形式记录&#xff0c;还包含语句所执行的消耗的时间&#xff0c;MySQL 的二进制日志是事务安全型的。 一般来说开启二进制日志大概会有 1%的性能损耗。 二进制日志包括两类文件&#xf…

seccon 2022 quals -simplemod

文章目录题外话调试思路如何找到对应的link_map分析do_lookup_x我的构造payloadall_exp总结题外话 这个题应该是seccon解题数量最少的了 这个题目其实和babyfile差不多&#xff0c;都是考虑0 lick,整体而言通过这两个题可以感受到出题者对于IO以及dl_resolv的理解深入 这个题目…

阻止移动端 touchmove 与 scroll 事件冲突

在移动端开发过程中&#xff0c;如果要实现一个元素或按钮的拖动定位&#xff0c;会出现很多坑。例如&#xff1a;元素上下移动过程中&#xff0c;会触发 body 的 scroll 事件&#xff0c;导致整体的位置偏移&#xff0c;这时就需要 阻止移动端 touchmove 与 scroll 事件冲突 。…

confluent-kafka-go依赖库编译体验优化

文章目录问题描述&#xff1a;解决方案1&#xff1a;编写Dockerfile文件2&#xff1a;运行Docker镜像3&#xff1a;进入镜像进行编译4&#xff1a;将编译成功的二进制文件复制到本机参考地址问题描述&#xff1a; ​ 在项目中使用了go的kafka库confluent-kafka-go&#xff0c;…

力扣(LeetCode)2. 两数相加(C++\C)

模拟 模拟加法运算&#xff0c;设置进位数 ttt &#xff0c; t(l1t(l1t(l1->vall2vall2vall2->valt)%10valt)\%10valt)%10 即为当前位上的数&#xff0c; t/10t/10t/10 即是进位数。 设置哑结点&#xff0c;便于操作头结点。 模拟上述操作&#xff0c;最后返回哑结点的…

Windows11更新最新系统版本后无法播放媒体声音

故障机器Dell为例 step1&#xff1a;检测系统提示音是否正常&#xff0c;正常可观察第二步&#xff1b; step2:打开计算机管理-设备管理器-观察声音设备是否正常&#xff0c;可右键编辑重启驱动 step3&#xff1a;打开无法播放媒体声音的设备查看设置&#xff0c;Firefox为例 …

腾讯魏巍:Eunomia云原生资源编排优化

2022年11月10日&#xff0c;在中国信通院、腾讯云、FinOps产业标准工作组联合发起的《原动力x云原生正发声 降本增效大讲堂》系列直播活动第10讲上&#xff0c;腾讯Light云计算平台负责人魏巍分享了Eunomia云原生资源编排优化实践。本文整理自魏巍的分享。 云上资源优化背景 相…

为你的服务器集成 LDAP 认证

本文内容 为什么需要 LDAP 认证如何集成 LDAP 认证Nginx 篇Apache 篇Backend 篇本文小结回顾我这些年的工作经历,面向企业(2B)和面向用户(2C)的项目都曾接触过。我个人觉得,面向企业的项目更注重业务,参与决策的人数多、周期长,目的是为企业提供生产经营价值,如缩减成本、…

kotlin coroutine源码解析之suspend挂起函数原理

目录suspend挂起函数join原理Await原理Suspend函数总结suspend挂起函数 在idea中写某些协程函数的时候&#xff0c;会有一个绿色箭头图标的出现&#xff0c;如下图&#xff1a; 而且这些方法不放在协程里面写的话&#xff0c;idea编辑器还会报错&#xff0c;如下图&#xff1…

基于python的人力资源管理系统

摘 要 随着当今社会的发展&#xff0c;时代的进步&#xff0c;各行各业也在发生着变化&#xff0c;比如人力资源管理这一方面&#xff0c;利用网络已经逐步进入人们的生活。传统的人力资源管理&#xff0c;都是员工去公司查看部门信息、招聘信息&#xff0c;这种传统方式局限性…

第一个 Go 程序,从 Hello World 开始

1、开发编辑器 Go 采用的是UTF-8编码的文本文件存放源代码&#xff0c;理论上使用任何一款文本编辑器都可以做 Go 语言开发&#xff0c;这里推荐使用 VS Code 和 Goland。 VS Code 是微软开源的编辑器&#xff0c;而 Goland 是 jetbrains 出品的付费IDE。GoLand 开发工具时收…

linux 安装微擎

前言 OS: CentOS Linux release 7.6.1810 (Core)nginx1.12.2微擎 v2.7.4 环境准备 PHP 7.0MYSQL 5.7 安装mysql 5.7 参考 【Docker】 安装 mysql 安装PHP 7.0 参考 Linux 利用yum源安装php7.0nginx PHP 支持 GD2 yum install php70w-gd*安装完成后重启php PHP 支持 D…

从感知机到神经网络

一、神经网络的一个重要性质 1.1 重要性质 自动从数据中学习到合适的权重参数 1.2 称呼 共n层神经元&#xff0c;称之为n-1层网络 输入层中间层&#xff08;隐藏层&#xff09;输出层 1.3计算神经网络 节点值*权重值偏置值输出值 根据输出值的大小计算出节点值 输出值…

ICV:全球首份量子重力测量仪器市场分析报告。传统测量行业地位正被量子传感器商业化严重威胁,中国有望成为量子重力测量仪器市场最大赢家!

本报告的主要内容是对量子重力测量仪器进行市场分析&#xff0c;并对量子重力测量仪器科研方向、主要企业、关键应用、产业现状&#xff08;科研需求、军用场景&#xff09;、未来趋势等方面进行分析及预测未来的市场发展。 量子重力传感器原理为在真空环境中利用激光和磁场捕获…

11、Service访问Pod、Service IP原理、DNS访问Service、外部访问service

Pod可能因为各种原因发生故障而死掉&#xff0c;Deployment等Controller会通过动态创建和销毁Pod来保障应用整体的健壮性。Pod是脆弱的&#xff0c;但应用是健壮的。每个Pod都有自己的IP地址&#xff0c;当controller用新的Pod替代发生故障的Pod时&#xff0c;新Pod会分配到新的…

【Linux】Ubuntu、Debian下对deb包进行修改后重新打包——以MySQL安装包为例

需求 关于MySQL的lower_case_table_names参数&#xff0c;在Windows系统下和在Linux系统下&#xff0c;默认值是不同的。 Unix&#xff0c;Linux下默认为0&#xff0c;大小写敏感。创建的库表名将原样保存在磁盘上。如create database TeSt;将会创建一个TeSt的目录&#xff0…

Java项目:SSH在线水果商城平台含管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为前后台&#xff0c;分为普通用户与管理员两个角色&#xff0c;前台为普通用户登录&#xff0c;后台为管理员登录&#xff1b; 管理员…

webpack5 Core-js解决async 函数、promise 对象等兼容问题

为什么Core-js 过去我们使用 babel 对 js 代码进行了兼容性处理&#xff0c;其中使用babel/preset-env 智能预设来处理兼容性问题。 它能将 ES6 的一些语法进行编译转换&#xff0c;比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法&#x…

《FFmpeg Basics》中文版-06-填充视频

正文 填充视频意味着向视频帧添加额外的区域以包含额外的内容。当输入应在具有不同宽高比的显示器上播放时&#xff0c; 通常需要填充视频。 填充视频基础知识 对于视频填充&#xff0c;我们使用表格中描述的填充过滤器。 描述在输入视频帧中添加彩色填充&#xff0c;该帧位…

HTML+CSS+JS大作业:商城网购网站设计——淘宝1页

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 在线商城购物 | 水果商城 | 商城系统建设 | 多平台移动商城 | H5微商城购物商城项目 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&a…