[Linux] 基础IO

news2024/9/30 21:27:33

🥁作者华丞臧.
📕​​​​专栏:【LINUX】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录

  • 一、文件I/O
    • 1.1 文件描述符fd
    • 1.2 重定向
      • dup2系统调用
    • 1.3 接口介绍
      • open
      • close
      • read
      • write
      • lseek
      • 接口实验
    • 1.4 缓冲区
      • 什么是缓冲区?
      • 为什么要有缓冲区?
      • 缓冲区的刷新策略
      • 缓冲区在哪里?
    • 1.5 模拟实现IO函数


一、文件I/O

C语言当中fopen和fclose通过文件的路径来打开和关闭文件,fread可以读取文件的内容,fwrite可以向文件中写入数据,在C语言当中还有各种入fputc、fputs、fget等用于读写文件的函数。

操作文件出了上述C接口,我们还可以使用系统调用接口来进行文件访问。

1.1 文件描述符fd

  • 文件存放在磁盘上的,也是数据,即便创建一个空文件也要占据磁盘空间;磁盘上的文件等于文件的内容加上文件的属性,即使内容为空描述该文件的属性也是不为空的。
  • 那么操作文件是在做什么呢?文件包括两个部分文件内容、文件属性,那么对文件的操作无疑就是对内容或者属性的操作。
  • 冯诺依曼体系结构规定,CPU只能从内存中读取数据,所以打开文件实质上是将磁盘中的文件加载到内存当中,并不是所有文件都是打开状态,而是需要使用时再打开。

在这里插入图片描述

open是一个系统调用接口,其原型如下:

int open(const char *pathname, int flags, mode_t mode);

C语言中fopen打开文件后返回的是一个文件指针,而这里返回的是一个整型,这个整型代表什么呢?

这个整型就是文件描述符,首先我们要知道在内存中的一个进程中可以打开多个文件,当一个程序运行时会默认打开三个输出流分别是stdin、stdout、stderr,并且在Linux操作系统下一切皆文件,底层的一些硬件对于操作系统而言都是文件,操作系统启动肯定会打开某一些文件如屏幕、磁盘等。

  • 对于加载到内存中的文件,操作系统需要管理他们,因此需要先描述内存中的文件,再组织这些描述文件的数据结构,实现对文件的管理。
  • 而在Linux操作系统中使用结构体来描述文件,结构体中描述了文件的各种属性,然后再将这些结构体的地址用一个数组组织起来管理,这也就是为什么文件描述符是整型,它相当于是数组的下标。
  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。0、1、2对应的物理设备一般是:键盘,显示器,显示器。
  • 每个进程的PCB都有一个指向file结构体数组的指针,指向自己打开的file结构体数组。

在这里插入图片描述
文件描述符分配规则:在files_struct数组中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。一般默认从3开始,因为0、1、2通常默认是标准输入stdin, 标准输出stdout, 标准错误stderr。

1.2 重定向

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

int main()
{
 	close(1);
 	int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
 	if(fd < 0){
 		perror("open");
 		return 1;
 	}
 	printf("fd: %d\n", fd);
 	fflush(stdout);
 
 	close(fd);
 	exit(0);
}

在这里插入图片描述
此时,我们发现本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, < 。
重定向本质就是改变file结构体数组中文件描述符位置上指针的指向。
在这里插入图片描述

dup2系统调用

函数原型如下:

#include <unistd.h>
//将oldfd重定向到newfd中
int dup2(int oldfd, int newfd);

oldfd:源文件描述符
newfd:目的文件描述符
返回值:
	成功,返回newfd
	失败,返回-1

使用方式如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() 
{
 	int fd = open("./log", O_CREAT | O_RDWR);
 	if (fd < 0) {
 		perror("open");
 		return 1;
	}
 	close(1);
 	dup2(fd, 1);
 	for (;;) 
 	{
 		char buf[1024] = {0};
 		ssize_t read_size = read(0, buf, sizeof(buf) - 1);
 		if (read_size < 0) {
 			perror("read");
 			break;
 		}
 		printf("%s", buf);
 		fflush(stdout);
 	}
 	return 0;
}

