Linux应用编程(文件IO进阶)

2023-10-27

一、Linux 系统如何管理文件

1.1、静态文件与 inode

文件存放在磁盘文件系统中,并且以一种固定的形式进行存放,我们把他们称为静态文件。
每一个文件都必须对应一个 inode,inode 实质上是一个结构体,这个结构体中有很多的元素,不同的元素记录了文件了不同信息,譬如文件字节大小、文件所有者、文件对应的读/写/执行权限、文件时间戳(创建时间、更新时间等)、文件类型、文件数据存储的 block(块)位置等等信息,如下图:
在这里插入图片描述
每一个文件都有唯一的一个 inode,每一个 inode 都有一个与之相对应的数字编号,通过这个数字编号就可以找到 inode table 中所对应的 inode。在 Linux 系统下,我们可以通过"ls -i"命令查看文件的 inode 编号,如下所示:

在这里插入图片描述
打开一个文件,系统内部会将这个过程分为三步:

  1. 系统找到这个文件名所对应的 inode 编号;
  2. 通过 inode 编号从 inode table 中找到对应的 inode 结构体;
  3. 根据 inode 结构体中记录的信息,确定文件数据所在的 block,并读出数据。

1.2、文件打开时的状态

当我们调用 open 函数去打开文件的时候,内核会申请一段内存(一段缓冲区),并且将静态文件的数据内容从磁盘这些存储设备中读取到内存中进行管理、缓存(也把内存中的这份文件数据叫做动态文件、内核缓冲区)。打开文件后,以后对这个文件的读写操作,都是针对内存中这一份动态文件进行相关的操作,而并不是针对磁盘中存放的静态文件。

当我们对动态文件进行读写操作后,此时内存中的动态文件和磁盘设备中的静态文件就不同步了,数据的同步工作由内核完成,内核会在之后将内存这份动态文件更新(同步)到磁盘设备中。

在 Linux 系统中,内核会为每个进程设置一个专门的数据结构用于管理该进程,譬如用于记录进程的状态信息、运行特征等,我们把这个称为进程控制块(PCB)。
在这里插入图片描述

1.3、返回错误处理与 errno

errno 本质上是一个 int 类型的变量,用于存储错误编号,但是需要注意的是,并不是执行所有的系统调用或 C 库函数出错时,操作系统都会设置 errno,那我们如何确定一个函数出错时系统是否会设置 errno 呢?其实这个通过 man 手册便可以查到,譬如以 open 函数为例,执行"man 2 open"打开 open 函数的帮助信息,找到函数返回值描述段,如下所示:
在这里插入图片描述

#include <stdio.h>
#include <errno.h>

int main(void)
{
	printf("%d\n", errno);
	return 0;
}

1.4、strerror 函数

strerror()函数可以将对应的 errno 转换成适合我们查看的字符串信息

#include <string.h>
char *strerror(int errnum);

测试代码如下:

#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;
}

1.5、perror 函数

调用此函数不需要传入 errno,函数内部会自己去获取 errno 变量的值,调用此函数会直接将错误提示字符串打印出来,而不是返回字符串,除此之外还可以在输出的错误提示字符串之前加入自己的打印信息,函数原型如下所示:

#include <stdio.h>
void perror(const char *s);

函数参数和返回值含义如下:

s:在错误提示字符串信息之前,可加入自己的打印信息,也可不加,不加则传入空字符串即可。
返回值:void 无返回值。

2、exit、_exit、_Exit

在 Linux 系统下,进程正常退出除了可以使用 return 之外,还可以使用 exit()、_exit()以及_Exit()

2.1、_exit()和_Exit()函数

_exit()函数原型如下所示:

#include <unistd.h>
void _exit(int status);
#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()函数原型如下所示:

#include <stdlib.h>
void _Exit(int status);

2.2、exit()函数

执行 exit()会执行一些清理工作,最后调用_exit()函数。exit()函数原型如下:

#include <stdlib.h>

void exit(int status);

