Linux C 应用编程 学习Day1-2(文件IO基础)

张开发
2026/4/13 21:19:38 15 分钟阅读

分享文章

Linux C 应用编程 学习Day1-2(文件IO基础)
一切从头开始打牢基础1. 应用编程概念首先插入一张内核系统调用与应用程序的关系图进一步探讨应用编程与裸机编程、驱动编程有什么区别裸机编程一般把没有操作系统支持的编程环境称为裸机编程环境譬如单片机上的编程开发编写直接在硬件上运行的程序没有操作系统支持。Linux 驱动编程指的是基于内核驱动框架开发驱动程序驱动开发工程师通过调用 Linux 内核提供的接口完成设备驱动的注册驱动程序负责底层硬件操作相关逻辑。Linux 应用编程系统编程则指的是基于 Linux 操作系统的应用编程在应用程序中通过调用系统调用 API 完成应用程序的功能和逻辑应用程序运行于操作系统之上。操作系统下有两种不同的状态内核态和用户态应用程序运行在用户态、而内核则运行在内核态。同时注意驱动程序属于内核的一部分当操作系统启动的时候会加载驱动程序。有操作系统支持的情况下应用程序处于用户态而驱动程序处于内核态。1.库函数是属于应用层而系统调用是内核提供给应用层的编程接口属于系统内核的一部分2.库函数运行在用户空间调用系统调用会由用户空间用户态陷入到内核空间内核态3.库函数通常是有缓存的而系统调用是无缓存的所以在性能、效率上库函数通常要优于系统调用4.可移植性库函数相比于系统调用具有更好的可移植性通常对于不同的操作系统其内核向应用层提供的系统调用往往都是不同譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的而对于 C 语言库函数来说由于很多操作系统都实现了 C 语言库 C 语言库在不同的操作系统之间其接口定义几乎是一样的所以库函数在不同操作系统之间相比于系统调用具有更好的可移植性。二、文件IO基础2.1 IO基础了解文件 I/O 指的是对文件的输入/输出操作说白了就是对文件的读写操作Linux 下一切皆文件文件作为 Linux 系统设计思想的核心理念在 Linux 系统下显得尤为重要所以对文件的 I/O 操作作既是基础也是最重要的部分。一个通用的 IO 模型通常包括打开文件、读写文件、关闭文件这些基本操作主要涉及到 4 个函数 open()、 read()、 write()以及 close()。#include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h int main(void) { char buff[1024]; int fd1, fd2; int ret; /* 打开源文件 src_file(只读方式) */ fd1 open(./src_file, O_RDONLY); if (-1 fd1) return fd1; /* 打开目标文件 dest_file(只写方式) */ fd2 open(./dest_file, O_WRONLY); if (-1 fd2) { ret fd2; goto out1; } /* 读取源文件 1KB 数据到 buff 中 */ ret read(fd1, buff, sizeof(buff)); if (-1 ret) goto out2; /* 将 buff 中的数据写入目标文件 */ ret write(fd2, buff, sizeof(buff)); if (-1 ret) goto out2; ret 0; out2: /* 关闭目标文件 */ close(fd2); out1: /* 关闭源文件 */ close(fd1); return ret; }从源文件 src_file 中读取 1KB 数据然后将其写入到目标文件 dest_file 中2.2 文件描述符调用 open 函数会有一个返回值在 open 函数执行成功的情况下会返回一个非负整数该返回值就是一个文件描述符file descriptor。对于 Linux 内核而言所有打开的文件都会通过文件描述符进行索引文件描述符就是 Linux 给打开的文件发的专属门牌号所有文件操作都靠这个号来定位系统还限制了每个进程最多能拿多少个门牌号防止占满内存。三个默认的「标准文件描述符」每个进程一启动系统就自动给它开了 3 个文件对应 3 个固定编号0标准输入stdin→ 对应键盘你用scanf()读输入就是用这个编号1标准输出stdout→ 对应屏幕你用printf()打印内容就是用这个编号2标准错误stderr→ 对应屏幕专门用来输出错误信息2.3 open 打开文件在 Linux 系统中要操作一个文件需要先打开该文件得到文件描述符然后再对文件进行相应的读写操作或其他操作最后在关闭该文件 open 函数用于打开文件当然除了打开已经存在的文件之外还可以创建一个新的文件#include sys/types.h #include sys/stat.h #include fcntl.h int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);在 Linux 系统下可以通过 man 命令也叫 man 手册来查看某一个 Linux 系统调用的帮助信息相当于帮助手册。注意man 命令后面跟着两个参数数字 2 表示系统调用 man 命令除了可以查看系统调用的帮助信息外还可以查看 Linux 命令对应数字 1以及标准 C 库函数对应数字 3所对应的帮助信息最后一个参数 open 表示需要查看的系统调用函数名。从图中可知在应用程序中使用 open 函数时需要包含 3 个头文件“ #include sys/types.h”、“#include sys/stat.h”、“#include fcntl.h”。flags 参数可以用位或运算将多个标志进行组合mode此参数用于指定新建文件的访问权限只有当 flags 参数中包含 O_CREAT 或O_TMPFILE标志时才有效O_TMPFILE 标志用于创建一个临时文件。权限对于文件来说是一个很重要的属性那么在 Linux系统中我们可以通过touch 命令新建一个文件此时文件会有一个默认的权限如果需要修改文件权限可通过 chmod 命令对文件权限进行修改譬如在 Linux 系统下我们可以使用ls -l命令来查看到文件所对应的权限。当我们调用 open 函数去新建一个文件时也需要指定该文件的权限而 mode 参数便用于指定此文件的权限接下来看看我们该如何通过 mode 参数来表示文件的权限首先 mode 参数的类型是 mode_t这是一个 u32 无符号整形数据权限表示方法如下所示关于什么是文件所属用户、同组用户以及其他用户这些都是 Linux 操作系统相关的基础知识大家都理解这些概念 3 个 bit 位中按照 rwx 顺序来分配权限位特殊权限除外最高位权值为 4表示读权限为 1 时表示具有读权限为 0 时没有读权限中间位权值为 2表示写权限为 1 时表示具有写权限为 0 时没有写权限最低位权值为 1表示执行权限为 1 时表示具有可执行权限为 0 时没有执行权限。最高权限表示方法 111111111二进制表示、 777八进制表示、 511十进制表示最高权限这里意味着所有用户对此文件都具有读权限、写权限以及执行权限。111000000二进制表示表示文件所属者具有读、写、执行权限而同组用户和其他用户不具有任何权限100100100二进制表示表示文件所属者、同组用户以及其他用户都具有读权限但都没有写、执行权限Tips open 函数 O_RDONLY、 O_WRONLY 以及 O_RDWR 这三个标志表示以什么方式去打开文件(3) 使用 open 函数打开一个指定的文件譬如/home/dengtao/hello使用可读可写方式,如果该文件是一个符号链接文件则不对其进行解引用直接返回错误2.4 write 写文件调用 write 函数可向打开的文件写入数据其函数原型如下所示可通过man 2 write查看#include unistd.h ssize_t write(int fd, const void *buf, size_t count);首先使用 write 函数需要先包含 unistd.h 头文件函数参数和返回值含义如下fd文件描述符。关于文件描述符前面已经给大家进行了简单地讲解这里不再重述我们需要将进行写操作的文件所对应的文件描述符传递给 write 函数。buf指定写入数据对应的缓冲区。count指定写入的字节数返回值如果成功将返回写入的字节数0 表示未写入任何字节如果此数字小于 count 参数这不是错误譬如磁盘空间已满可能会发生这种情况如果写入出错则返回-1。不管读还是写都必须知道从文件哪个地方开始这个 “开始位置” 就叫位置偏移量。默认从 0 开始文件开头。你读多少、写多少位置就自动往后挪多少。比如现在在 1000读写 500 字节位置就变成 1500。读写从当前位置开始 → 操作完自动往后挪 → 位置 旧位置 读写的字节数2.5 read 读文件调用 read 函数可从打开的文件中读取数据其函数原型如下所示可通过man 2 read查看#include unistd.h ssize_t read(int fd, void *buf, size_t count)首先使用 read 函数需要先包含 unistd.h 头文件函数参数和返回值含义如下fd文件描述符。与 write 函数的 fd 参数意义相同。buf指定用于存储读取数据的缓冲区。count:指定需要读取的数字返回值如果读取成功将返回读取到的字节数实际读取到的字节数可能会小于 count 参数指定的字节数也有可能会为 0譬如进行读操作时当前文件位置偏移量已经到了文件末尾。实际读取到的字节数少于要求读取的字节数譬如在到达文件末尾之前有 30 个字节数据而要求读取 100 个字节则 read 读取成功只能返回 30而下一次再调用 read 读它将返回 0文件末尾注意补充一点指针知识理解*有两个作用 解指针 和 声明指针*在这里不是 “取内容”而是用来声明「指针类型」表示这个参数是一个「内存地址」而不是一个普通变量。read要做的事从文件里读数据把读到的字节放到你指定的一块内存里。那怎么告诉系统 “放到哪块内存”不能直接传一块内存本身比如一个数组C 语言里函数传参只能传「值」只能传这块内存的起始地址系统拿到地址就知道往哪写数据了所以buf参数的类型必须是指针也就是void *bufvoid *是「通用指针类型」可以接收任意类型的地址比如char[]、int[]的首地址buf是指针变量存的是你给的缓冲区的内存地址char buf[1024]; buf 地址数组开头 *buf 内容buf[0] buf[0] 地址和 buf 完全一样 read(fd, buf, 100); # 这是正确写法 buf 是数组名代表数组首元素的地址类型是 char*ssize_t read(int fd, void *buf, size_t count);这句话中有void *bufvoid *buf最晕的地方void *buf我用最简单到不能再简单的话讲意思我需要一块内存的地址*→ 代表这是指针地址buf→ 这块内存的名字缓冲区void *→万能地址类型能接收任何内存地址所以void *buf给我一个地址我要把数据放到这里2.6 close 关闭文件可调用 close 函数关闭一个已经打开的文件其函数原型如下所示可通过man 2 close查看#include unistd.h int close(int fd);首先使用 close 函数需要先包含 unistd.h 头文件当我们对文件进行 IO 操作完成之后后续不再对文件进行操作时需要将文件关闭。函数参数和返回值含义如下fd文件描述符需要关闭的文件所对应的文件描述符。返回值如果成功返回 0如果失败则返回-1除了使用 close 函数显式关闭文件之外在 Linux 系统中当一个进程终止时内核会自动关闭它打开的所有文件也就是说在我们的程序中打开了文件如果程序终止退出时没有关闭打开的文件那么内核会自动将程序中打开的文件关闭。很多程序都利用了这一功能而不显式地用 close 关闭打开的文件。显式关闭不再需要的文件描述符往往是良好的编程习惯会使代码在后续修改时更具有可读性也更可靠进而言之文件描述符是有限资源当不再需要时必须将其释放、归还于系统。2.7 lseek对于每个打开的文件系统都会记录它的读写位置偏移量我们也把这个读写位置偏移量称为读写偏移量记录了文件当前的读写位置当调用 read()或 write()函数对文件进行读写操作时就会从当前读写位置偏移量开始进行数据读写。读写偏移量用于指示 read()或 write()函数操作时文件的起始位置会以相对于文件头部的位置偏移量来表示文件第一个字节数据的位置偏移量为 0。当打开文件时会将读写偏移量设置为指向文件开始位置处以后每次调用 read()、 write()将自动对其进行调整以指向已读或已写数据后的下一字节因此连续的调用 read()和 write()函数将使得读写按顺序递增对文件进行操作。我们先来看看 lseek 函数的原型如下所示可通过man 2 lseek查看。#include sys/types.h #include unistd.h off_t lseek(int fd, off_t offset, int whence);首先调用 lseek 函数需要包含sys/types.h和unistd.h两个头文件。函数参数和返回值含义如下fd 文件描述符。offset 偏移量以字节为单位。whence 用于定义参数 offset 偏移量对应的参考值该参数为下列其中一种宏定义SEEK_SET读写偏移量将指向 offset 字节位置处从文件头部开始算SEEK_CUR读写偏移量将指向当前位置偏移量 offset 字节位置处 offset 可以为正、也可以为负如果是正数表示往后偏移如果是负数则表示往前偏移SEEK_END读写偏移量将指向文件末尾 offset 字节位置处同样 offset 可以为正、也可以为负如果是正数表示往后偏移、如果是负数则表示往前偏移。返回值成功将返回从文件头部开始算起的位置偏移量字节为单位也就是当前的读写位置发生错误将返回-1。(1)将读写位置移动到文件开头处off_t off lseek(fd, 0, SEEK_SET); if (-1 off) return -1;(2)将读写位置移动到文件末尾off_t off lseek(fd, 0, SEEK_END); if (-1 off) return -1;(3)将读写位置移动到偏移文件开头 100 个字节处off_t off lseek(fd, 100, SEEK_SET); if (-1 off) return -1;(4)获取当前读写位置偏移量off_t off lseek(fd, 0, SEEK_CUR); if (-1 off) return -1;函数执行成功将返回文件当前读写位置。

更多文章