Linux-C/C++--深入探究文件 I/O (上)(文件的管理、函数返回错误、exit()、_Exit()、_exit())

        经过上一章内容的学习,相信各位读者对 Linux 系统应用编程中的基础文件 I/O 操作有了一定的认识和理解了,能够独立完成一些简单地文件 I/O 编程问题,如果你的工作中仅仅只是涉及到一些简单文件读写操作相关的问题,其实上一章的知识内容已经够你使用了。

        当然作为大部分读者来说,我相信你不会止步于此、还想学习更多的知识内容,那本章笔者将会同各位读者一起,来深入探究文件 I/O 中涉及到的一些问题、原理以及所对应的解决方法,譬如 Linux 系统下文件是如何进行管理的、调用函数返回错误该如何处理、open 函数的 O_APPENDO_TRUNC 标志以及等相关问题。

好了,废话不多说,开始本章的学习吧,加油!
本章将会讨论如下主题内容。
Linux 下文件的管理方式进行简单介绍;
函数返回错误的处理;
退出程序 exit() _Exit() _exit()
空洞文件的概念;
open 函数的 O_APPEND O_TRUNC 标志;
多次打开同一文件;
复制文件描述符;
文件共享介绍;
原子操作与竞争冒险;
系统调用 fcntl() ioctl() 介绍;
截断文件;

一、Linux 系统如何管理文件

1、静态文件与 inode

        文件在没有被打开的情况下一般都是存放在磁盘中的,譬如电脑硬盘、移动硬盘、U 盘等外部存储设备,文件存放在磁盘文件系统中,并且以一种固定的形式进行存放,我们把他们称为静态文件。

        文件储存在硬盘上,硬盘的最小存储单位叫做“扇区”(Sector),每个扇区储存 512 字节(相当于 0.5KB),操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个“块”(block)。这种由多个扇区组成的“块”,是文件存取的最小单位。“块”的大小,最常见的是 4KB,即连续八个 sector 组成一个 block

        所以由此可以知道,静态文件对应的数据都是存储在磁盘设备不同的“块”中,那么问题来了,我们在程序中调用 open 函数是如何找到对应文件的数据存储“块”的呢,难道仅仅通过指定的文件路径就可以实现?这里我们就来简单地聊一聊这内部实现的过程。

        我们的磁盘在进行分区、格式化的时候会将其分为两个区域,一个是数据区,用于存储文件中的数据;另一个是 inode 区,用于存放 inode tableinode 表),inode table 中存放的是一个一个的 inode(也成为 inode节点),不同的 inode 就可以表示不同的文件,每一个文件都必须对应一个 inodeinode 实质上是一个结构体,这个结构体中有很多的元素,不同的元素记录了文件了不同信息,譬如文件字节大小、文件所有者、文件对应的读//执行权限、文件时间戳(创建时间、更新时间等)、文件类型、文件数据存储的 block(块)位置等等信息,如图1 中所示(这里需要注意的是,文件名并不是记录在 inode 中,这个问题后面章节内容再给大家讲)。

1 inode table inode

        所以由此可知,inode table 表本身也需要占用磁盘的存储空间。每一个文件都有唯一的一个 inode,每一个 inode 都有一个与之相对应的数字编号,通过这个数字编号就可以找到 inode table 中所对应的 inode。在 Linux 系统下,我们可以通过"ls -i"命令查看文件的 inode 编号,如下所示:

上图中 ls 打印出来的信息中,每一行前面的一个数字就表示了对应文件的 inode 编号。除此之外,还可 以使用 stat 命令查看,用法如下:

由以上的介绍大家可以联系到实际操作中,譬如我们在 Windows 下进行 U 盘格式化的时候会有一个 “快速格式化”选项

通过以上介绍可知,打开一个文件,系统内部会将这个过程分为三步:

1) 系统找到这个文件名所对应的 inode 编号;

2) 通过 inode 编号从 inode table 中找到对应的 inode 结构体;

3) 根据 inode 结构体中记录的信息,确定文件数据所在的 block,并读出数据。

