【Linux基础IO之 内存文件操作】

2023-10-31

前言

打怪升级:第60天
在这里插入图片描述

一、引入

今天我们要来了解一下操作系统中对文件的操作过程,以及深入理解操作系统对文件的看待方式,那么在开始之前,我们回顾一下自己所学习过的计算机语言中的文件操作方式,这里以C语言为例。

C语言中的文件操作

这里是引用

我们对文件的操作不能仅限于这三个标准文件流,那么当我们想要向其他文件中录入和读取数据时就需要了解文件打开和关闭。

这里是引用


系统文件操作

先来见一见猪跑:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
    
#define LOG "log.txt"    
    
int main()    
{    
  int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);    
  printf("success open file\n");    
  close(fd);    
  printf("success close file\n");                                                                                                                            
  return 0;    
}    

这里是引用

open

在这里插入图片描述

位图

比特位为基本操作单位来存储数据的方式我们称之为位图
一个整形有32个比特位,就可以表示32种不同的情况;
下方我们进行演示:

#include<stdio.h>

#define ONE 0x1
#define TOW 0x2
#define THREE 0x4
#define FOUR 0x8
#define FIVE 0x10

void BitMap(int flags)
{
  if(flags & ONE) printf("ONE\n");
  if(flags & TOW) printf("TOW\n");
  if(flags & THREE) printf("THREE\n");
  if(flags & FOUR) printf("FOUR\n");
  if(flags & FIVE) printf("FIVE\n");
}

int main()
{

  BitMap(ONE | TOW);
  printf("\n");

  BitMap(FIVE);                                                                                                                                              
  printf("\n");

  BitMap(TOW | THREE | FOUR);
  printf("\n");

  return 0; 
}

这里是引用在这里插入图片描述

权限

补充一点:第二个open函数最后一个参数为:权限
我们设置的0666,表示它是一个八进制数 – 可读可写不可执行 – 当然这只是初始权限,真正的权限还需要和掩码进行运算,具体运算规则这里不再说明;
我们也可以手动设置该文件的掩码:

umask(0002);

close、write、read

现在我们可以打开文件,我们再来了解一下文件关闭以及读写操作。

这里是引用

我们来浅浅练习一下:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    // open
#include<unistd.h>    // close、write
#include<string.h>    // strlen
                                                                                                                                                             
#define LOG "log.txt"    
    
int main()    
{    
  umask(002);    
  int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);    // 只写 ,文件没有就创建,打开后清空
  const char* ptr = "hello friend\n"; // 我们手动添加一个\n    
  for(int i=0; i<5; ++i) // 写入5次    
    write(fd, ptr, strlen(ptr));    
    
  close(fd);    
  return 0;    
}

在这里插入图片描述

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
#define LOG "log.txt"    
    
int main()    
{    
  umask(002);    
  int fd = open(LOG, O_RDWR | O_CREAT | O_TRUNC, 0666); // 可读可写     
  const char* ptr = "hello friend\n"; // 我们手动添加一个\n    
  for(int i=0; i<5; ++i) // 写入5次    
    write(fd, ptr, strlen(ptr));    
    
  lseek(fd, SEEK_SET, 0); // 重定位文件读取位置                                                                                                              
                                                
  char str[1024]={0};                           
  read(fd, str, 1023);                          
  printf("%s", str);                            
                                                
                                                
                                                
  close(fd);    
  return 0;    
} 

这里是引用

lseek

这里我们就来介绍一个新的函数接口 – 修改文件位置

在这里插入图片描述

在这里插入图片描述


C语言中的文件操作函数与系统文件操作函数的联系

我们通过打开和关闭两个操作简单了解:

在这里插入图片描述在这里插入图片描述


三、文件描述符

上面我们已经了解了OS中对文件的操作,我们是否已经清楚了OS中的文件操作呢?好像是?
好像已经了解了,那么我们要问一问自己,

  1. 文件描述符是什么?
  2. C语言为什么不直接使用fd,反而要对它进行进一步封装?
  3. C语言又是如何进行封装的?
  4. 其他语言呢?

文件描述符属于OS层面的文件标识符,当一个进程想要操作文件时,就需要用到文件描述符,这里我们需要注意:在语言层面我们一般用到的是文件指针(例如C语言的FILE*),其实这是各个语言对OS层面的文件描述符进行了对应的封装;

下图是OS对文件的相关管理:

