文件基础IO

news2024/11/18 5:45:47

目录

前言

 用库进行文件操作

文件描述符

理解Linux一切皆文件

缓冲区

认识缓冲区

缓冲区缓冲策略

磁盘结构

磁盘分区

 软链接和硬链接

硬链接本质

软连接本质

动态库和静态库进阶

写一个静态库

动态库的产生和使用

动静态库的加载

总结:


前言

在我们了解基础文件IO时,我们首先需要接受下列事实:

 用库进行文件操作

任何语言都有进行文件操作的函数,不管是C/C++还是python、shell都有自己的库函数进行文件操作,但是我们知道文件操作的实质是对磁盘进行操作,这是操作系统做的是,所以不管是什么语言,文件操作都是调用的系统文件操作的接口。

 

 

 

如图可以看出,进程想要管理被打开的文件,在PCB中有指针指向一个结构体——文件描述符表,内部包含一个指针数组指向打开文件的属性结构体,实现对文件的操作。

文件描述符

文件描述符(fd)分配规则:从小到大,按照循序寻找最小且没有被占用的fd。

重定向的本质就是:上层用的fd不变,在内核中更改fd对应的struct files*的地址。 

这么一来,标准输出到显示屏中的内容都会被写到新文件之中。 

我们可以借助于系统调用接口dup2实现:

只要调用,就会用oldfd对应的文件对象替代newfd对应的文件对象,也就是原来newfd对应的文件已经关闭,对这个文件描述符的任何操作都转化为对oldfd对应的文件的操作。 

接下来是实现重定向:

#include<stdio.h>                                                                                                                                      
  #include<stdlib.h>
  #include<unistd.h>
  #include<sys/types.h>
  #include<sys/wait.h>
  #include<sys/stat.h>
  #include<fcntl.h>
  #include<assert.h>
  #include<string.h>
  #include<ctype.h>
  #include<errno.h>
  
  #define  LINESIZE 1024
  #define  ARGNUM 64
  #define  NONE_REDIR  0
  #define  OUT_REDIR  1
  #define  APPENED_REDIR  2
  #define  IN_REDIR  3
  #define  DELBLANK(command,start)  do{\
                                          while(isspace(command[start])) ++start;\
                                      }while(0)
  
  char* filename = NULL;
  int redir_type = 0;
  
  
  void  CommandCheck(char* command)
  {
      int start = 0;
      int end = strlen(command);
      while(start < end)
      {
          if('<' == command[start])
          {
              redir_type = IN_REDIR;
              command[start] = '\0';
              ++start;
              DELBLANK(command, start);
              filename = command + start;
              break;
          }
          else if('>' == command[start])
          {
              command[start] = '\0';
              ++start;
              if('>' == command[start])
              {
                  redir_type = APPENED_REDIR;
                  ++start;
                  DELBLANK(command, start);
                  filename = command + start;
              }
              else 
              {
                  redir_type = OUT_REDIR;
                  DELBLANK(command, start);
                  filename = command + start;
              }
              break;
          }                                                                                                                                              
          ++start;
      }
  
  }


  int main()
  {
      char* myarg[ARGNUM];
      char  command[LINESIZE];
      int status = 0;
  
      while(1)
      {
          filename = NULL;
          redir_type = NONE_REDIR;
  
          printf("[用户名@ 主机名 当前地址#] ");
          fflush(stdout);
          //接受指令  分割指令
          char* c = fgets(command,LINESIZE - 1,stdin);
          assert(c != NULL);
          (void)c;
          command[strlen(command) - 1] = 0;
          CommandCheck(command);
  
          myarg[0] = strtok(command, " ");
          int i = 1;
          while(myarg[i++] = strtok(NULL," "));
  
          //内建指令
          if(myarg[0] != NULL && strcmp(myarg[0],"cd") == 0)
          {
              if(myarg[1] != NULL)                                                                                                                       
              {
                  chdir(myarg[1]);
              }
              continue;
          }
          if(myarg[0] != NULL && strcmp(myarg[0],"echo") == 0)
          {
  
              if(myarg[1] != NULL && strcmp(myarg[1],"$?") == 0)
              {
                  printf("%d\n",status>>8&0xFF);
              }
              else if(myarg[1] != NULL)
              {
                  printf("%s\n",myarg[1]);
              }
              continue;
          }
          //执行指令
          pid_t id = fork();
          assert(id != -1);
          if(id == 0)
          {
              switch(redir_type)
              {
                  case NONE_REDIR:
                      break;
                  case OUT_REDIR:
                  case APPENED_REDIR:
                      {
                      int flag = O_WRONLY | O_CREAT;
                      if(redir_type == APPENED_REDIR)
                      {
                          flag |= O_APPEND;
                      }
                      else 
                      {
                          flag |= O_TRUNC;
                      }
                      int fd = open(filename, flag, 0666);
                      if(fd < 0)
                      {
                          perror(">/>>");
                          exit(errno);
                      }
                      dup2(fd, 1);
                      }
                      break;
                  case IN_REDIR:
                      {
                      int fd = open(filename, O_RDONLY);
                      if(fd < 0)
                      {
                          perror("open");
                          exit(errno);
                      }
                      dup2(fd, 0);                                                                                                                       
                      }
                      break;
                  default:
                      {
                      printf("bug?\n");

                      exit(1);
                      }
                      break;
              }
  
  
              int exeret = execvp(myarg[0],myarg);
              if(exeret == -1)
              {
                  exit(10);
              }
              exit(1);
  
          }
          waitpid(id,&status,0);
      }
  
      return 0;
  }        