介绍了 3 中终止进程的方法:
⚫ main 函数中运行 return;
⚫ 调用 Linux 系统调用_exit()或_Exit();
⚫ 调用 C 标准库函数 exit()。
不管你用哪一种都可以结束进程,但还是推荐大家使用 exit()

3、空文件概念

有一个 test_file,该文件的大小是 4K,通过 lseek 系统调用将该文件的读写偏移量移动到偏移文件头部 6000 个字节处,使用 write()函数对文件进行写入操作,此时将是从偏移文件头部 6000 个字节处开始写入数据,4096~6000 字节间出现了一个空洞,该文件也被称为空洞文件。

4、O_APPEND 和 O_TRUNC 标志

4.1、O_TRUNC 标志

O_TRUNC 这个标志的作用非常简单,如果使用了这个标志,调用 open 函数打开文件的时候会将文件原本的内容全部丢弃,文件大小变为 0;

4.2、O_APPEND 标志

如果 open 函数携带了 O_APPEND 标志,调用 open 函数打开文件,当每次使用 write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾,从文件末尾开始写入数据,也就是意味着每次写入数据都是从文件末尾开始。

O_APPEND 标志并不会影响读位置偏移量,即使使用了 O_APPEND标志,读文件位置偏移量默认情况下依然是文件头。

使用了 O_APPEND 标志,即使是通过 lseek 函数也是无法修改写文件时对应的位置偏移量(注意笔者这里说的是写文件,并不包括读),写入数据依然是从文件末尾开始,lseek 并不会该变写位置偏移量

5、多次打开同一个文件

5.1、验证一些现象

一个进程内多次 open 打开同一个文件,那么会得到多个不同的文件描述符 fd,同理在关闭文件的时候也需要调用 close 依次关闭各个文件描述符。

针对这个问题,我们编写测试代码进行测试,如下所示:

#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, fd3;
	 int ret;
	 /* 第一次打开文件 */
	 fd1 = open("./test_file", O_RDWR);
	 if (-1 == fd1) {
		 perror("open error");
		 exit(-1);
	}
	 /* 第二次打开文件 */
	 fd2 = open("./test_file", O_RDWR);
	 if (-1 == fd2) {
		 perror("open error");
		 ret = -1;
		 goto err1;
	 }
	 /* 第三次打开文件 */
	 fd3 = open("./test_file", O_RDWR);
	 if (-1 == fd3) {
		 perror("open error");
		 ret = -1;
		 goto err2;
	 }
	 /* 打印出 3 个文件描述符 */
	 printf("%d %d %d\n", fd1, fd2, fd3);
	 close(fd3);
	 ret = 0;
err2:
	 close(fd2);
err1:
	 /* 关闭文件 */
	 close(fd1);
	 exit(ret);
 }

在这里插入图片描述

一个进程内多次 open 打开同一个文件,在内存中并不会存在多份动态文件。

#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)
{
	 char buffer[4];
	 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);
	 }
	 /* 再次打开 test_file 文件 */
	 fd2 = open("./test_file", O_RDWR);
	 if (-1 == fd2) {
		 perror("open error");
		 ret = -1;
		 goto err1;
	 }
	 /* 通过 fd1 文件描述符写入 4 个字节数据 */
	 buffer[0] = 0x11;
	 buffer[1] = 0x22;
	 buffer[2] = 0x33;
	 buffer[3] = 0x44;
	 ret = write(fd1, buffer, 4);
	 if (-1 == ret) {
		 perror("write error");
		 goto err2;
	 }
	 /* 将读写位置偏移量移动到文件头 */
	 ret = lseek(fd2, 0, SEEK_SET);
	 if (-1 == ret) {
		 perror("lseek error");
		 goto err2;
	 }
	 /* 读取数据 */
	 memset(buffer, 0x00, sizeof(buffer));
	 ret = read(fd2, buffer, 4);
	 if (-1 == ret) {
		 perror("read error");
		 goto err2;
	 }
	 printf("0x%x 0x%x 0x%x 0x%x\n", buffer[0], buffer[1],buffer[2], buffer[3]);
	 ret = 0;