在这里插入图片描述
一个进程可以打开多个文件,那么操作系统就需要对这些文件进行管理 – 先描述再组织
OS会为每一个被打开的文件创建一个file结构体,用来存储该文件的属性信息(类比PCB),那么有了文件结构体file,进程还需要确定哪些文件是自己的,哪些是其他进程的,因此就需要对它们建立联系 – 添加指针
这里,操作系统将一个进程所打开的文件所有的地址全部存储在一个 files结构体中,当然files结构体并不仅仅存储了打开文件的地址,只是这部分最为重要,这些所打开文件的地址存储在fd_array数组中;
进程通过files*指针找到 files结构体,通过文件描述符(fd)在fd_array数组中找到对应文件的地址。

1.文件描述符是什么

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
#define LOG "log.txt"    
    
int main()    
{                                                                                                                   
  int fd = open(LOG, O_RDONLY); // 只读方式    
    
  printf("%d\n", fd);    
    
  close(fd);    
  return 0;    
}    

这里是引用
我们上面说文件描述符其实本质上是数组下标,那么既然是数组下标不是应该从0开始吗,这里为什么是3,
难道它是随机存储的吗?

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

#define LOG "log.txt"

int main()
{
  int fd1 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  int fd2 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  int fd3 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  int fd4 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  int fd5 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);                                                                                                   
  int fd6 = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  printf("%d\n", fd1);
  printf("%d\n", fd2);
  printf("%d\n", fd3);
  printf("%d\n", fd4);
  printf("%d\n", fd5);
  printf("%d\n", fd6);

  return 0;
}

这里是引用
此时我们又发现,每打开一个文件,它的文件标识符就加1,也就是顺序存储,
那么为什么文件的文件标识符是从3开始而不是0呢?
文件标识符真的不是从0开始的吗?

在最前面的C语言文件操作中我们提到过:C语言会默认打开三个文件–> stdin, stdout, stderr,
所以,并不是标识符是从3开始,而是,我们在一开始就打开了三个文件占用了0,1,2三个文件标识符,因此,之后打开的文件依次往后排–也就是从3开始;
并且这三个文件的打开顺序是固定的 : 0–>stdin , 1–>stdout, 2–>stderr。
下面我们来验证一下:
验证方法:
以stdout为例,
既然stdout等也是文件,并且它们占用的文件标识符是固定的,那么我们是否可以关闭它们?
– 当然可以。
既然stdout被关闭了,那么它所占用的文件描述符是否还可以分配给其他文件?
我们知道,printf是默认向标准输出 – 显示器,中打印数据的,那么如果stdout被关闭了printf还可以使用吗?

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
#define LOG "log.txt"    
    
int main()    
{    
  close(1);    
    
  int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);     
  printf("%d\n", fd);    
                                                                                                                                                             
  printf("close stdout\n");    
  printf("close stdout\n");    
  printf("close stdout\n");    
  return 0;    
}  

这里是引用在这里插入图片描述

我们能得出的结论:

  1. 文件描述符的分配规则为–>遍历数组,找到第一个没有被使用的位置,将文件的地址存入其中,返回该位置的下标作为文件的文件描述符;(其实应该多打开几个文件使结果更加明显,但是上面已经写好了就懒得改了,看到了朋友要注意一下欧~)
  2. 之前我们认为的printf函数是将信息打印到显示器,而现在我们发现好像并非如此:printf是将信息默认打印到 文件描述符为1的文件中,之前之所以打印到显示器,只是因为显示器文件占用着1号文件描述符而已,换了其他文件也可以!!

2.文件缓冲区

我们想要对文件中的内容进行操作就需要将文件信息加载到内存,既然需要加载到内存中,我们就需要找地方存储下来,这里就引入了文件缓冲区
操作系统对文件进行管理时会为它创建file结构体,file结构体中会有一块区域(数组)存放从文件的读取到的(向文件中写入的)数据,
而这块区域就称为缓冲区;

这里我们可以解答一个平时使用文件时遇到的问题:为什么台式机写文档的时候很担心突然断电?
– 因为,我们向文件中写入内容的时候并没有直接写到磁盘中,而是写入了文件的缓冲区,只有当OS对缓冲区中的内容进行刷新后,我们写入的内容才会被保存到磁盘,而编译器何时刷新完全由编译器自己决定,因此,如果编译器没有进行刷新时突然断电,内存中的数据就丢失了(当然我们也可以手动刷新)。

这里是引用

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
#define LOG "log.txt"    
    
