Linux系统IO(1)

1 目录

1.1 . IO缓冲

缓冲区:在内存中开辟一块空间,用来暂时存放数据,以便后续处理。

缓冲区的作用:

  • 减少IO操作,提高效率
  • 缓冲区可以提高数据的可靠性
  • 缓冲区可以提高数据的一致性

缓冲区的分类:

  • 全缓冲区:缓冲区的大小等于数据块的大小,数据被一次性读入缓冲区,并在缓冲区中处理。
  • 行缓冲区:缓冲区的大小等于数据块的大小,数据被分成一行一行的读入缓冲区,并在缓冲区中处理。
  • 无缓冲区:数据直接从磁盘读入内存,不经过缓冲区。

缓冲区的一般性选择:

  • 对于磁盘IO,选择全缓冲区或行缓冲区。
  • 对于网络IO,选择行缓冲区。
  • 对于内存IO,选择无缓冲区。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
// 功能:
// 设置文件流的缓冲模式和缓冲区大小。
// 参数:
// @stream: 文件指针。
// @buf: 用户提供的缓冲区指针。如果为 `NULL`,则使用系统默认缓冲区。
// @mode: 缓冲模式,可以是以下值之一:
// - `_IOFBF`: 全缓冲模式,数据在缓冲区填满后才写入文件。
// - `_IOLBF`: 行缓冲模式,遇到换行符或缓冲区满时写入数据。
// - `_IONBF`: 无缓冲模式,数据直接写入文件或从文件读取。
// @size: 缓冲区的大小(以字节为单位)。
// 返回值:
// 成功 返回 0,
// 失败 返回非零值。
1
2
3
4
5
6
7
8
9
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
// 功能:
// 设置文件流的缓冲区。与 `setvbuf` 不同,`setbuf` 只能设置全缓冲或无缓冲模式。
// 参数:
// @stream: 文件指针。
// @buf: 用户提供的缓冲区指针。如果为 `NULL`,则设置为无缓冲模式。
// 返回值:
// 无返回值。
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int fflush(FILE *stream);
// 功能:
// 强制将输出流缓冲区中的数据写入文件或刷新输入流缓冲区(如有必要)。
// 参数:
// @stream: 文件指针。如果为 `NULL`,则刷新所有输出流。
// 返回值:
// 成功 返回 0,
// 失败 返回 EOF。

2 . 标准错误

在 Linux 系统编程中,标准错误(Standard Error, stderr)是程序用于输出错误信息的流。它与标准输入(stdin)和标准输出(stdout)一起构成了三个标准 I/O 流。以下是关于 Linux 标准错误的一些关键概念和使用方法:

2.1 . 标准错误的作用

  • 错误信息stderr 主要用于输出错误信息或警告信息,以便用户能够了解程序执行过程中遇到的问题。
  • 分离输出:通过将错误信息发送到 stderr,而正常输出发送到 stdout,可以方便地将二者分开处理。例如,使用命令行重定向时,你可以只重定向标准输出而不影响标准错误。

2.2 . 标准错误的特点

  • 未缓冲stderr 默认是未缓冲的(unbuffered),这意味着写入 stderr 的内容会立即显示,而不会等待缓冲区满或刷新。这有助于确保错误信息及时出现,尤其是在程序崩溃的情况下。
  • 文件描述符stderr 的文件描述符为 2,而 stdinstdout 分别为 01

2.3 . 如何使用标准错误

  • C语言中的使用

    • 使用 fprintf 函数将信息写入 stderr。例如:

      1
      fprintf(stderr, "这是一个错误消息。\n");
    • 使用 perror 函数打印错误信息并附加系统错误信息。例如:

      1
      2
      3
      4
      if (stat("f001.txt",&stat_buf)) {
      perror("无法访问文件");
      }
      若 stat 调用失败,则会打印 "无法访问文件: No such file or directory"
    • 使用 strerror 函数返回一个指向错误描述字符串的指针,该字符串描述了由参数指定的错误码。你可以将返回的字符串与你自己的错误信息组合起来使用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <fcntl.h>

    int main() {
    int fd = open("nonexistent_file.txt", O_RDONLY);
    if (fd == -1) {
    fprintf(stderr, "open failed: %s\n", strerror(errno)); // 输出类似于 "open failed: No such file or directory"
    }
    return 0;
    }

