【Linux】基础IO(open、文件描述符、缓冲区)

news2025/1/10 23:51:11

文章目录

    • 1、从文件操作开始
      • 1.1 文件操作的系统调用接口
      • 1.2 文件描述符
    • 2、重定向
    • 3、缓冲区

1、从文件操作开始

在C语言阶段,接触了很多库函数,如fopen、fclose、fread和fwrte,这些函数帮助了程序实现了内存与磁盘的输入输出功能。

不过之前都是停留在用户层面,在Linux中我们需要关注在系统层面IO发生了哪些细节。

一个有fopen,fclose等调用的程序,代码编译后,形成二进制可执行程序,但是没有运行,这时候的文件操作是肯定没有被执行的,所以对文件的操作,肯定是进程和被打开文件的关系!

一个文件要被访问,一定要先打开。本章探讨的都是被打开的文件

1.1 文件操作的系统调用接口

文件都是在磁盘的,磁盘作为硬件受到操作系统管理,用户操作想访问磁盘就不能绕过操作系统,就需要使用操作系统的接口,使用文件级别的系统调用接口,而操作系统只有一个。

综上,库函数底层必须调用系统接口,库函数可以千变万化,但是底层不变,所以了解底层能降低我们的学习成本。

open()

在这里插入图片描述
open是用来打开文件的,其中:

pathname参数是打开文件所在的路径。
mode参数是让我们对权限进行设定的。
flags作为一种标记位,通过bit位传递选项。
open返回值是一个文件描述符,这个在后面会说。


flags用法类似这种,通过对应宏可使得文件有对应一个或多个操作。

#include <stdio.h>

#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)

void show(int num)
{
	if(num & ONE) printf("ONE\n");
	if(num & TWO) printf("TWO\n");
	if(num & THREE) printf("THREE\n");
	if(num & FOUR) printf("FOUR\n");
}

int main()
{
	show(ONE);
	printf("\n");
	show(ONE | TWO);
	show(ONE | TWO | THREE);
	show(ONE | TWO | THREE | FOUR);
	return 0;
}

使用open:

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

int main()
{
	umask(0);
	//int fd = open("./test", O_WRONLY | O_CREAT);
	int fd = open("./test", O_WRONLY | O_CREAT, 0666);
	if(fd < 0)
	{
		perror("open fail");
		return -1;
	}
	close(fd);
	return 0;
}

在flags中:

  1. O_RDONLY 代表只读
  2. O_WRONLY 代表只写
  3. O_RDWR 代表读,写打开
    (以上三个常量,必须指定一个)
  4. O_CREAT 若文件不存在,则创建它
  5. O_APPEND 追加写
  6. O_TRUNC 每次打开文件空白刷新文件

对于mode参数,如果没有初始权限,产生的文件权限就是乱的,没办法正常写入(读取就可以不用),所以一定要初始化权限,而shell中有对应umask会干扰我们设置的权限,我们在程序中可以通过umask(0)避免这种干扰。
在这里插入图片描述


write和read
在这里插入图片描述
write将buf中count字节大小写入fd中。
read将fd中count字节大小写入buf中。

在这里插入图片描述
lseek改变文件的读写位置,参数如下:

fd:文件描述符
offset:偏移量
whence:通过bit位传递选项。

offse和whence需要一起使用。
当whence是
SEEK_SET,偏移量将从文件的头开始算再加上offset。
SEEK_CUR,偏移量从当前位置加上offset。(offset可以是负数)
SEEK_END,偏移量从文件末尾开始加上offset。(offset可以是负数)

通过以上功能就可以通过系统调用完成文件操作了。

   #include <stdio.h>
   #include <unistd.h>
   #include <sys/types.h>
   #include <sys/stat.h>
   #include <fcntl.h>
   #include <stdlib.h>
   #include <string.h>
   int main()
   {
      int fd = open("./test", O_RDWR | O_CREAT, 0666);
      if(fd < 0)
      {
          perror("fd fail\n");
          exit(-1);
      }
  
      const char str[] = "hello world!\n";
      ssize_t ret = write(fd, str, strlen(str));                                                                                                                                                                                                                            
      if(ret < 0)
      {
          perror("write fail\n");
          exit(-1);
      }
 
      lseek(fd, 0, SEEK_SET);
      
      char buf[64] = {0};
      ret = read(fd, buf, 63);
      if(ret < 0)
      {
          perror("read fail\n");
          exit(-1);
      }
      printf("%s", buf);
      close(fd);
      return 0;
  }

1.2 文件描述符

