Linux(11)——基础IO(上)

news2025/6/7 15:46:22

目录

一、理解文件

二、回顾C文件的接口

📄 C语言文件操作函数表

​编辑📄 三个文件流  

三、系统文件I/O 

1️⃣open

2️⃣close

3️⃣write 

4️⃣read 

四、文件描述符

💡用户操作文件的底层逻辑是什么?

💡什么是进程创建的时候会默认打开0、1、2?

五、文件描述符的分配规则


一、理解文件

我们来以三个问题来切入

💡文件大小为0时,要不要占用空间呢?

答案毋庸置疑是要的,因为除了内容之外,还有文件的相关属性,这里我们要明确一点就是文件=内容+属性(也叫元数据)。

💡访问文件需要打开文件,谁来打开呢?

实际上是进程来打开文件,对文件的操作本质上还是进程对文件的操作,而根本上还是系统对于文件的一系列的调用。

💡操作系统要不要管理被打开的文件,怎么管理?

答案是要管理,还是我们之前说的先描述,再组织

二、回顾C文件的接口

📄 C语言文件操作函数表

函数名功能说明返回值说明
fopen()打开一个文件成功返回 FILE*,失败 NULL
fclose()关闭一个已打开的文件成功返回 0,失败 EOF
fread()从文件中读取数据返回实际读取的元素数量
fwrite()向文件写入数据返回实际写入的元素数量
fgetc()从文件读取一个字符成功返回字符,失败 EOF
fputc()向文件写入一个字符成功返回字符,失败 EOF
fgets()从文件读取一行字符串成功返回字符串,失败 NULL
fputs()向文件写入一个字符串成功返回非负值,失败 EOF
fprintf()向文件格式化输出返回写入的字符数
fscanf()从文件格式化输入成功读取项数
ftell()获取文件当前位置(偏移量)成功返回位置,失败 -1L
fseek()设置文件位置指针成功返回 0,失败非零
rewind()将文件位置指针重置到文件开头无返回值
feof()检查文件是否到达 EOF是返回非零,否返回 0
ferror()检查文件操作是否发生错误有错返回非零,无错返回 0
clearerr()清除文件错误和 EOF 标志无返回值
remove()删除一个文件成功返回 0,失败非零
rename()重命名一个文件成功返回 0,失败非零
tmpfile()创建一个临时二进制文件成功返回 FILE*,失败 NULL
setbuf()设置文件缓冲区无返回值
setvbuf()设置缓冲方式和缓冲区大小成功返回 0,失败非零

这里不过多介绍了,感兴趣的友友可以移步至:文件读写https://blog.csdn.net/2301_80065652/article/details/141182826

下面我们来写几个读写方面的例子:

写操作:

读操作: 

下面这个函数一定要注意了,函数原型是:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

参数说明:

参数名含义
ptr指向存储读取数据的内存地址(缓冲区)
size单个数据项的字节数
nmemb要读取的数据项个数
stream文件指针

返回值说明:返回成功读取的数据项数量(即不是字节数,是数据项个数),如果出错或到达文件末尾,返回值可能小于nmemb。 

所以下面这个代码是在每次读stelen(msg)(包含了回车)个字节,并在末尾置0以输出字符串。

输出到屏幕的操作:

其实这里的代码和上面的写操作是很一样的,这是因为输出到文件就是对屏幕文件进行写操作。



📄 三个文件流  

我们现在要明确一个概念那就是“一切皆文件”,也就是说在Linux下一切都是可以用文件来表示的,包括但不限于键盘文件和屏幕文件,电脑通过从键盘文件中获取数据然后再向屏幕文件中写数据来显示我们输入的内容。

但是我们在实际操作的时候并没有说是去打开某个键盘文件或是屏幕文件,实际上是操作系统默认帮我们打开了三个文件流,他们分别是标准输入流(stdin),标准输出流(stdout)和标准错误流(stderr),同时呢他们又分别是键盘文件,显示器文件和显示器文件。

#include <stdio.h>

extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

我们发现这里的返回值类型和我们fwrite函数和fprintf函数中的参数类型是一样的,都是FILE*类型。 

敲黑板:

其实我们见过的绝大多数语言是有这三个文件流的,比如Java和Python。

