【1++的Linux】之文件(二)

news2025/2/27 2:35:21

👍作者主页:进击的1++
🤩 专栏链接:【1++的Linux】

文章目录

  • 一,文件描述符
  • 二,重定向
  • 三,理解Linux下一切皆文件

一,文件描述符

我们先来看一段代码:

  #include<unistd.h>
  2 #include<stdio.h>
  3 #include <sys/types.h>
  4 #include <sys/stat.h>
  5 #include <fcntl.h>
  6 int main()
  7 {
  8     int fd1=open("log1.txt1",O_WRONLY|O_CREAT,0666);
  9     int fd2=open("log1.txt2",O_WRONLY|O_CREAT,0666);
 10     int fd3=open("log1.txt3",O_WRONLY|O_CREAT,0666);
 11     printf("%d\n",fd1);
 12     printf("%d\n",fd2);                                                                                     
 13     printf("%d\n",fd3);
 14     return 0;
 15 }

在这里插入图片描述

返回值fd是什么?为什么是345连续的,并且012哪去了呢?
并且当我们在用C库函数提供的文件函数时,FILE*又是什么呢?
下面我们依次来进行解释:
首先,我们现在是对文件进行读写操作,文件要被访问要被加载到内存中去,因此我们现在所说的文件都是内存级文件。
我们的进程要打开文件进行操作,一个进程可以打开多个文件,我们在上述代码中已经验证过。
一个进程打开多个文件,多个进程就能打开更多的文件,那么我们要不要将这些文件管理起来呢?
要的!!!怎么管理???先描述,后组织,这是操作系统进行各种管理的最重要的手段。
因此在OS内部,为了方便管理,OS会创建一个struct file结构体用来描述被打开的文件,创建一个
文件对象,并用双链表将对象链接起来,文件对象里面包含了文件的所有内容。
每个进程用fils_struct来记录文件描述符的使用情况,称为用户打开文件表。在这个结构中又有一个指针数组,用来存放文件对象的地址。我们的文件描述符就是数组下表,不同下表可以对应同一个文件对象,我们的标准输出和标准错误就是这样的。我们的C语言会默认打开三个文件:标准输入,标准输出,标准错误(即stdin,stdout,stderror),因此对应的0,1,2下表也就分给了他们,所以我们新建的文件下表只能从3开始了。file_struct则由我们的PCB:task_struct进行管理。
在这里插入图片描述

在这里插入图片描述

接下来我们再来谈谈FILE*

FILE是什么呢?是一个结构体,那么在这个结构体中有什么呢?
我们来看看源码:

struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;

signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

在我们的文件系统调用中,文件的读写都离不开文件描述符,那么C库函数的文件操作函数中也必定离不开文件描述符,因为C库的文件操作函数的都是经过将系统函数经过封装的。那么我们在使用库函数的时候怎么没有见过文件描述符呢?答案是:我们都间接的使用过,它在FILE结构体中int _fileno;
我们在上述的源码当中,还看到了一些由指针维护的空间,这是什么呢?
我们想一想在学习C语言时提到了缓冲区,那么这个缓冲区是在哪呢?由谁维护呢?
我们来看下面这段代码:

int main()
{
   // FILE* pf=fopen("test.txt","w");
    fwrite("hellow",1,strlen("hellow"),stdout);
    write(1,"hhh\n",3);
    //ffluh(stdout);
    fork();    
    return 0;
}

在这里插入图片描述**

加fflush后
在这里插入图片描述
我们通过结果可以看到加fflush前后结果是不同的。这是为什么呢?接下来我们进行分析。
我们直到父子进程的数据是读时共享,写时拷贝,那么缓冲区在在读时也是共享的,所以,当我们调用C库提供的文件接口时,其数据先会刷新到缓冲区中,fork之后父子共享缓冲区的数据,所以最后子进程要冲刷缓冲区时进行写时拷贝,创建自己的数据区,因此就会有两份“hellow",那么为啥”hhh“只有一份呢?因为其使用的是系统调用,数据在内核的缓冲区中,所以只有一份。
因此我们所了解的缓冲区也就只能由C库提供。在打开一个文件时,其也会被创建,并且由我们FILE*中的两个代表起始地址的指针维护。

