Linux中目录也是一种文件,目录文件的内容是一组"文件名→ inode编号"的映射。目录操作和文件属性获取是嵌入式Linux应用开发中的常见需求,比如扫描设备节点、读取 /proc 和 /sys 文件系统获取系统信息。
核心函数族:
opendir / readdir / closedir(类似C库的fopen/fread/fclose)stat / fstat / lstat(获取文件元数据)S_ISREG() / S_ISDIR() / S_ISCHR() 等宏#include <dirent.h>
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
DIR:目录流对象,类似FILE*
struct dirent:
struct dirent {
ino_t d_ino; // inode编号
off_t d_off; // 到下一个dirent的偏移(不常用)
unsigned short d_reclen; // 本条记录长度
unsigned char d_type; // 文件类型
char d_name[256]; // 文件名(不含路径前缀)
};
d_type文件类型:
| 值 | 宏 | 含义 |
|---|---|---|
| DT_REG | 普通文件 | — |
| DT_DIR | 目录 | — |
| DT_LNK | 符号链接 | — |
| DT_CHR | 字符设备 | 嵌入式常用 |
| DT_BLK | 块设备 | 嵌入式常用 |
| DT_FIFO | 管道 | — |
| DT_SOCK | socket | — |
| DT_UNKNOWN | 未知 | 某些文件系统不支持d_type |
注意:
d_type不是所有文件系统都支持(如ext2/ext3),此时返回DT_UNKNOWN,需要调用stat来判断文件类型。
#include <sys/stat.h>
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
区别:
stat:通过路径获取属性,跟随符号链接fstat:通过已打开的fd获取属性lstat:通过路径获取属性,不跟随符号链接(获取链接文件本身的属性)struct stat(核心字段):
struct stat {
dev_t st_dev; // 文件所在设备
ino_t st_ino; // inode编号
mode_t st_mode; // 文件类型 + 权限
nlink_t st_nlink; // 硬链接数
uid_t st_uid; // 所有者用户ID
gid_t st_gid; // 所有者组ID
dev_t st_rdev; // 特殊设备文件的设备号
off_t st_size; // 文件大小(字节)
blksize_t st_blksize; // 文件系统IO块大小
blkcnt_t st_blocks; // 占用的磁盘块数
time_t st_atime; // 最后访问时间
time_t st_mtime; // 最后修改时间
time_t st_ctime; // 最后状态变更时间
};
文件类型判断宏(从st_mode中提取):
| 宏 | 含义 |
|---|---|
S_ISREG(m) |
普通文件 |
S_ISDIR(m) |
目录 |
S_ISCHR(m) |
字符设备 |
S_ISBLK(m) |
块设备 |
S_ISFIFO(m) |
管道/FIFO |
S_ISSOCK(m) |
socket |
S_ISLNK(m) |
符号链接 |
权限判断宏:
| 宏 | 含义 |
|---|---|
S_IRUSR |
所有者读 |
S_IWUSR |
所有者写 |
S_IXUSR |
所有者执行 |
S_IRGRP |
组读 |
S_IWGRP |
组写 |
S_IXGRP |
组执行 |
S_IROTH |
其他读 |
S_IWOTH |
其他写 |
S_IXOTH |
其他执行 |
用法示例:
struct stat st;
if (stat("/dev/fb0", &st) == 0) {
if (S_ISCHR(st.st_mode)) {
printf("是字符设备, 设备号: %d.%d\n",
major(st.st_rdev), minor(st.st_rdev));
}
printf("权限: %o\n", st.st_mode & 0777);
}
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
int main(int argc, char *argv[]) {
const char *path = (argc > 1) ? argv[1] : ".";
DIR *dp = opendir(path);
if (!dp) {
perror("opendir");
return 1;
}
int reg_count = 0, dir_count = 0, dev_count = 0, other_count = 0;
struct dirent *entry;
while ((entry = readdir(dp)) != NULL) {
// 跳过 . 和 ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
// 优先使用d_type
if (entry->d_type != DT_UNKNOWN) {
switch (entry->d_type) {
case DT_REG: reg_count++; break;
case DT_DIR: dir_count++; break;
case DT_CHR:
case DT_BLK: dev_count++; break;
default: other_count++; break;
}
} else {
// d_type不支持时用stat
char fullpath[1024];
snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
struct stat st;
if (stat(fullpath, &st) == 0) {
if (S_ISREG(st.st_mode)) reg_count++;
else if (S_ISDIR(st.st_mode)) dir_count++;
else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) dev_count++;
else other_count++;
}
}
}
closedir(dp);
printf("目录 %s:\n", path);
printf(" 普通文件: %d\n", reg_count);
printf(" 目录: %d\n", dir_count);
printf(" 设备文件: %d\n", dev_count);
printf(" 其他: %d\n", other_count);
return 0;
}
编译:gcc -o scandir scandir.c
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
void traverse(const char *base, int depth) {
DIR *dp = opendir(base);
if (!dp) return;
struct dirent *entry;
while ((entry = readdir(dp)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char path[1024];
snprintf(path, sizeof(path), "%s/%s", base, entry->d_name);
// 缩进显示
for (int i = 0; i < depth; i++) printf(" ");
if (entry->d_type == DT_DIR) {
printf("[DIR] %s\n", entry->d_name);
traverse(path, depth + 1); // 递归进入子目录
} else if (entry->d_type == DT_REG) {
struct stat st;
if (stat(path, &st) == 0)
printf("[FILE] %s (%ld bytes)\n", entry->d_name, (long)st.st_size);
else
printf("[FILE] %s\n", entry->d_name);
} else {
printf("[?] %s\n", entry->d_name);
}
}
closedir(dp);
}
int main(int argc, char *argv[]) {
traverse(argc > 1 ? argv[1] : ".", 0);
return 0;
}
编译:gcc -o tree tree.c
#include <stdio.h>
#include <sys/stat.h>
int device_exists(const char *path) {
struct stat st;
if (stat(path, &st) < 0) return 0;
return S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode);
}
int main() {
const char *devices[] = {
"/dev/fb0", "/dev/input/event0", "/dev/ttyS0",
"/dev/i2c-0", "/dev/spidev0.0", NULL
};
for (int i = 0; devices[i]; i++) {
if (device_exists(devices[i]))
printf(" [√] %s\n", devices[i]);
else
printf(" [×] %s\n", devices[i]);
}
return 0;
}
编译:gcc -o checkdev checkdev.c
| 序号 | 坑点 | 说明 |
|---|---|---|
| 1 | readdir不是线程安全的 | 多线程中应该用 readdir_r(旧)或 readdir(glibc2.19+已线程安全) |
| 2 | d_type不保证支持 | 某些文件系统(ext2/ext3)d_type总是DT_UNKNOWN,必须用stat兜底 |
| 3 | opendir失败返回NULL | 必须检查返回值,常见原因:路径不存在、权限不足 |
| 4 | stat跟随符号链接 | 如果要获取链接文件本身属性,必须用 lstat |
| 5 | 权限用八进制表示 | st_mode & 0777 获取权限位,printf("%o", mode) 以八进制输出 |
| 6 | 时间字段是time_t | 用 ctime(&st.st_mtime) 转为可读字符串,或用 localtime + strftime |
| 7 | 递归遍历深度 | 目录嵌套太深可能导致栈溢出,嵌入式环境尤其注意 |
| 8 | /proc和/sys是虚拟文件系统 | 文件属性可能不完整(如文件大小为0),实际内容由内核动态生成 |
Q: stat和lstat的区别?
A: stat跟随符号链接,返回目标文件的属性;lstat不跟随符号链接,返回链接文件本身的属性。比如 stat("link_to_file", &st) 返回的是目标文件的属性,lstat("link_to_file", &st) 返回的是符号链接本身的属性。
Q: 如何判断一个文件是普通文件还是目录?
A: 用 stat 获取 struct stat,然后用 S_ISREG(st.st_mode) 判断是否为普通文件,S_ISDIR(st.st_mode) 判断是否为目录。
Q: readdir的d_type字段有什么限制? A: d_type不是所有文件系统都支持,ext2/ext3下可能返回DT_UNKNOWN。不可移植到所有Unix系统。需要兜底时用stat判断文件类型。
Q: 嵌入式中哪些场景会用到目录操作?
A: 扫描 /dev/ 目录查找设备节点、读取 /proc/ 和 /sys/ 获取系统信息、遍历配置文件目录加载配置。