三、系统文件I/O 

其实我们在C语言和C++中用用到的fopen(C语言)和ifstream(C++)都是语言层上面的调用,实际上呢在系统层面都是有自己行对应的接口的,而语言层只是对系统层的一个封装罢了,这样的封装更加易用且有自己的缓冲机制。接下来我们谈谈四大底层文件的接口:

1️⃣open

函数原型:

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

参数说明:

第一个参数pathname是要打开的文件名或是要创建的目标文件。

第二个参数是flags表示的是打开文件的方式,如图:

参数含义
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读写打开
O_CREAT文件不存在则创建(需配合 mode 参数)
O_APPEND写入时追加到文件尾
O_TRUNC如果文件存在则清空
O_EXCLO_CREAT 一起使用时,文件存在就报错
O_NONBLOCK非阻塞打开

这里就不得不提一下传递多个像这要的标志位的方法了:

给出一个例子来说明

#include <stdio.h>

#define ONE 0001 //0000 0001

#define TWO 0002 //0000 0010

#define THREE 0004 //0000 0100

void func(int flags) {
 if (flags & ONE) printf("flags has ONE! ");
 if (flags & TWO) printf("flags has TWO! ");
 if (flags & THREE) printf("flags has THREE! ");
 printf("\n");
}

int main() {
 func(ONE);
 func(THREE);
 func(ONE | TWO);
 func(ONE | THREE | TWO);
 return 0;
}

就是说实际上这里的flags的传递也是这样的方式,我们可以在bits/fcntl-linux.h文件中见一见:

可以见到:

/* From /usr/include/asm-generic/fcntl.h */
#define O_RDONLY        00
#define O_WRONLY        01
#define O_RDWR          02
#define O_CREAT       0100 /* not fcntl */
#define O_EXCL        0200 /* not fcntl */
#define O_TRUNC      01000 /* not fcntl */
#define O_APPEND     02000
#define O_NONBLOCK   04000

第三个参数是mode,表示文件创建出来的权限,这是一个可选项(因为也可以只是打开文件): 

我们在之前谈文件权限时提到了权限源码的概念,也就是说这里设置的权限需要进行计算(mode&(~umask))才是最终的权限,如果我们想让这个权限就是最终的权限可以将unmask设置为0。

返回值:

这个函数的返回值就是一个文件描述符(file descriptor),类型为int。

我么可以写个代码来看看:

#include <stdio.h>    
#include <sys/stat.h>    
#include <sys/types.h>    
#include <fcntl.h>    
    
int main()    
{    
    umask(0);    
    int fd1 = open("test1.txt", O_RDONLY | O_CREAT, 0666);    
    int fd2 = open("test2.txt", O_RDONLY | O_CREAT, 0666);    
    int fd3 = open("test3.txt", O_RDONLY | O_CREAT, 0666);    
    int fd4 = open("test4.txt", O_RDONLY | O_CREAT, 0666);    
    int fd5 = open("test5.txt", O_RDONLY | O_CREAT, 0666);    
    
    printf("fd1:%d\n", fd1);    
    printf("fd2:%d\n", fd2);    
    printf("fd3:%d\n", fd3);    
    printf("fd4:%d\n", fd4);    
    printf("fd5:%d\n", fd5);    
    return 0;                                                                                                                                                                       
}

我们也可以尝试打开不存在的文件试试:

我们发现他的文件描述符是-1。

这里的文件描述符实际上就是一个指针数组的下标,而指针数组中的指针就指向了被打开了的文件的文件的信息。这也就说明了我们打开文件其实就是依次增加被打开文件的个数相应的就增加了数组指针的下标了,而如果没有访问到这个文件那么就返回-1。但是我们会发现我们在上面的打印信息中的下标并不是从1开始的,这是因为我们之前讲的进程会默认打开三个文件,也就是标准输入0,标准输出1,标准错误2。

2️⃣close

这个接口没啥好说的了,就是关闭打开的文件:

#include <unistd.h>

int close(int fd);

传入要关闭的文件描述符,如果关闭成功就返回0,否则返回-1。

示例:

3️⃣write 

 用于将数据从内存写入一个文件描述符所代表的目标:

