当前位置: 首页 > news >正文

linux应用:文件描述符、lseek

文件描述符

概念

在 Linux 系统中,当一个程序打开一个文件或者建立一个网络连接等 I/O 操作时,内核会为其分配一个非负整数,这个整数就是文件描述符(File Descriptor)。可以简单将其理解为一个索引值,指向内核中一个代表该打开文件或 I/O 资源的结构体。每个进程都有自己独立的文件描述符表,记录着该进程所打开的所有文件描述符及其对应的文件信息。

作用

文件操作标识:在进行文件的读写、关闭等操作时,需要使用文件描述符来指定具体操作的对象。例如,使用read函数读取文件内容,第一个参数就是文件描述符,它明确了从哪个文件读取数据。
资源管理:内核通过文件描述符来管理系统中的各种 I/O 资源。通过文件描述符,内核能够追踪每个打开资源的状态,确保资源的正确使用和释放,避免资源泄漏等问题。
实现 I/O 复用:在网络编程等场景中,常常需要同时处理多个 I/O 操作。文件描述符使得可以利用诸如select、poll、epoll等 I/O 复用机制,通过监控一组文件描述符的状态变化,实现高效地处理多个并发的 I/O 事件,提升系统的并发处理能力。

常见文件描述符

标准输入(STDIN_FILENO):其值通常为 0,代表键盘输入。程序通过这个文件描述符从标准输入设备读取数据。例如,scanf函数默认从标准输入读取用户输入的数据,底层实际上就是通过标准输入文件描述符进行操作。
标准输出(STDOUT_FILENO):值通常为 1,用于将程序的输出发送到屏幕等标准输出设备。像printf函数输出的内容就是通过标准输出文件描述符显示在终端上。
标准错误(STDERR_FILENO):值通常为 2,专门用于输出程序运行过程中产生的错误信息到标准错误设备,一般也是显示在终端上。这样可以将正常输出和错误输出分开,便于调试和查看程序运行状态。

文件描述符的使用

打开文件获取文件描述符:使用open函数可以打开一个文件并返回一个新的文件描述符。

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

int main() {
    int fd;
    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    // 后续可使用fd进行文件操作
    close(fd);
    return 0;
}

open函数尝试以只读方式打开名为test.txt的文件,如果成功则返回一个文件描述符fd,否则返回 -1 并通过perror打印错误信息。

文件读写操作:

有了文件描述符后,可以使用read和write函数进行文件读写。

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

#define BUFFER_SIZE 1024

int main() {
    int fd, n;
    char buffer[BUFFER_SIZE];
    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    n = read(fd, buffer, BUFFER_SIZE);
    if (n == -1) {
        perror("read");
        close(fd);
        exit(EXIT_FAILURE);
    }
    write(STDOUT_FILENO, buffer, n);
    close(fd);
    return 0;
}

read函数从文件描述符fd对应的文件中读取最多BUFFER_SIZE字节的数据到buffer数组中,write函数将读取到的数据通过标准输出文件描述符STDOUT_FILENO输出到屏幕上。
3. 关闭文件描述符:使用完文件描述符后,要及时通过close函数关闭,以释放系统资源。如上述代码中的close(fd)操作,防止文件描述符泄漏导致系统资源浪费。

文件描述符复制(dup 函数)

dup函数用于复制一个现有的文件描述符,返回一个新的文件描述符,这个新的文件描述符与原文件描述符指向相同的文件或 I/O 资源,并且共享文件偏移量等状态信息。

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

oldfd是要被复制的现有文件描述符。如果复制成功,dup函数返回一个新的文件描述符,该文件描述符是当前进程未使用的最小整数值。如果失败,返回 -1 并设置errno以指示错误原因。

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

int main() {
    int fd, new_fd;
    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    new_fd = dup(fd);
    if (new_fd == -1) {
        perror("dup");
        close(fd);
        exit(EXIT_FAILURE);
    }
    // 现在fd和new_fd都指向同一个文件
    close(fd);
    close(new_fd);
    return 0;
}