err2:
	 close(fd2);
err1:
	 /* 关闭文件 */
	 close(fd1);
	 exit(ret);
}

在这里插入图片描述

一个进程内多次 open 打开同一个文件,不同文件描述符所对应的读写位置偏移量是相互独立的。

在这里插入图片描述

5.2、多次打开同一文件进行读操作与 O_APPEND 标志

如一个进程中两次调用 open 函数打开同一个文件,分别得到两个文件描述符 fd1 和 fd2,使用这两个文件描述符对文件进行写入操作,是分别写。
测试代码如下所示:

#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);
}

在这里插入图片描述
open 函数添加了 O_APPEND 标志,分别写已经变成了接续写。
在这里插入图片描述
6、复制文件描述符

使用旧的文件描述符对文件有读写权限,那么新的文件描述符同样也具有读写权限;在 Linux 系统下,可以使用 dup 或 dup2 这两个系统调用对文件描述符进行复制。
fd1 为原文件描述符,fd2 为复制得到的文件描述符,如下图所示:
在这里插入图片描述

6.1、dup 函数

#include <unistd.h>
int dup(int oldfd);

函数参数和返回值含义如下:
oldfd:需要被复制的文件描述符。
返回值:成功时将返回一个新的文件描述符,由操作系统分配,分配置原则遵循文件描述符分配原则;如果复制失败将返回-1,并且会设置 errno 值。

#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 分配原则),打印出来的数据显示为接续写,所以可知,通过复制文件描述符可以实现接续写。

6.2、dup2 函数

#include <unistd.h>
int dup2(int oldfd, int newfd);

函数参数和返回值含义如下:
oldfd:需要被复制的文件描述符。
newfd:指定一个文件描述符(需要指定一个当前进程没有使用到的文件描述符)。
返回值:成功时将返回一个新的文件描述符,也就是手动指定的文件描述符 newfd;如果复制失败将返回-1,并且会设置 errno 值。

7、文件共享

所谓文件共享指的是同一个文件(譬如磁盘上的同一个文件,对应同一个 inode)被多个独立的读写体同时进行 IO 操作同时进行 IO 操作指的是一个读写体操作文件尚未调用 close 关闭的情况下,另一个读写体去操作文件。

文件共享的核心是:如何制造出多个不同的文件描述符来指向同一个文件。

常见的三种文件共享的实现方式

(1)同一个进程中多次调用 open 函数打开同一个文件,各数据结构之间的关系如下图所示:
在这里插入图片描述
(2)不同进程中分别使用 open 函数打开同一个文件,其数据结构关系图如下所示:

在这里插入图片描述

(3)同一个进程中通过 dup(dup2)函数对文件描述符进行复制,其数据结构关系如下图所示:

在这里插入图片描述

二、原子操作与竞争冒险

1.1、竞争冒险简介

在这里插入图片描述
以上给大家所描述的这样一种情形就属于竞争状态(也成为竞争冒险),操作共享资源的两个进程(或线程),其操作之后的所得到的结果往往是不可预期的,因为每个进程(或线程)去操作文件的顺序是不可预期的,即这些进程获得 CPU 使用权的先后顺序是不可预期的,完全由操作系统调配,这就是所谓的竞争状态。

1.2、原子操作

原子操作要么一步也不执行,一旦执行,必须要执行完所有步骤,不可能只执行所有步骤中的一个子集。

(1)O_APPEND 实现原子操作
当 open 函数的 flags 参数中包含了 O_APPEND 标志,每次执行 write 写入操作时都会将文件当前写位置偏移量移动到文件末尾,然后再写入数据,这里“移动当前写位置偏移量到文件末尾、写入数据”这两个操作步骤就组成了一个原子操作,加入 O_APPEND 标志后,不管怎么写入数据都会是从文件末尾写,这样就不会导致出现“进程 A 写入的数据覆盖了进程 B 写入的数据”这种情况了。