#include <unistd.h>

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

参数说明:

fd:文件描述符

buf:指向要写入的数据的内存地址

count:要写入的字节数

返回值:

成功:返回写入的字节数(可能小于count)
失败:返回 -1

我们来写个代码来验证:

#include <stdio.h>      
#include <string.h>      
#include <unistd.h>      
#include <sys/types.h>      
#include <sys/stat.h>      
#include <fcntl.h>      
      
int main()      
{      
    int fd = open("test2.txt", O_WRONLY | O_CREAT, 0666);      
    if(fd < 0){      
        perror("open errror");      
        return 1;      
    }      
    const char* msg = "hello xywl\n";                                                                                                                                               
    for(int i = 0; i < 5; i++){      
        write(fd, msg, strlen(msg));      
    }                          
    close(fd);                 
    return 0;                  
}

输出结果:

  

4️⃣read 

用于从文件描述符中读取数据到内存:

#include <unistd.h>

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

参数说明:

fd:文件描述符
buf:接收数据的内存缓冲区(通常是一个数组)
count:希望读取的最大字节数

返回值:

情况返回值
读取成功实际读取的字节数(可能 < count)
读到文件结尾0(EOF)
出错

-1,并设置 errno(如 EBADFEINTR 等)

写个代码验证一下:

#include <fcntl.h>    
#include <unistd.h>    
#include <stdio.h>    
    
int main() {    
    int fd = open("example.txt", O_RDONLY);    
    if (fd == -1) {    
        perror("open");    
        return 1;    
    }    
    
    char buffer[128];    
    ssize_t bytes_read;    
    
    while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {    
       write(1, buffer, bytes_read);                                                                                                                                                
    }    
    
    if (bytes_read == -1) {    
        perror("read");    
    }    
    
    close(fd);    
    return 0;    
}

输出结果:

四、文件描述符

其实文件描述符就是操作系统内核用于表示打开的文件的一个非整数,这个我们在之前的内容中已经说明了,那么接下来我们来回答几个问题:

💡用户操作文件的底层逻辑是什么?

 我们都知道程序在运行起来的时候,操作系统会将代码和数据加载到内存之中,然后会创建对应的task_struct、mm_struct、页表等相关数据结构。我们管理文件就需要一个files_struct的结构体,我们创建的task_struct中有一个指针指向这个结构体,而这个结构体中就会有一个fd_arrray数组,这个数组中存的就是我们之前说的文件描述符了。

举个例子,我们的进程要打开一个log.txt的文件,我们首先要把文件加载到内存之中,然后形成struct file结构体并把它添加到双链表中,同时将该结构体的首地址填写到对应3下标的指针数组中,然后用户就可以获取了。

💡什么是进程创建的时候会默认打开0、1、2?

进程创建的时候后默认生成这三个的struct file并连接到了双链表中,因为他们是最先创建的,所以我们也不难知道他们分别对应了文件描述符的0,1,2的下标了,这样就默认打开了标准输入流,标准输出流和标准错误流。 

五、文件描述符的分配规则

我们可以以一个之前写的代码来引入:

#include <stdio.h>      
#include <sys/stat.h>      
#include <sys/types.h>      
#include <fcntl.h>      
      
int main()      
{      
    umask(0);      
    int fd1 = open("test1.txt", O_RDONLY | O_CREAT, 0666);      
    int fd2 = open("test2.txt", O_RDONLY | O_CREAT, 0666);      
    int fd3 = open("test3.txt", O_RDONLY | O_CREAT, 0666);      
    int fd4 = open("test4.txt", O_RDONLY | O_CREAT, 0666);      
    int fd5 = open("test5.txt", O_RDONLY | O_CREAT, 0666);      
      
    printf("fd1:%d\n", fd1);      
    printf("fd2:%d\n", fd2);      
    printf("fd3:%d\n", fd3);      
    printf("fd4:%d\n", fd4);      
    printf("fd5:%d\n", fd5);      
    return 0;                                                                                                                                                                                    
}

我们运行上面的发现实际上的fd是从3开始的,其实这个也不难理解,因为0,1,2是默认打开的,就相当于是已经被占用了。

所以我们这里可以试试关闭一些文件会怎么样,我们先还是关闭0来试试:

