文件描述符

当调用 oepn() 函数打开或创建一个文件时,内核会向进程返回一个文件描述符,用于代指被打开的文件,所有执行 I/O 操作的系统调用都是通过文件描述符来索引到对应的文件。

每一个被打开的文件在同一进程中都对应一个唯一的文件描述符,当文件被关闭后,它对应的文件描述符就会被释放。

每个线程的前三个文件描述符(0 ~ 2)是固定的,分别表示标准输入(0)、标准输出(1)和标准错误(2),所以我们创建或打开文件时分配的文件描述符最小为 3。

open() 打开文件

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

参数介绍

  • pathname: 要操作的文件,可以填文件路径(绝对路径或相对路径都行),如果 pathname 是一个符号链接,会对其进行解引用。

  • flags: 操作文件时的一些标志,open() 函数有很多种标志,下面介绍一些常用的:

除了上面这些,还有很多其他标志,如 O_ASYNC、OASYNC、O_DSYNC 等。

如果要同时使用多个标志,中间使用按位或运算符(‘|’) 连接。

  • mode: 该参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE(创建临时文件) 标志时才有效。

该参数支持的选项包括:

如果要同时使用多个标志,中间使用按位或运算符(‘|’) 连接。

返回值
成功时返回文件描述符,文件描述符是一个非负整数,失败时返回 -1。

write() 写文件

write() 函数可以向已经打开的文件写入数据,函数原型如下:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数介绍

  • fd: 文件描述符
  • buf: 要写入的数据
  • count: 要写入的字节数

返回值

成功时返回写入的字节数,失败时返回 -1。如果返回的字节数小于参数 count,可能是因为一些意外错误,比如磁盘空间已满。数据写入后,文件指针也会跟着偏移(比如写入前文件指针偏移量为 0,写入 100 字节后,文件指针偏移量将会变成 100)。

read() 读文件

read() 函数的作用是从已经打开的文件中读取数据,其原型如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数介绍

  • fd: 文件描述符
  • buf: 读出的数据缓存区
  • count: 要读取的字节数

返回值

返回成功读取到的字节数,实际读取字节数可能会小于参数 count(比如文件指针指向的位置后的字节数小于 count),也有可能是 0(文件指针已经指向文件末尾)。和 write() 函数类似,每次使用 read() 读取数据,文件指针会移动到上一次 read() 结束时的位置,比如一次性读完文件的字节数,再次使用 read(),将会返回 0,因为文件指针已经指向文件末尾。

close() 关闭文件

close() 函数可以关闭一个已经打开的文件,原型如下:

#include <unistd.h>
int close(int fd);

参数介绍

  • fd: 文件描述符

返回值

返回 0 表示关闭成功,返回 -1 表示关闭失败。

在很多代码中,程序一直在一个 while(1) 死循环中执行文件操作,而 close() 函数在循环之外,也就是说程序并不会执行 close(),但这种情况文件的数据并不会意外丢失。这是因为在 Linux 系统中,当一个进程结束时,内核会自动关闭它所有打开的文件。但即便如此,在不需要使用文件时调用 close() 是一个良好的编程习惯,因为它能增强程序的可读性和可靠性。

lseek() 文件指针偏移

每个打开的文件,系统都会记录下它们读写位置偏移量,可以把它成为读写偏移量,它记录了文件当前读写位置。每次调用 read() 或者 write() 函数时,就会从读写偏移量所在位置进行数据读写。文件头的偏移量为 0。

lseek() 函数可以调整读写偏移量,该函数原型如下:

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

参数介绍

  • fd: 文件描述符
  • offset: 偏移量,单位为字节,可正可负,正表示右移,负表示左移
  • whence: 偏移量的参考值,参考值可以为以下宏定义的其中之一:
  • SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处;
  • SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头开始算);
  • SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处

返回值

成功时返回从文件头部开始算起的位置偏移量(字节),错误时返回 -1。

一个简单的练习

实现的功能:新建一个文件 src_file.txt,向里面写入 1K Byte 数据,然后打开一个现有文件 dest_file.txt,获取该文件的大小,然后将 src_file.txt 的内容追加拷贝到 dest_file.txt 文件中,最后再获取一次 dest_file.txt 文件的大小。

我的测试代码(仅供参考)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

#define SRC_FILE "./src_file.txt"
#define DEST_FILE "./dest_file.txt"

char buff[1024];

int main()
{
	int i = 0;
	int src_fd, dest_fd;
	int cnt = 0, offset = 0;
	char tmp[256];

	/* 填充 buff */
	for(i = 0; i < 1024; i++)
	{
		buff[i] = 'a' + i % 26;
	}

	/* 创建 src_file.txt */
	src_fd = open(SRC_FILE, O_RDWR|O_CREAT, S_IRWXU);

	if(src_fd < 0)
	{
		printf("%s open failed.\n", SRC_FILE);
		return -1;
	}

	/* 向 src_file.txt 写入数据 */
	cnt = write(src_fd, buff, sizeof(buff));

	printf("Successfully wrote %d bytes data to %s.\n", cnt, SRC_FILE);

	/* 打开 dest_file.txt */
	dest_fd = open(DEST_FILE, O_RDWR);

	if(dest_fd < 0)
	{
		printf("%s open failed.\n", DEST_FILE);
		return -1;
	}

	/* 获取 dest_file.txt 文件末尾的文件指针偏移量, 同时也将指针指向文件尾部 */
	offset = lseek(dest_fd, 0, SEEK_END);

	printf("The size of %s is %d bytes.\n", DEST_FILE, offset);

	/* 将 src_file.txt 的文件指针指向文件头 */
	lseek(src_fd, 0, SEEK_SET);

	/* 将 src_file.txt 的数据写入到 dest_file.txt 中 */
	while(1)
	{
		cnt = read(src_fd, tmp, sizeof(tmp));
		if(cnt > 0)
			write(dest_fd, tmp, sizeof(tmp));
		else
			break;
	}

	/* 获取 dest_file.txt 文件末尾的文件指针偏移量 */
	offset = lseek(dest_fd, 0, SEEK_END);

	printf("The size of %s is %d bytes.\n", DEST_FILE, offset);

	/* 关闭文件 */
	close(src_fd);
	close(dest_fd);
}

程序运行结果:

Linux C 应用编程学习笔记——(2)文件 I/O 基础-LMLPHP

07-04 19:29