上述代码的功能就是将log文件重定向到文件描述符1的位置上,重定向完成后fd:1表示log文件的地址。
在这里插入图片描述

1.3 接口介绍

open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:这些参数都是宏,系统传递标记为是用位图结构来进行传递的。
 	O_RDONLY: 只读打开
 	O_WRONLY: 只写打开
 	O_RDWR  : 读,写打开
 			  这三个常量,必须指定一个且只能指定一个
 	O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
 	O_APPEND: 追加写
 返回值:
 	成功:新打开的文件描述符
 	失败:-1

open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

系统调用库函数

  • fopen、fclose、fread、fwrite都是C标准库当中的函数,称之为库函数。
  • 而open、close、read、write、lseek都是系统提供的接口,称之为系统调用接口。
  • C语言当中关于文件读写的库函数都是对系统调用接口的封装,方便二次开发。

close

#include <unistd.h>
//关闭文件描述符对应的文件
int close(int fd);

返回值:
	成功,返回0
	失败,返回-1

read

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

fd:文件描述符
buf:存放读取数据的空间
count:读取数据字节数

返回值:
	成功,返回读取到的字节数
	失败,返回-1

write

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

fd:文件描述符
buf:需要写入数据的地址
count:写入数据的字节数

返回值:
	成功,返回写入数据的字节数
	失败,返回-1

lseek

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);


fd:文件描述符
offset:偏移量	
whence:
	SEEK_SET:偏移量设置为偏移字节。
	SEEK_CUR:偏移量被设置为其当前位置加上偏移字节。
	SEEK_END:偏移量设置为文件大小加上偏移字节。

返回值:
	成功,返回从文件开始的以字节为单位的结果偏移位置
	失败,返回-1

接口实验

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

int main() 
{
    umask(0);
 	int fd = open("myfile", O_CREAT | O_RDWR | O_TRUC, 0666); //O_TRUC表示打开时清空文件内容
 	if (fd < 0) {
 	    perror("open");
 	    return 1;
	}

 	char buf[] = "hello world!";
 	ssize_t read_size = read(fd, buf, sizeof(buf));
 	if (read_size < 0) {
 		perror("read");
 	}

    int write_size = write(fd, buf, sizeof(buf));
 	if(write_size < 0)
    {
        perror("write");
    }
    return 0;
}

在运行上述代码后,可以看到在当前目录下创建了一个myfile的文件,打开该文件其内容如下图:
在这里插入图片描述

1.4 缓冲区

什么是缓冲区?

  • 缓冲区本质就是一段内存。

为什么要有缓冲区?

  • 解放使用缓冲区进程的时间,进程不用等待传输数据的时间。
  • 缓冲区的存在可以集中处理数据刷新,减少IO的次数,可以提高整机的效率。
  • 缓冲区类似快递驿站,再快递到来之前我们不需要在驿站等待快递,可以去做其他的事情;快递到达驿站后,我们接收到通知去取快递。

缓冲区的刷新策略

  • 常规策略

    • 无缓冲(立即刷新)
    • 行缓冲(逐行刷新)如:显示器
    • 全缓冲(缓冲区满时刷新)如:块设备对应的文件,磁盘文件
  • 特殊策略

    • 进程退出
    • 用户强制刷新

缓冲区在哪里?

首先来看下面这段代码,其运行结果是什么呢?按照代码执行的顺序应该是先打印出 “printf” 再打印 “write” ,真的是这样吗?

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("printf"); //printf("printf\n");
    write(1, "write", 5);
    sleep(5);
    //close(stdout->_fileno); //关闭标准输出,stdout是C语言封装的文件指针类型,_fileno表示该文件指针对应的文件描述符
    return 0;
}

在这里插入图片描述
可以看到结果并不是顺序打印,printf 是封装了 write 系统调用的函数,上述结果也说明了printf函数首先会将数据写入缓冲区中,当数据积累到一定程度才会刷新缓冲区,write会将数据写入文件中。