int main()    
{    
  fprintf(stdout, "write in stout\n");    
  fprintf(stdout, "write in stout\n");    
  fprintf(stdout, "write in stout\n");    
  fprintf(stdout, "write in stout\n");    
  fprintf(stdout, "write in stout\n");    
    
  fprintf(stderr, "write in stderr\n");    
  fprintf(stderr, "write in stderr\n");                                                                                                                      
  fprintf(stderr, "write in stderr\n");    
  fprintf(stderr, "write in stderr\n");    
  fprintf(stderr, "write in stderr\n");    
  return 0;    
}  

这里是引用
我们分别向标准输出和标准错误打印数据,可以同时打印到显示器上,说明在 文件标识符1合2中存储的都是显示器文件的地址;
第二步我们将文件中的内容重定向到 log.txt文件,此时我们发现,只有打印到标准输出的内容重定向到log.txt文件,标准错误的信息仍然打印到了显示器,这是因为重定向和printf类似,也是默认只重定向 文件标识符 1 所指向文件的内容,虽然1 和 2都是指向显示器,但是2的指向并没有改变。

再谈重定向

那么我们可以怎么将打印到2中的内容也重定向到log.txt文件呢?

  1. ./a.out &> log.txt (&>本身就是很好的分隔符,所以 &> 两边加不加空格都可以)

在这里插入图片描述
理解:将标准输出和标准错误输出到 log.txt文件。

  1. ./a.out >&log.txt(同上)

这里是引用
理解:同上, 将标准输出和标准错误输出到 log.txt文件。

  1. ./a.out > log.txt 2>&1
    (文件标识符与操作符不可间断,中间不可有空格 – 为什么? – 因为文件中间加空格,OS可以识别区分,但是这些数字如果分开写,OS无法明确这些数字的含义)

在这里插入图片描述
理解:将标准输出重定向到 log.txt文件,再将标准错误重定向到标准输出;

  1. ./a.out 2>log.txt 1>&2
    在这里插入图片描述

理解:同上,将标准错误重定向到 log.txt文件,再将标准输出重定向到标准错误;


四、文件缓冲区分类

语言级缓冲区

我们在一开始提出的第二个问题:C语言提供的FILE结构体在哪里,我们并没有手动设置为什么就可以使用?
C语言为什么要再提供一个FILE结构体而不是直接使用OS提供的fd进行文件操作,难道只是为了展现语言自己的特色吗?
在FILE结构体中到底有什么?

#include<stdio.h>      
#include<sys/types.h>      
#include<sys/stat.h>      
#include<fcntl.h>      
#include<unistd.h>      
#include<string.h>      
      
#define LOG "log.txt"      
      
int main()      
{      
  fprintf(stdout, "printf\n");      
      
  const char* ptr = "write\n";      
  write(1, ptr, strlen(ptr));      
    
  fork();    
                                                                                                                                                             
  return 0;                                                                                                                               
}   

这里是引用

这里,我们就需要引入第二个缓冲区的问题:语言自己提供的缓冲区
我们知道,语言封装的函数底层还是调用系统调用的,因此FILE结构体中一定有fd,
有fd,但是如果只是为了调用系统调用而封装了一个FILE结构体,那可就完全是“脱裤子放屁 ” - 多此一举了。
其实在FILE内部,也会为我们封装一个缓冲区,每个打开的文件都会有一个FILE结构体,也就都会有自己的缓冲区 – 语言级别,
下面我们通过图示进一步了解:

在这里插入图片描述

也就是说:对于一个已经打开的文件,它是有两个缓冲区的!而我们平时所说的文件缓冲区指的一般都是语言级别的缓冲区。
关于缓冲区,它的刷新方式一般有三种:

  • 无缓冲
  • 行缓冲
  • 全缓存

我们来解释一下:
无缓冲指的是 不管我们往缓冲区中写入多少数据,只要写入结束就立即从语言级换冲区刷新到系统缓冲区中;
行缓冲指的是 当我们写入结束,并且最后一个写入的是换行符就会刷新到系统缓冲区;
最后的全缓冲则是 无论我们写入的是什么内容,只有当我们把缓冲区写满的时候才会进行刷新。

各举出一个例子:
无缓冲:打印错误,sdterr;
行缓冲:往显示器上打印,stdout;
全缓冲:往一般文件中写入。

需要注意的一点是:我们上面所说的都是从语言级缓冲区刷新到系统级缓冲区的刷新策略,
至于系统级缓冲区何时将内容刷新到磁盘,这是完全由系统自己决定的。

下面我们来验证一下缓冲区的存在:

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

