深入探究文件 I/O
深入探究文件 I/O 中涉及到的一些问题、原理以及所对应的解决方法,譬如 Linux 系统下文件是如何进行管理的、调用函数返回错误该如何处理、open 函数的 O_APPEND、O_TRUNC 标志以及等相关问题;
-
vim打开编辑的不是文件,是缓冲区
-
文件分两个部分存放
数据区:用于存储文件中的数据; inode 区:存放 inode table(inode 表);
Linux 文件管理方式
静态文件与 inode
静态文件
文件在没有被打开的情况下,以一种固定的形式进行存放在磁盘文件(磁盘、硬盘、U 盘等)系统中;
操作系统读取硬盘
而是一次性连续读取多个扇区,即一次性读取一个“块(文件存取的最小单位)”(block)
磁盘分区、格式化
数据区:用于存储文件中的数据; inode 区:存放 inode table(inode 表);
inode 表
每一个文件都必须对应一个 inode,inode 实质上是一个结构体;
结构体中有很多不同的元素,记录了文件不同信息,文件名并不是记录在 inode 中;
inode table 表本身也需要占用磁盘的存储空间;
打开文件系统过程
ls -i"命令查看文件的 inode 编号
-
系统找到这个文件名所对应的 inode 编号;
-
通过 inode 编号从 inode table 中找到对应的 inode 结构体;
-
根据 inode 结构体中记录的信息,确定文件数据所在的 block,并读出数据。
文件打开时的状态
读写对象
open 函数打开文件,内核申请一段内存(一段缓冲区),读写操作针对内存中对应动态文件进行相关的操作,而非磁盘中存放的静态文件;
读写同步机制
对动态文件进行读写操作后,内存中的动态文件和磁盘设备中的静态文件不同步,数据的同步工作由内核完成,内核会在之后将内存这份动态文件更新(同步)到磁盘设备中;
/*典例*/
打开一个大文件的时候会比较慢;
文档写了一半没保存,电脑掉电关机,重启电脑后,打开编写的文档,发现之前写的内容已经丢失。
不同步原因
磁盘、硬盘、U 盘等存储设备基本都是 Flash 块设备,块设备硬件本身有读写限制(块为单位)等特征;
内存可以按字节为单位来操作,而且可以随机操作任意地址数据;
所以对于操作系统来说,会先将磁盘中的静态文件读取到内存中进行缓存,读写操作都是针对这份动态文
件,而不是直接去操作磁盘中的静态文件,不但操作不灵活,效率也会下降很多,因为内存的读写速率远比
磁盘读写快得多。
函数返回错误的处理与 errno
Linux 系统下对常见的错误做了一个编号,每一个编号都代表着每一种不同的错误类型;
#include <errno.h>
strerror 函数
该函数可将对应的 errno 转换成适合我们查看的字符串信息;调用此函数需包含头文件<string.h>;
/*man 3 strerror*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void)
{
int fd;
/* 打开文件 */
fd = open("./test_file", O_RDONLY);
if (-1 == fd)
{
printf("Error: %s\n", strerror(errno));
return -1;
}
close(fd);
return 0;
}
直观的知道 open 函数执行的错误原因是文件不存在!
perror 函数
查看错误信息,一般用的最多的还是这个函数,
/*函数功能*/
此函数不需要传入 errno,函数内部会自己去获取 errno 变量的值;
调用此函数会直接将错误提示字符串打印出来,而不是返回字符串;
除此之外还可以在输出的错误提示字符串之前加入自己的打印信息;
/*函数原型*/
#include <stdio.h>
void perror(const char *s);
/*函数参数和返回值含义*/
s:在错误提示字符串信息之前,可加入自己的打印信息,也可不加,不加则传入空字符串即可;
返回值:void 无返回值。
/*示例代码 3.2.2 perror 测试代码*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int fd;
/* 打开文件 */
fd = open("./test_file", O_RDONLY);
if (-1 == fd)
{
perror("open error");
return -1;
}
close(fd);
return 0;
}
退出程序 exit()、-_Exit()、-_exit()
main 函数中使用 return 后返回,return 执行后把控制权交给调用函数,结束该进程。
_exit()
调用_exit()函数会清除其使用的内存空间,并销毁其在内核中的各种数据结构,关闭进程的所有文件描述符,并结束进程、将控制权交给操作系统。_exit()函数原型如下所示:
#include <unistd.h>
void _exit(int status);
调用函数需要传入status 状态标志,0 表示正常结束,若为其它值则表示检测到有错误发生;
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int fd;
/* 打开文件 */
fd = open("./test_file", O_RDONLY);
if (-1 == fd) {
perror("open error");
_exit(-1);
}
close(fd);
_exit(0);
}
_exit()和__Exit()两者等价,用法作用一样;
exit()
exit()是一个标准 C 库函数,该函数需要包含头文件<stdlib.h>,而_exit()和_Exit()是系统调用;
#include <stdlib.h>
void exit(int status);
其他用法一样;
空洞文件
概念
第一个实例其实也就是写入一个空洞文件,发现是乱码;
/*示例代码 3.4.1 空洞文件测试代码*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int fd;
int ret;
char buffer[1024];
int i;
/* 打开文件 */
fd = open("./hole_file", O_WRONLY | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == fd)
{
perror("open error");
exit(-1);
}
/* 将文件读写位置移动到偏移文件头 4096 个字节(4K)处 */
ret = lseek(fd, 4096, SEEK_SET);
if (-1 == ret)
{
perror("lseek error");
goto err;
}
/* 初始化 buffer 为 0xFF */
memset(buffer, 0xFF, sizeof(buffer));
/* 循环写入 4 次,每次写入 1K */
for (i = 0; i < 4; i++)
{
ret = write(fd, buffer, sizeof(buffer));
if (-1 == ret)
{
perror("write error");
goto err;
}
}
ret = 0;
err:
/* 关闭文件 */
close(fd);
exit(ret);
}
open 函数的 O_APPEND 和 O_TRUNC 标志
O_TRUNC
标志作用:调用 open 函数打开文件时,将文件原本的内容全部丢弃,文件大小变为 0;
/*示例代码 3.5.1 O_TRUNC 标志测试*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
/* 打开文件 */
fd = open("./test_file", O_WRONLY | O_TRUNC);
if (-1 == fd)
{
perror("open error");
exit(-1);
}
/* 关闭文件 */
close(fd);
exit(0);
}
O_APPEND
调用 open 函数打开文件时,写入数据都是从文件末尾开始;
使用了 O_APPEND 标志,再使用 lseek 函数失效;
???
当 open 函数同时携带了 O_APPEND 和 O_TRUNC 两个标志时会有什么作用?
多次打开同一文件
-
一个进程内多次 open 打开同一个文件,可多个不同的文件描述符fd,同理在关闭文件的时候也需要调用 close 依次关闭各个文件描述符;
-
一个进程内多次 open 打开同一个文件,在内存中并不会存在多份动态文件;
-
一个进程内多次 open 打开同一个文件,不同文件描述符所对应的读写位置偏移量相互独立;
-
多次打开同一文件进行读操作与 O_APPEND 标志,实现分别写与接续写
因为这两个文件描述符所对应的读写位置偏移量是相互独立的,所以是分别写;
使用 O_APPEND 标志来解决这个问题,也就是将分别写更改为接续写;
/*示例代码 3.6.3 多次打开同一个文件测试代码 3*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
unsigned char buffer1[4], buffer2[4];
int fd1, fd2;
int ret;
int i;
/* 创建新文件 test_file 并打开 */
fd1 = open("./test_file", O_RDWR | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == fd1) {
perror("open error");
exit(-1);
}
/* 再次打开 test_file 文件 */
fd2 = open("./test_file", O_RDWR);
if (-1 == fd2) {
perror("open error");
ret = -1;
goto err1;
}
/* buffer 数据初始化 */
buffer1[0] = 0x11;
buffer1[1] = 0x22;
buffer1[2] = 0x33;
buffer1[3] = 0x44;
buffer2[0] = 0xAA;
buffer2[1] = 0xBB;
buffer2[2] = 0xCC;
buffer2[3] = 0xDD;
/* 循环写入数据 */
for (i = 0; i < 4; i++) {
ret = write(fd1, buffer1, sizeof(buffer1));
if (-1 == ret) {
perror("write error");
goto err2;
}
ret = write(fd2, buffer2, sizeof(buffer2));
if (-1 == ret) {
perror("write error");
goto err2;
}
}
/* 将读写位置偏移量移动到文件头 */
ret = lseek(fd1, 0, SEEK_SET);
if (-1 == ret) {
perror("lseek error");
goto err2;
}
/* 读取数据 */
for (i = 0; i < 8; i++) {
ret = read(fd1, buffer1, sizeof(buffer1));
if (-1 == ret) {
perror("read error");
goto err2;
}
printf("%x%x%x%x", buffer1[0], buffer1[1],
buffer1[2], buffer1[3]);
}
printf("\n");
ret = 0;
err2:
close(fd2);
err1:
/* 关闭文件 */
close(fd1);
exit(ret);
}
分别写:
接续写:
仅在示例代码 3.6.3 的基础上,open 函数添加了 O_APPEND 标志
复制文件描述符
-
可以使用 dup 或 dup2 这两个系统调用对文件描述符进行复制,得到一个新的文件描述符;
-
使用新和旧文件描述符都可 IO 操作,拥有相同的权限;
-
复制得到的文件描述符与旧的文件描述符都指向了同一个文件表;
dup 函数
dup 函数用于复制文件描述符
/*man 2 dup*/
#include <unistd.h>
int dup(int oldfd);
函数参数
oldfd:需要被复制的文件描述符
返回值
成功:返回一个新的文件描述符,由操作系统分配,分配置原则遵循文件描述符分配原则;
失败:返回-1,并且会设置 errno 值;
可以在不使用O_APPEND标志的情况下,通过文件描述符复制来实现接续写;
之前是多窗口打开同一文件,现在是多个文件描述符打开同一文件;
/*示例代码 3.7.1 dup 函数测试代码*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
unsigned char buffer1[4], buffer2[4];
int fd1, fd2;
int ret;
int i;
/* 创建新文件 test_file 并打开 */
fd1 = open("./test_file", O_RDWR | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == fd1) {
perror("open error");
exit(-1);
}
/* 复制文件描述符 */
fd2 = dup(fd1);
if (-1 == fd2) {
perror("dup error");
ret = -1;
goto err1;
}
printf("fd1: %d\nfd2: %d\n", fd1, fd2);
/* buffer 数据初始化 */
buffer1[0] = 0x11;
buffer1[1] = 0x22;
buffer1[2] = 0x33;
buffer1[3] = 0x44;
buffer2[0] = 0xAA;
buffer2[1] = 0xBB;
buffer2[2] = 0xCC;
buffer2[3] = 0xDD;
/* 循环写入数据 */
for (i = 0; i < 4; i++) {
ret = write(fd1, buffer1, sizeof(buffer1));
if (-1 == ret) {
perror("write error");
goto err2;
}
ret = write(fd2, buffer2, sizeof(buffer2));
if (-1 == ret) {
perror("write error");
goto err2;
}
}
/* 将读写位置偏移量移动到文件头 */
ret = lseek(fd1, 0, SEEK_SET);
if (-1 == ret) {
perror("lseek error");
goto err2;
}
/* 读取数据 */
for (i = 0; i < 8; i++) {
ret = read(fd1, buffer1, sizeof(buffer1));
if (-1 == ret) {
perror("read error");
goto err2;
}
printf("%x%x%x%x", buffer1[0], buffer1[1],
buffer1[2], buffer1[3]);
}
printf("\n");
ret = 0;
err2:
close(fd2);
err1:
/* 关闭文件 */
close(fd1);
exit(ret);
}
由打印信息可知,fd1 等于 6,复制得到的新的文件描述符为 7(遵循 fd 分配原则);
打印出来的数据显示为接续写,所以可知,通过复制文件描述符可以实现接续写;
dup2 函数
dup 系统调用分配的文件描述符是由系统分配的,遵循文件描述符分配原则,并不能自己指定一个文件描述符,这是 dup 系统调用的一个缺陷;
dup2 系统调用修复了这个缺陷,可以手动指定文件描述符,不需要遵循文件描述符分配原则;
函数原型
/*man 2 dup2*/
#include <unistd.h>
int dup2(int oldfd, int newfd);
函数参数
oldfd:需要被复制的文件描述符。
newfd:指定一个文件描述符(需要指定一个当前进程没有使用到的文件描述符)。
返回值
成功:返回一个新的文件描述符,也就是手动指定的文件描述符 newfd;
失败:返回-1,并且会设置 errno 值。
示例代码 3.7.2 dup2 函数测试代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd1, fd2;
int ret;
/* 创建新文件 test_file 并打开 */
fd1 = open("./test_file", O_RDWR | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == fd1) {
perror("open error");
exit(-1);
}
/* 复制文件描述符 */
fd2 = dup2(fd1, 100);
if (-1 == fd2) {
perror("dup error");
ret = -1;
goto err1;
}
printf("fd1: %d\nfd2: %d\n", fd1, fd2);
ret = 0;
close(fd2);
err1:
/* 关闭文件 */
close(fd1);
exit(ret);
}
测试代码使用 dup2 函数复制文件描述符 fd1,指定新的文件描述符为 100,复制成功将其打印:
同一个文件描述符 fd 调用 dup 或 dup2 函数可复制多次。
文件共享
概念:同一文件(譬如磁盘上的同一文件,对应同一 inode)被多个独立读写体同时进行 IO 操作;
典例:当使用 fd1 对文件进行写操作之后,并没有关闭 fd1,而此时使用 fd2 对文件再进行写操作;
功能:多用于多进程或多线程编程环境中,譬如我们可以通过文件共享的方式来实现多个线程同时操作同一个大文件,以减少文件读写时间、提升效率;
核心:如何制造出多个不同的文件描述符来指向同一个文件。其实方法在上面的内容中都已经给大家介绍过了,譬如多次调用 open 函数重复打开同一个文件得到多个不同的文件描述符、使用 dup()或 dup2()函数对文件描述符进行复制以得到多个不同的文件描述符。
常见的三种文件共享的实现方式
文件共享时关心的是:存在着竞争冒险、不同的读写体之间是分别写还是接续写;
原子操作与竞争冒险
情景引入
原子操作
-
原子操作,是有多步操作组成的一个操作,原子操作要么一步也不执行,一旦执行,必须要执行完所有步骤,不可能只执行所有步骤中的一个子集;
-
竞争冒险解决办法就是将这两个操作步骤合并成一个原子操作;
O_APPEND 实现原子操作
-
这里“移动当前写位置偏移量到文件末尾、写入数据”这两个操作步骤就组成了一个原子操作;
-
加入 O_APPEND 标志后,不管怎么写入数据都会是从文件末尾写,这样就不会导致出现“进程 A 写入的数据覆盖了进程 B 写入的数据”这种情况;
pread()和 pwrite()
-
pread()和 pwrite()是系统调用,与 read()、write()函数作用一致,但pread()和 pwrite()可实现原子操作;
-
pread()和 pwrite()可用于实现原子操作,调用 pread 函数或 pwrite 函数可传入一个位置偏移量 offset 参数,用于指定文件当前读或写的位置偏移量,所以调用 pread 相当于调用 lseek 后再调用 read;
-
移动当前位置偏移量、读或写;
函数原型
/*man 2 pread"或"man 2 pwrite*/
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
函数参数
fd、buf、count 参数与 read 或 write 函数意义相同;
offset:表示当前需要进行读或写的位置偏移量;
**返回值:**返回值与 read、write 函数返回值意义一样;
虽然 pread(或 pwrite)函数相当于 lseek 与 pread(或 pwrite)函数的集合;
但还是有下列区别:
⚫ 调用 pread 函数时,无法中断其定位和读操作(也就是原子操作);
⚫ 不更新文件表中的当前位置偏移量
/*示例代码 3.9.1 pread 函数测试*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
unsigned char buffer[100];
int fd;
int ret;
/* 打开文件 test_file */
fd = open("./test_file", O_RDWR);
if (-1 == fd)
{
perror("open error");
exit(-1);
}
/* 使用 pread 函数读取数据(从偏移文件头 1024 字节处开始读取) */
ret = pread(fd, buffer, sizeof(buffer), 1024);
if (-1 == ret)
{
perror("pread error");
goto err;
}
/* 获取当前位置偏移量 */
ret = lseek(fd, 0, SEEK_CUR);
if (-1 == ret)
{
perror("lseek error");
goto err;
}
printf("Current Offset: %d\n", ret);
ret = 0;
err:
/* 关闭文件 */
close(fd);
exit(ret);
}
1>打开 test_file 文件,然后直接使用 pread 函数读取100 个字节数据,从偏移文件头部 1024 字节处,读取完成之后再使用 lseek 函数获取到文件当前位置偏移量,并将其打印出来;
2>假如 pread 函数会改变文件表中记录的当前位置偏移量,则打印出来的数据应该是1024 + 100 = 1124;如果不会改变文件表中记录的当前位置偏移量,则打印出来的数据应该是 0
上图中可知,打印出来的数据为 0,正如前面所介绍那样,pread 函数确实不会改变文件表中记录的当前位置偏移量;同理,pwrite 函数也是如此;
如果把 pread 函数换成 read(或 write)函数,那么打印出来的数据就是 100;
创建一个文件
open 函数的 O_EXCL 标志的时候,也提到了原子操作;O_EXCL 可以用于测试一个文件是否存在,如果不存在则创建此文件,如果存在则返回错误,这使得测试和创建两者成为一个原子操作。
fcntl()和 ioctl()
系统调用
fcntl()
fcntl()函数可以对一个已经打开的文件描述符执行一系列控制操作;
/* 复制一个文件描述符(与 dup、dup2 作用相同)、获取/设置文件描述符标志、获取/设置文件状态标志等;
类似于一个多功能文件描述符管理工具箱 */
/*man 2 fcntl*/
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ )
函数参数
fd:文件描述符。
cmd:操作命令。此参数表示我们将要对 fd 进行什么操作,cmd 参数支持很多操作命令,
man 手册查看到这些操作命令的详细介绍,这些命令都是以 F_XXX 开头的;
譬如 F_DUPFD、F_GETFD、F_SETFD 等,不同的 cmd 具有不同的作用,cmd 操作命令大致可分为以下 5 种功能:
⚫ 复制文件描述符(cmd=F_DUPFD 或 cmd=F_DUPFD_CLOEXEC);
⚫ 获取/设置文件描述符标志(cmd=F_GETFD 或 cmd=F_SETFD);
⚫ 获取/设置文件状态标志(cmd=F_GETFL 或 cmd=F_SETFL);
⚫ 获取/设置异步 IO 所有权(cmd=F_GETOWN 或 cmd=F_SETOWN);
⚫ 获取/设置记录锁(cmd=F_GETLK 或 cmd=F_SETLK);
返回值
失败情况下,返回-1,并且会设置 errno;
执行成功的情况下,其返回值与 cmd(操作命令)有关;
譬如 cmd=F_DUPFD(复制文件描述符)将返回一个新的文件描述符、cmd=F_GETFD(获取文
件描述符标志)将返回文件描述符标志、cmd=F_GETFL(获取文件状态标志)将返回文件状态标志等。
fcntl 使用示例
1>复制文件描述符
当前目录下存在 test_file 文件,上述代码会打开此文件,得到文件描述符 fd1,之后再使用 fcntl 函数复制 fd1 得到新的文件描述符 fd2,并将 fd1 和 fd2 打印出来:
/*示例代码 3.10.1 fcntl 复制文件描述符&/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd1, fd2;
int ret;
/* 打开文件 test_file */
fd1 = open("./test_file", O_RDONLY);
if (-1 == fd1) {
perror("open error");
exit(-1);
}
/* 使用 fcntl 函数复制一个文件描述符 */
fd2 = fcntl(fd1, F_DUPFD, 0);
if (-1 == fd2) {
perror("fcntl error");
ret = -1;
goto err;
}
printf("fd1: %d\nfd2: %d\n", fd1, fd2);
ret = 0;
close(fd2);
err:
/* 关闭文件 */
close(fd1);
exit(ret);
}
可知复制得到的文件描述符是 4,因为在执行 fcntl 函数时,传入的第三个参数是 0,也就时指定复制得
到的新文件描述符必须要大于或等于 0,但是因为 0~3 都已经被占用了,所以分配得到的 fd 就是 4;
如果传入的第三个参数是 100,那么 fd2 就会等于 100;
2>获取/设置文件状态标志
cmd=F_GETFL 可用于获取文件状态标志,cmd=F_SETFL 可用于设置文件状态标志。cmd=F_GETFL 时不需要传入第三个参数,返回值成功表示获取到的文件状态标志;cmd=F_SETFL 时,需要传入第三个参数,此参数表示需要设置的文件状态标志。
这些标志指的就是我们在调用 open 函数时传入的 flags 标志,可以指定一个或多个(通过位或 | 运算符组合),但是文件权限标志(O_RDONLY、O_WRONLY、O_RDWR)以及文件创建标志(O_CREAT、O_EXCL、O_NOCTTY、O_TRUNC)不能被设置、会被忽略;在 Linux 系统中,只有 O_APPEND、O_ASYNC、O_DIRECT、O_NOATIME 以及 O_NONBLOCK 这些标志可以被修改,这里面有些标志并没有给大家介绍过,后面我们在用到的时候再给大家介绍。所以对于一个已经打开的文件描述符,可以通过这种方式添加或移除标志。
代码会打开 test_file 文件,得到文件描述符 fd,之后调用 fcntl(fd, F_GETFL)来获取到当前文件状态标志 flag,并将其打印来;接着调用 fcntl(fd, F_SETFL, flag | O_APPEND)设置文件状态标志,在原标志的基础添加O_APPEND 标志:
示例代码 3.10.2 fcntl 读取/设置文件状态标志
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
int ret;
int flag;
/* 打开文件 test_file */
fd = open("./test_file", O_RDWR);
if (-1 == fd) {
perror("open error");
exit(-1);
}
/* 获取文件状态标志 */
flag = fcntl(fd, F_GETFL);
if (-1 == flag) {
perror("fcntl F_GETFL error");
ret = -1;
goto err;
}
printf("flags: 0x%x\n", flag);
/* 设置文件状态标志,添加 O_APPEND 标志 */
ret = fcntl(fd, F_SETFL, flag | O_APPEND);
if (-1 == ret) {
perror("fcntl F_SETFL error");
goto err;
}
ret = 0;
err:
/* 关闭文件 */
close(fd);
exit(ret);
}
ioctl 函数
ioctl()可以认为是一个文件 IO 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件
或硬件外设,此函数将会在进阶篇中使用到,譬如可以通过 ioctl 获取 LCD 相关信息;
函数原型
/*man 2 ioctl*/
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
函数参数
fd:文件描述符。
request:此参数与具体要操作的对象有关,没有统一值,表示向文件描述符请求相应的操作;
…:此函数是一个可变参函数,第三个参数需要根据 request 参数来决定,配合 request 来使用;
返回值
成功返回 0,失败返回-1
截断文件
-
系统调用 **truncate()**或 **ftruncate()**可将普通文件截断为指定字节长度;
-
ftruncate()使用文件描述符 fd 来指定目标文件;
-
truncate()使用文件路径 path 来指定目标文件,其功能一样;
-
文件目前的大小大于参数 length 所指定的大小,则多余的数据将被丢失;
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
使用 ftruncate()函数进行文件截断操作之前,必须调用 open()函数打开该文件得到文件描述符,并且必
须要具有可写权限,也就是调用 open()打开文件时需要指定 O_WRONLY 或 O_RDWR;
调用这两个函数并不会导致文件读写位置偏移量发生改变,所以截断之后一般需要重新设置文件当前的读写位置偏移量,以免由于之前所指向的位置已经不存在而发生错误;
调用成功返回 0,失败将返回-1,并设置 errno 以指示错误原因
使用示例
演示了文件的截断操作,分别使用 ftruncate()和 truncate()将当前目录下的文件 file1 截断为长度 2、将文件 file2 截断为长度 1024 个字节;
首先使用 open()函数打开文件 file1,得到文件描述符 fd,接着使用 ftruncate()系统调用将文件截断为 2 长度,传入 file1 文件对应的文件描述符;接着调用 truncate()系统调用将文件 file2 截断为 1024字节长度,传入 file2 文件的相对路径。
/*示例代码 3.11.1 文件截断操作*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
/* 打开 file1 文件 */
if (0 > (fd = open("./file1", O_RDWR))) {
perror("open error");
exit(-1);
}
/* 使用 ftruncate 将 file1 文件截断为长度 2 字节 */
if (0 > ftruncate(fd, 2)) {
perror("ftruncate error");
exit(-1);
}
/* 使用 truncate 将 file2 文件截断为长度 1024 字节 */
if (0 > truncate("./file2", 1024)) {
perror("truncate error");
exit(-1);
}
/* 关闭 file1 退出程序 */
close(fd);
exit(0);
}
程序运行之后,file1 文件大小变成了 0,而 file2 文件大小变成了 1024 字节,预期结果一致!
成功返回 0,失败将返回-1,并设置 errno 以指示错误原因
##### 使用示例
演示了文件的截断操作,分别使用 ftruncate()和 truncate()将当前目录下的文件 file1 截断为长度 2、将文件 file2 截断为长度 1024 个字节;
首先使用 open()函数打开文件 file1,得到文件描述符 fd,接着使用 ftruncate()系统调用将文件截断为 2 长度,传入 file1 文件对应的文件描述符;接着调用 truncate()系统调用将文件 file2 截断为 1024字节长度,传入 file2 文件的相对路径。
```c
/*示例代码 3.11.1 文件截断操作*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
/* 打开 file1 文件 */
if (0 > (fd = open("./file1", O_RDWR))) {
perror("open error");
exit(-1);
}
/* 使用 ftruncate 将 file1 文件截断为长度 2 字节 */
if (0 > ftruncate(fd, 2)) {
perror("ftruncate error");
exit(-1);
}
/* 使用 truncate 将 file2 文件截断为长度 1024 字节 */
if (0 > truncate("./file2", 1024)) {
perror("truncate error");
exit(-1);
}
/* 关闭 file1 退出程序 */
close(fd);
exit(0);
}
程序运行之后,file1 文件大小变成了 0,而 file2 文件大小变成了 1024 字节,预期结果一致!