基于系统调用实现简易版readdir的教学性方案探讨
基于getdents的可递归目录遍历实现(适配现代Linux)
针对K&R《The C Programming Language》第8章8.6节示例在现代Linux的兼容性问题,这里提供一个基于getdents系统调用的read_dirent实现,支持递归遍历子目录,同时保留底层系统调用的教学价值。
核心思路
- 用
getdents替代read:现代Linux通过getdents系统调用读取变长目录项,一次调用可获取多个条目,效率更高。 - 维护内部缓冲区:为避免频繁系统调用,
read_dirent会缓存一批目录项,逐个返回给调用者。 - 递归处理子目录:结合
stat判断文件类型,跳过.和..后递归遍历子目录。
完整实现代码
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/syscall.h> #include <string.h> #include <dirent.h> // Linux x86_64平台的getdents系统调用编号 #define GETDENTS_SYSCALL 217 // 目录项缓存区大小(可根据实际场景调整) #define DIR_BUF_SIZE 4096 typedef struct { int fd; // 目录文件描述符 char buf[DIR_BUF_SIZE]; // 缓存getdents读取的批量目录项 int pos; // 当前缓冲区的处理偏移 int count; // 缓冲区剩余未处理字节数 } DirInfo; // 打开目录并初始化DirInfo结构体 DirInfo* dir_open(const char* path) { int fd = open(path, O_RDONLY | O_DIRECTORY); if (fd == -1) { perror("open failed"); return NULL; } DirInfo* dir = malloc(sizeof(DirInfo)); if (!dir) { close(fd); perror("malloc failed"); return NULL; } dir->fd = fd; dir->pos = 0; dir->count = 0; return dir; } // 读取单个目录项,封装getdents的批量读取逻辑 struct dirent* dir_read(DirInfo* dir) { if (!dir) return NULL; // 缓冲区无剩余条目时,调用getdents填充 if (dir->count == 0) { dir->count = syscall(GETDENTS_SYSCALL, dir->fd, dir->buf, DIR_BUF_SIZE); if (dir->count <= 0) { // 读取完毕或出错 return NULL; } dir->pos = 0; } // 从缓冲区取出当前目录项 struct dirent* dent = (struct dirent*)(dir->buf + dir->pos); // 更新缓冲区偏移和剩余字节数 dir->pos += dent->d_reclen; dir->count -= dent->d_reclen; return dent; } // 关闭目录并释放资源 void dir_close(DirInfo* dir) { if (dir) { close(dir->fd); free(dir); } } // 递归遍历目录并打印文件大小 void traverse_dir(const char* path) { DirInfo* dir = dir_open(path); if (!dir) return; struct dirent* dent; while ((dent = dir_read(dir)) != NULL) { // 跳过当前目录和父目录,避免无限递归 if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) { continue; } // 拼接文件完整路径 char full_path[1024]; snprintf(full_path, sizeof(full_path), "%s/%s", path, dent->d_name); // 获取文件状态信息 struct stat st; if (stat(full_path, &st) == -1) { perror("stat failed"); continue; } // 打印文件名和大小 printf("%s: %ld bytes\n", full_path, st.st_size); // 如果是目录,递归遍历子目录 if (S_ISDIR(st.st_mode)) { traverse_dir(full_path); } } dir_close(dir); } int main(int argc, char* argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <directory>\n", argv[0]); exit(EXIT_FAILURE); } traverse_dir(argv[1]); return 0; }
关键细节说明
getdents系统调用:直接通过syscall()触发,避开glibc封装的readdir,保留底层调用的教学价值。注意不同架构的系统调用编号不同(x86是141,x86_64是217)。- 缓冲区管理:
DirInfo结构体统一管理目录描述符、缓存区和处理状态,确保每次dir_read仅返回单个目录项,对齐K&R示例的调用逻辑。 - 递归控制:通过
S_ISDIR判断文件类型,跳过.和..避免循环递归,拼接完整路径后进入子目录遍历。 - 错误处理:保留简洁的
perror输出,便于教学场景下理解系统调用的错误触发场景。
编译与运行
用gcc编译:
gcc -o dir_traverse dir_traverse.c
运行时指定目标目录:
./dir_traverse /path/to/target/directory
内容的提问来源于stack exchange,提问作者Hosein Rahnama
相关产品推荐
相关产品推荐