(2)pread()和 pwrite()
pread()和 pwrite()都是系统调用,与 read()、write()函数的作用一样,用于读取和写入数据。区别在于,pread()和 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 函数时,无法中断其定位和读操作(也就是原子操作);
⚫ 不更新文件表中的当前位置偏移量。
关于第二点我们可以编写一个简单地代码进行测试,测试代码如下所示:

#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);
}

在当前目录下存在一个文件 test_file,上述代码中会打开 test_file 文件,然后直接使用 pread 函数读取100 个字节数据,从偏移文件头部 1024 字节处,读取完成之后再使用 lseek 函数获取到文件当前位置偏移量,并将其打印出来。假如 pread 函数会改变文件表中记录的当前位置偏移量,则打印出来的数据应该是1024 + 100 = 1124;如果不会改变文件表中记录的当前位置偏移量,则打印出来的数据应该是 0,接下来编译代码测试:
在这里插入图片描述
(3)创建一个文件

在这里插入图片描述当 open 函数中同时指定了 O_EXCL O_CREAT 标志,如果要打开的文件已经存在,则 open 返回错误;如果指定的文件不存在,则创建这个文件,这里就提供了一种机制,保证进程是打开文件的创建者,将“判断文件是否存在、创建文件”这两个步骤合成为一个原子操作,有了原子操作,就保证不会出现上图情况。

2、fcntl 和 ioctl

2.1、fcntl 函数

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ )

函数参数和返回值含义如下:

fd:文件描述符。
cmd:操作命令。此参数表示我们将要对 fd 进行什么操作,cmd 参数支持很多操作命令,大家可以打开 man 手册查看到这些操作命令的详细介绍
:fcntl 函数是一个可变参函数,第三个参数需要根据不同的 cmd 来传入对应的实参,配合 cmd 来使用。
返回值:执行失败情况下,返回-1,并且会设置 errno;执行成功的情况下,其返回值与 cmd(操作命令)有关,譬如 cmd=F_DUPFD(复制文件描述符)将返回一个新的文件描述符、cmd=F_GETFD(获取文件描述符标志)将返回文件描述符标志、cmd=F_GETFL(获取文件状态标志)将返回文件状态标志等。

fcntl 使用示例

(1)复制文件描述符

#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);
} 

在这里插入图片描述
可知复制得到的文件描述符是 7,因为在执行 fcntl 函数时,传入的第三个参数是 0,也就时指定复制到的新文件描述符必须要大于或等于 0,但是因为 0~6 都已经被占用了,所以分配得到的 fd 就是 7;如果传入的第三个参数是 100,那么 fd2 就会等于 100。

(2)获取/设置文件状态标志

在 Linux 系统中,只有 O_APPEND、O_ASYNC、O_DIRECT、O_NOATIME 以及 O_NONBLOCK 这些标志可以被修改

#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);
}

2.2、ioctl 函数

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

函数参数和返回值含义如下:
fd:文件描述符。
request:此参数与具体要操作的对象有关,没有统一值,表示向文件描述符请求相应的操作;后面用到的时候再给大家介绍。
:此函数是一个可变参函数,第三个参数需要根据 request 参数来决定,配合 request 来使用。
返回值:成功返回 0,失败返回-1。

三、截断文件

使用系统调用 truncate()或 ftruncate()可将普通文件截断为指定字节长度,其函数原型如下所示:

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

这两个函数的区别在于:ftruncate()使用文件描述符 fd 来指定目标文件,而 truncate()则直接使用文件路径 path 来指定目标文件,其功能一样。

使用 ftruncate()函数进行文件截断操作之前,必须调用 open()函数打开该文件得到文件描述符,并且必须要具有可写权限,也就是调用 open()打开文件时需要指定 O_WRONLY 或 O_RDWR。

使用示例

