getdents64系统调用及示例

getdents64 函数详解

  1. 函数介绍

getdents64 是 Linux 系统中用于读取目录内容的底层系统调用。可以把这个函数想象成一个”目录内容扫描仪”——它能够高效地扫描目录中的所有文件和子目录,就像超市的扫描枪快速读取商品条码一样。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

与高级的目录操作函数(如 readdir)不同,getdents64 是最底层的接口,直接与内核交互,提供了最大的灵活性和性能。它返回的是原始的目录项数据,包含文件名、inode 号、文件类型等信息。

  1. 函数原型
1
2
3
4
5
#include <dirent.h>     /* 或者 <unistd.h> */
#include <sys/syscall.h>

int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);

  1. 功能

getdents64 函数用于从已打开的目录文件描述符中读取目录项(directory entries)。它一次可以读取多个目录项,比逐个读取效率更高。

  1. 参数
  • fd: 已打开的目录文件描述符(通过 open() 或 opendir() 获得)

  • dirp: 指向缓冲区的指针,用于存储读取的目录项数据

  • count: 缓冲区的大小(以字节为单位)

  1. struct linux_dirent64 结构体
1
2
3
4
5
6
7
8
struct linux_dirent64 {
ino64_t d_ino; /* 64位 inode 号 */
off64_t d_off; /* 到下一个目录项的偏移 */
unsigned short d_reclen; /* 此目录项的长度 */
unsigned char d_type; /* 文件类型 */
char d_name&#91;]; /* 文件名(以 null 结尾) */
};

  1. 文件类型(d_type 字段)

类型值宏定义说明0DT_UNKNOWN未知类型1DT_FIFO命名管道2DT_CHR字符设备4DT_DIR目录6DT_BLK块设备8DT_REG普通文件10DT_LNK符号链接12DT_SOCK套接字

  1. 返回值
  • 成功: 返回实际读取的字节数(0 表示到达目录末尾)

  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EBADF: fd 不是有效的目录文件描述符

  • EFAULT: dirp 指针无效

  • EINVAL: 参数无效

  • ENOENT: 目录不存在

  1. 相似函数或关联函数
  • getdents: 旧版本的目录读取函数(32位 inode)

  • readdir: POSIX 标准的目录读取函数(更高级的接口)

  • opendir/fdopendir: 打开目录

  • closedir: 关闭目录

  • scandir: 扫描目录并排序

  • ls: 命令行目录列表工具

  1. 示例代码

示例1:基础用法 - 读取目录内容

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <string.h>

// 目录项结构体(64位版本)
struct linux_dirent64 {
ino64_t d_ino; /* Inode number */
off64_t d_off; /* Offset to next structure */
unsigned short d_reclen; /* Size of this structure */
unsigned char d_type; /* File type */
char d_name&#91;]; /* Filename (null-terminated) */
};

// 获取文件类型字符串
const char* get_file_type_string(unsigned char d_type) {
switch (d_type) {
case DT_REG: return "普通文件";
case DT_DIR: return "目录";
case DT_LNK: return "符号链接";
case DT_CHR: return "字符设备";
case DT_BLK: return "块设备";
case DT_FIFO: return "命名管道";
case DT_SOCK: return "套接字";
case DT_UNKNOWN:
default: return "未知";
}
}

// 获取文件类型字符
char get_file_type_char(unsigned char d_type) {
switch (d_type) {
case DT_REG: return 'f';
case DT_DIR: return 'd';
case DT_LNK: return 'l';
case DT_CHR: return 'c';
case DT_BLK: return 'b';
case DT_FIFO: return 'p';
case DT_SOCK: return 's';
case DT_UNKNOWN:
default: return '?';
}
}

