嵌入式Linux系统编程 — 1.2 文件管理与错误处理

目录

1 Linux 系统如何管理文件

1.1 什么是静态文件

1.2 扇区(Sector)和块(Block)概念?

1.3 inode

1.4 进程控制块(PCB)

2 返回错误处理与 errno

2.1 errno变量介绍

2.3 perror函数介绍

3 exit、 _exit、 _Exit函数

3.1 exit函数

3.2 _exit函数

3.3 _Exit函数

3.4 示例代码


1 Linux 系统如何管理文件

当我们使用open函数打开一个文件时,操作系统内核会分配一块内存区域作为缓冲区,并将磁盘上存储的静态文件数据加载到这个内存区域中,以便进行管理和缓存。这个加载到内存中的文件数据被称为动态文件或内核缓冲区。此后,对该文件的所有读写操作都是针对内存中的动态文件进行的,而不是直接操作磁盘上的静态文件。当对动态文件进行修改后,内存中的数据与磁盘上的数据就会不同步了,数据的同步是由内核负责的,内核会在适当的时候将内存中的动态文件内容刷新回磁盘,确保磁盘上的文件与内存中的数据保持一致。

在Linux操作系统中,进程控制块(PCB)是操作系统用来存储和管理每个进程信息的核心数据结构,其中包含文件表,记录了进程打开的所有文件的文件描述符和状态信息。文件表中的每个条目都指向一个inode,inode是文件系统中的一个数据结构,包含了文件的元数据和指向文件数据块的指针。inode table是存储所有inode的表,操作系统通过它来访问和管理文件的inode。磁盘是物理存储设备,由扇区和块组成,文件数据最终存储在这些磁盘块中。

文件描述符表、文件表以及 inode 之间的关系(参考正点原子教程)

1.1 什么是静态文件

在Linux系统中,静态文件通常指的是那些在创建后内容不经常改变的文件,例如系统配置文件、编译后的程序可执行文件、库文件等。这些文件在系统运行过程中保持不变,不需要动态生成或频繁更新,因此被称为静态文件。静态文件的特点是它们在磁盘上占用固定的存储空间,并且可以通过文件路径和inode号直接访问。

1.2 扇区(Sector)和块(Block)概念?

在计算机存储设备中,扇区(Sector)和块(Block)是两个基本的概念,它们用于描述数据存储的基本单位:

扇区(Sector):

  • 扇区是磁盘上最小的物理存储单元,所有数据都存储在扇区中。
  • 扇区的大小通常是固定的,常见的扇区大小有512字节、4KB等。
  • 每个扇区都有唯一的地址,操作系统通过扇区地址来访问磁盘上的数据。
  • 扇区是磁盘读写操作的最小单位,即使是很小的数据也需要占用一个完整的扇区。

块(Block):

  • 块是文件系统中用于分配存储空间的基本单位,块的大小可以由文件系统决定。
  • 块的大小通常大于扇区,例如4KB、8KB、16KB等,这样可以减少文件系统中的碎片。
  • 文件系统中的块大小是可配置的,较大的块大小可以提高大文件的读写效率,但可能会浪费存储空间。
  • 块是文件系统管理数据的方式,操作系统通过块来分配和管理磁盘空间。

扇区和块之间的关系:

  • 一个块可以包含一个或多个扇区。例如,如果块的大小是4KB,而扇区大小是512字节,那么一个块将包含8个扇区。
  • 文件系统在分配存储空间时,会将文件数据存储在连续的块中,而每个块又是由连续的扇区组成的。
  • 当文件大小超过一个块的大小时,文件系统会在磁盘上分配多个块来存储文件数据。

所以由此可以知道,静态文件对应的数据都是存储在磁盘设备不同的“块”中, 那么我们在程序中调用 open 函数是如何找到对应文件的数据存储“块”的呢?

1.3 inode