#define LOG "log.txt"

int main()
{
  fprintf(stdout, "printf");
  sleep(3);
  
  const char* ptr = "write";
  write(1, ptr, strlen(ptr));
  sleep(3);

  printf("\n");
  return 0;
}

在这里插入图片描述

实验现象:等待三秒、打印 “write” , 等待三秒,打印 “printf”
但是如果按照我们平时的想法:打印 “printf” , 等待三秒,打印 “write” ,等待三秒
那么为什么会和我们的想法出入这么大 – 主要是 “printf” 的打印?
我们这里都是向显示器打印的,
首先是语言层,由于此时语言层面的刷新策略是行缓冲,由于没有遇到换行符,所以 “printf” 会一直在语言层面的缓冲区,而没有被刷新到系统缓冲区 ;
第二步,等待三秒;
第三步,我们直接调用系统接口往显示器上打印,那么此时就没有语言层的缓冲区什么事情,直接写入系统缓冲区,之后系统缓冲区根据自己的刷新策略将 “write” 刷新到了显示器;
第四步,等待三秒;
最后一步:我们 使用 printf向显示器文件写入了一个 换行符,语言层面的刷新策略将 语言级缓冲区中的信息刷新到系统缓冲区,再到显示器,所以到此时 “printf\n” 才被打印。

那下面我们就回到上面的问题中:为什么写入增加一个 fork()之后,将内容重定向输出会多打印一次呢?
其实这里涉及到写时拷贝的问题:

在这里插入图片描述在这里插入图片描述

这里的write我们就不谈了,它是直接进行系统调用写入到系统级文件缓冲区的;
我们有两点需要注意:1是输出重定向, 2是创建子进程
由于我们将打印内容从显示器重定向到文件,所以语言级缓冲区的刷新策略也从 行缓冲 变为了 全缓存,
那么在fork之前我们就将 "printf\n"写入到了缓冲区中,之后创建子进程,子进程共享父进程的代码和数据,因此缓冲区中的内容父子都可以看到;
再之后程序运行结束,父子进程相继退出,而进程在退出之前都会清空缓冲区中的内容,
那么此时子进程去清空缓冲区时是不是就相当于一次写入,
所以,OS就会进行写时拷贝, 将 “printf\n” 拷贝到其他地方让子进程去刷新,子进程退出,
父进程也刷新缓冲区,父进程退出。

这,就是我们在 log.txt 中查看到两个 "printf\n"的原因。

为什么要有两个缓冲区

为什么C语言要专门封装一个 FILE结构体,再给我们提供一个缓冲区呢?
– 为了提高IO效率。
我们知道,write、read这些函数都是系统调用,每次使用系统调用都需要占用cpu资源,如果没有语言级的缓冲区,我们每次写入一个字节,就进行一次系统调用,每写入一个字节,就进行一次系统调用,这样做是不是会严重拖慢我们cpu的工作效率?
就比如送快递,到底是每有一个快递,快递公司就立即派一名快递员开始派送,还是等待一段时间,等到收到的快递足够装一车时才进行统一配送?
我想我们大多数朋友都经历过:在网上买一个东西,两天过去了快递仍然没有发出的情况吧(特别是双十一期间!)
采用统一配送的方式可以节省很多人力物力,因此,在我们的计算机中也使用着这种方式 – OS级别的缓存区也是这么个道理。


五、仿写c语言之 FILE结构体

我们采用分文件编写的方式,简单模拟文件操作的几个函数:fopen,fclose,fread,fwrite

my_stdio.h

#pragma once 
#include<stdio.h>

#define BUFSIZE 1024
enum flush_way  //  缓存方式
{
  FLUSH_NONE, // 出错
  FLUSH_ANAY, // 不缓存
  FLUSH_LINE,  // 行缓存
  FLUSH_ALL    // 全缓存
};
typedef struct __C_IO_FILE
{
  int _fd;  //  文件标识符  -- 任何语言的文件操作都必须有
  char _buf[BUFSIZE]; // c库提供的缓冲区   --  也可malloc到堆区
  size_t _cur;  //当前位置
  enum flush_way fway; // 记录刷新方式
}MYFILE;  



MYFILE* my_fopen(const char *path, const char *mode);

int my_fclose(MYFILE *fp);

size_t my_fread(void *ptr, size_t size, size_t nmemb, MYFILE *stream);

size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MYFILE *stream);


my_stdio.c

#include"my_stdio.h"
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<assert.h>
#include<malloc.h>