我们可以看到,代码变成了从0开始,然后再是3的递增。

接下来我们可以来试着将0和1都关闭来看看结果,这里之所以不管是因为他是标准输出流,没有他屏幕上将没有内容。

我们发现输出的fd变成了从0开始然后再是2的递增的数字。

结论:文件描述符是从最小但是没有被使用的fd_array数组下标开始进行分配的。

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

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

相关文章

ABP-Book Store Application中文讲解 - Part 9: Authors: User Interface

ABP-Book Store Application中文讲解 - Part 9: Authors: User Interface TBD 1. 汇总 ABP-Book Store Application中文讲解-汇总-CSDN博客 2. 前一章 ABP-Book Store Application中文讲解 - Part 8: Authors: Application Layer-CSDN博客 项目之间的引用关系。 ​​ 目…

Hive自定义函数案例(UDF、UDAF、UDTF)

目录 前提条件 背景 概念及适用场景 UDF&#xff08;User-Defined Function&#xff09; 概念 适用场景 UDAF&#xff08;User-Defined Aggregate Function&#xff09; 概念 适用场景 UDTF&#xff08;User-Defined Table-Generating Function&#xff09; 概念 适…

【学习笔记】Circuit Tracing: Revealing Computational Graphs in Language Models

Circuit Tracing: Revealing Computational Graphs in Language Models 替代模型(Replacement Model)&#xff1a;用更多的可解释的特征来替代transformer模型的神经元。 归因图(Attribution Graph)&#xff1a;展示特征之间的相互影响&#xff0c;能够追踪模型生成输出时所采用…

STM32标准库-TIM定时器

文章目录 一、TIM定时器1.1定时器1.2定时器类型1.1.1 高级定时器1.1.2通用定时器1.1.3基本定时器 二、定时中断基本结构预分频器时器计时器时序计数器无预装时序计数器有预装时序RCC时钟树 三、定时器定时中断3.1 接线图3.2代码3.3效果&#xff1a; 四、定时器外部中断4.1接线图…

Kafka 如何保证顺序消费

在消息队列的应用场景中&#xff0c;保证消息的顺序消费对于一些业务至关重要&#xff0c;例如金融交易中的订单处理、电商系统的库存变更等。Kafka 作为高性能的分布式消息队列系统&#xff0c;通过巧妙的设计和配置&#xff0c;能够实现消息的顺序消费。接下来&#xff0c;我…

【算法题】算法一本通

每周更新至完结&#xff0c;建议关注收藏点赞。 目录 待整理文章已整理的文章方法论思想总结模版工具总结排序 数组与哈希表栈双指针&#xff08;滑动窗口、二分查找、链表&#xff09;树前缀树堆 优先队列&#xff08;区间/间隔问题、贪心 &#xff09;回溯图一维DP位操作数学…

Modbus转Ethernet IP赋能挤出吹塑机智能监控

在现代工业自动化领域&#xff0c;小疆智控Modbus转Ethernet IP网关GW-EIP-001与挤出吹塑机的应用越来越广泛。这篇文章将为您详细解读这两者的结合是如何提高生产效率&#xff0c;降低维护成本的。首先了解什么是Modbus和Ethernet IP。Modbus是一种串行通信协议&#xff0c;它…

什么是终端安全管理系统(终端安全管理软件2024科普)

在当今数字化迅速发展的时代&#xff0c;企业面临着越来越多的信息安全威胁。为了应对这些挑战&#xff0c;保障公司数据的安全性和完整性&#xff0c;终端安全管理系统&#xff08;Endpoint Security Management System&#xff09;应运而生。 本文将为您深入浅出地科普2024年…

【JVM】Java类加载机制

【JVM】Java类加载机制 什么是类加载&#xff1f; 在 Java 的世界里&#xff0c;每一个类或接口在经过编译后&#xff0c;都会生成对应的 .class 字节码文件。 所谓类加载机制&#xff0c;就是 JVM 将这些 .class 文件中的二进制数据加载到内存中&#xff0c;并对其进行校验…

《C++初阶之入门基础》【C++的前世今生】

