getdents64 函数详解
1. 函数介绍
getdents64
是 Linux 系统中用于读取目录内容的底层系统调用。可以把这个函数想象成一个”目录内容扫描仪”——它能够高效地扫描目录中的所有文件和子目录,就像超市的扫描枪快速读取商品条码一样。
与高级的目录操作函数(如 readdir
)不同,getdents64
是最底层的接口,直接与内核交互,提供了最大的灵活性和性能。它返回的是原始的目录项数据,包含文件名、inode 号、文件类型等信息。
2. 函数原型
#include <dirent.h> /* 或者 <unistd.h> */
#include <sys/syscall.h>
int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
3. 功能
getdents64
函数用于从已打开的目录文件描述符中读取目录项(directory entries)。它一次可以读取多个目录项,比逐个读取效率更高。
4. 参数
- fd: 已打开的目录文件描述符(通过
open()
或opendir()
获得) - dirp: 指向缓冲区的指针,用于存储读取的目录项数据
- count: 缓冲区的大小(以字节为单位)
5. struct linux_dirent64 结构体
struct linux_dirent64 {
ino64_t d_ino; /* 64位 inode 号 */
off64_t d_off; /* 到下一个目录项的偏移 */
unsigned short d_reclen; /* 此目录项的长度 */
unsigned char d_type; /* 文件类型 */
char d_name[]; /* 文件名(以 null 结尾) */
};
6. 文件类型(d_type 字段)
类型值 | 宏定义 | 说明 |
---|---|---|
0 | DT_UNKNOWN | 未知类型 |
1 | DT_FIFO | 命名管道 |
2 | DT_CHR | 字符设备 |
4 | DT_DIR | 目录 |
6 | DT_BLK | 块设备 |
8 | DT_REG | 普通文件 |
10 | DT_LNK | 符号链接 |
12 | DT_SOCK | 套接字 |
7. 返回值
- 成功: 返回实际读取的字节数(0 表示到达目录末尾)
- 失败: 返回 -1,并设置相应的 errno 错误码
常见错误码:
EBADF
: fd 不是有效的目录文件描述符EFAULT
: dirp 指针无效EINVAL
: 参数无效ENOENT
: 目录不存在
8. 相似函数或关联函数
- getdents: 旧版本的目录读取函数(32位 inode)
- readdir: POSIX 标准的目录读取函数(更高级的接口)
- opendir/fdopendir: 打开目录
- closedir: 关闭目录
- scandir: 扫描目录并排序
- ls: 命令行目录列表工具
9. 示例代码
示例1:基础用法 – 读取目录内容
#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[]; /* 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[]) {
int fd;
char buf[4096];
int nread;
char *dir_path;
// 获取目录路径参数
if (argc != 2) {
printf("用法: %s <目录路径>\n", argv[0]);
dir_path = "."; // 默认当前目录
printf("使用当前目录: %s\n\n", dir_path);
} else {
dir_path = argv[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:带过滤和统计的目录扫描器
#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[];
};
// 统计信息结构体
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[]) {
int fd;
char buf[8192];
int nread;
char *dir_path;
unsigned char filter_type = 0; // 0 表示不过滤
struct dir_stats stats = {0};
// 解析命令行参数
if (argc < 2) {
printf("用法: %s <目录路径> [过滤类型]\n", argv[0]);
printf("过滤类型: d(目录), f(文件), l(链接), 其他类型字符\n");
dir_path = ".";
} else {
dir_path = argv[1];
}
// 设置过滤类型
if (argc > 2) {
switch (argv[2][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[2][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[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:递归目录遍历工具
#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[];
};
// 全局统计变量
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[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("[%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[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 [%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[]) {
char *start_path;
int show_depth = 3; // 默认显示3层
// 解析命令行参数
if (argc < 2) {
printf("用法: %s <起始路径> [显示深度]\n", argv[0]);
start_path = ".";
} else {
start_path = argv[1];
}
if (argc > 2) {
show_depth = atoi(argv[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;
}
编译和运行说明
# 编译示例程序
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 的比较
特性 | getdents64 | readdir |
---|---|---|
级别 | 系统调用 | 标准库函数 |
效率 | 高(批量读取) | 较低(逐个读取) |
使用复杂度 | 复杂 | 简单 |
可移植性 | Linux 特有 | 跨平台 |
功能 | 原始数据 | 解析后的结构体 |
实际应用场景
- 文件管理器: 快速显示目录内容
- 备份工具: 扫描需要备份的文件
- 搜索工具: 快速遍历文件系统
- 系统监控: 监控目录变化
- 磁盘分析: 统计磁盘使用情况
- 安全扫描: 检查可疑文件
这些示例展示了 getdents64
函数的各种使用方法,从基本的目录读取到复杂的递归遍历,帮助你掌握这个高效的目录操作接口。
getdents64
是 Linux 系统底层目录扫描函数,用于高效读取目录内容。它直接与内核交互,返回包含文件名、inode号、文件类型等信息的原始目录项数据。相比高级接口如 readdir
,getdents64
提供了更高的灵活性和性能。使用时需提供目录文件描述符、缓冲区和大小参数,返回实际读取字节数或错误码。该函数常用于需要精细控制目录扫描的场景,如文件系统工具开发。示例代码展示了如何用 getdents64
实现目录内容扫描和文件类型统计功能。