通过dup函数复制了fd文件描述符得到new_fd,它们都指向test.txt文件。之后可以通过new_fd继续对该文件进行操作,就如同使用fd一样,并且关闭其中一个文件描述符不会影响另一个对文件的访问,直到两个文件描述符都被关闭,对应的文件资源才会真正被释放。

fcntl 函数介绍:

fcntl函数是一个功能强大的文件控制函数,它可以对已打开的文件描述符进行各种操作,包括复制文件描述符、改变文件状态标志、设置和获取文件的访问控制权限等。

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

fd:需要操作的文件描述符。
cmd:指定要执行的操作命令,常见的命令如下:
F_DUPFD:复制文件描述符fd,返回一个新的文件描述符,新描述符是当前进程中未使用的最小整数值。与dup函数类似,但fcntl通过F_DUPFD提供了更多控制选项。

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    int fd, new_fd;
    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    new_fd = fcntl(fd, F_DUPFD, 0);
    if (new_fd == -1) {
        perror("fcntl");
        close(fd);
        exit(EXIT_FAILURE);
    }
    // 现在fd和new_fd都指向同一个文件
    close(fd);
    close(new_fd);
    return 0;
}

fcntl函数使用F_DUPFD命令复制了文件描述符fd,第三个参数 0 表示新文件描述符的最小取值为 0(通常使用 0,由系统选择未使用的最小整数值)。

F_GETFL:获取文件描述符fd的状态标志,返回值是open函数中设置的文件状态标志,如O_RDONLY、O_WRONLY、O_RDWR、O_APPEND等的按位或结果。

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    int fd, flags;
    fd = open("test.txt", O_RDONLY | O_APPEND);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl");
        close(fd);
        exit(EXIT_FAILURE);
    }
    if (flags & O_APPEND) {
        printf("文件以追加模式打开\n");
    }
    close(fd);
    return 0;
}

通过fcntl函数的F_GETFL命令获取了文件描述符fd的状态标志,并检查是否设置了O_APPEND标志。
F_SETFL:设置文件描述符fd的状态标志。可以修改文件的打开模式,如添加或移除O_APPEND、O_NONBLOCK等标志,但不能修改O_RDONLY、O_WRONLY、O_RDWR这些初始打开模式。

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    int fd, flags;
    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl");
        close(fd);
        exit(EXIT_FAILURE);
    }
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("fcntl");
        close(fd);
        exit(EXIT_FAILURE);
    }
    // 文件描述符fd现在处于非阻塞模式
    close(fd);
    return 0;
}

先获取文件描述符fd的当前状态标志,然后通过按位或操作添加O_NONBLOCK标志,最后使用F_SETFL命令将修改后的标志重新设置到文件描述符上。

总结

文件描述符是 Linux 系统中进行 I/O 操作的核心概念,它为进程与系统资源之间的交互提供了统一的接口。熟练掌握文件描述符的使用,对于编写高效、稳定的 Linux 应用程序,特别是涉及文件处理和网络编程等方面的程序,具有至关重要的意义

lseek

函数定义与头文件

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

#include <unistd.h>是包含lseek函数声明的头文件。在使用lseek函数前,必须确保包含该头文件,否则编译器将无法识别lseek函数。

fd参数是文件描述符,它是一个非负整数,用于标识一个已打开的文件。文件描述符通常由open、creat等函数返回,通过它,操作系统能够准确地定位到对应的文件,进而对文件执行各种操作。

offset参数表示偏移量,它是一个有符号整数(类型为off_t)。偏移量的具体含义取决于whence参数的值。当whence为SEEK_SET时,offset表示从文件开头开始的偏移量;当whence为SEEK_CUR时,offset表示从当前文件偏移量位置开始的偏移量;当whence为SEEK_END时,offset表示从文件末尾开始的偏移量。

whence参数用于指定偏移的基准位置,它有三个预定义的常量值:
SEEK_SET:表示偏移量从文件开头开始计算。
SEEK_CUR:表示偏移量从当前文件指针位置开始计算。
SEEK_END:表示偏移量从文件末尾开始计算。