3 文件IO

Linux 提供了一套丰富的文件 I/O API,用于执行文件的创建、打开、读取、写入、关闭等操作。以下是 Linux 文件 I/O 中最常用的一些 API 函数及其简要说明:

3.1 . 打开和创建文件

  • open:打开或创建一个文件。

    1
    2
    3
    #include <fcntl.h>
    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    • pathname:文件路径。
    • flags:指定文件的打开模式(如 O_RDONLY, O_WRONLY, O_RDWR)和其他选项(如 O_CREAT, O_TRUNC)。
    • mode:当使用 O_CREAT 标志时,指定新文件的权限。
  • creat:创建一个新文件(相当于 open 使用 O_WRONLY | O_CREAT | O_TRUNC 模式)。

    1
    2
    #include <fcntl.h>
    int creat(const char *pathname, mode_t mode);

3.2 . 关闭文件

  • close:关闭一个打开的文件描述符。

    1
    2
    #include <unistd.h>
    int close(int fd);
    • fd:文件描述符。

3.3 . 读取和写入文件

  • read:从文件描述符读取数据到缓冲区。

    1
    2
    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
    • fd:文件描述符。
    • buf:指向缓冲区的指针。
    • count:要读取的最大字节数。
  • write:将缓冲区中的数据写入文件描述符。

    1
    2
    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
    • fd:文件描述符。
    • buf:指向缓冲区的指针。
    • count:要写入的字节数。

3.4 . 文件位置控制

  • lseek:改变文件描述符的位置指针。

    1
    2
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
    • fd:文件描述符。
    • offset:偏移量。
    • whence:参考点(SEEK_SETSEEK_CURSEEK_END)。

3.5 . 获取文件状态

  • stat:获取文件或目录的状态信息。

    1
    2
    #include <sys/stat.h>
    int stat(const char *pathname, struct stat *statbuf);
  • fstat:通过文件描述符获取文件状态信息。

    1
    2
    #include <sys/stat.h>
    int fstat(int fd, struct stat *statbuf);
  • lstat:获取符号链接本身的状态信息,而不是符号链接指向的文件。

    1
    2
    #include <sys/stat.h>
    int lstat(const char *pathname, struct stat *statbuf);

3.6 . 删除文件和目录

  • unlink:删除一个文件。

    1
    2
    #include <unistd.h>
    int unlink(const char *pathname);
  • rmdir:删除一个空目录。

    1
    2
    #include <unistd.h>
    int rmdir(const char *pathname);

3.7 . 目录操作

  • opendir:打开一个目录流。

    1
    2
    #include <dirent.h>
    DIR *opendir(const char *name);
  • readdir:读取目录流中的下一个条目。

    1
    2
    #include <dirent.h>
    struct dirent *readdir(DIR *dirp);
  • closedir:关闭一个目录流。

    1
    2
    #include <dirent.h>
    int closedir(DIR *dirp);

测试1:实现类似于 ls -l 命令的功能,打印文件或目录的详细信息。
测试2:实现类似于 ls -R 命令的功能,打印目录树。

head.h :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma once

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include </usr/include/x86_64-linux-gnu/sys/types.h>
#include </usr/include/x86_64-linux-gnu/sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>



#define ERRLOG(STR) printf("说明:%s,文件:%s,行号:%d,错误:%s\n",STR,__FILE__,__LINE__,strerror(errno));

dirRecursion.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131

#include "head.h"

void dirRecursion(const char *dirname);
void fileInformtion(const char *dirname);
char getFileTypeChar(mode_t mode);

int main(int argc, char const *argv[]) {
if (argc < 2) {
ERRLOG("缺少参数");
return -1;
}
fileInformtion(argv[1]);
// dirRecursion(argv[1]);
return 0;
}


