You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于系统调用实现简易版readdir的教学性方案探讨

基于getdents的可递归目录遍历实现(适配现代Linux)

针对K&R《The C Programming Language》第8章8.6节示例在现代Linux的兼容性问题,这里提供一个基于getdents系统调用的read_dirent实现,支持递归遍历子目录,同时保留底层系统调用的教学价值。

核心思路

  1. 用getdents替代read:现代Linux通过getdents系统调用读取变长目录项,一次调用可获取多个条目,效率更高。
  2. 维护内部缓冲区:为避免频繁系统调用,read_dirent会缓存一批目录项,逐个返回给调用者。
  3. 递归处理子目录:结合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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 22:15:05