那为什么要有缓冲区呢?
当没有缓冲区时,我们要像文件中多次写入少量数据,那么每一次写入,都得需要将这分数据写到磁盘上才能够继续写入(写的过程还包括了打开磁盘,关闭磁盘等)这样效率就会大大的降低,用户的响应速度也会很低。我们将这种模式称为写透模式。
若我们有缓冲区,此时我们就可以将要写的数据直接放到缓冲区中(写入时,最耗时的其实是机械操作(磁盘的寻道等这样的动作),所以我们先将数据放到缓冲区中,根据缓冲区的刷新策略刷新到磁盘中,这样就减少了IO过程,从而提高了整机的效率与用户响应速度。这样的方式其实类似于我们生活中的发快递的过程。这样的模式称为写回模式。
缓冲区的刷新策略有哪几种呢?

  1. 立即刷新
  2. 行刷新 aaaaa\n
    3.满刷新(全缓冲)
    特殊情况:
    用户强制刷新(fflush)
    进程退出

下面是我们模拟实现的一个缓冲区:

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

struct MyFile_
{
    int fd;
    char buff[1024];
    int end;

};
typedef struct MyFile_ MyFile;

MyFile* fopen_(const char *pathname,const char *mode)
{
    assert(pathname);
    assert(mode);
    MyFile* fp=NULL;
    if(strcmp(mode,"r")==0)
    {

    }
    else if(strcmp(mode,"w")==0)
    {
        int fd=open(pathname,O_WRONLY|O_TRUNC|O_CREAT,0666);
        if(fd>0)
        {
            fp=(MyFile*)malloc(sizeof(MyFile));
            assert(fp);
            memset(fp,0,sizeof(MyFile));
            fp->fd=fd;
        }

    }
    else if(strcmp(mode,"w+")==0)
    {

    }
    else{

    }

    return fp;

    
}

void fputs_(char *message,MyFile* fp)
{
    assert(fp);
    assert(message);
    strcpy(fp->buff+fp->end,message);
    fp->end+=strlen(message);
    if(fp->fd==0)
    {

    }
    else if(fp->fd==1)
    {
        if(fp->buff[fp->end-1]=='\n')
        {
            fprintf(stderr,"%s",fp->buff);
            write(fp->fd,fp->buff,fp->end);
            fp->end=0;

        }

    }
    else if(fp->fd==2)
    {

    }
    else{

    }

}

void fflush_(MyFile* fp)
{
    assert(fp);
    if(fp->end!=0)
    {
      write(fp->fd,fp->buff,fp->end);
      syncfs(fp->fd);//将数据写到磁盘
      fp->end=0;
    
    }
}

void fclose_(MyFile* fp)
{
    assert(fp);
    fflush_(fp);
    close(fp->fd);
    free(fp);
    fp=NULL;

}
int main()
{
    close(1);
    MyFile* fp=fopen_("log.txt","w");
    if(fp==NULL)
    {
        printf("open error\n");
        return 1;
    }
    fputs_("hellow world\n",fp);
   // fflush_(fp);
    fputs_("hyp",fp);
    fputs_("zkn\n",fp);
   // fflush_(fp);
   // fork();
    fclose_(fp);
    return 0;
}

二,重定向

先来看一段代码:

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

struct MyFile_
{
    int fd;
    char buff[1024];
    int end;

};
typedef struct MyFile_ MyFile;

MyFile* fopen_(const char *pathname,const char *mode)
{
    assert(pathname);
    assert(mode);
    MyFile* fp=NULL;
    if(strcmp(mode,"r")==0)
    {

    }
    else if(strcmp(mode,"w")==0)
    {
        int fd=open(pathname,O_WRONLY|O_TRUNC|O_CREAT,0666);
        if(fd>0)
        {
            fp=(MyFile*)malloc(sizeof(MyFile));
            assert(fp);
            memset(fp,0,sizeof(MyFile));
            fp->fd=fd;
        }

    }
    else if(strcmp(mode,"w+")==0)
    {

    }
    else{

    }

    return fp;

    
}

void fputs_(char *message,MyFile* fp)
{
    assert(fp);
    assert(message);
    strcpy(fp->buff+fp->end,message);
    fp->end+=strlen(message);
    if(fp->fd==0)
    {

    }
    else if(fp->fd==1)
    {
        if(fp->buff[fp->end-1]=='\n')
        {
            fprintf(stderr,"%s",fp->buff);
            write(fp->fd,fp->buff,fp->end);
            fp->end=0;

        }

    }
    else if(fp->fd==2)
    {

    }
    else{

    }

}

void fflush_(MyFile* fp)
{
    assert(fp);
    if(fp->end!=0)
    {
      write(fp->fd,fp->buff,fp->end);
      syncfs(fp->fd);//刷新内核的缓冲区//将数据写到磁盘
      fp->end=0;
    
    }
}

void fclose_(MyFile* fp)
{
    assert(fp);
    fflush_(fp);
    close(fp->fd);
    free(fp);
    fp=NULL;

}
int main()
{
    close(1);
    MyFile* fp=fopen_("log.txt","w");
    if(fp==NULL)
    {
        printf("open error\n");
        return 1;
    }
    fputs_("hellow world\n",fp);
   // fflush_(fp);
    fputs_("hyp",fp);
    fputs_("zkn\n",fp);
   // fflush_(fp);
   // fork();
    fclose_(fp);
    return 0;
}

在这里插入图片描述
若我们直接执行该程序,则屏幕上会输出“hellow world” 但若我们将命令改为./test>log.txt后,本应该输出的内容却被写到了log.txt中,这是为什么呢?
文件的写入读取都要靠文件描述符去找到所对应的文件,既然本应该向屏幕中写入的内容,写到了其他的文件中,那么一定与文件描述符有关,我们前面说过,文件描述符是数组的下表,这个数组存储的是文件对象的指针,那么就好理解了,发生上述现象的原因,就是该下标中的内容发生了改变,使得原本指向屏幕文件,被替换为了Log.txt这个文件对象的指针。所以就被写入到了log.txt这个文件中。
那么 它具体是怎么实现的呢?
来看一段代码:

int main()
{
  int fd=open("./log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  dup2(fd,1);
  printf("hellow world\n");
  return 0;
}

在这里插入图片描述

dup函数会将参数一所所指向的内容拷贝到参数二中。本质就是更改了文件描述符对应的内容的指向。

三,理解Linux下一切皆文件

感性的认识:
站在系统的角度,我们将屏幕输出可以认为是一种写的过程,键盘输入是一种读的过程,那么我们就可以定义广义的文件概念:只要能进行读和写的设备就叫做文件。将所有设备都看作是文件,方便了我们对他们的操作变得统一和方便。

理性的认识:
在OS的软件设计层面,我们将文件的成员属性和方法都放在结构体中,其方法我们使用文件指针的形式,统一了方法的接口,但是不同的对象去调用,会有不同的结果。其实上述这种也就是用C语言实现面向对象和多态的一种方法。这种方法使得看待所有的文件的方式都一样,也就没有了硬件间的差别了。

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

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

相关文章

什么是CDN内容分发网络?如何做到加速的?

这里写目录标题 CDN如何做到加速的&#xff1f; CDN content delivery network内容分发网络是建立并覆盖在承载网上&#xff0c;由遍布全球的边缘节点服务器群组成的分布式网络。 CDN将源站资源缓存在遍布全球的边缘加速节点服务器上&#xff0c;当客户需要访问和获取资源的时…

如何使用Net2FTP搭建免费web文件管理器打造个人网盘

文章目录 1.前言2. Net2FTP网站搭建2.1. Net2FTP下载和安装2.2. Net2FTP网页测试 3. cpolar内网穿透3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 文件传输可以说是互联网最主要的应用之一&#xff0c;特别是智能设备的大面积使用&#xff0c;无论是个人…

助力乡村教育 泰格智能AI英语开展捐赠实施线上培训

2023年10月11日晚8点至9点&#xff0c;泰格智能AI英语创始人李勤骞老师举行了一场为期一小时的线上培训&#xff0c;旨在详细解答泰格智能AI英语联合中国善网捐赠千词切APP训练账号的意义&#xff0c;探讨乡村教育、学习英语的现状和方法&#xff0c;以及项目的实施和落地要求。…

阿里健康携手15家药企发起“慢病减压计划”,为职场人的健康“减负”

近年来&#xff0c;慢性疾病患者的发病年龄正在逐年递减。一组来自阿里健康研究院内部分析数据印证了这一趋势。数据显示&#xff0c;当代80后职场人群中&#xff0c;三成用户购买过慢病药品。 30-39岁已经成为肝胆、心脑等相关疾病药品的购药主力人群 。35-39岁是高血压疾病药…

204、RabbitMQ 之 使用 topic 类型的 Exchange 实现通配符路由

目录 ★ 使用topic实现通配符路由代码演示topic通配符类型的Exchange代码演示:ConstantUtilConnectionUtilProducerConsumer01执行结果生产者消费者01消费者02 完整代码&#xff1a;ConstantUtilConnectionUtilProducerConsumer01Consumer02pom.xml ★ 使用topic实现通配符路由…

Linux系统移植框架简介

一、系统移植简介 系统移植就是给开发板安装一个linux系统。需要从官方下载u-boot源码&#xff0c;linux内核源码&#xff0c;根文件系统的源码&#xff0c;对源码进行配置和编译&#xff0c;生成对应的源码的镜像文件&#xff0c;将镜像文件部署到开发板中&#xff0c;使开发…

上海亚商投顾:沪指高开高走 锂电等新能源赛道大反攻

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日高开后强势震荡&#xff0c;创业板指盘中一度翻绿&#xff0c;随后探底回升再度走高。碳酸锂期货合约…

制药行业中的设备管理系统和CSV最佳实践

在制药行业&#xff0c;合规性和质量是关键要素&#xff0c;而设备管理和计算机化系统验证&#xff08;CSV&#xff09;是确保这些要素的关键。之前我们介绍过设备健康管理系统如何帮助制药企业实现CSV合规性>>PreMaint设备健康管理系统&#xff1a;实现制药企业的CSV合规…

Activiti 8.0.0 发布,业务流程管理与工作流系统

导读Activiti 8.0.0 现已发布。Activiti 是一个业务流程管理 (BPM) 和工作流系统&#xff0c;适用于开发人员和系统管理员。其核心是超快速、稳定的 BPMN2 流程引擎。Activiti 可以在任何 Java 应用程序、服务器、集群或云中运行&#xff0c;与 Spring 完美集成。 具体更新内容…

声音克隆,定制自己的声音,使用最新版Bert-VITS2的云端训练+推理记录

说明 本次训练服务器使用Google Colab T4 GPUBert-VITS2库为&#xff1a;https://github.com/fishaudio/Bert-VITS2&#xff0c;其更新较为频繁&#xff0c;使用其2023.10.12的commit版本&#xff1a;主要参考&#xff1a;B站诸多大佬视频&#xff0c;CSDN:https://blog.csdn.…

Visual Studio主题颜色及字体

一、打开VS上方的工具 二、选择“选项” 三、在“常规”中选择“颜色主题” 一共四个颜色供大家参考&#xff1a; ①深色 ②浅色 ③蓝色 ④蓝&#xff08;额外对比度&#xff09; 四、字体设置 左边环境中下划

【实训项目】“优品果园”-线上水果商城小程序

1.项目背景 随着现代人对消费水平的追求以及对食物安全的需要&#xff0c;无污染、产地直销的有机水果受到越来越多市民的喜欢。交易过程的简洁化是现代消费者的追求&#xff0c;产地直销也是近期流行的一种新型的交易模式。产地直销的交易模式使得交易过程更加简便快捷&#…

产业集群的转型升级需要各个方面的协同转型——以河北吉力宝为例

随着全球经济的不断演进&#xff0c;产业集群的转型升级已经成为确保企业持续竞争力的必经之路。产业集群是一种由相关产业企业在相对密集的地理区域内紧密互联的经济组织形式&#xff0c;它们通常共享类似的供应链、市场和技术基础。其中&#xff0c;河北吉力宝是中国鞋业产业…

【教学类-32-04】20231013十二生肖4.0(标记+1位数字+排序+左右分类)(大班:《我是中国人》偏科学-数)

作品展示 一位数字、汉字对齐排列 背景需求&#xff1a; 1、优化教案&#xff0c;参与比赛 10月份有一个幼儿园原创教案比赛&#xff0c;我把这个活动《十二生肖排排队》上报了。 2、把两位数序号改成一位数。 “老师&#xff0c;我觉得猴子最大&#xff0c;他是90”。 因…

关于Vue+webpack使用unocss编写CSS,打包后CSS没加前缀

关于Vuewebpack使用unocss编写CSS&#xff0c;打包后CSS没加前缀&#xff0c;封装了一个插件去解决了这个问题 unocss-postcss-webpack-plugin unocss在vite中使用配置&#xff0c;关于unocss在vite中使用&#xff0c;自行查阅官网 https://unocss.dev/integrations/vite ,vi…

Jetson ORIN NX安装Ubuntu20.04,配置CUDA,cuDNN 等注意事项

大体准备工作与流程与博客英伟达官方源Jetson Xavier NX安装Ubuntu20.04&#xff0c;配置CUDA&#xff0c;cuDNN&#xff0c;Pytorch等环境教程&#xff08;基于NVIDIA官方教程&#xff0c;理论适用其它Jetson设备&#xff09;一致&#xff0c;主要在步骤二、三处有一些区别&am…

专利分为哪些?如何查询一个公司的专利信息?

今天来分享专利的相关知识。 专利从字面上是指专有的权利和利益。“专利”一词来源于拉丁语Litterae patentes&#xff0c;意为公开的信件或公共文献&#xff0c;是中世纪的君主用来颁布某种特权的证明&#xff0c;后来指英国国王亲自签署的独占权利证书。专利在现代一般是由政…

湖南省政协副主席赖明勇一行莅临麒麟信安调研

10月11日下午&#xff0c;湖南省政协副主席、民建湖南省委会主委赖明勇&#xff0c;省政协提案委主任袁海平&#xff0c;民建省委会专职副主委、一级巡视员段安娜等长沙先进计算及信息安全产业调研组领导莅临麒麟信安调研国产操作系统企业发展情况。长沙市政府副市长彭涛&#…

Java 拷贝

Java 中的拷贝分为两种&#xff0c;浅拷贝和深拷贝&#xff0c;关于为什么要有两种拷贝方式而不是一种&#xff0c;就要涉及到 Java 的两种类型数据了。Java 的深浅拷贝都是针对于引用类型而言的&#xff0c;基本类型是没有深浅拷贝之分的&#xff0c;类似于 C 语言&#xff0c…

C# AnimeGAN 漫画风格迁移 动漫风格迁移 图像卡通化 图像动漫化

效果 项目 模型 animeganv3_H40_model.onnx animeganv3_H50_model.onnx animeganv3_H64_model.onnx AnimeGANv3_JP_face_v1.0.onnx AnimeGANv3_PortraitSketch_25.onnx Hayao-60.onnx Hayao_64.onnx Paprika_54.onnx Shinkai_53.onnx 下载 可执行文件exe下载 源码下载