理解Linux一切皆文件

 外设的功能就是IO,也就是读和写,但是不同的硬件读写方式不一样,所以就需要有不同的函数方法,但是在操作系统管理外设时是将不同的外设看成文件统一管理的,在files对象中有两个指针分别指向这个外设所使用的读写方法,实现对不同外设的统一管理。

缓冲区

认识缓冲区

我们通过一个简单的实验现象来观察缓冲区:

同样的代码,将内容写入标准输出时和写入文件时的结果完全不同,当写入标准输出显示在显示屏上的结果符合预期,但是写入文件中的结果出乎预料,系统调用被写入一次,但是函数调用被写入两次。

这是由于在重定向到文件中时,函数调用的内容被写入到缓冲区,并且文件打开缓冲区使用的规则是全缓冲 ,即缓冲区满之后才把内容做刷新。

并且这个缓冲区一定不在内核之中,如果在内核中系统调用接口也应该打印两次,这个缓冲区是一个用户级语言层面给我们提供的缓冲区。

缓冲区缓冲策略

缓冲区的缓冲策略一共有三种:

a  立即刷新,无缓冲

b  按行刷新,行缓冲

c  缓冲区满刷新,全缓冲

还有两种情况,用户主动刷新和退出之前会有对缓冲区进行刷新。

我们来写一个简易版的缓冲区:

#pragma once     
#include<sys/stat.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<fcntl.h>    
#include<string.h>    
#include<stdlib.h>    
#include<assert.h>    
    
#define SYNC_NONE  1    
#define SYNC_LINE  2    
#define SYNC_FULL  4    
    
typedef struct FILE_    
{    
    int _fd;    
    int _buftype;    
    char _buffer[1024];    
    int _cap;    
    int _size;    
}FILE_;    
    
    
FILE_ * fopen_(const char* file_name, char mode);    
void fwrite_(const void* str, int num, FILE_* fp);    
void fclose_(FILE_* fp);    
void fflush_(FILE_* fp);                                                                                                                                 

#include"mystdio.h"    
    