MYFILE* my_fopen(const char *path, const char *mode)
{
  // 1.分析权限
  int flags = 0; 
  if(strcmp(mode, "w") == 0)
  {
    flags |= O_WRONLY | O_CREAT | O_TRUNC;
  } 
  else  if(strcmp(mode, "r") == 0)
  {
    flags |= O_RDONLY;
  }
  else if(strcmp(mode, "a") == 0)
  {
    flags |= O_WRONLY | O_CREAT | O_APPEND;
  }
  else  // w+ 等等
  {}
    //2. 打开文件
    int fd = -1;
if(flags | O_CREAT)  fd = open(path, flags, 0666);  // 如果创建文件需要设置权限
else  fd = open(path, flags);
  assert(fd >= 0);
  //3. 配置文件信息   --  注意此处需要malloc,之后也需要手动关闭
  MYFILE* fp = (MYFILE*)malloc(sizeof(MYFILE));
  assert(fp);
  fp->_cur = 0;
  fp->_fd = fd;
  fp->fway = FLUSH_LINE;
  memset(fp->_buf, '\0', BUFSIZE);
  //4. 返回文件指针
  return fp;
}

void my_fflush(MYFILE* stream)  //  刷新库缓冲区
{
     write(stream->_fd, stream->_buf, stream->_cur);
     stream->_cur = 0;
}

int my_fclose(MYFILE *fp)
{
  // 1. 不能传空指针
  assert(fp);
  // 清空缓冲区
  my_fflush(fp);
// 2. 关闭os中的文件结构体
  close(fp->_fd);
  // 3.释放c库的文件结构体
  free(fp);
  fp = NULL;
  return 0;
}

size_t my_fread(void *ptr, size_t size, size_t nmemb, MYFILE *stream)
{
  assert(ptr);
  assert(stream);
  // 首先c库缓存中需要有数据 -- 没有就补满
  if(stream->_cur == 0)
  {
  	 ssize_t rsize = read(stream->_fd, stream->_buf, BUFSIZE);
      assert(rsize >=0);
      if(rsize == 0) return 0; // 文件内容读取结束
 }
  size_t usrSize = size * nmemb;
  for(size_t i=0; i<usrSize; )
  {
    // c库缓存中的数据足够用户使用
    if(usrSize - i < BUFSIZE - stream->_cur) 
    {
      strcpy((char*)ptr + i, stream->_buf + stream->_cur);
      stream->_cur += usrSize - i;
      i = usrSize;
    }
      // 不够用户使用的时候,读完之后重新补满
    else 
    {
      strncpy((char*)ptr + i, stream->_buf + stream->_cur, usrSize - i);
       ssize_t rsize =read(stream->_fd, stream->_buf, BUFSIZE);
       assert(rsize >=0);
      if(rsize == 0) break; // 文件内容读取结束
       stream->_cur = 0;
    }
  }
  return usrSize;
}

size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MYFILE *stream)
{
  assert(ptr);
  assert(stream);
  // 判断当前缓冲区有多少内容 -- 满了 需要刷新 并清空
  if(stream->_cur == BUFSIZE)
  {
    my_fflush(stream);
    stream->_cur = 0;
  }
  // 计算用户需要我们写入的数据量 , 开始写入 
  size_t usrSize = size * nmemb;
  for(size_t i=0; i<usrSize; i+=BUFSIZE)
  {
    size_t tmp =  BUFSIZE - stream->_cur;
    if(usrSize - i > tmp) // 1. 现有的加上新添加的 如果大于剩余容量,不写入语言缓冲区,直接刷新到系统缓冲区,只写入我们能写的
    {
      if(usrSize - i > BUFSIZE + stream->_cur)
      {
        write(stream->_fd, stream->_buf, stream->_cur);
        stream->_cur = 0;
        write(stream->_fd, (char*)ptr+i, BUFSIZE);
      }
      else 
      {
        strncpy(stream->_buf + stream->_cur, (char*)ptr + i, tmp);
        my_fflush(stream);
        strncpy(stream->_buf , (char*)ptr + i + tmp, usrSize - i - tmp);
      }
    }
    else // 2. 不大于容量,写入用户需要写入的全部
    {
      strcat(stream->_buf, (char*)ptr);
      stream->_cur += usrSize - i;
    }
    
  }
  // 3. 判断刷新条件 - 全缓冲  行缓存  无缓冲
  if((stream->fway == FLUSH_LINE && stream->_buf[stream->_cur-1]=='\n')
   || (stream->fway == FLUSH_ALL && stream->_cur == BUFSIZE) 
   || (stream->fway == FLUSH_ANAY))
  {
    my_fflush(stream);
  }
  return usrSize;
}