2、文件打开时的状态

        当我们调用 open 函数去打开文件的时候,内核会申请一段内存(一段缓冲区),并且将静态文件的数据内容从磁盘这些存储设备中读取到内存中进行管理、缓存(也把内存中的这份文件数据叫做动态文件、内核缓冲区)。打开文件后,以后对这个文件的读写操作,都是针对内存中这一份动态文件进行相关的操作,而并不是针对磁盘中存放的静态文件。

        当我们对动态文件进行读写操作后,此时内存中的动态文件和磁盘设备中的静态文件就不同步了,数据的同步工作由内核完成,内核会在之后将内存这份动态文件更新(同步)到磁盘设备中。由此我们也可以联系到实际操作中,譬如说:

打开一个大文件的时候会比较慢;

 文档写了一半,没记得保存,此时电脑因为突然停电直接掉电关机了,当重启电脑后,打开编写的文档,发现之前写的内容已经丢失。

        想必各位读者在工作当中都遇到过这种问题吧,通过上面的介绍,就解释了为什么会出现这种问题。

二、返回错误处理与 errno

        在上一章节中,笔者给大家编写了很多的示例代码,大家会发现这些示例代码会有一个共同的特点,那就是当判断函数执行失败后,会调用 return 退出程序,但是对于我们来说,我们并不知道为什么会出错,什么原因导致此函数执行失败,因为执行出错之后它们的返回值都是-1

        难道我们真的就不知道错误原因了吗?其实不然,在 Linux 系统下对常见的错误做了一个编号,每一个编号都代表着每一种不同的错误类型,当函数执行发生错误的时候,操作系统会将这个错误所对应的编号赋值给 errno 变量,每一个进程(程序)都维护了自己的 errno 变量,它是程序中的全局变量,该变量用于存储就近发生的函数执行错误编号,也就意味着下一次的错误码会覆盖上一次的错误码。所以由此可知道,当程序中调用函数发生错误的时候,操作系统内部会通过设置程序的 errno 变量来告知调用者究竟发生了什么错误!

        errno 本质上是一个 int 类型的变量,用于存储错误编号,但是需要注意的是,并不是执行所有的系统调用或 C 库函数出错时,操作系统都会设置 errno,那我们如何确定一个函数出错时系统是否会设置 errno 呢?其实这个通过 man 手册便可以查到,譬如以 open 函数为例,执行"man 2 open"打开 open 函数的帮助信息,找到函数返回值描述段,如下所示:

        当函数返回错误时会设置 errno,当然这里是以 open 函数为例,其它的系统调用也可以这样查找你可以直接认为此变量就是在<errno.h>头文件中的申明的,好,我们来测试下:

#include <stdio.h>
#include <errno.h>
int main(void)
{
    printf("%d\n", errno);
    return 0;
}
以上的这段代码是不会报错的,大家可以自己试试!

1、strerror 函数

在 C 语言中, strerror 函数是一个标准库函数,用于根据错误代码返回一个描述性字符串,该字符串详细说明了错误的原因。该函数是 <string.h> 头文件的一部分
#include <string.h>
char *strerror(int errnum);
  • errnum:这是一个整数,表示错误代码,通常是由系统调用或库函数返回的错误代码。常见的错误代码是由 <errno.h> 头文件定义的常量,例如 EIOENOMEMEINVAL 等。
  • 返回一个指向描述该错误的字符串的指针。如果错误代码有效,返回一个以 null 结尾的错误描述字符串。如果传入的错误代码无效或无法识别,返回一个指向标准错误消息的字符串。
示例:
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main() {
    FILE *file = fopen("nonexistent_file.txt", "r");
    
    if (file == NULL) {
        printf("Error opening file: %s\n", strerror(errno));
    }

    return 0;
}

在这个示例中,如果文件 nonexistent_file.txt 无法打开,fopen 会返回 NULL,并且 errno 会被设置为一个表示错误的代码。strerror(errno) 会返回一个与该错误代码相关的描述性字符串,比如 "No such file or directory"

常见的错误代码与对应的描述

  • EINVAL:无效的参数。
  • ENOMEM:内存不足。
  • EIO:输入/输出错误。
  • EBADF:坏的文件描述符。
  • EACCES:权限被拒绝。
  • ENOSPC:没有足够的空间。