FILE_* fopen_(const char* file_name, char mode)    
{    
    FILE_* ret = (FILE_*)malloc(sizeof(FILE_));    
    assert(ret);    
    int flags = 0;    
    if(mode == 'r')    
    {    
        flags = O_RDONLY;    
        ret->_fd = open(file_name, flags);    
    }    
    else if(mode == 'w')    
    {    
        flags = O_WRONLY | O_CREAT | O_TRUNC;    
        ret->_fd = open(file_name, flags, 0666);    
    }    
    else if(mode == 'a')    
    {    
        flags = O_WRONLY | O_CREAT | O_APPEND;    
        ret->_fd = open(file_name, flags, 0666);    
    }    
    else{    
        free(ret);    
        return NULL;    
    }    
    ret->_buftype = SYNC_LINE;    
    memset(ret->_buffer, 0, 1024);                                                                                                                       
    ret->_cap = 1024;    
    ret->_size = 0;    
    return ret;    
}    

void fwrite_(const void * str, int num, FILE_* fp)
{
    memcpy(fp->_buffer,str, num);
    fp->_size += num;
    if(fp->_buftype == SYNC_LINE && num + fp->_size < fp->_cap)                                                                                          
    {
        if(fp->_buffer[fp->_size - 1] == '\n')
        {
            write(fp->_fd, fp->_buffer, fp->_size);
            fp->_size = 0;
        }
    }
    else if(fp->_buftype == SYNC_FULL)
    {
        if(fp->_size == fp->_cap)
        {

            write(fp->_fd, fp->_buffer, fp->_size);
            fp->_size = 0;
        }
    }
}

void fclose_(FILE_* fp)
{
    fflush_(fp);
    close(fp->_fd);
    free(fp);
}



void fflush_(FILE_* fp)
{
    if(fp->_size != 0)
    {
        write(fp->_fd, fp->_buffer, fp->_size);
        fp->_size = 0;
    }
}

#include"mystdio.h"                                                                                                                                      
#include<stdio.h>    
    
int main()    
{    
    FILE_* fp = fopen_("./log.txt", 'w');    
    int cnt  = 10;    
    while(cnt)    
    {    
        char str[64];    
        sprintf(str,"cnt = %d \n",cnt);    
        --cnt;    
        fwrite_(str,strlen(str),fp);    
        sleep(1);    
    }    
    fclose_(fp);    
}    

上面模拟的是行缓冲,标准输出因为是给用户看的,所以需要及时刷新,使用行缓存,而对文件进行读写则使用全缓冲。

磁盘结构

磁盘结构的内容可以参看:https://blog.csdn.net/cyynid/article/details/129118760?spm=1001.2014.3001.5502

这里大佬写的博客已经比较清楚了,这里只补充一点:

 使用逻辑抽象主要是为了方便管理,以及降低代码和硬件之间的耦合(毕竟有些硬盘用的可不是CHS)。

根据局部性原理:在最近的将来将用到的信息很可能与正在使用的信息在空间地址上是临近的。

虽然磁盘对应的访问的基本空间是512字节,这依然显得太小了。在os内部的文件系统中定制了多个扇区的读写——1 / 2 / 4 kb。

内存划分为4kb大小的空间——页框。

磁盘中的文件尤其是可执行文件划分为4kb大小的空间——页帧。

磁盘分区

Surper Block : 保存了整个文件系统的信息,备份防止损坏。

Group Descriptor Table : 快组描述表(GDT)对应分组的宏观描述信息,Inode table 以及Date block的使用情况。

Block bitmap : Date blocks的位图,对应Date blocks 的占用情况。

Inode Bitmap : Inode  table的位图,对应Inode  table 的占用情况。

Inode Table :  保存了分组内部所有(可用的 + 已使用的)Inode, 一个inode占128字节.

Date Blocks : 保存了分组内部所有文件的数据块。

文件 = 属性 + 内容。

几乎所有属性都储存在Inode 中,文件名不在Inode中。

文件内容都储存在Date blocks 中。