打开文件后返回的fd是什么?在官方文档中,说返回新的文件描述符。
在这里插入图片描述

   int main()  
   {
      int fd = open("test", O_RDWR | O_CREAT, 0666);
      
      printf("fd:%d\n", fd);                                                                                                                          
  
      close(fd);                                                                                                                             
      return 0;                                                                                                                              
  } 

在这里插入图片描述
那这个所谓的文件描述符是什么呢?
需要再回到理解文件中:

进程是可以打开多个文件的,当系统有大量被打开的文件时,系统需要管理这些文件,所以系统需要给这些文件创建对应的内核结构体表示文件。

其中struct file{} 表示一个个被打开的文件,内部有着文件的属性。
所有被打开的文件struct file{} 通过对应数据结构串起。
那么进程是如何和文件联系起来的呢?
在进程对应PCB中有struct files_struct* files,这个指针指向一个文件描述符表files_struct,这个表最重要的部分就是包含了一个指针数组,每个元素都是一个指向打开文件的指针。
在这里插入图片描述
而文件描述符就是该fd_array指针数组里的下标,拿着文件描述符就能找到对应的文件。
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。

   int main()
   {
      printf("stdin:%d\n", stdin->_fileno);
      printf("stdout:%d\n", stdout->_fileno);
      printf("stderr:%d\n", stderr->_fileno);                                                                                                         
      int fd = open("test", O_RDWR | O_CREAT, 0666);
  
      printf("fd:%d\n", fd);
  
      close(fd);
      return 0;
  }

在这里插入图片描述
所以这也是为什么fd是3的原因。

其实将标准IO流文件描述符的打印再结合这张图
在这里插入图片描述
可以很清楚的知道C语言的文件描述符,其实就在FILE结构体中定义。

总结:

  1. 在系统层面,文件描述符其实就是PCB中指向的文件表中记录打开文件的索引。
  2. 在语言层面,在特定的文件类里也一定会有一个字段表示文件描述符(如C语言的FILE结构体)。

2、重定向

首先是文件描述符的分配规则
从小到大,按照遵循寻找最小的没有被占用的fd。
也就是在默认打开0,1,2后,如果关闭1,再打开文件,文件的fd就会占用1。

理解了分配规则,再看看close()
在这里插入图片描述
关闭对应文件描述符。

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

关闭1号文件描述符关闭标准输出,再打开文件,文件就被数组1下标指向了。
printf调用stdout,stdout内核对应文件描述符还是1,但是文件描述符表数组中fd内的指针指向打开的文件。

那么后续打印就打印到文件中了。
在这里插入图片描述

这种操作也称为
重定向: 上层用的文件描述符不变,在内核中更改文件描述符对应的文件信息操作不同文件。


另一个方法通过系统调用dup2实现
在这里插入图片描述
将oldfd的内容拷贝到newfd中。
成功返回oldfd,失败返回-1。

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

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

3、缓冲区

首先得知道为什么要有缓冲区?

就如我们寄快递一样,物流公司是位了节约我们的时间。
进程将数据读写到磁盘,为了节约进程进行数据IO的时间,会先将数据写入到缓冲区。
(进程在内存,如果让进程直接访问外设,它的速度就会变的很低)

数据的写入,在底层实现其实就是数据的拷贝,将数据从进程拷贝到缓冲区或者外设中!

缓冲区的刷新策略

首先以下策略都是在C语言层面上的:
a.无缓冲 直接刷新到磁盘 不怎么用。
b.行缓冲 一行一行刷新,为了适应用户(比如显示屏)。
c.全缓冲 缓冲区满后全部刷新(比如磁盘)。

也有特殊情况
1.用户使用fflush强制刷新
2.进程退出刷新

先看一个现象

#include <stdio.h>
#include <string.h>
int main()
{
	const char *msg0="hello printf\n";
	const char *msg1="hello fwrite\n";
	const char *msg2="hello write\n";
	printf("%s", msg0);
	fwrite(msg1, strlen(msg0), 1, stdout);
	write(1, msg2, strlen(msg2));
	fork();
	return 0;
}

在这里插入图片描述
为什么在重定向到文件后,系统函数打印一遍,而与库函数相关的函数打印了两遍?
首先这个问题出现在缓冲区,其次如果出现在缓冲区,这个缓冲区一定不在系统层面,不然write也会打印两次。

实际上,在没有>之前,C库函数数据打印在屏幕上,属于行缓冲,所以一句一句直接打印。
在>之后,C库函数是打印在文件中,这时候就成了全缓冲,打印在stdout缓冲区中的还没到一定量,所以还在缓冲区里,fork之后创建子进程,随后进程立即退出,刷新了缓冲区(做了修改),所以会出现写时拷贝,C库函数就打印了两遍。