int main(int argc, char *argv&#91;]) {
int fd;
char buf&#91;4096];
int nread;
char *dir_path;

// 获取目录路径参数
if (argc != 2) {
printf("用法: %s <目录路径>\n", argv&#91;0]);
dir_path = "."; // 默认当前目录
printf("使用当前目录: %s\n\n", dir_path);
} else {
dir_path = argv&#91;1];
}

// 打开目录
fd = open(dir_path, O_RDONLY | O_DIRECTORY);
if (fd == -1) {
perror("open");
return 1;
}

printf("=== 目录 '%s' 的内容 ===\n", dir_path);
printf("%-12s %-10s %-8s %s\n", "INODE", "类型", "大小", "名称");
printf("%-12s %-10s %-8s %s\n", "----", "----", "----", "----");

// 循环读取目录项
while (1) {
nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
if (nread == -1) {
perror("getdents64");
close(fd);
return 1;
}

if (nread == 0) {
break; // 到达目录末尾
}

// 解析目录项
for (int bpos = 0; bpos < nread;) {
struct linux_dirent64 *d;
d = (struct linux_dirent64 *)(buf + bpos);

// 显示目录项信息
printf("%-12llu %-10s %-8d %s\n",
(unsigned long long)d->d_ino,
get_file_type_string(d->d_type),
d->d_reclen,
d->d_name);

bpos += d->d_reclen;
}
}

close(fd);
return 0;
}

示例2:带过滤和统计的目录扫描器

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>

struct linux_dirent64 {
ino64_t d_ino;
off64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name&#91;];
};

// 统计信息结构体
struct dir_stats {
int total_files;
int directories;
int regular_files;
int symlinks;
int devices;
int fifos;
int sockets;
int unknown;
long long total_size;
};

// 更新统计信息
void update_stats(struct dir_stats *stats, unsigned char d_type) {
stats->total_files++;

switch (d_type) {
case DT_DIR:
stats->directories++;
break;
case DT_REG:
stats->regular_files++;
break;
case DT_LNK:
stats->symlinks++;
break;
case DT_CHR:
case DT_BLK:
stats->devices++;
break;
case DT_FIFO:
stats->fifos++;
break;
case DT_SOCK:
stats->sockets++;
break;
case DT_UNKNOWN:
default:
stats->unknown++;
break;
}
}

// 显示统计信息
void print_stats(const struct dir_stats *stats) {
printf("\n=== 统计信息 ===\n");
printf("总计文件数: %d\n", stats->total_files);
printf("目录数: %d\n", stats->directories);
printf("普通文件: %d\n", stats->regular_files);
printf("符号链接: %d\n", stats->symlinks);
printf("设备文件: %d\n", stats->devices);
printf("命名管道: %d\n", stats->fifos);
printf("套接字: %d\n", stats->sockets);
printf("未知类型: %d\n", stats->unknown);
}

// 过滤函数:只显示特定类型的文件
int filter_by_type(unsigned char d_type, unsigned char filter_type) {
if (filter_type == 0) return 1; // 不过滤
return d_type == filter_type;
}

int main(int argc, char *argv&#91;]) {
int fd;
char buf&#91;8192];
int nread;
char *dir_path;
unsigned char filter_type = 0; // 0 表示不过滤
struct dir_stats stats = {0};

// 解析命令行参数
if (argc < 2) {
printf("用法: %s <目录路径> &#91;过滤类型]\n", argv&#91;0]);
printf("过滤类型: d(目录), f(文件), l(链接), 其他类型字符\n");
dir_path = ".";
} else {
dir_path = argv&#91;1];
}

// 设置过滤类型
if (argc > 2) {
switch (argv&#91;2]&#91;0]) {
case 'd': filter_type = DT_DIR; break;
case 'f': filter_type = DT_REG; break;
case 'l': filter_type = DT_LNK; break;
case 'c': filter_type = DT_CHR; break;
case 'b': filter_type = DT_BLK; break;
case 'p': filter_type = DT_FIFO; break;
case 's': filter_type = DT_SOCK; break;
}
if (filter_type != 0) {
printf("过滤类型: %c\n", argv&#91;2]&#91;0]);
}
}

// 打开目录
fd = open(dir_path, O_RDONLY | O_DIRECTORY);
if (fd == -1) {
perror("open");
return 1;
}

printf("=== 目录 '%s' 的内容 ===\n", dir_path);
if (filter_type != 0) {
printf("(已过滤)\n");
}
printf("%c %-20s %12s %10s\n",
'T', "名称", "INODE", "大小(字节)");
printf("%c %-20s %12s %10s\n",
'-', "----", "-----", "----------");

// 读取目录项
while (1) {
nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
if (nread == -1) {
perror("getdents64");
close(fd);
return 1;
}

if (nread == 0) {
break; // 到达目录末尾
}

// 解析目录项
for (int bpos = 0; bpos < nread;) {
struct linux_dirent64 *d;
d = (struct linux_dirent64 *)(buf + bpos);

// 应用过滤器
if (filter_by_type(d->d_type, filter_type)) {
// 获取文件大小(对于普通文件)
long long file_size = 0;
if (d->d_type == DT_REG) {
char full_path&#91;1024];
struct stat st;
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, d->d_name);
if (stat(full_path, &st) == 0) {
file_size = st.st_size;
stats.total_size += file_size;
}
}

printf("%c %-20s %12llu %10lld\n",
get_file_type_char(d->d_type),
d->d_name,
(unsigned long long)d->d_ino,
file_size);
}

// 更新统计信息
update_stats(&stats, d->d_type);

bpos += d->d_reclen;
}
}

close(fd);

// 显示统计信息
print_stats(&stats);

return 0;
}

示例3:递归目录遍历工具

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>

struct linux_dirent64 {
ino64_t d_ino;
off64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name&#91;];
};

// 全局统计变量
int total_files = 0;
int total_dirs = 0;
long long total_size = 0;
int max_depth = 0;

// 递归遍历目录
void traverse_directory(const char *path, int current_depth, int max_show_depth) {
int fd;
char buf&#91;8192];
int nread;

// 更新最大深度
if (current_depth > max_depth) {
max_depth = current_depth;
}

// 打开目录
fd = open(path, O_RDONLY | O_DIRECTORY);
if (fd == -1) {
printf("无法打开目录: %s\n", path);
return;
}

// 显示当前目录(如果在显示范围内)
if (current_depth <= max_show_depth) {
for (int i = 0; i < current_depth; i++) {
printf(" ");
}
printf("&#91;%s]\n", path);
}

// 读取目录项
while (1) {
nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
if (nread == -1) {
printf("读取目录失败: %s\n", path);
close(fd);
return;
}

if (nread == 0) {
break; // 到达目录末尾
}

// 解析目录项
for (int bpos = 0; bpos < nread;) {
struct linux_dirent64 *d;
d = (struct linux_dirent64 *)(buf + bpos);

// 跳过 . 和 ..
if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
bpos += d->d_reclen;
continue;
}

// 构造完整路径
char full_path&#91;1024];
snprintf(full_path, sizeof(full_path), "%s/%s", path, d->d_name);

if (d->d_type == DT_DIR) {
total_dirs++;
// 显示目录(如果在显示范围内)
if (current_depth < max_show_depth) {
for (int i = 0; i <= current_depth; i++) {
printf(" ");
}
printf("├── %s/\n", d->d_name);
} else if (current_depth == max_show_depth) {
for (int i = 0; i <= current_depth; i++) {
printf(" ");
}
printf("├── %s/ (子目录未展开)\n", d->d_name);
}

// 递归遍历子目录
traverse_directory(full_path, current_depth + 1, max_show_depth);

} else if (d->d_type == DT_REG) {
total_files++;
// 获取文件大小
struct stat st;
if (stat(full_path, &st) == 0) {
total_size += st.st_size;
}

// 显示文件(如果在显示范围内)
if (current_depth < max_show_depth) {
for (int i = 0; i <= current_depth; i++) {
printf(" ");
}
printf("├── %s (%lld bytes)\n", d->d_name,
(long long)(stat(full_path, &st) == 0 ? st.st_size : 0));
}
} else {
total_files++;
// 显示其他类型的文件
if (current_depth < max_show_depth) {
for (int i = 0; i <= current_depth; i++) {
printf(" ");
}
printf("├── %s &#91;%c]\n", d->d_name, get_file_type_char(d->d_type));
}
}

bpos += d->d_reclen;
}
}

close(fd);
}

// 获取文件类型字符
char get_file_type_char(unsigned char d_type) {
switch (d_type) {
case DT_REG: return 'f';
case DT_DIR: return 'd';
case DT_LNK: return 'l';
case DT_CHR: return 'c';
case DT_BLK: return 'b';
case DT_FIFO: return 'p';
case DT_SOCK: return 's';
default: return '?';
}
}

int main(int argc, char *argv&#91;]) {
char *start_path;
int show_depth = 3; // 默认显示3层

// 解析命令行参数
if (argc < 2) {
printf("用法: %s <起始路径> &#91;显示深度]\n", argv&#91;0]);
start_path = ".";
} else {
start_path = argv&#91;1];
}

if (argc > 2) {
show_depth = atoi(argv&#91;2]);
if (show_depth < 1) show_depth = 1;
}

printf("=== 递归目录遍历工具 ===\n");
printf("起始路径: %s\n", start_path);
printf("显示深度: %d\n\n", show_depth);

// 开始遍历
traverse_directory(start_path, 0, show_depth);

// 显示统计信息
printf("\n=== 遍历统计 ===\n");
printf("总目录数: %d\n", total_dirs);
printf("总文件数: %d\n", total_files);
printf("总大小: %.2f MB (%lld bytes)\n",
total_size / (1024.0 * 1024.0), total_size);
printf("最大深度: %d\n", max_depth);

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
# 编译示例程序
gcc -o getdents64_example1 example1.c
gcc -o getdents64_example2 example2.c
gcc -o getdents64_example3 example3.c

# 运行示例
./getdents64_example1
./getdents64_example1 /etc
./getdents64_example2 /home
./getdents64_example2 /usr/bin f # 只显示普通文件
./getdents64_example2 /dev d # 只显示目录
./getdents64_example3 /usr 2 # 显示2层目录结构

重要注意事项

缓冲区大小: 建议使用较大的缓冲区(如 4KB-8KB)以提高效率

目录文件描述符: 必须使用 O_DIRECTORY 标志打开目录

错误处理: 始终检查返回值和 errno

字符串处理: 目录项名称是 null 结尾的,但结构体大小可能包含填充

性能: getdents64 比 readdir 更高效,因为它一次返回多个目录项

移植性: 这是 Linux 特有的系统调用

与 readdir 的比较

特性getdents64readdir级别系统调用标准库函数效率高(批量读取)较低(逐个读取)使用复杂度复杂简单可移植性Linux 特有跨平台功能原始数据解析后的结构体

实际应用场景

文件管理器: 快速显示目录内容

备份工具: 扫描需要备份的文件

搜索工具: 快速遍历文件系统

系统监控: 监控目录变化

磁盘分析: 统计磁盘使用情况

安全扫描: 检查可疑文件

这些示例展示了 getdents64 函数的各种使用方法,从基本的目录读取到复杂的递归遍历,帮助你掌握这个高效的目录操作接口。

getdents64 是 Linux 系统底层目录扫描函数,用于高效读取目录内容。它直接与内核交互,返回包含文件名、inode号、文件类型等信息的原始目录项数据。相比高级接口如 readdir,getdents64 提供了更高的灵活性和性能。使用时需提供目录文件描述符、缓冲区和大小参数,返回实际读取字节数或错误码。该函数常用于需要精细控制目录扫描的场景,如文件系统工具开发。示例代码展示了如何用 getdents64 实现目录内容扫描和文件类型统计功能。

https://www.calcguide.tech/2025/09/08/getdents64系统调用及示例/

data-ad-format="auto" data-full-width-responsive="true">