每个inode在文件系统中都有一个编号(下图左侧):

 补充一点:目录也是一个普通文件,目录文件内容是当前文件下文件名和Inode编号的映射关系

 软链接和硬链接

我们先来见见创建软硬链接的过程:

分别对同一个文件—— myfile.txt 建立软连接和硬链接之后我们可以发现以下几点:

1.软连接和原文件的inode编号不同

2.硬链接和原文件的inode编号相同 

3.添加硬链接之后,文件属性中有一个值从1变为了2

4.软硬链接都可以打开文件

下面我们根据现象来揭开本质:

软硬链接的区别是什么?

区别是软连接有独立的Inode,而硬链接没有独立的Inode.

硬链接本质

硬链接没有独立的inode,所以硬链接没有新增文件,只是新增了新的文件名与原文件Inode编号相映射的关系,inode内部记录了被指向(映射)的数目(硬链接数),rm是减硬链接数,只有为0时才真正被删除。

软连接本质

软链接类似于Windows下的快捷方式,有独立的Inode,是一个新的文件,内部为一个指向原文件的路径(只认名字,不认inode编号),也就是说只要是在这个路径下这个文件名的文件,软连接就能打开,与inode无关。

 目录中的. 和 .. 是硬链接,操作系统不允许用户给目录建立硬链接。

补充:三个时间

Access 最后访问时间

Modify 文件内容最后修改时间

Change 属性最后修改时间

动态库和静态库进阶

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库

动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文 件的整个机器码

在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个 过程称为动态链接(dynamic linking)

动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

写一个静态库

-c  生成 .o 文件(可重定位目标二进制文件)

我们来看看.o文件是怎么用的:

//add.h文件
#include<stdio.h>
int add(int a, int b);
//add.c文件
#include "add.h"                                                                                                                                                                                                                                              

int add(int a, int b)
{
    return a + b;
}

要使用库就得有.h文件,因为我们自己写的代码(main.c)引用了头文件。

其实.o文件和库的用法已经有些相似了,库其实就是将许多.o文件打包在一起,形成一个库。

 形成静态库的命令:

ar  -rc  xxx.a   xxx.o

通过makefile文件来看看静态库是如何产生的:

 再将形成的库打包发送到要使用的目录下。

查看静态库中的目录列表

[root@localhost linux]# ar -tv libmymath.a

rw-r--r-- 0/0 1240 Sep 15 16:53 2017 add.o

rw-r--r-- 0/0 1240 Sep 15 16:53 2017 sub.o

t:列出静态库中的文件

v:verbose 详细信息

要调用库有下列几种方法:

 1.定向找到头文件路径,定向找到库文件路径,链接第三方库需指明库名:

-I  include的第一个字母

-L  lib的第一个字母

-l  库文件名去前缀和后缀

 2.将头文件和库文件分别拷贝到/usr/include/和/lib64/中去。

这就是安装的过程,第三方库即使被安装也需要指明文件名。

动态库的产生和使用

动态库的产生和静态库有一些不一样:

1. 生成.o文件时,要多加一个选项 -fPIC(位置无关码,相应的函数在库文件中的相对位置)。

2.生成.so文件时多加一个 -shared

但是直接像静态库一样调用动态库会失败,因为动态库在运行时也需要知道库的位置。

有四个方法可以解决这个问题:

1.改环境变量(重新启动后失效)

 export    LD_LIBRARY_PATH=$LD_LIBRARY_PATH:路径

 2.将动态库路径添加到/etc/ld.so.conf.d/路径下的任意一个.conf文件 + 命令行指向 ldconfig即可

且重启后仍然有效

3.在当前目录下建立软链接

 注意:链接名得和库名一样

4.这种方法和静态库的第二种方法相同

动静态库的加载

静态库中的代码在可执行程序中,储存在代码区(调用时使用绝对地址)