系统接口函数write没有FILE,不存在该缓冲区,所以直接打印。

这个缓冲区在哪?
其实缓冲区就在FILE结构体中
在C语言中(用户级别中),缓冲区其实就在FILE结构体内。
在这里插入图片描述

缓冲区和系统之间

其实在语言层面上的数据放入缓冲区后不是直接的放入磁盘。
在放入缓冲区后,会经过一个内核缓冲区,这里面的刷新策略由OS自主决定,随后刷新到磁盘。

不过为了避免在一些特殊情况造成数据丢失,系统调用接口fsync()可以使得数据直接从缓冲区放到磁盘。
这里是引用

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

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

相关文章

轻松搭建MQTT服务器,开发流程全透明

1、使用场景 MQTT服务器适用场景就不多介绍了&#xff0c;基本上实在IOT圈发光发热&#xff0c;所以说是特定领域的一个服务端软件&#xff0c;我们是用在车联网的环境里&#xff0c;用来发布消息。 2、选型 最早说需要使用mqtt服务器&#xff0c;然后我以为需要自己开发服务…

专利代理机构代理专利流程

代理申请专利流程是怎么样的&#xff1f; (一)咨询 1、 确定发明创造的内容是否属于可以申请专利的内容。 (二)技术交底 1、申请人向专利代理人提供有关发明创造的背景资料或委托检索有关内容; 2、申请人详细介绍发明创造的内容&#xff0c;帮助专利代理人充分理解发明创造…

【windows下nginx反向代理 ip地址和域名(含启动重启和关闭3个脚本)】 1.vue案例必须修改vue.config.js

例子1&#xff1a;代理vue的web服务器 1)去官网下nginx: download 下载windows版本nginx,并写好脚本,总体目录如下: StartNginx.bat // 启动脚本 echo off echo "nginx start !!!" nginx.exe -c conf/nginx.conf pause ReStartNginx.bat // 修改nginx.conf后重启脚…

redis学习笔记(基础)

Redis简介 Redis是一个开源&#xff08;BSD许可&#xff09;&#xff0c;内存存储的数据结构服务器&#xff0c;可用作数据库&#xff0c;高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合&#xff0c;位图&#xff0c;hyperloglogs等数据类型。内置复制、…

飞腾FT-2000/4处理器+复旦微FPGA+国产操作系统解决方案

XM-1203-FPGA飞腾定制主板 自主可控&#xff0c;国产CPU、BIOS和国产Linux操作系统 性能稳定&#xff0c;FT-2000/4处理器 功能接口多样化&#xff0c;可扩展性强 高度集成&#xff0c;具有丰富的接口和电磁兼容性能. XM-1203-FPGA基于飞腾4 核CPU FT-2000/4的处理器板&#x…

【Javassist】快速入门系列09 当检测到数组创建时用代码块替换

系列文章目录 01 在方法体的开头或结尾插入代码 02 使用Javassist实现方法执行时间统计 03 使用Javassist实现方法异常处理 04 使用Javassist更改整个方法体 05 当有指定方法调用时替换方法调用的内容 06 当有构造方法调用时替换方法调用的内容 07 当检测到字段被访问时使用语…

springboot整合之统一结果返回

特别说明&#xff1a;本次项目整合基于idea进行的&#xff0c;如果使用Eclipse可能操作会略有不同&#xff0c;不过总的来说不影响。 springboot整合之如何选择版本及项目搭建 springboot整合之版本号统一管理 springboot整合mybatis-plusdurid数据库连接池 springboot整合…

树与二叉树(遍历)

一、树 树结构是一种非常重要的非线性数据结构&#xff0c;该结构中的一个数据元素可以有两个或者两个以上的直接后继元素。 1、树的定义 树是由 n(n>0)个有限结点组成一个具有层次关系的集合&#xff0c;当 n0时称为空树&#xff0c;当 n>0时称为非空树。 对于非空树来…

水上交通AIS常见问题解决方案

水上交通AIS常见问题解决方案 AIS无信号的几种情况 情况一&#xff1a;AIS未开启。 解决方法&#xff1a;开启AIS设备。 情况二&#xff1a;AIS开启但MMSI码和证书不一致。 解决方法&#xff1a;船长向直属海事部门申请MMSI码&#xff0c;并请专业人员将正确的MMSI码写入AIS设…

数据结构之堆的实现以及实现堆排序和建堆解决Top K问题

