Day20 Linux 文件 I/O、目录操作及文件链接与 EDID
day20 Linux 文件 I/O、目录操作及文件链接与 EDID
文件 I/O —— 系统调用函数
操作对象
- 文件描述符(File Descriptor):用于标识一个已打开的文件,是整数(非负),由系统调用如
open
返回。
具体操作
- 打开:
open
- 读写:
read
、write
- 定位:
lseek
- 注意:
- 并非所有文件类型都支持定位(如管道、FIFO)。
lseek
不是关闭操作,而是用于移动文件读写位置指针。
- 注意:
- 关闭:
close
示例代码:BMP 图像像素合并
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>// a.out 1.bmp 2.bmp
int main(int argc, const char *argv[])
{// 检查命令行参数是否正确if (argc != 3){printf("Usage : %s <1.bmp> <2.bmp>\n", argv[0]);return -1;}// 打开两个 BMP 文件:只读和可读写int fd1 = open(argv[1], O_RDONLY); // 源图像(只读)int fd2 = open(argv[2], O_RDWR); // 目标图像(可修改)// 检查打开是否成功if (fd1 < 0 || fd2 < 0){perror("open fail");return -1;}// 定义缓冲区存储 BMP 数据(800x600 RGB + 54字节头部)unsigned char buf1[800*600*3+54];unsigned char buf2[800*600*3+54];// 读取两个文件的内容到缓冲区read(fd1, buf1, sizeof(buf1));read(fd2, buf2, sizeof(buf2));// 遍历像素数据(跳过前54字节文件头),将非白色像素复制到目标图像int i = 0;for (i = 54; i < 800*600*3+54; ++i){if (buf1[i] != 255) // 若源图像该通道值不为255(非白色){buf2[i] = buf1[i]; // 复制该像素值到目标图像}}// 将文件指针重置到目标文件开头lseek(fd2, 0, SEEK_SET);// 写回修改后的数据到目标文件write(fd2, buf2, sizeof(buf2));// 关闭文件描述符close(fd1);close(fd2);return 0;
}
✅ 理想运行结果:
$ gcc -o merge_bmp merge_bmp.c $ ./merge_bmp 1.bmp 2.bmp (无输出表示成功,2.bmp 的图像中非白色部分被 1.bmp 覆盖)
目录操作
1. 打开目录:opendir
#include <sys/types.h>
#include <dirent.h>DIR *opendir(const char *name);
- 功能:打开指定目录,返回一个目录流指针。
- 参数:
name
:目录路径名。
- 返回值:
- 成功:指向
DIR
结构的指针。 - 失败:
NULL
,并设置errno
。
- 成功:指向
示例代码:打开目录
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>int main(void)
{DIR *dp = opendir("123");if (dp == NULL){perror("opendir fail");return -1;}printf("opendir success\n");return 0;
}
✅ 理想运行结果:
$ mkdir 123 $ gcc opendir.c -o opendir $ ./opendir opendir success
❌ 若目录不存在或非目录类型:
opendir fail: No such file or directory opendir fail: Not a directory
2. 读取目录:readdir
#include <dirent.h>struct dirent *readdir(DIR *dirp);
- 结构体
dirent
字段说明:
struct dirent {ino_t d_ino; /* inode 编号 */off_t d_off; /* 目录项偏移(系统内部使用) */unsigned short d_reclen; /* 该记录长度 */unsigned char d_type; /* 文件类型(部分文件系统支持) */char d_name[256]; /* 文件名(以 '\0' 结尾) */
};
-
常见
d_type
值:DT_BLK
— 块设备DT_CHR
— 字符设备DT_DIR
— 目录DT_FIFO
— 命名管道(FIFO)DT_LNK
— 符号链接DT_REG
— 普通文件DT_SOCK
— UNIX 域套接字DT_UNKNOWN
— 类型未知
-
功能:从目录流中读取下一项,每次调用返回一个目录项。
-
返回值:
- 成功:指向
struct dirent
的指针。 - 到达末尾或出错:
NULL
(需结合errno
判断)。
- 成功:指向
示例:读取单个目录项
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>int main(void)
{DIR *dp = opendir("123");if (dp == NULL){perror("opendir fail");return -1;}printf("opendir success\n");struct dirent *pdir = readdir(dp);printf("name = %s\n", pdir->d_name);closedir(dp);return 0;
}
✅ 理想运行结果:
opendir success name = .
练习 1:打印目录下所有文件的 inode 和名字
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>int main(int argc, const char *argv[])
{if (argc != 2){printf("Usage: %s <dirname>\n", argv[0]);return -1;}DIR *dp = opendir(argv[1]);if (dp == NULL){perror("opendir fail");return -1;}struct dirent *pdir = NULL;while ((pdir = readdir(dp)) != NULL){printf("inode = %ld name = %s\n", pdir->d_ino, pdir->d_name);}closedir(dp);return 0;
}
✅ 理想运行结果:
$ ./list_dir /home/linux/testdir inode = 12345 name = . inode = 12344 name = .. inode = 12346 name = file1.txt inode = 12347 name = subdir
练习 2:实现 ls
功能(忽略隐藏文件)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>int main(int argc, const char *argv[])
{if (argc != 2){printf("Usage: %s <dirname>\n", argv[0]);return -1;}DIR *dp = opendir(argv[1]);if (dp == NULL){perror("opendir fail");return -1;}struct dirent *pdir = NULL;while ((pdir = readdir(dp)) != NULL){// 跳过以 '.' 开头的隐藏文件(包括 . 和 ..)if (pdir->d_name[0] != '.'){printf("%s\n", pdir->d_name);}}putchar('\n'); // 输出换行closedir(dp);return 0;
}
✅ 理想运行结果:
$ ./ls_sim /home/linux/testdir file1.txt document.pdf
练习 3:统计目录中普通文件与子目录数量
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>int main(int argc, const char *argv[])
{if (argc != 2){printf("Usage: %s <dirname>\n", argv[0]);return -1;}DIR *dp = opendir(argv[1]);if (dp == NULL){perror("opendir fail");return -1;}int r_cnt = 0; // 普通文件计数int d_cnt = 0; // 目录文件计数struct dirent *pdir = NULL;while ((pdir = readdir(dp)) != NULL){// 跳过隐藏文件(以 '.' 开头)if (pdir->d_name[0] == '.')continue;if (pdir->d_type == DT_DIR){d_cnt++;}else if (pdir->d_type == DT_REG){r_cnt++;}}closedir(dp);printf("dir cnt = %d reg cnt = %d\n", d_cnt, r_cnt);return 0;
}
✅ 理想运行结果:
$ ./count_files /home/linux/testdir dir cnt = 2 reg cnt = 5
3. 关闭目录:closedir
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
- 功能:关闭目录流。
- 参数:
dirp
— 目录流指针。 - 返回值:
- 成功:
0
- 失败:
-1
,并设置errno
- 成功:
✅ 使用建议:每次
opendir
后必须closedir
,防止资源泄漏。
文件构成及软连接、硬连接详解
Linux 文件组成结构
一个文件由三部分构成:
- 目录项(struct dirent):包含文件名和对应的 inode 编号。
- inode 表:存储文件元信息(如大小、权限、类型、硬链接数、时间戳等)。
- 数据块:实际文件内容存储位置。
文件访问流程:
- 根据文件名查找目录项,获取 inode 编号。
- 通过 inode 编号访问 inode 表,获取文件属性和数据块地址。
- 读取数据块内容。
硬链接(Hard Link)
-
本质:为文件创建别名,多个目录项指向同一个 inode。
-
特点:
- 不占用额外磁盘空间(仅增加目录项)。
- 不能跨文件系统(ext4、NTFS 等之间不行)。
- 不能对目录创建硬链接(避免循环引用)。
- 删除一个硬链接不影响其他链接;只有当 inode 的链接数为 0 且无进程打开时,文件才真正被删除。
-
命令:
ln 源文件 硬链接文件
✅ 示例:
ln hello.c hello_hard.c ls -i hello.c hello_hard.c # 显示相同 inode 号
软链接(Symbolic Link / Soft Link)
-
本质:类似 Windows 快捷方式,是一个独立的小文件,保存目标文件路径。
-
特点:
- 占用磁盘空间(存储路径字符串)。
- 可以跨文件系统。
- 可以对目录创建软链接。
- 若原文件被删除,软链接变为“悬空”,无法访问。
-
命令:
ln -s 源文件 软链接文件
✅ 示例:
ln -s /mnt/hgfs/share ~/share
硬链接 vs 软链接 对比
特性 | 硬链接 | 软链接 |
---|---|---|
inode 编号 | 与原文件相同 | 不同 |
是否占用空间 | 否(仅目录项) | 是(存储路径) |
能否跨文件系统 | 否 | 是 |
能否链接目录 | 否 | 是 |
原文件删除后状态 | 仍可访问 | 变为无效(悬空链接) |
获取文件状态:stat
系统调用
#include <sys/stat.h>
int stat(const char *pathname, struct stat *statbuf);
- 功能:获取指定路径文件的状态信息。
- 参数:
pathname
:文件路径。statbuf
:接收信息的struct stat
缓冲区。
- 返回值:
- 成功:
0
- 失败:
-1
,并设置errno
- 成功:
struct stat
主要字段
struct stat {dev_t st_dev; /* 包含文件的设备 ID */ino_t st_ino; /* inode 编号 */mode_t st_mode; /* 文件类型和权限 */nlink_t st_nlink; /* 硬链接数 */uid_t st_uid; /* 所有者 UID */gid_t st_gid; /* 所属组 GID */off_t st_size; /* 文件大小(字节) */blkcnt_t st_blocks; /* 分配的 512 字节块数 */time_t st_atime; /* 最后访问时间 */time_t st_mtime; /* 最后修改时间 */time_t st_ctime; /* 最后状态变更时间(如权限修改) */
};
文件类型宏定义(st_mode & S_IFMT
)
宏定义 | 含义 | 对应字符 |
---|---|---|
S_IFSOCK | 套接字 | s |
S_IFLNK | 符号链接 | l |
S_IFREG | 普通文件 | - |
S_IFBLK | 块设备 | b |
S_IFDIR | 目录 | d |
S_IFCHR | 字符设备 | c |
S_IFIFO | FIFO(命名管道) | p |
权限宏定义(掩码)
类别 | 宏定义 | 说明 |
---|---|---|
用户 | S_IRUSR | 读权限 |
S_IWUSR | 写权限 | |
S_IXUSR | 执行权限 | |
组 | S_IRGRP | 组读权限 |
S_IWGRP | 组写权限 | |
S_IXGRP | 组执行权限 | |
其他 | S_IROTH | 其他人读权限 |
S_IWOTH | 其他人写权限 | |
S_IXOTH | 其他人执行权限 |
示例:完整解析文件信息
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>int main(int argc, const char *argv[])
{if (argc != 2){printf("Usage: %s <filename>\n", argv[0]);return -1;}struct stat st;if (stat(argv[1], &st) < 0){perror("stat fail!\n");return -1;}// 文件类型名称与符号映射char type_name[7][20] = {"socket", "symbolic", "regular", "block", "directory", "character", "FIFO"};char type[7] = {'s', 'l', '-', 'b', 'd', 'c', 'p'};int i = 0;// 判断文件类型switch (st.st_mode & S_IFMT){case S_IFSOCK: i = 0; break;case S_IFLNK: i = 1; break;case S_IFREG: i = 2; break;case S_IFBLK: i = 3; break;case S_IFDIR: i = 4; break;case S_IFCHR: i = 5; break;case S_IFIFO: i = 6; break;}// 输出基本信息printf("File: %s\n", argv[1]);printf("Size: %ld\tBlocks: %ld\tIO Blocks: %ld\t %s\n",st.st_size, st.st_blocks, st.st_blksize, type_name[i]);printf("Device: %ld\t Inode: %ld\t Links: %ld\n",st.st_dev, st.st_ino, st.st_nlink);// 输出权限字符串printf("Access: (%#o/", (st.st_mode & S_IRWXU) | (st.st_mode & S_IRWXG) | (st.st_mode & S_IRWXO));putchar(type[i]);st.st_mode & S_IRUSR ? putchar('r') : putchar('-');st.st_mode & S_IWUSR ? putchar('w') : putchar('-');st.st_mode & S_IXUSR ? putchar('x') : putchar('-');st.st_mode & S_IRGRP ? putchar('r') : putchar('-');st.st_mode & S_IWGRP ? putchar('w') : putchar('-');st.st_mode & S_IXGRP ? putchar('x') : putchar('-');st.st_mode & S_IROTH ? putchar('r') : putchar('-');st.st_mode & S_IWOTH ? putchar('w') : putchar('-');st.st_mode & S_IXOTH ? putchar('x') : putchar('-');putchar(')');printf(" Uid: (%d) Gid: (%d)\n", st.st_uid, st.st_gid);// 格式化输出时间struct tm *ptm = localtime(&st.st_atim.tv_sec);printf("Access: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,ptm->tm_hour, ptm->tm_min, ptm->tm_sec);ptm = localtime(&st.st_mtim.tv_sec);printf("Modify: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,ptm->tm_hour, ptm->tm_min, ptm->tm_sec);ptm = localtime(&st.st_ctim.tv_sec);printf("Change: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,ptm->tm_hour, ptm->tm_min, ptm->tm_sec);return 0;
}
✅ 理想运行结果:
$ ./file_info hello.c File: hello.c Size: 1234 Blocks: 8 IO Blocks: 4096 regular Device: 2052 Inode: 123456 Links: 1 Access: (0644/-rw-r--r--) Uid: (1000) Gid: (1000) Access: 2025-08-14 11:30:12 Modify: 2025-08-14 11:29:45 Change: 2025-08-14 11:29:45
小结
-
文本文件操作:
- 标准 I/O(
fopen
,fprintf
等) - 文件 I/O(
open
,read
,write
,close
等系统调用)
- 标准 I/O(
-
目录文件操作:
opendir
:打开目录readdir
:逐项读取closedir
:关闭目录流stat
:获取文件详细信息
EDID 项目说明
背景知识
1. HDMI 是什么?
HDMI(High-Definition Multimedia Interface)是一种高清晰度多媒体接口,支持未压缩的高清视频和多声道音频同步传输。广泛用于电视、显示器、电脑等设备。版本从 1.0 到 2.1,带宽和功能逐步增强(如支持 8K、动态 HDR、VRR)。
2. EDID 是什么?各字段作用?
EDID(Extended Display Identification Data)是 VESA 制定的标准,存储在显示设备中,描述其能力:
- 基本信息:制造商、型号、序列号、生产日期。
- 显示参数:分辨率支持、刷新率、屏幕尺寸、纵横比、伽马值。
- 颜色特性:色度坐标、白点信息。
- 用途:让源设备(如显卡)自动匹配最佳输出模式。
3. EDID 与 HDMI 的关系?
HDMI 连接时,源设备通过 DDC 通道读取显示设备的 EDID,据此协商输出格式,实现“即插即用”。若 EDID 错误,可能导致无法显示、花屏、音频异常等问题。
4. EDID 数据错误的影响?
- 图像问题:黑屏、花屏、分辨率异常。
- 音频问题:无声、声道错乱。
- 功能受限:HDR、高帧率等功能无法启用。
- 兼容性下降:设备间无法正常通信。
5. 常用 EDID 工具
- EDID Inspector:解析 EDID 数据,可视化展示。
- MonitorInfo:支持查看与编辑 EDID。
- NVIDIA/AMD 显卡驱动工具:提供 EDID 管理界面。
项目需求说明
功能需求
- 显示 EDID 基础信息。
- 修改厂商名称。
- 修改产品名称。
- 修改物理地址(位于扩展块中)。
- 支持命令行短选项操作(如
-v
,-m
,-p
)。 - 支持退出机制。
可选扩展
- 删除或添加 VIC/DMT 时序。
- 添加 VSDB 数据块(用于 Atmos 支持)。
- 移植到 Tizen 平台。
- 成果展示(GUI 或日志输出)。