inode(索引节点)和(inode 表)是什么?我们的磁盘在进行分区、格式化的时候会将其分为两个区域,一个是数据区,用于存储文件中的数据;另一个是 inode 区,用于存放 inode table(inode 表), inode table 中存放的是一个一个的 inode(也成为 inode节点),不同的 inode 就可以表示不同的文件,每一个文件都必须对应一个 inode, inode 实质上是一个结构体,这个结构体中有很多的元素,不同的元素记录了文件了不同信息,譬如文件字节大小、文件所有者、文件对应的读/写/执行权限、文件时间戳(创建时间、更新时间等)、 文件类型、 文件数据存储的 block(块)位置等等信息, 如图所示。

inode table 与 inode(参考正点原子文档)

inode(索引节点)是什么?在Linux系统中,inode(索引节点)是一种存储文件和目录元数据的数据结构,它包含了文件的权限、所有者、大小、创建和修改时间等信息,但不包含文件的实际数据内容。每个文件和目录都有一个唯一的inode号,文件系统通过inode来管理和定位文件。目录项将文件名与inode号关联起来,而硬链接是指向相同inode的多个文件名,共享文件数据;软链接则是指向其他文件inode的文件。了解inode的概念对于管理文件权限、创建链接以及有效管理文件系统至关重要。

如何查看文件的 inode 编号?

每一个文件都有唯一的一个 inode, 每一个 inode 都有一个与之相对应的数字编号,通过这个数字编号就可以找到 inode table 中所对应的 inode。

在Linux系统中,可以使用ls命令结合-i选项来查看文件的inode编号。具体命令如下:

ls -i filename

这里,filename是你想要查看inode编号的文件名。执行这个命令后,ls会列出文件及其对应的inode编号。

如果你想查看一个目录下所有文件的inode编号,可以省略文件名,直接使用:

ls -i
ls -il

1.4 进程控制块(PCB)

在 Linux 系统中, 内核会为每个进程设置一个专门的数据结构用于管理该进程,我们把这个称为进程控制块(Process control block,缩写PCB) 。进程控制块(PCB)是操作系统中用于存储和管理进程信息的关键数据结构,它包含了进程的唯一标识符(PID)、当前状态、程序计数器、寄存器集合、调度信息、内存管理信息、文件表、I/O状态、信号处理以及上下文切换信息等,是操作系统调度进程、管理资源和处理进程切换的基础,确保了进程的正确执行和管理。

2 返回错误处理与 errno

2.1 errno变量介绍

在Linux中,错误处理通常通过返回一个错误代码来实现,而更具体的错误信息则存储在全局变量errno中。errno是一个宏,它在头文件<errno.h>中定义。当系统调用或库函数失败时,它们会设置errno为特定的错误代码。

以下是处理错误的一般步骤:

  1. 调用系统函数:执行如打开文件、读取数据等操作。

  2. 检查返回值:如果函数返回-1或其他错误指示值,表示函数执行失败。

  3. 检查errno:调用perror()strerror()strerror_r()函数来获取errno对应的错误描述。

  4. 错误处理:根据错误类型决定后续操作,如重试操作、记录日志、清理资源或向用户报告错误。

以下是一个简单的示例代码,演示了如何在Linux程序中使用错误处理和errno

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

int main() {
    int fd;
    char *filename = "file.txt"; // 一个不存在的文件名

    // 尝试打开文件
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        // 如果open失败,打印错误信息
        fprintf(stderr, "Error opening file '%s': %s\n", filename, strerror(errno));
        return 1; // 返回非零值表示出错
    }

    // 关闭文件
    close(fd);

    return 0; // 正常退出
}

在这个示例中,我们尝试打开一个名为file.txt的文件。由于这个文件不存在,open函数将失败并返回-1。然后,我们检查fd是否等于-1,如果是,我们使用strerror(errno)函数来获取与errno相关的错误消息,并打印出来。strerror函数将错误代码转换为可读的字符串。运行结果如下: 