2、perror 函数

        在 C 语言中,perror 函数是一个用于打印错误信息的标准库函数,它将标准错误流 stderr 中输出一条错误消息。该消息是由一个给定的字符串和当前 errno 错误代码的描述组成的。

#include <stdio.h>
void perror(const char *s);
  • s:这是一个字符串参数,它会被输出在错误信息前面,用来描述错误的上下文。s 后面会附带一个冒号和空格(如果 s 为空,错误信息只包含系统默认的错误消息)。

功能,perror 会使用全局变量 errno 来生成错误信息。errno 是由操作系统或 C 库的系统调用或库函数设置的,它代表了最近一次的错误代码。perror 会将 errno 对应的错误描述与 s 字符串一起打印到标准错误流(stderr)。

具体来说,它会输出如下格式的消息:

s: <error_description>

其中,<error_description> 是由 errno 值所指示的错误信息。例如,如果发生了 "文件未找到" 错误,perror 会打印相关的错误描述。

返回值:perror 函数没有返回值(void 类型)

示例:
#include <stdio.h>
#include <errno.h>

int main() {
    FILE *file = fopen("nonexistent_file.txt", "r");

    if (file == NULL) {
        perror("Error opening file");
    }

    return 0;
}

在这个例子中,程序试图打开一个不存在的文件。如果文件打开失败,perror 会输出如下信息:

Error opening file: No such file or directory

三、exit_exit_Exit

        当程序在执行某个函数出错的时候,如果此函数执行失败会导致后面的步骤不能在进行下去时,应该在出错时终止程序运行,不应该让程序继续运行下去,那么如何退出程序、终止程序运行呢?有过编程经验的读者都知道使用 return,一般原则程序执行正常退出 return 0,而执行函数出错退出 return -1,前面我们所编写的示例代码也是如此。

        在 Linux 系统下,进程(程序)退出可以分为正常退出和异常退出,注意这里说的异常并不是执行函数出现了错误这种情况,异常往往更多的是一种不可预料的系统异常,可能是执行了某个函数时发生的、也有可能是收到了某种信号等,这里我们只讨论正常退出的情况。

        在 Linux 系统下,进程正常退出除了可以使用 return 之外,还可以使用 exit()_exit()以及_Exit(),下面我们分别介绍。

1、_exit()_Exit()函数

        main 函数中使用 return 后返回,return 执行后把控制权交给调用函数,结束该进程。调用_exit()函数会清除其使用的内存空间,并销毁其在内核中的各种数据结构,关闭进程的所有文件描述符,并结束进程、将控制权交给操作系统。_exit()函数原型如下所示:

#include <unistd.h>
void _exit(int status);

调用函数需要传入 status 状态标志,0 表示正常结束、若为其它值则表示程序执行过程中检测到有错误发生。使用示例如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
    int fd;
    /* 打开文件 */
    fd = open("./test_file", O_RDONLY);
    if (-1 == fd) {
        perror("open error");
        _exit(-1);
    }
    close(fd);
    _exit(0);
}

用法很简单,大家可以自行测试!

_Exit()函数原型如下所示:

#include <stdlib.h>
void _Exit(int status);

_exit()_Exit()两者等价,用法作用是一样的,这里就不再讲了,需要注意的是这 2 个函数都是系统调用。

2、exit()函数

exit()函数_exit()函数都是用来终止进程的,exit()是一个标准 C 库函数,而_exit()_Exit()是系统调用。执行 exit()会执行一些清理工作,最后调用_exit()函数。exit()函数原型如下:

#include <stdlib.h>
void exit(int status);
        该函数是一个标准 C 库函数,使用该函数需要包含头文件 <stdlib.h> ,该函数的用法和 _exit()/_Exit() 是一样的,这里就不再多说了。

本小节就给大家介绍了 3 中终止进程的方法:

main 函数中运行 return

调用 Linux 系统调用_exit()_Exit()

调用 C 标准库函数 exit()

        不管你用哪一种都可以结束进程,但还是推荐大家使用 exit(),其实关于 returnexit_exit/_Exit()之间的区别笔者在上面只是给大家简单地描述了一下,甚至不太确定我的描述是否正确,因为笔者并不太多去关心其间的差异,对这些概念的描述会比较模糊、笼统,如果大家看不明白可以自己百度搜索相关的内容,当然对于初学者来说,不太建议大家去查找这些东西,至少对你现阶段来说,意义不是很大。好,本小节就介绍这么多,我们接着学习下一小节的内容。