那么上述缓冲区在哪里呢?修改上述代码在休眠五秒后关闭标准输出,再运行程序结果如下图:
在这里插入图片描述
当我们将标准输出关闭时,看到程序最后并没有打印出 “printf”,这说明printf并没有将数据写入内核级的缓冲区中,而是将数据写入C语言提供的语言级缓冲区中,而write是直接写入到文件中。再来看看下面的这段代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    char *str1 = "hello printf\n";
    char *str2 = "hello fprintf\n";
    char *str3 = "hello fputs\n";
    char *str4 = "hello write\n";

    printf(str1);
    fprintf(stdout, str2);
    fputs(str3, stdout);

    write(stdout->_fileno, str4, strlen(str4));

    fork();
}

在这里插入图片描述
结果原理:

  • 第一次运行test程序时,是将数据写入到stdout即显示器文件中,显示器属于行缓冲因此会每一行每一行刷新,而我们写入的数据中结尾都带有\n,所以会立即刷新,当fork创建子进程时缓冲区数据已经刷新到显示器上了。
  • 第二次运行test程序并重定向到log.txt文件时,是将数据写入磁盘文件属于全缓冲,C接口缓冲区是自己的FILE内部维护的,属于父进程的数据区域,因此fork创建子进程会写时拷贝父进程的数据,所以父子进程都会刷新一次,write是直接将数据写入到文件中,fork时数据已经写入到文件中去了。

1.5 模拟实现IO函数

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

#define NUM 1024 

#define NONE_FLUSH 0x0
#define LINE_FLUSH 0x1
#define FULL_FLUSH 0x2

typedef struct MyFile
{
    int _fileno;
    char _buffer[NUM];
    int _end;
    int _flags;
}MyFile;

MyFile *my_fopen(const char *filename, const char *method)
{
    assert(filename);
    assert(method);
    int flags = O_RDONLY; 

    if(strcmp(method, "r") == 0)
    {
        flags = O_RDONLY;
    }
    else if(strcmp(method, "w") == 0)
    {
        flags = O_WRONLY | O_CREAT | O_TRUNC;
    }
    else if(strcmp(method, "r+") == 0)
    {
        flags = O_RDWR | O_CREAT;
    }
    else if(strcmp(method, "w+") == 0)
    {
        flags = O_RDWR | O_CREAT;
    }
    else if(strcmp(method, "a") == 0)
    {
        flags = O_WRONLY | O_CREAT | O_APPEND;
    }
    else if(strcmp(method, "a+") == 0)
    {
        flags = O_RDWR | O_CREAT | O_APPEND;
    }
    umask(0);
    int fileno = open(filename, flags, 0666);
    MyFile *fp = (MyFile*)malloc(sizeof(MyFile));
    if(fp == NULL) 
    {
        perror("malloc file\n");
        return NULL;
    }
    memset(fp, 0, sizeof(MyFile));

    fp->_fileno = fileno;
    if(fp->_fileno < 0) return NULL;
    fp->_flags |= LINE_FLUSH;
    fp->_end = 0;

    return fp;
}

void my_fwrite(MyFile *fp, const char * start, int size)
{
    assert(fp);
    assert(start);
    assert(size > 0);

    strncpy(fp->_buffer + fp->_end, start, size); //将数据写到缓冲区
    fp->_end += size;

    if(fp->_flags & NONE_FLUSH)
    {}
    else if(fp->_flags & LINE_FLUSH)
    {
        if(fp->_end > 0 && fp->_buffer[fp->_end - 1] == '\n')
        {
            write(fp->_fileno, fp->_buffer, fp->_end);
            fp->_end = 0;
        }
    }
    else if(fp->_flags & FULL_FLUSH)
    {}
}

void my_fflush(MyFile *fp)
{
    assert(fp);

    if(fp->_end > 0)
    {
        write(fp->_fileno, fp->_buffer, fp->_end);
        fp->_end = 0;
    }
}

void my_fclose(MyFile *fp)
{
    my_fflush(fp);
    close(fp->_fileno);
    free(fp);
}


int main()
{
    MyFile *fp = my_fopen("log.txt", "w");
    if(fp == NULL)
    {
        printf("my_fopen error\n");
        return 1;
    }

    const char *s = "hello myfile\n";
    my_fwrite(fp, s, strlen(s));
    printf("\n");
    sleep(3);

    const char *ss = "hello myfile";
    my_fwrite(fp, ss, strlen(ss));
    sleep(3);

    printf("写入了一个不满足条件的字符串\n");

    my_fclose(fp);
    return 0;
}