char getFileTypeChar(mode_t mode) {
if (S_ISREG(mode)) return '-'; // 普通文件
if (S_ISDIR(mode)) return 'd'; // 目录
if (S_ISLNK(mode)) return 'l'; // 符号链接
if (S_ISCHR(mode)) return 'c'; // 字符设备
if (S_ISBLK(mode)) return 'b'; // 块设备
if (S_ISSOCK(mode)) return 's'; // 套接字
if (S_ISFIFO(mode)) return 'p'; // 命名管道
return '?'; // 未知类型
}

void fileInformtion(const char *dirname)
{
DIR *dir_p = opendir(dirname);
if (dir_p == NULL) {
ERRLOG("文件夹打开失败");
return;
}
struct dirent *dirInfo;
char file[128];
char infoBuffer[1024];
while ((dirInfo = readdir(dir_p)) != NULL)
{

// 忽略 "." 和 ".." 目录
if (!strcmp(dirInfo->d_name, ".") || !strcmp(dirInfo->d_name, "..")) {
continue;
}
struct stat file_stat;

strcpy(file,dirInfo->d_name);
// 获取文件状态信息
if (stat(file, &file_stat) == -1) {
perror("stat 失败");
return;
}


struct passwd * u_struct;
u_struct = getpwuid(file_stat.st_uid);
struct group * g_struct;
g_struct = getgrgid(file_stat.st_gid);


char modeStr[11] = "----------";
modeStr[0] = getFileTypeChar(file_stat.st_mode);
modeStr[1] = (file_stat.st_mode & S_IRUSR) ? 'r' : '-';
modeStr[2] = (file_stat.st_mode & S_IWUSR) ? 'w' : '-';
modeStr[3] = (file_stat.st_mode & S_IXUSR) ? 'x' : '-';
modeStr[4] = (file_stat.st_mode & S_IRGRP) ? 'r' : '-';
modeStr[5] = (file_stat.st_mode & S_IWGRP) ? 'w' : '-';
modeStr[6] = (file_stat.st_mode & S_IXGRP) ? 'x' : '-';
modeStr[7] = (file_stat.st_mode & S_IROTH) ? 'r' : '-';
modeStr[8] = (file_stat.st_mode & S_IWOTH) ? 'w' : '-';
modeStr[9] = (file_stat.st_mode & S_IXOTH) ? 'x' : '-';

// 使用 snprintf 合并所有信息
snprintf(infoBuffer, sizeof(infoBuffer),
"%ld %s %ld %s %s %10ld %-30s %s",
(long) file_stat.st_ino,
modeStr,
(long) file_stat.st_nlink,
u_struct->pw_name,
g_struct->gr_name,
(long) file_stat.st_size,
(char *)dirInfo->d_name,
ctime(&file_stat.st_atime));


// 打印所有信息
printf("%s", infoBuffer);

}
closedir(dir_p);
}

void dirRecursion(const char *dirname) {
DIR *dir_p = opendir(dirname);
if (dir_p == NULL) {
ERRLOG("文件夹打开失败");
return;
}

struct dirent *dirInfo;

// 动态分配足够的空间用于存储路径
char path[512];
printf("\n");
while ((dirInfo = readdir(dir_p)) != NULL) {
// 忽略 "." 和 ".." 目录
if (!strcmp(dirInfo->d_name, ".") || !strcmp(dirInfo->d_name, "..")) {
continue;
}
// printf(" %s ", dirInfo->d_name);
// 构建完整路径
snprintf(path, sizeof(path), "%s/%s", dirname, dirInfo->d_name);


if (dirInfo->d_type == DT_DIR) {
// 打印目录文件名并递归进入子目录
printf("目录文件:%s\n", dirInfo->d_name);
printf("----------------进入到 %s 目录中----------------\n", path);
dirRecursion(path);
} else {
// 处理文件
printf(" %s ", dirInfo->d_name);
}
}

closedir(dir_p);
printf("\n");
printf("----------------退出目录----------------\n");
}

结果如下: