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

news/2024/10/18 9:16:30/

👍作者主页:进击的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.ppmy.cn/news/1158545.html

相关文章

从代码入手理解卡尔曼滤波器的原理之预测步骤(二)

// 预测步骤void predict() {// 对于 1D,没有控制输入,因此预测就是使用上一个状态的值。// 但不确定度会增加预测噪声P += Q;}在卡尔曼滤波器中,预测步骤是估算系统在下一个时间点的状态,同时更新状态估算的不确定性(方差)。让我们结合上述的室温测量例子来理解这个 pre…

【精华系列】跟着Token学习数据挖掘-1

Hello&#xff0c;大家好&#xff01;这里是Token的博客&#xff0c;欢迎您的到来 今天整理的笔记时数据挖掘方向的基础入门&#xff0c;了解数据分析使用的一些基础的Python库&#xff0c;为后面的数据处理做好准备 01-数据分析工具介绍 准备&#xff1a;Python的安装、平台搭…

【MySQL】索引的增删查

上篇博客讲解了索引的底层结构 本篇介绍索引的使用 文章目录 一. 主键索引二. 唯一键索引三. 普通索引四. 全文索引五. 查询索引六. 删除索引结束语 一. 主键索引 MySQL默认会按照主键索引进行排序 关键字&#xff1a;primary key 即使建表时没有指明主键&#xff0c;MySQL也会…

SSL证书有效期越来越短是什么原因?

随着互联网的普及和数据安全意识的提高&#xff0c;SSL证书的使用变得日益普遍。SSL证书是一种用于加密数据传输并验证网站身份的安全协议。它们通过加密在用户浏览器和网站服务器之间传输的数据&#xff0c;从而确保数据的隐私和完整性。此外&#xff0c;SSL证书还通过数字签名…

C++入门篇(3)---引用

1.引用 你有没有被人起过外号?比如身边的朋友,喊他的时候不会叫他的全名,像我很好的朋友,我一般都喜欢叫他"阿威",而不会去称呼全名.我叫他"阿威",他还是他没有什么问题. 这里新登场的引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&am…

浅谈智能照明控制系统应用在城市轨道交通

叶根胜 江苏安科瑞电器制造有限公司 江苏江阴 214405 摘要&#xff1a;在传统的城市轨道交通设计方面&#xff0c;照明设计方案具有一定的弊端。随着计算机技术的发展&#xff0c;智能化技术渐渐步入人们的生活并成为主流&#xff0c;故在城市轨道交通中应用新型的照明控制设…

小程序开发平台源码系统+活动在线报名小程序功能 带完整的搭建教程

今天来给大家分享一下小程序开发平台源码系统的活动在线报名小程序功能。活动在线报名小程序是一种方便快捷的活动报名方式&#xff0c;可以通过小程序进行宣传和报名&#xff0c;让参与者可以方便快捷地进行报名和参加活动。小程序有完整的搭建教程&#xff0c;以下是部分功能…

rabbitMQ的知识点

RabbitMQ是一种消息队列软件&#xff0c;它实现了高度可靠的消息传递机制。RabbitMQ支持多种消息协议&#xff0c;包括AMQP、STOMP、MQTT等&#xff0c;比较灵活。以下是一些rabbitmq的知识点&#xff1a; 1. 消息队列&#xff1a;消息队列是一种分布式系统中广泛使用的通信模…