☰
30天速通Linux 第四章 文件I/O操作
2026/10/10 2:33:17 网站建设 项目流程

学习资料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)的步骤:

  1. 从inode表中分配空闲inode,存储文件属性(权限、时间等)。
  2. 从数据块中分配空闲块,写*入数据,将块地址记录到inode中。
  3. 更新inode位图和块位图,将对应位置标记为已使用。
  4. 将文件名与inode号作为目录项,存入所属目录的数据块中。

2.3.2 读取文件

读取more a.txt的步骤:

  1. 在当前目录中查找a.txt的目录项,获取其inode号。
  2. 根据inode号读取inode信息,获取数据块地址。
  3. 从数据块读取数据到内核缓冲区,再复制到用户空间。

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_devdev_t包含该文件的设备ID
st_inoino_tinode号
st_modemode_t文件类型与权限模式(16位)
st_nlinknlink_t硬链接数
st_uiduid_t所有者用户ID
st_gidgid_t所有者组ID
st_sizeoff_t文件大小(字节)
st_atimetime_t最近访问时间
st_mtimetime_t最近数据修改时间
st_ctimetime_t最近状态改变时间
st_blksizeblksize_t文件系统块大小
st_blocksblkcnt_t分配的数据块数

3.2 文件或目录模式(st_mode)

3.2.1 模式结构(16位)

  • 最高4位:文件类型
  • 中间3位:特殊标志(如setuid等,本部分未展开)
  • 最低9位:权限位(所有者/组/其他用户的读、写、执行)

3.2.2 文件类型(最高4位)

二进制八进制常量宏(S_IFMT)含义
01000040000S_IFDIR目录
00100020000S_IFCHR字符设备
01100060000S_IFBLK块设备
10000100000S_IFREG普通文件
10100120000S_IFLNK符号链接
11000140000S_IFSOCKSocket
00010010000S_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_IRUSR0000400所有者读
S_IWUSR0000200所有者写
S_IXUSR0000100所有者执行
S_IRGRP0000040组读
S_IWGRP0000020组写
S_IXGRP0000010组执行
S_IROTH0000004其他读
S_IWOTH0000002其他写
S_IXOTH0000001其他执行
  • 组权限 = 所有者权限 >> 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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询