my_main.c

为了方便大家阅读,减少文章长度:此处给出第一份测试代码,之后的测试代码省略,只放截图。


/*  my_main.c  */

#include"my_stdio.h"
#include<string.h>
#include<unistd.h>

int main()
{

  MYFILE* fp = my_fopen("log.txt", "w");

  //printf("open myfile success\n");

  const char* msg = "i write in file of log.txt";
  for(int i=1; 1; ++i)
  {
    my_fwrite(msg, strlen(msg), 1, fp);
    sleep(1);
    if(i % 5 == 0) // 采用行缓冲,每五行录入一个换行符
      my_fwrite("\n", strlen("\n"), 1, fp);
  }
  my_fclose(fp);

  //printf("close myfile success\n");
  return 0;
}

功能测验

w - 写入操作:
在这里插入图片描述
在这里插入图片描述

a - 追加操作
在这里插入图片描述
在这里插入图片描述

r - 读取测试
在这里插入图片描述
在这里插入图片描述


总结

今天我们了解了文件操作的系统调用接口:open、close、write、read、lseek;
搞清楚了文件描述符的含义 – 数组下标;
知道了文件的缓冲区 – OS级 和 语言级;
并且清楚了这两个缓冲区存在的位置,以及为什么存在 – 提高io效率;
最后我们也尝试 写了自己的 FILE结构体 ,
这里需要补充一句:并非只有c语言对有语言级的缓存区,其他语言也会有,并且,不管是哪一个语言,不管他如何进行封装,在这些封装的文件结构体内部,必定存在文件描述符


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux基础IO之 内存文件操作】 的相关文章

  • 使用 sed 更新 xml 属性(Windows + cygwin 和 Linux)?

    我需要使用 sed 命令对 xml 文件进行更新 但我在这方面遇到了麻烦 它需要在 Windows 使用 cygwin 和 Linux 上运行 XML 具有以下元素
  • 域套接字“sendto”遇到“errno 111,连接被拒绝”

    我正在使用域套接字从另一个进程获取值 就像 A 从 B 获取值一样 它可以运行几个月 但最近 A 向 B 发送消息时偶尔会失败 出现 errno 111 连接被拒绝 我检查了B域套接字绑定文件 它是存在的 我也在另一台机器上做了一些测试 效
  • 加载数据infile,Windows和Linux的区别

    我有一个需要导入到 MySQL 表的文件 这是我的命令 LOAD DATA LOCAL INFILE C test csv INTO TABLE logs fields terminated by LINES terminated BY n
  • 如何在数组中存储包含双引号的命令参数?

    我有一个 Bash 脚本 它生成 存储和修改数组中的值 这些值稍后用作命令的参数 对于 MCVE 我想到了任意命令bash c echo 0 0 echo 1 1 这解释了我的问题 我将用两个参数调用我的命令 option1 without
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe
  • 两种情况或 if 哪个更快? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我必须制作一个 非常 轻的脚本 它将接受用户的选项并调用脚本中的函数来执行一些任务 现在我可以使用 IF 和 CASE 选项 但我想知道两
  • Linux:在文件保存时触发 Shell 命令

    我想在修改文件时自动触发 shell 命令 我认为这可以通过注册 inotify 挂钩并调用来在代码中完成system 但是是否有更高级别的 bash 命令可以完成此任务 尝试 inotify 工具 我在复制链接时遇到问题 抱歉 但 Git
  • iptables通过注释删除特定规则

    我需要删除一些具有相同评论的规则 例如 我有带有 comment test it 的规则 所以我可以像这样获得它们的列表 sudo iptables t nat L grep test it 但是我怎样才能删除所有带有注释 测试它 的 PR
  • jpegtran 优化而不更改文件名

    我需要优化一些图像 但不更改它们的名称 jpegtran copy none optimize image jpg gt image jpg 但是 这似乎创建了 0 的文件大小 当我对不同的文件名执行此操作时 大小仍然完全相同 怎么样 jp
  • 如何授予 apache 使用 NTFS 分区上的目录的权限?

    我在一台带有 20GB 硬盘的旧机器上运行 Linux Lubutu 12 10 我有一个 1 TB 外部硬盘 上面有一个 NTFS 分区 在该分区上 有一个 www 目录 用于保存我的网页内容 它在启动时自动安装为 media t515
  • 为arm构建WebRTC

    我想为我的带有arm926ej s处理器的小机器构建webrtc 安装 depot tools 后 我执行了以下步骤 gclient config http webrtc googlecode com svn trunk gclient s
  • PHP 无法打开流:是一个目录

    非常简单的 PHP 脚本 我在我亲自设置的 Ubuntu Web 服务器上的 EE 模板中运行 我知道这与权限有关 并且我已经将我尝试写入的目录的所有者更改为 Apache 用户 我得到的错误是 遇到 PHP 错误 严重性 警告 消息 fi
  • docker容器大小远大于实际大小

    我正在尝试从中构建图像debian latest 构建后 报告的图像虚拟大小来自docker images命令为 1 917 GB 我登录查看尺寸 du sh 大小为 573 MB 我很确定这么大的尺寸通常是不可能的 这里发生了什么 如何获
  • 多处理:仅使用物理核心?

    我有一个函数foo它消耗大量内存 我想并行运行多个实例 假设我有一个有 4 个物理核心的 CPU 每个核心有两个逻辑核心 我的系统有足够的内存来容纳 4 个实例foo并行但不是 8 个 此外 由于这 8 个核心中的 4 个是逻辑核心 我也不
  • 如何使用 GOPATH 的 Samba 服务器位置?

    我正在尝试将 GOPATH 设置为共享网络文件夹 当我进入 export GOPATH smb path to shared folder I get go GOPATH entry is relative must be absolute
  • 是否可以创建一个脚本来保存和恢复权限?

    我正在使用 Linux 系统 需要对一组嵌套文件和目录进行一些权限实验 我想知道是否没有某种方法可以保存文件和目录的权限 而不保存文件本身 换句话说 我想保存权限 编辑一些文件 调整一些权限 然后将权限恢复到目录结构中 将更改的文件保留在适
  • 我不明白 execlp() 在 Linux 中如何工作

    过去两天我一直在试图理解execlp 系统调用 但我还在这里 让我直奔主题 The man pageexeclp 将系统调用声明为int execlp const char file const char arg 与描述 execl exe
  • Linux:如何设置进程的时区?

    我需要设置在 Linux 机器上启动的各个进程的时区 我尝试设置TZ变量 在本地上下文中 但它不起作用 有没有一种方法可以使用与系统日期不同的系统日期从命令行运行应用程序 这可能听起来很愚蠢 但我需要一种sandbox系统日期将被更改的地方
  • 进程退出后 POSIX 名称信号量不会释放

    我正在尝试使用 POSIX 命名信号量进行跨进程同步 我注意到进程死亡或退出后 信号量仍然被系统打开 在进程 打开它 死亡或退出后是否有办法使其关闭 释放 早期的讨论在这里 当将信号量递减至零的进程崩溃时 如何恢复信号量 https sta
  • 如何在c linux中收听特定接口上的广播?

    我目前可以通过执行以下操作来收听我编写的简单广播服务器 仅广播 hello int fd socket PF INET SOCK DGRAM 0 struct sockaddr in addr memset addr 0 sizeof ad