调用动态库函数时,使用位置无关码,确定函数定义在动态库中的相对偏移位置(偏移量),动态库被写入共享区,通过偏移量调用相应函数。

总结:

 

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

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

相关文章

SE | 哇哦!让人不断感叹真香的数据格式!~

1写在前面 最近在用的包经常涉及到SummarizedExperiment格式的文件&#xff0c;不知道大家有没有遇到过。&#x1f912; 一开始觉得这种格式真麻烦&#xff0c;后面搞懂了之后发现真是香啊&#xff0c;爱不释手&#xff01;~&#x1f61c; 2什么是SummarizedExperiment 这种cla…

lighthouse的介绍和基本使用方法

Lighthouse简介 Lighthouse是一个开源的自动化性能测试工具&#xff0c;我们可以使用该功能检测我们的页面存在那些性能方面的问题&#xff0c;并会生成一个详细的性能报告来帮助我们来优化页面 使用方式 LH一共有四种使用方式 Chrome开发者工具Chrome扩展Node 命令行Node …

数据结构与算法(一)-软件设计(十七)

设计模式&#xff08;十五&#xff09;-面向对象概念https://blog.csdn.net/ke1ying/article/details/129171047 数组 存储地址的计算&#xff1a; 一维数组a[n]&#xff0c;当a[2]的存储地址为&#xff1a;a2*len&#xff0c;如果每一个数组元素只占用一个字节&#xff0c;那…

Spring Batch 高级篇-分区步骤

目录 引言 概念 分区器 分区处理器 案例 转视频版 引言 接着上篇&#xff1a;Spring Batch 高级篇-并行步骤了解Spring Batch并行步骤后&#xff0c;接下来一起学习一下Spring Batch 高级功能-分区步骤 概念 分区&#xff1a;有划分&#xff0c;区分意思&#xff0c;在…

中国ETC行业市场规模及未来发展趋势

中国ETC行业市场规模及未来发展趋势编辑根据市场调研在线网发布的2023-2029年中国ETC行业发展策略分析及战略咨询研究报告分析&#xff1a;随着政府坚持实施绿色出行政策&#xff0c;ETC行业也受到了极大的支持。根据中国智能交通协会统计&#xff0c;2017年中国ETC行业市场规模…

浅析Linux内核进程间通信(信号量)

信号灯与其他进程间通信方式不大相同&#xff0c;它主要提供对进程间共享资源访问控制机制。相当于内存中的标志&#xff0c;进程可以根据它判定是否能够访问某些共享资源&#xff08;临界区&#xff0c;类似于互斥锁&#xff09;&#xff0c;同时&#xff0c;进程也可以修改该…

FreeRTOS任务基础知识

单任务和多任务系统单任务系统单任务系统的编程方式&#xff0c;即裸机的编程方式&#xff0c;这种编程方式的框架一般都是在main&#xff08;&#xff09;函数中使用一个大循环&#xff0c;在循环中顺序的执行相应的函数以处理相应的事务&#xff0c;这个大循环的部分可以视为…

Linux内核共享内存使用常见陷阱与分析

所谓共享内存就是使得多个进程可以访问同一块内存空间&#xff0c;是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制&#xff0c;如 信号量结合使用&#xff0c;来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的…

