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

news2024/7/4 5:11:11

一、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);
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/422106.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】初阶指针(指针运算、二级指针及指针数组)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 指针运算 4.1 指针-整数 1.2 指针 - 指针 1.3 指针的关系运算 2. 指针和数组 3. 二级指针 4. 指针数组 1. 指针运算 指针-整数指针-指针指针的关系运算 4.1 指针-整数 上面这个程序的作用是将数组中每个元…

【攻城狮计划】Renesas RA2E1 开发板

&#x1f6a9;WRITE IN FRONT&#x1f6a9; &#x1f50e;介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四"&#x1f50e;&#x1f3c5;荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2022博客之星TOP10…

Scalable Recognition with a Vocabulary Tree(词汇树)

视觉单词 参考 视觉词袋&#xff08;BoVW&#xff0c;Bag of Visual Words&#xff09;模型&#xff0c;是“词袋”&#xff08;BoW&#xff0c;Bag of Words&#xff09;模型从自然语言处理与分析领域向图像处理与分析领域的一次自然推广。对于任意一幅图像&#xff0c;BoVW模…

jwt生成和解密-jose4j

jwt生成和解密-jose4j jwt的概念和生成意义在这里就不描述了&#xff0c;百度能搜到很多&#xff0c;直接上代码 官网地址 https://bitbucket.org/b_c/jose4j/wiki/Home maven <dependency><groupId>org.bitbucket.b_c</groupId><artifactId>jose4j…

【微信小程序】-- 配置uni-app的开发环境(四十八)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

Echarts图表显示不完全(多种图表解决方案)

前言 在使用Echarts画图的时候&#xff0c;有时候图表在固定大小的盒子模型&#xff08;dom容器&#xff09;中会显示不完全&#xff0c;因此我们需要对图表进行相关的调整使得图表内容显示完全。结合最近遇到的情况&#xff0c;提出一些解决方向 &#xff08;比较片面&#x…

Linux操作系统ARM体系结构处理器机制原理与实现

ARM 的概念ARM(Advanced RISC Machine)&#xff0c;既可以认为是一个公司的名字&#xff0c;也可以认为是对一类微处理器的通称&#xff0c;还可以认为是一种技术的名字。ARM 公司并不生产芯片也不销售芯片&#xff0c;它只出售芯片技术授权。其合作公司针对不同需求搭配各类硬…

【2023 · CANN训练营第一季】昇腾AI入门课(Pytorch)——第二章学习笔记

第二章 PyTorch模型迁移&调优 目标 了解 Pytorch 是如何适配到昇腾平台上的了解 Davinci 硬件架构以及什么样的模型在昇腾上更亲和了解软件术语和 Ascend - Pytorch 的安装步骤了解如何将原生 Pytorch 的模型代码是如何适配到 Ascend - Pytorch 前置知识 对原生 Pytorc…

足够惊艳,使用Alpaca-Lora基于LLaMA(7B)二十分钟完成微调,效果比肩斯坦福羊驼

之前尝试了从0到1复现斯坦福羊驼&#xff08;Stanford Alpaca 7B&#xff09;&#xff0c;Stanford Alpaca 是在 LLaMA 整个模型上微调&#xff0c;即对预训练模型中的所有参数都进行微调&#xff08;full fine-tuning&#xff09;。但该方法对于硬件成本要求仍然偏高且训练低效…

Java基础——IO流+字节流使用

&#xff08;1&#xff09;IO流的概述&#xff1a; IO流也称为输入&#xff0c;输出流&#xff0c;就是用来读写数据的。I表示input&#xff0c;是数据从硬盘文件读入到内存的过程&#xff0c;称之输入&#xff0c;负责读。O表示output&#xff0c;是内存程序的数据从内存到写…

CSS:transform顺序问题(translate()+rotate())

问题&#xff1a;下面两行代码在执行效果上有区别吗&#xff1f; transform: translate(100px,100px) rotate(45deg);transform: rotate(45deg) translate(100px,100px);translate(X,Y)&#xff0c;可以使元素在x轴和y轴上平移。&#xff08;在translate中&#xff0c;x轴右为…

设计模式之观察者模式(C++)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、观察者模式是什么&#xff1f; 观察者模式是一种行为型的软件设计模式&#xff0c;定义对象间的一种一对多的依赖关系&#x…

51 openEuler搭建PostgreSQL数据库服务器-安装、运行和卸载

文章目录51 openEuler搭建PostgreSQL数据库服务器-安装、运行和卸载51.1 安装51.2 运行51.2.1 初始化数据库51.2.2 启动数据库51.2.3 登录数据库51.2.4 配置数据库账号密码51.2.5 退出数据库51.2.6 停止数据库51.3 卸载51 openEuler搭建PostgreSQL数据库服务器-安装、运行和卸载…

【Webpack5】核心原理

介绍 本章节我们主要学习&#xff1a; loader 原理自定义常用 loaderplugin 原理自定义常用 plugin Loader 原理 loader 概念 帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块。 loader 执行顺序 分类 pre&#xff1a; 前置 loadernormal&#xff1a; 普通 …

【golang项目-GeeCache】动手写分布式缓存 day1 - 实现LRU算法

介绍 LRU 内存淘汰算法 LRU(Least Recently Used) 最近最少使用 算法 &#xff0c;系统认为如果这个数据最近使用过那么它被再次使用的概率会高&#xff0c;所以系统会先淘汰最久没被使用的数据 基本逻辑 -----------------------------------------------------------------…

手把手教你学习IEC104协议和编程实现 十一-定值的概念讲解、定值的操作过程以及部分代码的实现

从本章开始,我们开始研究定值部分; 定值是什么? 了解过终端的可能都知道,定值就是保护定值,就是设定了一组参数,当终端的采样值达到这个参数的时候,终端就会做出一系列的反应。这样的目的,是为了保护电网,让电网正常运行,具体为什么这么做,不做详细的解释,如果有…

李宏毅2021春季机器学习课程视频笔记13-自注意力机制

【(强推)李宏毅2021/2022春机器学习课程】 Slide地址 一、问题引入 1.模型的输入 无论是预测视频观看人数、视频处理、语言识别&#xff0c;这些所有的model中&#xff0c;输入数据都可以视作为一个向量&#xff08;vector&#xff09;&#xff0c;模型的输出为一个数值或者一…

UDP的报文结构及注意事项

UDP的报文结构及注意事项&#x1f50e;UDP的报文结构源端口和目的端口报文长度校验和&#x1f50e;UDP的注意事项端口号报文长度校验和&#x1f50e;结尾&#x1f50e;UDP的报文结构 图片来自网络 源端口和目的端口 如果将 源IP 和 目的IP 看作是两台计算机在网络中的地址 那么…

完美解决丨#在python中,如果引用的变量未定义,则会报告NameError: name ‘变量名‘ is not defined。

NameError 在python中&#xff0c;如果引用的变量未定义&#xff0c;则会报告NameError: name 变量名 is not defined。 如下代码抛出了一个异常&#xff1a; !/usr/bin/env python -- coding:utf-8 -- print hello world print hello %s % name 报错信息如下&#xff1a; Trac…

基于springboot和ajax的简单项目 02 代码部分实现,思路 (上)

01.由于是对功能的实现&#xff0c;应该是按照功能的需要去写代码&#xff0c;所以&#xff0c;先看前端html文件的代码。 02.项目的开始界面是starter.html文件。 关键的script标签 <script type"text/javascript">$(function(){//页面加载完成之后执行doLo…