在这里插入图片描述

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

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

相关文章

SQL Server用户定义的函数(UDF)使用详解

SQL Server用户定义的函数一、背景知识1.1、用户定义函数的优点1.2、函数类型1.3、指引1.4、函数中的有效语句1.5、架构绑定函数1.6、指定参数二、创建用户定义函数2.1、限制和权限2.2、标量函数示例&#xff08;标量 UDF&#xff09;2.3、表值函数示例2.3.1、内联表值函数 &am…

leetcode26.删除数组中的重复项

1.原题目链接&#xff1a;力扣 2.题目&#xff1a; 3. 思路&#xff1a;使用两个指针:src与dst,刚开始均指向起始位置&#xff0c;如果src的值与dst值相同&#xff0c;src,如果src的值与dst的值不相同&#xff0c;dst,src的值赋值给dst,src,即两个指针比较&#xff0c;值不相同…

图像去模糊:MIMO-UNet 模型详解

本内容主要介绍实现图像去模糊的 MIMO-UNet 模型。 论文&#xff1a;Rethinking Coarse-to-Fine Approach in Single Image Deblurring 代码&#xff08;官方&#xff09;&#xff1a;https://github.com/chosj95/MIMO-UNet 1. 背景 由于深度学习的成功&#xff0c;基于卷…

docker搭建linux网络代理

docker搭建linux网络代理 1.准备 config.yaml 配置文件&#xff08;含订阅节点、规则&#xff0c;一般机场或者本地配置中含有&#xff09; 在root下创建文件夹命名为clash。上传配置好的config.yaml至clash文件夹。 2.配置 端口: port: 7890 ; socks-port: 7891 运行局域网…

Python网络爬虫之HTTP原理

写爬虫之前&#xff0c;我们还需要了解一些基础知识&#xff0c;如HTTP原理、网页的基础知识、爬虫的基本原理、Cookies的基本原理等。本文中&#xff0c;我们就对这些基础知识做一个简单的总结。 &#x1f31f;HTTP 基本原理 在本文中&#xff0c;我们会详细了解 HTTP的基本原…

医学图像分割之MedNeXt

论文&#xff1a;MedNeXt: Transformer-driven Scaling of ConvNets for Medical Image Segmentation ConvNeXt网络是一种借鉴Transformer的思想进行了改进实现的全卷积网络&#xff0c;其通过全卷积网络和逆向残差瓶颈单元的设计&#xff0c;可以实现比较大的空间感受野。本文…

【MySQL】聚合查询

目录 1、前言 2、插入查询结果 3、聚合查询 3.1 聚合函数 3.1.1 count 3.1.2 sum 3.1.3 avg 3.1.4 max 和 min 4、GROUP BY 子句 5、HAVING 关键字 1、前言 前面的内容已经把基础的增删改查介绍的差不多了&#xff0c;也介绍了表的相关约束&#xff0c; 从本期开始…

windows将exe或者bat封装成系统服务进行管理

NSSM介绍 NSSM(the Non-Sucking Service Manager)是Windows环境下一款免安装的服务管理软件&#xff0c;它可以将应用封装成服务&#xff0c;使之像windows服务可以设置自动启动等。并且可以监控程序运行状态&#xff0c;程序异常中断后自动启动&#xff0c;实现守护进程的功能…

和利时:自主可控 安全高效

4月13—15日&#xff0c;由易派客电子商务有限公司、中国石油和石油化工设备工业协会、北京长城电子商务有限公司共同主办的2023第二届易派客工业品展览会在苏州国际博览中心成功召开。本次展会以“绿色智造融通赋能”为主题&#xff0c;杭州和利时自动化有限公司&#xff08;简…

Cesium:Particle Systems粒子系统

官网文档,点击此处查看。 粒子系统简述 粒子系统是一种用于模拟复杂物理效果的图形学技术,它是一系列小图片的集合,当这些小图片被放在一起查看时,会形成一种更为模糊的对象,例如:火苗、烟、天气或者烟花。 粒子系统效果在电影和游中是十分普遍的。例如:飞机失…