本小节内容到此结束。下一篇介绍:空洞文件的概念;open 函数的 O_APPEND O_TRUNC 标志;多次打开同一文件;复制文件描述符;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

内网渗透测试工具及渗透测试安全审计方法总结

1. 内网安全检查/渗透介绍 1.1 攻击思路 有2种思路&#xff1a; 攻击外网服务器&#xff0c;获取外网服务器的权限&#xff0c;接着利用入侵成功的外网服务器作为跳板&#xff0c;攻击内网其他服务器&#xff0c;最后获得敏感数据&#xff0c;并将数据传递到攻击者&#xff0…

豆包MarsCode:小C点菜问题

问题描述 思路分析 这道题的核心任务是找出所有不超过给定价格 m 的菜肴中&#xff0c;最常见的菜肴价格&#xff0c;最后返回该价格的出现次数。 1. 题意理解&#xff1a; 给定一个最大价格 m&#xff0c;小C只会选择价格不超过 m 的菜。菜单上有 n 道菜&#xff0c;每道菜…

从Windows通过XRDP远程访问和控制银河麒麟ukey v10服务器,以及多次连接后黑屏的问题

从Windows通过XRDP远程访问和控制银河麒麟ukey v10服务器&#xff0c;以及多次连接后黑屏的问题。 安装 rdp 服务&#xff1a; yum install -y epel-release yum install -y xrdp或者如下&#xff1a; 可以通过下载rpm软件包&#xff0c;然后rpm方式安装。访问xrdp官网https…

【PowerQuery专栏】实现JSON数据的导入

Json 格式数据是在互联网数据格式传输使用的非常频繁的一类数据,图7.44为Json数据格式中比较典型的数据格式。 PowerQuery进行Json数据解析使用的是Json.Document进行数据解析,Json.Document目前有2个参数。 参数1为内容数据,数据类型为二进制类型,值为需要解析的Json数据参…

Java基础(3)

Java 数据类型详解 九、运算符 1. 基本运算符 Java 提供了多种运算符来执行不同的操作&#xff1a; 算术运算符&#xff1a;&#xff08;加&#xff09;、-&#xff08;减&#xff09;、*&#xff08;乘&#xff09;、/&#xff08;除&#xff09;、%&#xff08;取模&…

PostgreSQL学习笔记:PostgreSQL vs MySQL

PostgreSQL 和 MySQL 都是广泛使用的关系型数据库管理系统&#xff0c;它们有以下一些对比&#xff1a; 一、功能特性 1. 数据类型支持 PostgreSQL&#xff1a;支持丰富的数据类型&#xff0c;包括数组、JSON、JSONB、范围类型、几何类型等。对于复杂数据结构的存储和处理非…

Linux下PostgreSQL-12.0安装部署详细步骤

一、安装环境 postgresql-12.0 CentOS-7.6 注意&#xff1a;确认linux系统可以正常连接网络&#xff0c;因为在后面需要添加依赖包。 二、pg数据库安装包下载 下载地址&#xff1a;PostgreSQL: File Browser 选择要安装的版本进行下载&#xff1a; 三、安装依赖包 在要安…

C语言内存之旅:从静态到动态的跨越

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一 动态内存管理的必要性二 动态…

[STM32 HAL库]串口中断编程思路

一、前言 最近在准备蓝桥杯比赛&#xff08;嵌入式赛道&#xff09;&#xff0c;研究了以下串口空闲中断DMA接收不定长的数据&#xff0c;感觉这个方法的接收效率很高&#xff0c;十分好用。方法配置都成功了&#xff0c;但是有一个点需要进行考虑&#xff0c;就是一般我们需要…

PyTorch使用教程(10)-torchinfo.summary网络结构可视化详细说明

1、基本介绍 torchinfo是一个为PyTorch用户量身定做的开源工具&#xff0c;其核心功能之一是summary函数。这个函数旨在简化模型的开发与调试流程&#xff0c;让模型架构一目了然。通过torchinfo的summary函数&#xff0c;用户可以快速获取模型的详细结构和统计信息&#xff0…