2.2 strerror函数strerror

strerror函数用于将错误代码转换成一个人类可读的字符串。这个函数的原型如下:

char *strerror(int errnum);

其中errnum是错误代码,通常是errno宏的值,errno在发生系统调用错误时被设置。

以下是一个简单的strerror示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main() {
    // 假设我们有一个错误的errno值
    errno = EACCES; // 访问被拒绝

    // 打印错误消息
    printf("Error: %s\n", strerror(errno));

    return 0;
}

在这个示例中,我们手动设置errnoEACCES(访问被拒绝),然后使用strerror获取对应的错误消息,并打印出来。运行结果如下:

2.3 perror函数介绍

perror函数用于输出当前errno值对应的错误消息到标准错误(stderr)。这个函数的原型如下:

void perror(const char *str);

其中str是一个可选的前缀字符串,它会在错误消息之前输出,以提供上下文信息。

以下是一个简单的perror示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main() {
    FILE *fp;

    // 尝试打开一个不存在的文件
    fp = fopen("nonexistent_file.txt", "r");
    if (fp == NULL) {
        // 文件打开失败,使用perror打印错误消息
        perror("Failed to open file: ");
    } else {
        // 文件成功打开,执行其他操作
        fclose(fp);
    }

    return 0;
}

在这个示例中,我们尝试打开一个不存在的文件nonexistent_file.txt。如果文件打开失败(fpNULL),perror将输出一个带有前缀"Failed to open file: "的错误消息。运行结果如下:

3 exit、 _exit、 _Exit函数

在 Linux 系统下, 进程正常退出除了可以使用 return 之外, 还可以使用exit_exit_Exit,是3个函数用于终止进程的函数,它们的行为略有不同:

3.1 exit函数

exit函数是标准C库函数,当调用时会执行一些清理操作,比如关闭所有打开的文件描述符、刷新标准I/O库的缓冲区、调用所有注册的退出处理程序等。exit的原型如下:

void exit(int status);

参数status是一个整数,通常用于表示程序的退出状态,0通常表示成功,非0表示出错。

3.2 _exit函数

_exit是POSIX标准的函数,与exit类似,但它不会执行任何清理操作,直接终止进程。这使得_exitexit更快,因为它跳过了所有清理步骤。_exit的原型如下:

void _exit(int status);

参数status同样用于表示程序的退出状态。

3.3 _Exit函数

_Exit_exit的一个宏定义,行为与_exit完全相同。在某些系统中,_Exit可能被定义为_exit的宏,或者两者都是系统调用。

3.4 示例代码

以下是一个简单的示例,演示了如何使用exit_exit函数:

#include <stdio.h>
#include <stdlib.h>

void cleanup(void) {
    // 这里可以执行一些清理工作
    printf("Cleanup resources...\n");
}

int main() {
    // 注册exit函数
    atexit(cleanup);

    printf("Normal exit using exit()\n");
    exit(0); // 正常退出,执行清理工作

    // 下面的代码不会被执行,因为exit已经终止了程序
    printf("This will not be printed\n");
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

// 一个示例函数,演示_exit的使用
void immediate_exit() {
    printf("Immediate exit using _exit()\n");
    _exit(1); // 立即退出,不执行任何清理工作
}

int main() {
    immediate_exit(); // 调用上面的函数

    // 下面的代码不会被执行,因为_exit已经终止了程序
    printf("This will not be printed\n");
    return 0;
}

在这个示例中,main函数中使用了exit,它在退出之前会调用所有注册的退出处理程序(例如cleanup函数)。而immediate_exit函数中使用了_exit,它将立即终止程序,不会执行任何清理工作。在实际使用中,如果你需要立即退出程序且不需要执行任何清理工作,可以使用_exit_Exit;如果你希望在退出前执行一些清理工作,应该使用exit。第一段代码运行结果如下:

  • 24
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

几度春风里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值