当前位置: 首页 > news >正文

Day20 Linux 文件 I/O、目录操作及文件链接与 EDID

day20 Linux 文件 I/O、目录操作及文件链接与 EDID


文件 I/O —— 系统调用函数

操作对象

  • 文件描述符(File Descriptor):用于标识一个已打开的文件,是整数(非负),由系统调用如 open 返回。

具体操作

  • 打开open
  • 读写readwrite
  • 定位lseek
    • 注意:
      1. 并非所有文件类型都支持定位(如管道、FIFO)。
      2. 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 文件组成结构

一个文件由三部分构成:

  1. 目录项(struct dirent):包含文件名和对应的 inode 编号。
  2. inode 表:存储文件元信息(如大小、权限、类型、硬链接数、时间戳等)。
  3. 数据块:实际文件内容存储位置。
文件访问流程:
  1. 根据文件名查找目录项,获取 inode 编号。
  2. 通过 inode 编号访问 inode 表,获取文件属性和数据块地址。
  3. 读取数据块内容。

硬链接(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_IFIFOFIFO(命名管道)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 等系统调用)
  • 目录文件操作

    • 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 管理界面。

项目需求说明

功能需求
  1. 显示 EDID 基础信息。
  2. 修改厂商名称。
  3. 修改产品名称。
  4. 修改物理地址(位于扩展块中)。
  5. 支持命令行短选项操作(如 -v, -m, -p)。
  6. 支持退出机制。
可选扩展
  • 删除或添加 VIC/DMT 时序。
  • 添加 VSDB 数据块(用于 Atmos 支持)。
  • 移植到 Tizen 平台。
  • 成果展示(GUI 或日志输出)。

http://www.dtcms.com/a/330610.html

相关文章:

  • 小杰python(six day)——网络编程
  • 前端Vite介绍(现代化前端构建工具,由尤雨溪开发,旨在显著提升开发体验和构建效率)ES模块(ESM)、与传统Webpack对比、Rollup打包
  • 20250814 最小生成树总结
  • Vue 3 + TypeScript:package.json 示例 / 详细注释说明
  • Linux 上手 UDP Socket 程序编写(含完整具体demo)
  • 如何通过WiFi将文件从安卓设备传输到电脑
  • 计算机视觉(opencv)实战二——图像边界扩展cv2.copyMakeBorder()
  • 机器学习 - Kaggle项目实践(3)Digit Recognizer 手写数字识别
  • 分布式事务、锁、链路追踪
  • 读取数据excel
  • 高效TypeScript开发:VSCode终极配置指南
  • 待办事项小程序开发
  • (第十六期)HTML布局标签详解:div与span的深度解析
  • 【读代码】深度解析 context-engineering-intro:开源上下文工程实践原理与应用
  • 群晖 NAS 影音访问:通过 cpolar 内网穿透服务实现 Nastool 远程管理
  • java集合 之 多列集合
  • Python/Node.js 调用taobao API:构建实时商品详情数据采集服务
  • 使用HalconDotNet实现异步多相机采集与实时处理
  • Mybatis学习笔记(六)
  • 桥接模式C++
  • 成都国际影像产业园:接重庆五一职院实训就业考察
  • [系统架构设计师]软件工程基础知识(五)
  • 系统思考:转型困扰与突破
  • 【软考中级网络工程师】知识点之入侵检测深度剖析
  • 开源安全云盘存储:Hoodik 实现端到端数据加密,Docker快速搭建
  • 分享一个基于Hadoop+spark的超市销售数据分析与可视化系统,超市顾客消费行为分析系统的设计与实现
  • Java应用架构实战指南:主流模式解析与Spring落地实践
  • 从零开始学Python之数据结构(字符串以及数字)
  • Java 大视界 -- Java 大数据机器学习模型在金融欺诈检测与防范策略制定中的应用(397)
  • 工业一体机5G通讯IC/ID刷卡让MES系统管理更智能