学习资料https://pan.baidu.com/s/1NZpC4NyH0OHGOPPbO9J10g?pwd=h9m2 提取码: h9m2
一. 文件I/O操作
1.1 文件I/O基本流程
- 文件复制过程:打开源文件A → 创建目标文件B → 读取A内容到内存 → 将内存内容写入B → 关闭A → 删除A → 关闭B。
- 关键系统调用:
creat、open、write、read、close、unlink、lseek、fcntl。
1.2 文件的创建(creat)
- 函数原型:
int fd = creat(char *filename, mode_t mode); - 头文件:
<fcntl.h> - 返回值:成功返回文件描述符,失败返回-1并设置
errno。 - 权限计算:实际权限 =
mode&~umask(新建文件掩码)。 - 示例:
creat("addressbook", 0765),若umask=0022,实际权限为0745(rwxr-x)。
1.3 文件的打开与关闭(open / close)
- open函数:
- 原型:
int fd = open(char *name, int how);或int fd = open(char *name, int how, mode_t mode); - 模式:
O_RDONLY、O_WRONLY、O_RDWR。 - 附加标志:
O_APPEND(追加)、O_TRUNC(清空)、O_CREAT(不存在则创建)、O_EXCL(配合O_CREAT确保新建)、O_NONBLOCK(非阻塞)、O_SYNC等。 - 权限宏:
S_IWUSR、S_IRGRP等,可通过位或组合。
- 原型:
- close函数:
int result = close(int fd);成功返回0,失败返回-1。
1.4 文件的读取与写入(read / write)
- read函数:
ssize_t numread = read(int fd, void *buf, size_t qty);- 返回实际读取字节数,可能小于
qty,出错返回-1。
- 返回实际读取字节数,可能小于
- write函数:
ssize_t result = write(int fd, void *buf, size_t amt);- 返回实际写入字节数,可能小于
amt(如磁盘满),出错返回-1。
- 返回实际写入字节数,可能小于
1.5 文件定位(lseek)
- 函数原型:
off_t oldpos = lseek(int fd, off_t dist, int base); - 基准位置:
SEEK_SET(文件头)、SEEK_CUR(当前位置)、SEEK_END(文件尾)。 - 用途:移动文件位置指针,实现随机读写或覆盖写入。
- 示例:先
lseek到指定偏移,再write覆盖原有内容。
1.6 文件删除(unlink)
- 函数原型:
int res = unlink(char *path); - 作用:删除文件路径链接,将i-node链接数减1,当链接数为0时真正删除文件。
1.7 文件描述符控制(fcntl)
- 函数原型:
int fcntl(int fd, int cmd, ...); - 常用
cmd:F_GETFL/F_SETFL:获取/设置文件状态标志(如追加、非阻塞)。F_DUPFD、F_GETFD、F_SETFD、F_GETOWN、F_SETOWN、F_GETLK、F_SETLK、F_SETLKW。
- 修改标志示例:
- 设置非阻塞:
flags |= O_NONBLOCK; fcntl(fd, F_SETFL, flags); - 取消追加模式:
flags &= ~O_APPEND; fcntl(fd, F_SETFL, flags);
- 设置非阻塞:
1.8 文件锁机制(记录锁)
- 锁类型:
- 建议性锁:需进程自觉检查,内核不强制执行。
- 强制性锁:内核强制阻止冲突操作。
- 记录锁(
fcntl实现):- 读锁(共享锁):
F_RDLCK,允许多个进程同时读取同一区域。 - 写锁(排斥锁):
F_WRLCK,同一区域只能有一个写锁。
- 读锁(共享锁):
- 锁操作结构
struct flock:l_type:锁类型(读/写/解锁)l_whence:偏移基准(SEEK_SET/CUR/END)l_start、l_len:锁定区域l_pid:持有锁的进程ID(F_GETLK返回)
- 锁冲突表:读锁允许其他读锁,阻塞写锁;写锁阻塞所有读写(阻塞模式)或返回
EAGAIN(非阻塞)。
二. 目录
2.1 目录结构
- Linux以树形层次结构组织文件和目录,根目录为
/。 - 每个目录下可包含子目录和文件,同一目录下不允许同名文件,但不同目录下可以有同名文件(通过绝对路径区分)。
- 绝对路径:从根目录
/开始到目标文件的完整路径。 - 相对路径:从当前工作目录开始,不包含根目录。
- 特殊目录项:
.:表示当前目录..:表示上级目录
- 目录内容永远不会为空,始终包含
.和..。
2.2 ext2文件系统基本结构
ext2是Linux典型文件系统,其磁盘布局如下:
| 组成部分 | 说明 |
|---|---|
| Boot Block(引导块) | 大小1KB,存储分区信息和启动代码,文件系统不使用 |
| Block Group(块组) | 将数据块分组管理,每个块组包含以下内容 |
每个块组结构:
Super Block(超级块):存储整个文件系统信息(inode总数、块总数、空闲块数、时间戳等),每个块组有备份,内存中只加载一次。Group Descriptor Table(组描述符表):包含每个块组的描述符(块位图地址、inode位图地址、inode表地址、空闲块/inode数、目录数等)。Block Bitmap(块位图):用位图记录数据块使用情况(1=已占用,0=空闲),占用一个数据块。Inode Bitmap(inode位图):用位图记录inode使用情况,占用一个数据块。Inode Table(inode表):存储该组所有inode,每个inode描述一个文件/目录的元数据(权限、大小、时间戳、链接数、数据块指针等)。- Data Blocks(数据块):
- 普通文件:存储文件实际数据
- 目录文件:存储子目录/文件的名称及其对应的
inode号
inode主要内容:
- 文件类型与权限(
i_mode) - 所有者UID/GID(
i_uid,i_gid) - 文件大小、时间戳(访问/创建/修改/删除)
- 链接数(
i_link_count) - 数据块指针数组(
i_block[])
2.3 文件创建与读取过程(ext2)
2.3.1 创建文件
创建creat("a.txt",0744)的步骤:
- 从
inode表中分配空闲inode,存储文件属性(权限、时间等)。 - 从数据块中分配空闲块,写*入数据,将块地址记录到
inode中。 - 更新
inode位图和块位图,将对应位置标记为已使用。 - 将文件名与
inode号作为目录项,存入所属目录的数据块中。
2.3.2 读取文件
读取more a.txt的步骤:
- 在当前目录中查找
a.txt的目录项,获取其inode号。 - 根据
inode号读取inode信息,获取数据块地址。 - 从数据块读取数据到内核缓冲区,再复制到用户空间。
2.4 目录相关系统调用
| 函数 | 头文件 | 功能 |
|---|---|---|
opendir | <dirent.h> | 打开目录,返回DIR*指针 |
readdir | <dirent.h> | 读取目录项,返回struct dirent*,包含文件名和inode号 |
closedir | <dirent.h> | 关闭目录流 |
telldir | <dirent.h> | 返回当前目录流位置 |
seekdir | <dirent.h> | 设置目录流读取位置 |
rewinddir | <dirent.h> | 重置目录流到起始位置 |
mkdir | <sys/stat.h> | 创建目录(需指定权限) |
rmdir | <unistd.h> | 删除空目录 |
chdir | <unistd.h> | 改变当前进程的工作目录 |
rename | <unistd.h> | 重命名或移动文件/目录(不复制数据,仅移动目录项) |
getcwd | <unistd.h> | 获取当前工作目录的绝对路径 |
2.4.1 目录读取示例:
- 使用
opendir打开目录,循环调用readdir读取每个目录项,打印文件名,最后closedir关闭。 - 输出结果包括
.、..和所有子目录/文件名。
2.4.2 重命名原理:
rename("a.txt", "b.txt"):将目录项从(inode, "a.txt")改为(inode, "b.txt"),数据本身不移动。rename("a.txt", "/b.txt"):将目录项移动到上级目录并改名。
三. 文件或目录属性
3.1 获取文件或目录属性
3.1.1 系统调用
| 函数 | 头文件 | 功能 |
|---|---|---|
stat | <sys/stat.h> | 通过文件路径获取属性,若为符号链接则跟踪到目标文件 |
lstat | <sys/stat.h> | 与stat类似,但不跟踪符号链接,获取链接本身属性 |
fstat | <sys/stat.h> | 通过文件描述符获取属性 |
原型:
intstat(constchar*path,structstat*buf);intlstat(constchar*restrict path,structstat*restrict buf);intfstat(intfildes,structstat*buf);- 成功返回0,失败返回-1并设置
errno。
3.1.2struct stat结构体主要成员
| 成员 | 类型 | 说明 |
|---|---|---|
st_dev | dev_t | 包含该文件的设备ID |
st_ino | ino_t | inode号 |
st_mode | mode_t | 文件类型与权限模式(16位) |
st_nlink | nlink_t | 硬链接数 |
st_uid | uid_t | 所有者用户ID |
st_gid | gid_t | 所有者组ID |
st_size | off_t | 文件大小(字节) |
st_atime | time_t | 最近访问时间 |
st_mtime | time_t | 最近数据修改时间 |
st_ctime | time_t | 最近状态改变时间 |
st_blksize | blksize_t | 文件系统块大小 |
st_blocks | blkcnt_t | 分配的数据块数 |
3.2 文件或目录模式(st_mode)
3.2.1 模式结构(16位)
- 最高4位:文件类型
- 中间3位:特殊标志(如setuid等,本部分未展开)
- 最低9位:权限位(所有者/组/其他用户的读、写、执行)
3.2.2 文件类型(最高4位)
| 二进制 | 八进制常量 | 宏(S_IFMT) | 含义 |
|---|---|---|---|
| 0100 | 0040000 | S_IFDIR | 目录 |
| 0010 | 0020000 | S_IFCHR | 字符设备 |
| 0110 | 0060000 | S_IFBLK | 块设备 |
| 1000 | 0100000 | S_IFREG | 普通文件 |
| 1010 | 0120000 | S_IFLNK | 符号链接 |
| 1100 | 0140000 | S_IFSOCK | Socket |
| 0001 | 0010000 | S_IFIFO | 命名管道(FIFO) |
类型掩码:
S_IFMT = 0170000(二进制 1111000000000000)判断方法:
if((st_mode&S_IFMT)==S_IFREG)// 普通文件便捷宏:
S_ISDIR(mode)// 目录?S_ISREG(mode)// 普通文件?S_ISCHR(mode)// 字符设备?S_ISBLK(mode)// 块设备?S_ISFIFO(mode)// FIFO?S_ISLNK(mode)// 符号链接?
3.2.3 权限位(最低9位)
| 宏 | 八进制 | 含义 |
|---|---|---|
S_IRUSR | 0000400 | 所有者读 |
S_IWUSR | 0000200 | 所有者写 |
S_IXUSR | 0000100 | 所有者执行 |
S_IRGRP | 0000040 | 组读 |
S_IWGRP | 0000020 | 组写 |
S_IXGRP | 0000010 | 组执行 |
S_IROTH | 0000004 | 其他读 |
S_IWOTH | 0000002 | 其他写 |
S_IXOTH | 0000001 | 其他执行 |
- 组权限 = 所有者权限 >> 3,其他权限 = 组权限 >> 3。
3.3 链接(硬链接与符号链接)
3.3.1 硬链接
- 本质:多个文件名指向同一个
inode,共享同一数据块。 - 特性:
- 删除源文件不影响其他硬链接(仅链接数减1)。
- 不能跨文件系统,不能对目录创建硬链接。
- 所有硬链接文件属性(大小、权限、
inode号)相同。
- 创建命令:
ln src dest - 系统调用:
link(const char *src, const char *dest)
3.3.2 符号链接(软链接)
- 本质:是一个独立文件,存储指向目标文件的路径字符串。
- 特性:
- 有自己的
inode和权限(通常为lrwxrwxrwx)。 - 读写操作实际作用于目标文件。
- 删除目标文件后,符号链接成为“悬空链接”,访问报错。
- 可跨文件系统,可指向目录。
- 有自己的
- 创建命令:
ln -s src dest - 系统调用:
symlink(const char *src, const char *dest)
3.3.3 关键区别
| 比较项 | 硬链接 | 符号链接 |
|---|---|---|
inode | 相同 | 不同 |
| 数据块 | 共享 | 独立(存储路径) |
| 删除源文件 | 数据仍存在 | 链接失效 |
| 跨文件系统 | 不支持 | 支持 |
| 对目录 | 不支持 | 支持 |
stat行为 | 跟踪到目标 | 跟踪到目标 |
lstat行为 | 获取链接本身 | 获取链接本身 |
3.4 修改文件属性
3.4.1 更改所有者/组 —chown
#include<unistd.h>intchown(constchar*path,uid_towner,gid_tgroup);- 示例:
chown("file.txt", 200, 40);// 设所有者200,组40 -1表示不改变该项。
3.4.2 更改权限模式 —chmod
#include<sys/stat.h>intchmod(constchar*path,mode_tmode);- 直接设置新权限,不受
umask影响。 - 示例:
chmod("A.txt", 0777);// 所有用户读写执行
3.4.3 更改访问/修改时间 —utime
#include<utime.h>intutime(constchar*path,conststructutimbuf*times);times == NULL:设置为当前时间。struct utimbuf:structutimbuf{time_tactime;// 访问时间time_tmodtime;// 修改时间};
四. 标准文件I/O
4.1 主要函数
| 函数 | 头文件 | 功能 |
|---|---|---|
fopen | <stdio.h> | 打开文件,返回FILE*流指针 |
fread | <stdio.h> | 从流中读取数据 |
fwrite | <stdio.h> | 向流中写入数据 |
fseek | <stdio.h> | 移动文件位置指针 |
ftell | <stdio.h> | 获取当前文件位置 |
fclose | <stdio.h> | 关闭文件流 |
函数原型:
FILE*fopen(constchar*restrict filename,constchar*restrict mode);size_tfread(void*restrict ptr,size_tsize,size_tnitems,FILE*restrict stream);intfseek(FILE*stream,longoffset,intwhence);longftell(FILE*stream);size_tfwrite(constvoid*restrict ptr,size_tsize,size_tnitems,FILE*restrict stream);intfclose(FILE*stream);4.2 与系统调用的关系
- 标准I/O函数底层依赖系统调用(如
read、write、open等)。 - 标准I/O是带用户态缓冲的,而系统调用(如
read/write)仅依赖内核缓冲区。
4.3 缓冲机制对比
1. 标准I/O(用户态缓冲)
- 数据先写入用户空间缓冲区。
- 当缓冲区满或显式刷新(如
fflush)时,才调用系统调用将数据写入内核。 - 读操作优先从用户缓冲区取数据,若缓冲区为空则调用系统调用从内核读取。
2. 系统调用(内核态缓冲)
- 用户空间无缓冲。
- 内核中有缓冲区,数据先进入内核缓冲区,再由内核适时写入磁盘。
3. 缓冲对性能的影响
- 减少系统调用次数可提高性能(每次系统调用涉及用户态/内核态切换,开销较大)。
- 例4-13:复制5MB文件,缓冲区越大,系统调用次数越少,执行时间越短。
- 示例:
BUFFERSIZE=100需10次read,BUFFERSIZE=200需5次read。
- 示例:
五. 系统调用错误处理 —perror
5.1 背景
- 系统调用失败时返回-1,并设置全局变量
errno(错误编号)。 - 错误原因众多,手动用
if/switch转换繁琐。
5.2perror函数
#include<errno.h>voidperror(constchar*s);- 功能:将
errno对应的错误描述输出到标准错误(stderr)。 - 输出格式:先打印参数
s字符串,再输出冒号和错误原因。 - 注意事项:必须在发生错误的系统调用之后立即调用,中间不能有其他系统调用,否则
errno可能被覆盖。
5.3 示例
#include<stdio.h>#include<errno.h>#include<unistd.h>intmain(void){intfd;fd=open("/root/noexistfile",O_WRONLY);if(fd<0){perror("/root/noexistfile");return0;}}- 若文件不存在,输出类似于:
/root/noexistfile: No such file or directory
5.4 关键概念总结
| 项目 | 标准I/O(C库) | 系统调用 |
|---|---|---|
| 缓冲位置 | 用户空间 | 内核空间 |
| 用户态/内核态切换 | 较少(批量操作) | 较多(每次调用) |
| 适用场景 | 高级、可移植性要求高 | 低级、对性能精细控制 |
| 文件操作函数 | fopen/fread/fwrite等 | open/read/write等 |
| 错误处理 | 通过ferror()或检查返回值 | 检查返回值+errno/perror |