文章目录前言1.堆的相关介绍1.什么是堆2.堆的结构2.堆的相关接口具体实现1.堆的声明和堆的初始化2.堆插入数据和删除数据3.堆的其他函数接口3.堆的实际运用1.建堆算法2.堆的应用之堆排序3.堆解决Top k问题4.总结前言 之前对树的相关知识概念进行了简单介绍&#xff0c;本文将实…

odps-jdbc提交MaxCompute数据源SQL报错HTTP/1.0 is not allowed

概述 最近负责的一款数据产品。有个功能&#xff1a;选择某个数据源&#xff0c;比如阿里云的MaxCompute数据源&#xff0c;然后手写SQL&#xff0c;点击自动生成字段&#xff08;即获取前置SQL里的查询字段。前置SQL可以有drop then create table动作子句&#xff0c;但是最后…

【PS-选区速成】快速选择工具、魔棒工具、对象选择工具

目录 快速选择工具 1、位置 2、3种模式&#xff1a;新选区、添加到选区、从选区减去 3、画笔的设置参数 画笔大小&#xff1a;识别的范围 硬度&#xff1a;边缘的识别能力 间距&#xff1a;识别的连贯程度 跟【选区工具】配套使用的快捷键 1、按【ALT】减区 2、放大…

数据结构之排序【快速排序和归并排序的非递归代码实现及分析】

引言&#xff1a; 今天因为要写论文&#xff0c;所以现在有点迟了&#xff0c;并且此时是北京时间&#xff1a;2022/12/28/1:41 ,我发现晚睡我真的是专业的&#xff0c;当然睡觉我也是专业的&#xff0c;懂的都懂&#xff0c;现在有点迟加上天大寒&#xff0c;手指不可屈伸&am…

android之View的滑动

其实不管是哪种滑动方式&#xff0c;基本思想都是类似的&#xff1a;当点击事件传递到View时&#xff0c;系统记下触摸点的坐标&#xff0c;手指移动的时候&#xff0c;系统记下移动后的坐标&#xff0c;并计算出偏移量&#xff0c;并通过偏移量来修改View的坐标。 下面我们来…

黑客比程序员高在哪里?

黑客其实和一般的程序员一样&#xff0c;但是他们的关注点不一样。黑客关注的是如何破坏&#xff0c;通过这些有创造性的破坏来获取利益&#xff0c;展现自己的能力。而程序员关注的是如何创造&#xff0c;通过创造来获取利益&#xff0c;展现自己的能力。 就如同一个硬币的两…

CCF BDCI|算能赛题决赛选手说明论文-01

基于TPU平台实现人群密度估计 加速器队伍 黄显钧 个人名义参赛 中国-广东广州peterhuang0323qq.com 团队简介 加速器队伍队长&#xff1a;黄显钧&#xff0c;现任某科技公司的高级工程师&#xff0c;技术栈涉足嵌入式全栈开发&#xff0c;AI 开发等领域&#xff0c;对技术充满…

云桌面 Vscode 远程debug python

云桌面 Vscode 远程debug python1、进入云桌面2、下载VScode配套软件3、挂载本地磁盘4、安装软件4.1 安装VScode4.2 安装插件vsix文件4.3 在服务端安装vscode server5、VScode 配置6、远程调试6.1 python解释器选择6.2 设置debug7. mtu 配置Author: 沧海一阳1、进入云桌面 根据…

傻白入门芯片设计,盘点计算机体系结构顶会

目录 一、集成电路/半导体领域的三大顶会&#xff1a; &#xff08;1&#xff09;ISSCC &#xff08;2&#xff09;IEDM &#xff08;3&#xff09;VLSI 二、计算机体系结构四大顶会 &#xff08;1&#xff09;ISCA &#xff08;2&#xff09;HPCA &#xff08;3&#x…

42. 网络中的网络(NiN)

LeNet、AlexNet和VGG都有一个共同的设计模式&#xff1a;通过一系列的卷积层与汇聚层来提取空间结构特征&#xff1b;然后通过全连接层对特征的表征进行处理。 AlexNet和VGG对LeNet的改进主要在于如何扩大和加深这两个模块。 或者&#xff0c;可以想象在这个过程的早期使用全连…

利用GithubPage和Hexo搭建个人博客

title: 利用Github搭建个人博客 date: 2022-11-28 20:55:30 tags: [blogs] categories: Hexo 建立Git远程仓库 固定格式为&#xff1a;name.github.io ![]](https://img-blog.csdnimg.cn/fa9d7320d1cc422a8a79f2b41dd8458e.png) 开启Github Pages 设置github的token登陆 连接…