#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 文件截断为长度 0 字节 */
	 if (0 > ftruncate(fd, 0)) 
	 {
		 perror("ftruncate error");
		 exit(-1);
	 }
	 /* 使用 truncate 将 file2 文件截断为长度 1024 字节 */
	 if (0 > truncate("./file2", 1024)) 
	 {
		 perror("truncate error");
		 exit(-1);
	 }
	 /* 关闭 file1 退出程序 */
	 close(fd);
	 exit(0);
}

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux应用编程(文件IO进阶) 的相关文章

  • Linux 阻塞与非阻塞串行读取

    I have 这段代码 https stackoverflow com questions 6947413 how to open read and write from serial port in c用于在Linux中从串行读取 但我不
  • 如何从外部模块导出符号?

    我在内核源代码树之外进行编码 有两个模块 第一个printt有一个功能printtty 将字符串打印到当前 tty 以及第二个模块hello这会调用printtty 在初始化期间 我已经添加了EXPORT SYMBOL printtty 在
  • Laravel 内存问题?

    各位 我在 DO 服务器上遇到这样的问题 我已经尝试了一切 整个网站在使用 Homestead 的 Linux 服务器上 100 正常工作 但上传后 它只能工作一次 在重新加载或刷新页面后会多次下降 我尝试增加 apache 服务器的内存
  • _dl_runtime_resolve -- 共享对象何时加载到内存中?

    我们有一个对性能要求很高的消息处理系统 最近我们注意到第一条消息比后续消息花费的时间要长很多倍 当它通过我们的系统时 会发生大量转换和消息增强 其中大部分是通过外部库完成的 我刚刚描述了这个问题 使用 callgrind 将仅一条消息的 运
  • 动态加载库和共享全局符号

    由于我在动态加载的库中观察到全局变量的一些奇怪行为 因此我编写了以下测试 首先我们需要一个静态链接库 头文件test hpp ifndef BASE HPP define BASE HPP include
  • 选择多个模式的 awk 代码

    这是我的输入文件 比如modified txt r4544 n479826 2012 08 28 07 12 33 0400 Tue 28 Aug 2012 1 line Changed paths M branches 8 6 0 con
  • Linux shell 标题大小写

    我正在编写一个 shell 脚本并有一个如下所示的变量 something that is hyphenated 我需要在脚本中的各个点使用它 如下所示 something that is hyphenated somethingthati
  • Docker 容器可以访问 DNS,但无法解析主机

    我在运行 docker 容器时遇到一个有趣的问题 突然间 我无法从容器内解析 DNS 这是一个概要 一切都没有解决 apt get pip 一次性 ping 容器等正在运行docker run it dns 8 8 8 8 ubuntu p
  • CMake:使用其他平台的生成器。如何?

    如何使用 CMake 在 Linux 上生成 Visual Studio 项目文件 你不能 您必须在 Windows 上运行 CMake 才能为 Visual Studio 生成
  • BlueZ D-Bus C,应用 BLE

    我正在尝试编写一个应用程序来搜索附近的蓝牙设备并与它们通信 我的应用程序将用 C 语言编写 并打算在 Linux 下工作 是否有通过 C 中的 D Bus 使用 BlueZ 的教程或示例 此应用程序的目的是从 BLE 中的文件发送数据 你能
  • 在linux中使用setcap [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 要将 cap net raw 功能添加到 例如 bin ping 我们使用以下命令 setcap cap net raw ep bin ping ep
  • C 标准库函数与系统调用。哪个是“open()”?

    I know fopen 在C标准库中 所以我绝对可以调用fopen C 程序中的函数 我感到困惑的是为什么我可以打电话给open 功能也一样 open 应该是系统调用 所以它不是标准库中的C函数 因为我能够成功地调用open 函数 我调用
  • gnutls_handshake() 失败:握手失败 GIT

    一切都工作正常 但突然我收到错误 致命 无法访问 https 电子邮件受保护 cdn cgi l email protection name repo name git gnutls handshake 失败 握手失败 我在我的计算机和 E
  • 如何搭建qtwayland?

    我花了一整天的时间尝试使用QtWayland Compositor 1 0在 Qt 创建者中 我已经遵循了从那里开始的所有步骤https wiki qt io QtWayland https wiki qt io QtWayland但我收到
  • 检查上次更改密码的时间[关闭]

    Closed 这个问题是与编程或软件开发无关 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的
  • 如何从 swagger 文档生成静态 html 文件?

    我创建了一个 Swagger 文档yaml文件位于 api swagger swagger yaml 现在我想分享一个静态 HTML 文档及其定义 但它已在招摇项目 https github com swagger api swagger
  • “以下软件包将被更高优先级的频道取代”是什么意思?

    我正在尝试将 fuzzywuzzy 安装到 64 位 Linux 中的 Anaconda 发行版上 当我这样做时 它试图改变我的conda and conda env to conda forge渠道 如下 我通过以下方式在 anacond
  • Tomcat 中的 403 访问被拒绝

    我有以下内容tomcat users xml
  • 第一次如何配置postgresql?

    我刚刚安装了 postgresql 并在安装过程中指定了密码 x 当我尝试做的时候createdb并指定我收到消息的任何密码 createdb 无法连接到数据库 postgres 致命 用户密码身份验证失败 同样适用于createuser
  • AMD OpenCL 在 Linux 上工作所需的最小必要文件子集是什么?

    我已经使用 buildroot 构建了 Linux 内核 我已将开源 amdgpu 驱动程序和所需的固件合并到其中 驱动程序很好 检测 GPU 模式设置运行良好 调整 小文本 的分辨率 启动后会显示命令行 现在我需要运行 OpenCL 程序

随机推荐

  • RealBasicVSR训练(三)用自己的数据集训练

    由于上一篇中的方法只能用1个gpu训练 故重新采取之前的训练方法 第一步 RealBasicVSR master mim train mmedit configs realbasicvsr wogan c64b20 2x30x8 lr1e
  • 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java基于HTML5的流浪动物领养平台yww0b

    很多大学生 成考 自考 全日制本科 大专的学生都因为毕设没有完成而延时毕业的情况 现在分享给大家选题 下面有2023年做的选题 最后面有选题 源码 论文下载网站给大家学习 如今计算机技术的飞速发展 大约三 四年前 软件工程是市场的热门领域
  • 电荷泵电路(Charge Pump)用于升压的解析

    升压的电荷泵电路 Charge Pump 也称为开关电容转换器 Switched Capacitor Converter 老粉丝都知道 公众号很久之前就发布了一篇阐述电感 电容 二极管构成的BOOST升压方案的文章 那为什么还要讨论电荷泵方
  • 小谈类机制相关

    小谈类机制相关 本文主要涉及类相关的一些常见面试问题 以及相关特性 包括 this 指针 拷贝构造函数相关以及类机制 一 this指针 编译器在编译普通成员函数时 会隐式的分配一个形参指针 即this指针 并且当实例化对象调用该成员函数时
  • Python 时间比较大小 并从dataframe中提取满足时间条件的量

    之前一直用时间数据相互加减然后判断是否大于0来判断大小 但是发现时间数据居然可以直接比较 Python 时间比较大小 可以直接用比较运算符 gt lt 输出bool类型 True False 先定义一个包含时间数据的dataframe t1
  • python None理解与应用

    官方文档 None是NoneType类型的唯一值 所以None既不是空列表 也不是空字符串 None通常用来代表空值 或者表示函数默认没有入参 如下图 None不能被赋值 否则会报错 它跟True False一样也是built in con
  • android Intent 全面点的介绍

    第一种方式 用action来跳转 1 使用Action跳转 如果有一个程序的AndroidManifest xml中的某一个Activity的IntentFilter段中 定义了包含了相同的Action那么这个Intent就与这个目标Act
  • Linux线程

    目录 1 进程线程区别 2 线程 创建退出等待 3 互斥量 锁 3 什么是死锁 4 条件 5 线程初始化宏 6 生产者消费者 1 进程线程区别 1 进程占内存 比如父子进程copy内存空间 线程共享内存空间 2 线程切换和创建速度比进程快
  • 主板24pin接口详图_工控电脑一般需要几个供电接口

    工控电脑也叫做工控机 是使用在工业上的计算机 由机箱 主板 CPU 内存 硬盘和电源等硬件设备所组成 既然工控电脑是计算机的一种 那它工作的时候肯定是需要供电才能启动 那么工控电脑一般需要几个供电接口 一定要说工控电脑一般需要几个供电接口
  • 雷军的开源情怀

    2007 年 iPhone 发布 智能手机时代真正拉开帷幕 2009 年 Google 发布了开源的手机操作系统 Android 同年 9 月 第一款 Android 手机 G1 发布 尽管当时 Android 手机体验还很粗糙 但我认为
  • (MySql) InnoDB索引的本质和快速查询过程

    本文涉及的范围包括 1 到底什么是InnoDB引擎的索引 它的本质是什么 是如何实现的 实现的思路是什么 2 根据索引的实现思路 当我们要查询一条数据 行记录 时 查询语句的查询过程是什么 说到数据库引擎的索引 我们都知道它的作用是提高数据
  • MATLAB 中的randn函数

    matlab函数 randn 产生正态分布的随机数或矩阵的函数 randn 产生均值为0 方差 2 1 标准差 1的正态分布的随机数或矩阵的函数 用法 Y randn n 返回一个n n的随机项的矩阵 如果n不是个数量 将返回错误信息 Y
  • ESP8266和腾讯云的使用

    1 ESP8266简介 在乐鑫官网 ESP芯片技术厂家 可以看到 乐鑫把ESP8266称之为面向物联网应用的高性价比 高度集成的 Wi Fi MCU 简单来说 ESP8266可以有两种功能 一是WiFi模块 二是32位MCU WiFi模块
  • 向量与矩阵的相乘

    在学习计算机图形学的时候 最常遇到的就是矩阵的乘法了 下面我们就简单的介绍下 使用程序如何编写两个矩阵的相乘呢 其实这个问题 大一的孩子都会写的 不是很难的 但是呢 为了构建一个完整的学习过程 还是记录一下基础知识 1 向量乘以矩阵 如上图
  • 全栈开发学习(Node+Vue+Mongodb)(八)——移动端页面搭建(主页部分)

    前面我们完成了后台管理界面的基本功能 接下来就需要完成移动端页面的搭建与数据的展示 移动端的搭建主要以旧版王者荣耀官网主页样式为模板 本文主要介绍前端搭建的流程与一些基本组件的使用 1 准备工作 样式 思路 使用SASS规范化我们的所有样式
  • Obsidian同步方案(win+android)

    官方 Obsidian Git Mgit Onedirve Onedrive SyncTrayzor Syncthing Obsidian Git Mgit 步骤 下载 按照俩个教程配置 有问题私聊 几天内回复 注意点 ObsidianGi
  • RLE压缩算法详解

    RLE压缩算法详解 RLE Run Length Encoding 行程长度压缩算法 也称游程长度压缩算法 是最早出现 也是最简单的无损数据压缩算法 RLE算法的基本思路是把数据按照线性序列分成两种情况 一种是连续的重复数据块 另一种是连续
  • 数据挖掘个人理解

    lt 1 gt 数据挖掘 1 通过对大量数据进行分析 从大量数据中发现一些客观规律 结论 2 主要有数据准备 规律寻找 规律表示3大步 3 步骤 采集数据 采集相关技术 整合检查数据 去除错误数据 建立合适模型进行数据分析 进行数据挖掘工作
  • Markdown基础语法详细版

    文章目录 1 Markdown简介 2 Markdown特点 3 Markdown基本语法 3 1 标题 3 2 斜体和粗体 3 3 换行 3 4 分割线 3 5 列表 3 5 1 无序列表 3 5 2 有序列表 3 5 3 定义型列表 3
  • Linux应用编程(文件IO进阶)

    一 Linux 系统如何管理文件 1 1 静态文件与 inode 文件存放在磁盘文件系统中 并且以一种固定的形式进行存放 我们把他们称为静态文件 每一个文件都必须对应一个 inode inode 实质上是一个结构体 这个结构体中有很多的元素