文件描述符
当调用 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);
}
程序运行结果: