30天速通Linux 第四章 文件I/O操作
学习资料https://pan.baidu.com/s/1NZpC4NyH0OHGOPPbO9J10g?pwdh9m2 提取码: 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)若umask0022实际权限为0745rwxr-x。1.3 文件的打开与关闭open / closeopen函数原型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 / writeread函数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, ...);常用cmdF_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 flockl_type锁类型读/写/解锁l_whence偏移基准SEEK_SET/CUR/ENDl_start、l_len锁定区域l_pid持有锁的进程IDF_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 Bitmapinode位图用位图记录inode使用情况占用一个数据块。Inode Tableinode表存储该组所有inode每个inode描述一个文件/目录的元数据权限、大小、时间戳、链接数、数据块指针等。Data Blocks数据块普通文件存储文件实际数据目录文件存储子目录/文件的名称及其对应的inode号inode主要内容文件类型与权限i_mode所有者UID/GIDi_uid,i_gid文件大小、时间戳访问/创建/修改/删除链接数i_link_count数据块指针数组i_block[]2.3 文件创建与读取过程ext22.3.1 创建文件创建creat(a.txt,0744)的步骤从inode表中分配空闲inode存储文件属性权限、时间等。从数据块中分配空闲块写*入数据将块地址记录到inode中。更新inode位图和块位图将对应位置标记为已使用。将文件名与inode号作为目录项存入所属目录的数据块中。2.3.2 读取文件读取more a.txt的步骤在当前目录中查找a.txt的目录项获取其inode号。根据inode号读取inode信息获取数据块地址。从数据块读取数据到内核缓冲区再复制到用户空间。2.4 目录相关系统调用函数头文件功能opendirdirent.h打开目录返回DIR*指针readdirdirent.h读取目录项返回struct dirent*包含文件名和inode号closedirdirent.h关闭目录流telldirdirent.h返回当前目录流位置seekdirdirent.h设置目录流读取位置rewinddirdirent.h重置目录流到起始位置mkdirsys/stat.h创建目录需指定权限rmdirunistd.h删除空目录chdirunistd.h改变当前进程的工作目录renameunistd.h重命名或移动文件/目录不复制数据仅移动目录项getcwdunistd.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 系统调用函数头文件功能statsys/stat.h通过文件路径获取属性若为符号链接则跟踪到目标文件lstatsys/stat.h与stat类似但不跟踪符号链接获取链接本身属性fstatsys/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包含该文件的设备IDst_inoino_tinode号st_modemode_t文件类型与权限模式16位st_nlinknlink_t硬链接数st_uiduid_t所有者用户IDst_gidgid_t所有者组IDst_sizeoff_t文件大小字节st_atimetime_t最近访问时间st_mtimetime_t最近数据修改时间st_ctimetime_t最近状态改变时间st_blksizeblksize_t文件系统块大小st_blocksblkcnt_t分配的数据块数3.2 文件或目录模式st_mode3.2.1 模式结构16位最高4位文件类型中间3位特殊标志如setuid等本部分未展开最低9位权限位所有者/组/其他用户的读、写、执行3.2.2 文件类型最高4位二进制八进制常量宏S_IFMT含义01000040000S_IFDIR目录00100020000S_IFCHR字符设备01100060000S_IFBLK块设备10000100000S_IFREG普通文件10100120000S_IFLNK符号链接11000140000S_IFSOCKSocket00010010000S_IFIFO命名管道FIFO类型掩码S_IFMT 0170000二进制 1111000000000000判断方法if((st_modeS_IFMT)S_IFREG)// 普通文件便捷宏S_ISDIR(mode)// 目录S_ISREG(mode)// 普通文件S_ISCHR(mode)// 字符设备S_ISBLK(mode)// 块设备S_ISFIFO(mode)// FIFOS_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#includeunistd.hintchown(constchar*path,uid_towner,gid_tgroup);示例chown(file.txt, 200, 40);// 设所有者200组40-1表示不改变该项。3.4.2 更改权限模式 —chmod#includesys/stat.hintchmod(constchar*path,mode_tmode);直接设置新权限不受umask影响。示例chmod(A.txt, 0777);// 所有用户读写执行3.4.3 更改访问/修改时间 —utime#includeutime.hintutime(constchar*path,conststructutimbuf*times);times NULL设置为当前时间。struct utimbufstructutimbuf{time_tactime;// 访问时间time_tmodtime;// 修改时间};四. 标准文件I/O4.1 主要函数函数头文件功能fopenstdio.h打开文件返回FILE*流指针freadstdio.h从流中读取数据fwritestdio.h向流中写入数据fseekstdio.h移动文件位置指针ftellstdio.h获取当前文件位置fclosestdio.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文件缓冲区越大系统调用次数越少执行时间越短。示例BUFFERSIZE100需10次readBUFFERSIZE200需5次read。五. 系统调用错误处理 —perror5.1 背景系统调用失败时返回-1并设置全局变量errno错误编号。错误原因众多手动用if/switch转换繁琐。5.2perror函数#includeerrno.hvoidperror(constchar*s);功能将errno对应的错误描述输出到标准错误stderr。输出格式先打印参数s字符串再输出冒号和错误原因。注意事项必须在发生错误的系统调用之后立即调用中间不能有其他系统调用否则errno可能被覆盖。5.3 示例#includestdio.h#includeerrno.h#includeunistd.hintmain(void){intfd;fdopen(/root/noexistfile,O_WRONLY);if(fd0){perror(/root/noexistfile);return0;}}若文件不存在输出类似于/root/noexistfile: No such file or directory5.4 关键概念总结项目标准I/OC库系统调用缓冲位置用户空间内核空间用户态/内核态切换较少批量操作较多每次调用适用场景高级、可移植性要求高低级、对性能精细控制文件操作函数fopen/fread/fwrite等open/read/write等错误处理通过ferror()或检查返回值检查返回值errno/perror