Spark 之 解析json的复杂和嵌套数据结构

本文主要使用以下几种方法&#xff1a; 1&#xff0c;get_json_object()&#xff1a;从一个json 字符串中根据指定的json 路径抽取一个json 对象 2&#xff0c;from_json()&#xff1a;从一个json 字符串中按照指定的schema格式抽取出来作为DataFrame的列 3&#xff0c;to_j…

【洋桃一号板】STM32F103CBT6标准库函数驱动TM1640点亮数码管

一、今天介绍如何使用STM32F103CBT6驱动TM1640点亮数码管&#xff0c;硬件用的洋桃开发板&#xff0c;点亮后效果如下&#xff0c;六个数码管依次显示0.1.2.3.4.5.6.7 硬件原理图如下&#xff0c;只用到了单片机的两个IO口即可实现上图的效果&#xff0c;该开发板上用的是PA11…

chapter-3 -数据库数据模型

以下内容来源于MOOC学习—原课程请见&#xff1a;数据库原理与应用 考研复习 概述 关系及关系模式 笛卡尔积 定义在一组域上的有序对的集合&#xff0c; 域是一组具有相同类型的集合&#xff0c;比如自然数&#xff0c;长度小于n的字符串结合等【比如int age】 从n个域的每…

Linux工具make与makefile

Linux项目自动化构建工具-make/Makefile 目录Linux项目自动化构建工具-make/Makefile引言1、make && makefile2、make执行步骤2.1 依赖关系2.2 依赖方法3、项目清理4、伪目标 .PHONY5、文件的三个时间6、make的工作原理7、Linux下的第一个小程序认识缓冲区进度条①函数…

T5模型简单介绍

目录 一、概要 二、深入扩展 2.1 两个要素 2.2 预训练方法 一、概要 谷歌公司的研究人员提出的 T5&#xff08;Text-to-Text Transfer Transformer&#xff0c;有5个T开头的单词&#xff0c;所以叫做T5&#xff09;模型采用了一种与前述模型截然不同的策略&#xff1a;将不…

RUAS论文阅读笔记

这是CVPR2021的一篇暗光增强的论文 Retinex增强和去噪部分 第一部分的核心公式是一种retinex公式&#xff08;用于暗图增强的retinex公式有几种类型&#xff0c;虽然本质一样但是对于各个分量的定义不一样&#xff09;&#xff1a;yx⊗tyx\otimes tyx⊗t&#xff0c;其中x是正…

Trie|并查集|堆|

目录 初始化 插入 查询 合并集合 连通块中点的数量 堆排序 模拟堆 Trie树是用来快速存储和查找字符串集合的数据结构 #include<iostream> using namespace std; const int N 100010; int son[N][26];//本题为小写因为字母&#xff0c;每个节点最多有26个子节点…

JUC编程之——synchronized的底层实现与分析

1 synchronized关键字 synchronized 是 Java 中的关键字&#xff0c;是一种同步锁(也是一种悲观锁)。它修饰的对象有以下几种&#xff1a; 作用于实例方法&#xff0c;当前实例加锁&#xff0c;进入同步代码前要获得当前实例的锁——对象锁&#xff1b;作用于代码块&#xff…

水文水利数据对接详解

数据对接 水雨情监测及视频监控系统需要与什么平台进行对接&#xff1f; 答&#xff1a;水雨情监测及视频监控系统由省统一接收的方式&#xff0c;数据接收中心设在***水利云。 2.水雨情数据接收中心有哪些组成部分&#xff1f; 答&#xff1a;水雨情数据接收中心主要由硬件…

pdf如何压缩变小,pdf压缩教程四招快速学

PDF是我们日常工作中经常使用的文件格式之一。这种文件格式方便易用&#xff0c;能够确保文件在传输和接收过程中不会出现错版等问题。为了方便发送&#xff0c;我们通常会将编辑好的内容转换为PDF格式。但是有时候文件过大&#xff0c;无法通过传输渠道发送怎么办&#xff1f;…