【华为OD机试模拟题】用 C++ 实现 - 最小叶子节点(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 获得完美走位(2023.Q1) 文章目录 最近更新的博客使用说明最小叶子节点题目输入输出示例一输入输出示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华…

oracle数据库使用JDBC导入ClickHouse数据

一、背景 需求要把oracle中的数据导入到clickhouse中&#xff0c;使用clickhouse的jdbc表引擎&#xff0c;把oracle11g的数据导入到clickhouse中。 二、方案 通过clickhouse-jdbc-bridge&#xff1a;是clickhouse提供的一个jdbc组件&#xff0c;用于通过JDBC的方式远程访问其他…

[面试直通版]网络协议面试核心之IP,TCP,UDP-TCP与UDP协议的区别

点击->计算机网络复习的文章集<-点击 目录 前言 UDP TCP 区别小总结 前言 TCP和UDP都是在传输层&#xff0c;在程序之间传输数据传输层OSI模型&#xff1a;第四层TCP/IP模型&#xff1a;第三层关键协议&#xff1a;TCP协议、UDP协议传输层属于主机间不同进程的通信传…

Unity Lighting -- 光照入门

识别光源 首先来看一张图&#xff0c;看看我们能在这个场景中找到几个光源。 相信大家能够很容易看出来&#xff0c;四盏路灯模型带有四个光源&#xff0c;右边的红绿蓝三个发光的灯也是光源。场景中还有一个光源&#xff0c;这个光源来自天空&#xff0c;让场景看起来有点日落…

尚医通(二十四)就医提醒和预约统计

目录一、就医提醒1、搭建定时任务模块二、后台管理系统-预约统计功能1、开发每天预约数据接口2、封装远程调用接口4、整合统计功能前端一、就医提醒 我们通过定时任务&#xff0c;每天8点执行&#xff0c;提醒就诊 1、搭建定时任务模块 &#xff08;1&#xff09;添加依赖 &l…

【MySQL】调控 字符集

一、 MySQL 启动选项 & 系统变量 启动选项 是在程序启动时我们程序员传递的一些参数&#xff0c;而 系统变量 是影响服务器程序运行行为的变量 1.1 启动项 MySQL 客户端设置项包括&#xff1a; 允许连入的客户端数量 、 客户端与服务器的通信方式 、 表的默认存储引擎 、…

zookeeper入门到精通

文章目录一、zookeeper入门1. 概述zookeeper的工作机制2.特点3.数据结构4.应用场景4.1.统一命名服务4.2.统一配置管理4.3.统一集群管理4.4.服务器节点动态上下线4.5.软负载均衡5.下载地址二、zookeeper安装1.本地模式安装2.配置参数解读三、zookeeper集群操作1.集群操作1.1 集群…

C++学习笔记-继承

继承的基本概念 类与类之间的关系 has-A&#xff0c;包含关系&#xff0c;用以描述一个类由多个“部件类”构成&#xff0c;实现has-A关系用类的成员属性表示&#xff0c;即一个类的成员属性是另一个已经定义好的类。 use-A&#xff0c;一个类使用另一个类&#xff0c;通过类…

前端面试题整理6-react

React 中 keys 的作用是什么&#xff1f; Keys是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识 在开发过程中&#xff0c;我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中React 会借助元素的 Key 值来判断该元素是新近创建的还…

第五章 Opencv图像的几何变换

目录1.缩放图像1-1.resize()方法2.翻转图像2-1.flip()方法3.仿射变换图像3-1.warpAffine()方法3-2.平移3-3.旋转3-4.倾斜4.透视图像4-1.warpPerspective()方法几何变换是指改变图像的几何结构&#xff0c;例如大小、角度和形状等&#xff0c;从而使图像呈现出缩放、翻转、仿射和…

KUKA机器人外部自动运行模式的相关信号配置

KUKA机器人外部自动运行模式的相关信号配置 通过例如PLC这样的控制器来进行外部自动运行控制时,运行接口向机器人控制系统发出机器人进程的相关信号(例如运行许可、故障确认、程序启动等),机器人向上级控制系统发送有关运行状态和故障状态的信息。 必需的配置:  配置CEL…

Oracle-01-简介篇

&#x1f3c6;一、Oracle的历史和发展 Oracle公司成立于1977年&#xff0c;由拉里埃里森&#xff08;Larry Ellison&#xff09;、鲍勃明特&#xff08;Bob Miner&#xff09;和埃德奥茨&#xff08;Ed Oates&#xff09;共同创立。起初&#xff0c;公司的主要业务是开发和销售…