【C的前世今生】目录 前言&#xff1a;---------------起源---------------一、历史背景二、横空出世---------------发展---------------三、标准立世C98&#xff1a;首个国际标准版本C03&#xff1a;小修订版本 四、现代进化C11&#xff1a;现代C的开端C14&#xff1a;对C11的…

Apache APISIX

目录 Apache APISIX是什么&#xff1f; Lua Lua 的主要特点&#xff1a; Lua 的常见应用&#xff1a; CVE-2020-13945(Apache APISIX默认API Token导致远程Lua代码执行) ​编辑Lua脚本解析 CVE-2021-45232(Apache APISIX Dashboard API权限绕过导致RCE) Apache …

如何在 git dev 中创建合并请求

先将 自己的代码 推到 自己的远程的 分支上 在 创建 合并请求 根据提示 将 自己的远程的 源码 合并到 对应的分支上 然后 创建 合并请求 等待 对应的 人 来 进行合并就行

基于nlohmann/json 实现 从C++对象转换成JSON数据格式

C对象的JSON序列化与反序列化 基于JsonCpp库实现C对象序列化与反序列化 JSON 介绍 JSON作为一种轻量级的数据交换格式&#xff0c;在Web服务和应用程序中广泛使用。 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读…

《T/CI 404-2024 医疗大数据智能采集及管理技术规范》全面解读与实施分析

规范背景与详细信息 《T/CI 404-2024 医疗大数据智能采集及管理技术规范》是由中国国际科技促进会联合河南科技大学、河南科技大学第一附属医院、深圳市人民医院等十余家医疗机构与企业共同制定的团体标准,于2024年5月正式发布实施。该规范是我国医疗大数据领域的重要技术标准…

国产三维CAD皇冠CAD在「金属压力容器制造」建模教程:蒸汽锅炉

面对蒸汽锅炉设计中复杂的曲面封头、密集的管板开孔、多变的支撑结构以及严格的强度与安全规范&#xff08;如GB150、ASME等&#xff09;&#xff0c;传统二维设计手段往往捉襟见肘&#xff0c;易出错、效率低、协同难。国产三维CAD皇冠CAD&#xff08;CrownCAD&#xff09;凭借…

C++中单例模式详解

在C中&#xff0c;单例模式 (Singleton Pattern) 确保一个类只有一个实例&#xff0c;并提供一个全局访问点来获取这个实例。这在需要一个全局对象来协调整个系统行为的场景中非常有用。 为什么要有单例模式&#xff1f; 在许多项目中&#xff0c;某些类从逻辑上讲只需要一个实…

舆情监控系统爬虫技术解析

之前我已经详细解释过爬虫在系统中的角色和技术要点&#xff0c;这次需要更聚焦“如何实现”这个动作。 我注意到上次回复偏重架构设计&#xff0c;这次应该拆解为更具体的操作步骤&#xff1a;从目标定义到数据落地的完整流水线。尤其要强调动态调度这个容易被忽视的环节——…

Vue3中Ant-design-vue的使用-附完整代码

前言 首先介绍一下什么是Ant-design-vue Ant Design Vue 是基于 Vue 3 的企业级 UI 组件库&#xff08;同时兼容 Vue 2&#xff09;&#xff0c;是蚂蚁金服开源项目 Ant Design 的 Vue 实现版本。它遵循 Ant Design 的设计规范&#xff0c;提供丰富的组件和高质量的设计体系&…

Redis Sorted Set 深度解析:从原理到实战应用

Redis Sorted Set 深度解析&#xff1a;从原理到实战应用 在 Redis 丰富的数据结构家族中&#xff0c;Sorted Set&#xff08;有序集合&#xff09;凭借独特的设计和强大的功能&#xff0c;成为处理有序数据场景的得力工具。无论是构建实时排行榜&#xff0c;还是实现基于时间的…

若依框架修改模板,添加通过excel导入数据功能

版本&#xff1a;我后端使用的是RuoYi-Vue-fast版本&#xff0c;前端是RuoYi-Vue3 需求: 我需要每个侧边栏功能都需要具有导入excel功能&#xff0c;但是若依只有用户才具备&#xff0c;我需要代码生成的每个功能都拥有导入功能。​ 每次生成一个一个改实在是太麻烦了。索性…