lseek函数返回一个off_t类型的值,它表示文件指针的新位置。如果lseek函数执行成功,返回的是从文件开头到新位置的字节偏移量。如果执行失败,lseek函数将返回-1,并且会设置全局变量errno来指示错误原因。常见的错误原因包括无效的文件描述符、偏移量超出文件系统支持的范围等。

实现文件的随机读写

通过lseek函数将文件指针移动到指定位置,然后使用read或write函数进行读写操作,从而实现文件的随机读写。例如,在一个包含学生信息的文件中,每个学生信息占用固定的字节数,通过lseek计算出第 n 个学生信息在文件中的位置,然后读取或修改该学生的信息

// 假设每个学生信息占100字节,读取第5个学生的信息
off_t studentOffset = lseek(fd, 4 * 100, SEEK_SET);
if (studentOffset!= -1) {
    char studentData[100];
    ssize_t bytesRead = read(fd, studentData, sizeof(studentData));
    if (bytesRead == -1) {
        perror("read");
    }
}

获取文件大小

通过将文件指针移动到文件末尾(SEEK_END),然后获取此时的偏移量,即可得到文件的大小

off_t fileSize = lseek(fd, 0, SEEK_END);
if (fileSize!= -1) {
    printf("The size of the file is %ld bytes.\n", fileSize);
}

创建空洞文件

空洞文件是指文件在磁盘上占用的空间大于其实际存储的数据量。通过lseek函数将文件指针移动到一个较大的偏移量位置,然后执行write操作,就可以创建一个空洞文件。

// 创建一个10MB的空洞文件
off_t largeOffset = lseek(fd, 10 * 1024 * 1024, SEEK_SET);
if (largeOffset!= -1) {
    char data = 'A';
    ssize_t bytesWritten = write(fd, &data, 1);
    if (bytesWritten == -1) {
        perror("write");
    }
}

注意

文件打开方式的影响:lseek函数的行为可能会受到文件打开方式的影响。例如,以追加模式(O_APPEND)打开的文件,每次调用lseek函数设置文件指针位置后,实际的写入操作仍会从文件末尾开始,因为O_APPEND标志会强制文件指针在每次写入前移动到文件末尾。
设备文件的支持:并非所有的设备文件都支持lseek函数。例如,像字符设备(如终端设备)通常不支持随机访问,调用lseek函数可能会返回错误。在对设备文件使用lseek函数前,需要了解设备的特性和支持的操作。
文件系统的限制:不同的文件系统对文件的最大偏移量、文件大小等有不同的限制。在使用lseek函数时,需要确保设置的偏移量在文件系统支持的范围内,否则可能会导致未定义行为或错误。

相关文章:

  • RAGflow 无法加载Embedding模型
  • Java阻塞队列深度解析:高并发场景下的安全卫士
  • AI绘画软件Stable Diffusion详解教程(7):图生图基础篇
  • 数字电路基础——逻辑门
  • 获取视频第一帧兼容ios
  • 数据库复习(第五版)- 第八章 存储过程
  • Docker save命令怎么用
  • 大型WLAN组网部署(Large scale WLAN network deployment)
  • PDF转JPG(并去除多余的白边)
  • Linux开启命令审计功能记录用户的每一步操作
  • python基础课程整理--元组的基础
  • 期望、方差和协方差
  • 【VTK】三种面切片数据 加载模型 scalars设置颜色透明度 加载raw 医学数据
  • QT——线程
  • SQL_语法
  • 逐行拆解 C 语言:数据类型、变量
  • 【初探数据结构】线性表——链表(二)带头双向循环链表(详解与实现)
  • MySQL 架构、索引优化、DDL解析、死锁排查
  • 在ubuntu20.4中如何创建一个虚拟环境(亲测有效)
  • ubuntu20.04已安装 11.6版本 cuda,现需要通过源码编译方式安装使用 cuda 加速的 ffmpeg 步骤
  • 北京比较大的网站建设公司/广东seo点击排名软件哪家好
  • 北辰正方建设集团有限公司官方网站/怎么搭建属于自己的网站
  • 合肥地区网站制作/今日军事头条
  • 做网站建设业务员好吗/海外新闻app
  • 设计网站做多大合适/企业seo排名哪家好
  • 苏州新海通网站建设/广州百度首页优化