随机推荐

  • 论文配色整理

    配色的重要意义 这是一张色彩鲜艳的论文配图 首先我们看上面一副图 里面配有其中颜色 红 蓝 黄 橙色紫色应有尽有 虽然色彩很鲜艳 但是问题在哪 在于 颜色过于鲜艳 也就是说 每个元素都太明亮 导致图中作者要表达的科学信息不明确 这两张图使用
  • .net调用接口Post,Get数据实例,很详细

    var userId HttpContext Current Request userId var imgUrl HttpContext Current Request imgUrl Dictionary
  • 6. 生信技能树——TCGA癌症数据1

    因为是癌症方面 自己不研究这一方面 所以不常用 但是GEO的转录组数据 是根据这个文件改写的 0 安装包 options repos c CRAN http mirrors tuna tsinghua edu cn CRAN if requ
  • 新能源汽车的充电、电池包的组成、充电的设备

    一 新能源汽车的电池包 1 电动汽车电池包的组成 电动汽车的电池包主要由电池单体 模组构成 电池单体指的是单个独立的锂电池 将多个电池单体组合在一起就成了模组 再把多个模组组合起来最终构成电池包 不过这里有个特例 那就是比亚迪的刀片电池 由
  • Java-抽象类和接口的区别及其使用场景

    一 抽象类 一直不太理解为什么要使用抽象类 实际所有使用抽象类的实现都可以用普通类代替 昨晚突发奇想 把这块深入的啃一下 看到一个很好的例子 由此例子做进一步剖析 比如公司有工程师 管理员 二者都有姓名 name 工号 id 工资 pay
  • 1-Cesium中文教程-快速开始

    快速开始 这是一个基于Cesium 应用真实地理数据构建3D应用程序的快速开始上手的教程 你将学习到如何构建Cesium的web应用页面 像这样 第一步 创建一个账户并获得一个token Cesium ion 是一个开放的数据流平台 托管保
  • 图解 SQL,这也太形象了吧!

    点击上方 码农突围 马上关注这里是码农充电第一站 回复 666 获取一份专属大礼包真爱 请设置 星标 或点个 在看 来源 r6d cn pQFm 本文介绍关系数据库的设计思想 在 SQL 中 一切皆关系 在计算机领域有许多伟大的设计理念和思
  • system.exit(0)和system.exit(1)区别

    1 查看java lang System的源代码 我们可以找到System exit status 这个方法的说明 代码如下 Terminates the currently running Java Virtual Machine The
  • 单击删除所在行

  • MySQL--事务回滚机制与原理

    事务回滚机制 其实 讨论MySQL的事务回滚机制 也就是在说MySQL的事务原子性是如何实现的 关于事务之前文章中有过简单介绍 所谓原子性 就是指一个事务是一个不可分割的工作单位 其中的操作要么都做 要么都不做 如果事务中的一个sql语句执
  • 自旋锁(spin lock)与互斥量(mutex)的比较——多核编程学习笔记2

    自旋锁是一种非阻塞锁 也就是说 如果某线程需要获取自旋锁 但该锁已经被其他线程占用时 该线程不会被挂起 而是在不断的消耗CPU的时间 不停的试图获取自旋锁 互斥量是阻塞锁 当某线程无法获取互斥量时 该线程会被直接挂起 该线程不再消耗CPU时
  • 概率论知识点--上半学期

    第一章 概率论的基本概念 素材来源于B站猴博士 如有侵权立即删除 文章仅供学渣享用 大佬请移步 这里大部分是初中学的 引入了很少的几个概念 P A overline A A 1 P A 意思就是一件事的逆 一定等于一减这件事 P
  • 重装VMware后,导入保存在硬盘上的虚拟机系统,报错:不存在功能misc.rsba_no,但实际情况下应该存在。

    主机重装系统后 重装VMware 用新安装的VMware打开硬盘上的虚拟机系统文件 报错 不存在功能 misc rsba no 但实际情况下应当存在 模块 FeatureCompatLate 启动失败 未能启动虚拟机 是因为 iso 文件找
  • 数据结构:10大经典排序

    排序 1 冒泡排序 2 选择排序 3 插入排序 4 希尔排序 5 快速排序 6 归并排序 7 堆排序 8 计数排序 9 桶排序 10 基数排序 1 冒泡排序 冒泡排序 include
  • uni-app实战之社区交友APP(1)项目介绍和环境搭建

    文章目录 前言 一 项目介绍 二 环境搭建和创建项目 1 开发环境搭建 2 创建uni app项目 三 多端调试环境搭建 1 安卓手机调试配置 2 iOS真机调试配置 3 微信小程序调试配置 4 支付宝小程序调试配置 总结 如需查看本项目实
  • 微信小程序生成二维码

    1 复制 weapp qrcode js QR Code Generator for JavaScript Copyright c 2009 Kazuhiko Arase URL http www d project com License
  • flutter 的像素尺寸

    一般我们在android ios中都有自己的尺寸 如 dp pt 但是在flutter中写尺寸是没有单位的 如 SizedBox height 736 width 375 child Container color Colors light
  • 推荐一个超好用的视觉算法可视化分析工具

    First of all 先甩个项目github链接 https github com aiyojun cv algo analysis 如果觉得不错的给个star吧 鉴于视觉软件的开发成本太高 所以本人基于历史经验写了一个超级方便的可视化
  • vue项目编译打包到服务器,vue项目打包部署到服务器报错

    报错如下 webpack prod配置如下 const webpack require webpack const HtmlWebpackPlugin require html webpack plugin const ExtractTex
  • 【Linux基础IO之 内存文件操作】

    目录 前言 一 引入 C语言中的文件操作 系统文件操作 open 位图 权限 close write read lseek C语言中的文件操作函数与系统文件操作函数的联系 三 文件描述符 1 文件描述符是什么 2 文件缓冲区 再谈重定向 四