Java模拟路由协议-rip(路由器仿真实验)

前言&#xff1a; 好久不见&#xff0c;有段时间没有写文章了&#xff0c;本篇文章&#xff0c;由Blue我带大家来复现rip协议。我们以 b站湖南教师匠所讲rip的视频中的例子为我这篇文章所模拟的路由路径 如图&#xff1a; 模拟路径 视频&#xff1a;http://【深入浅出计算机网络…

32V/4A,降压DCDC转换器CP8384百分百占空比输出ESOP8封装,可适用HUB等电路设计

特点&#xff1a; ● Supply Voltage Range: 4.1V~32V ● Input voltage up to 40V ● 4A Continuous Output Current ● Up to 95% Output Efficiency ● CC/CV control ● 350kHz Switching Frequency ● Built-in Soft Start ● 100% Maximum Duty Cycle ● No External Com…

缓存、数据库双写一致性解决方案

双写一致性问题的核心是确保数据库和缓存之间的数据同步&#xff0c;以避免缓存与数据库数据不同步的问题&#xff0c;尤其是在高并发和异步环境下。本文将探讨双写一致性面临的主要问题和解决方案&#xff0c;重点关注最终一致性。 本文讨论的是最终一致性问题 双写一致性面…

【学习笔记15】如何在非root服务器中,安装属于自己的redis

一、下载安装包 官网下载黑马程序员给的安装包&#xff08;redis-6.2.6&#xff09; 二、将安装包上传至服务器 我将安装包上传在我的文件夹/home/XXX&#xff0c;指定路径中/src/local/redis/&#xff0c;绝对路径为/home/XXX/src/local/redis/解压安装包 XXXomega:~$ cd …

计算机网络 (51)鉴别

前言 计算机网络鉴别是信息安全领域中的一项关键技术&#xff0c;主要用于验证用户或信息的真实性&#xff0c;以及确保信息的完整性和来源的可靠性。 一、目的与重要性 鉴别的目的是验明用户或信息的正身&#xff0c;对实体声称的身份进行唯一识别&#xff0c;以便验证其访问请…

【大模型】ChatGPT 高效处理图片技巧使用详解

目录 一、前言 二、ChatGPT 4 图片处理介绍 2.1 ChatGPT 4 图片处理概述 2.1.1 图像识别与分类 2.1.2 图像搜索 2.1.3 图像生成 2.1.4 多模态理解 2.1.5 细粒度图像识别 2.1.6 生成式图像任务处理 2.1.7 图像与文本互动 2.2 ChatGPT 4 图片处理应用场景 三、文生图操…

后端:MyBatis

文章目录 1. MyBatis1-1. Mybatis 工具类的封装1-2. Mybatis 通过集合或实体类传递参数-实现插入数据(增)1-3. MyBatis 实现删除数据(删)1-4. MyBatis 实现修改数据(改)1-5. MyBatis 实现查询数据(查) 2. MyBatis 配置文件中的一些标签和属性2-1.environments标签2-2. dataSour…

将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch(1.标准版)

问题 项目里使用了 AzureBlob 存储了用户上传的各种资源文件&#xff0c;近期 AzureBlob 的流量费用增长很快&#xff0c;想通过分析Blob的日志&#xff0c;获取一些可用的信息&#xff0c;所以有了这个需求&#xff1a;将存储账户的日志&#xff08;读写&#xff0c;审计&…

数字化时代,传统代理模式的变革之路

在数字化飞速发展的今天&#xff0c;线上线下融合&#xff08;O2O&#xff09;成了商业领域的大趋势。这股潮流&#xff0c;正猛烈冲击着传统代理模式&#xff0c;给它带来了新的改变。 咱们先看看线上线下融合现在啥情况。线上渠道那是越来越多&#xff0c;企业纷纷在电商平台…

【AI | pytorch】torch.polar的使用

一、torch.polar的使用 torch.polar 是 PyTorch 中用来生成复数张量的一个函数&#xff0c;但它与数学中的复数表达式 ( z re^{i\theta} ) 是等价的。 具体来说&#xff0c;torch.polar(abs, angle) 接受两个实数张量参数&#xff1a; abs&#xff1a;表示复数的模长&#…