【嵌入式Linux应用开发基础】lseek函数

目录

一、函数原型

二、参数说明

三、函数功能

四、返回值

五、典型应用场景

5.1. 文件随机读写

5.2. 获取文件大小

5.3. 文件截断

5.4. 创建空洞文件

5.5. 数据备份与恢复

六、关键注意事项

6.1. 错误处理必须严谨

6.2. 文件打开模式的影响

6.3. 嵌入式设备的特殊限制

6.4. 大文件支持(LFS)

6.5. 不可寻址设备的处理

6.6. 并发与原子性

6.7. 偏移量溢出风险

6.8. 性能优化

七、常见问题

7.1. lseek函数返回值判断

7.2. lseek与文件大小的关系

7.3. lseek在不同文件类型上的行为

7.4. lseek与文件描述符的关系

7.5. lseek与文件指针的同步问题


在嵌入式Linux应用开发中,lseek函数是一个非常重要的系统调用,用于移动文件描述符的读写指针。

一、函数原型

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

二、参数说明

  • fd:文件描述符,用于指定要操作的文件。通过open函数打开文件时返回的标识,代表了一个已打开的文件或设备。
  • offset:偏移量,以字节为单位,用于指定从参考点(由whence参数指定)开始移动的距离。可以是正数(表示向前移动)、负数(表示向后移动)或零(表示不移动)。
  • whence:指定偏移的参考点,取值及含义如下:
    • SEEK_SET:从文件开头开始计算偏移量,此时offset的值就是新的文件偏移量。
    • SEEK_CUR:从当前文件位置开始计算偏移量,新的文件偏移量为当前位置加上offset的值。
    • SEEK_END:从文件末尾开始计算偏移量,新的文件偏移量为文件大小加上offset的值。

三、函数功能

lseek函数主要用于设置文件的偏移量,也就是确定下一次对文件进行读写操作时的起始位置。可以实现文件指针的随机定位,方便对文件中的任意位置进行读写操作,而不仅仅是顺序读写。

四、返回值

  • 成功时,返回新的文件偏移量。
  • 出错时,返回-1,并设置errno变量以指示错误原因,常见的错误有:
    • EBADF:文件描述符fd无效。
    • ESPIPE:文件描述符fd指向的是管道、FIFO 或套接字,而不是普通文件,不支持随机访问。
    • EINVALwhence参数的值无效,或者offset的值超出了文件大小的有效范围。

五、典型应用场景

5.1. 文件随机读写

  • 场景描述:在处理大型文件时,可能只需要访问文件中的特定位置的数据,而无需从头开始顺序读取。例如,在一个数据库文件中,每条记录都有固定的长度,我们可以通过 lseek 函数直接定位到指定记录的起始位置进行读写操作,提高数据访问效率。
  • 代码示例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define RECORD_SIZE 100
#define RECORD_NUMBER 5

int main() {
    int fd = open("database.dat", O_RDWR);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 计算要访问的记录的偏移量
    off_t offset = (RECORD_NUMBER - 1) * RECORD_SIZE;
    // 使用 lseek 定位到指定记录的起始位置
    if (lseek(fd, offset, SEEK_SET) == -1) {
        perror("lseek");
        close(fd);
        return 1;
    }

    char buffer[RECORD_SIZE];
    // 读取指定记录的数据
    ssize_t bytes_read = read(fd, buffer, RECORD_SIZE);
    if (bytes_read == -1) {
        perror("read");
    } else if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("Read record %d: %s\n", RECORD_NUMBER, buffer);
    }

    close(fd);
    return 0;
}

5.2. 获取文件大小

  • 场景描述:在某些情况下,需要知道文件的大小,例如在进行文件传输时,需要根据文件大小来分配缓冲区或显示传输进度。可以使用 lseek 函数将文件指针移动到文件末尾,返回的偏移量就是文件的大小。
  • 代码示例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 将文件指针移动到文件末尾
    off_t file_size = lseek(fd, 0, SEEK_END);
    if (file_size == -1) {
        perror("lseek");
        close(fd);
        return 1;
    }

    printf("File size: %ld bytes\n", file_size);

    close(fd);
    return 0;
}

5.3. 文件截断

  • 场景描述:有时候需要将文件的大小调整为指定的长度,即截断文件。可以先使用 lseek 函数将文件指针定位到指定位置,然后使用 ftruncate 函数将文件截断到该位置。
  • 代码示例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define NEW_FILE_SIZE 50

int main() {
    int fd = open("test.txt", O_RDWR);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 将文件指针移动到指定位置
    if (lseek(fd, NEW_FILE_SIZE, SEEK_SET) == -1) {
        perror("lseek");
        close(fd);
        return 1;
    }

    // 截断文件到指定位置
    if (ftruncate(fd, NEW_FILE_SIZE) == -1) {
        perror("ftruncate");
        close(fd);
        return 1;
    }

    printf("File truncated to %d bytes\n", NEW_FILE_SIZE);

    close(fd);
    return 0;
}

5.4. 创建空洞文件

  • 场景描述:空洞文件是指文件中存在一段没有实际数据的区域,但文件系统为其预留了空间。通过 lseek 函数将文件指针移动到某个位置,然后进行写入操作,就可以在文件中创建空洞。空洞文件常用于磁盘映像、虚拟内存文件等场景。
  • 代码示例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define HOLE_OFFSET 1024
#define DATA_SIZE 100

int main() {
    int fd = open("hole_file.dat", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 将文件指针移动到指定位置创建空洞
    if (lseek(fd, HOLE_OFFSET, SEEK_SET) == -1) {
        perror("lseek");
        close(fd);
        return 1;
    }

    char data[DATA_SIZE] = "This is some data after the hole.";
    // 在空洞后写入数据
    ssize_t bytes_written = write(fd, data, DATA_SIZE);
    if (bytes_written == -1) {
        perror("write");
    }

    close(fd);
    return 0;
}

5.5. 数据备份与恢复

  • 场景描述:在进行数据备份时,可能需要跳过文件中的某些部分,只备份关键数据。通过 lseek 函数可以定位到关键数据的起始位置进行备份。在恢复数据时,同样可以使用 lseek 函数将文件指针定位到正确的位置进行数据恢复。
  • 代码示例(简单备份部分数据)
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define SKIP_OFFSET 200
#define BACKUP_SIZE 300

int main() {
    int src_fd = open("source_file.dat", O_RDONLY);
    if (src_fd == -1) {
        perror("open source file");
        return 1;
    }

    int dest_fd = open("backup_file.dat", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (dest_fd == -1) {
        perror("open backup file");
        close(src_fd);
        return 1;
    }

    // 跳过前 200 字节
    if (lseek(src_fd, SKIP_OFFSET, SEEK_SET) == -1) {
        perror("lseek");
        close(src_fd);
        close(dest_fd);
        return 1;
    }

    char buffer[BACKUP_SIZE];
    ssize_t bytes_read = read(src_fd, buffer, BACKUP_SIZE);
    if (bytes_read == -1) {
        perror("read");
    } else if (bytes_read > 0) {
        ssize_t bytes_written = write(dest_fd, buffer, bytes_read);
        if (bytes_written == -1) {
            perror("write");
        }
    }

    close(src_fd);
    close(dest_fd);
    return 0;
}

六、关键注意事项

6.1. 错误处理必须严谨

  • 返回值检查lseek返回新的偏移量(off_t类型),失败时返回(off_t)-1并设置errno

off_t new_offset = lseek(fd, offset, whence);
if (new_offset == (off_t)-1) {
    perror("lseek failed");
    // 处理错误(如重试、回退或终止)
}
  • 典型错误场景

    • EBADF:无效文件描述符。

    • ESPIPE:文件不支持寻址(如管道、套接字)。

    • EINVAL:无效的whence参数或负偏移。 

6.2. 文件打开模式的影响

  • 追加模式(O_APPEND:若文件以O_APPEND打开,写入操作会始终发生在文件末尾,即使lseek调整了偏移量。

int fd = open("file.txt", O_WRONLY | O_APPEND);
lseek(fd, 0, SEEK_SET);  // 写入仍会跳转到末尾!
write(fd, data, len);    // 数据被追加到文件结尾

6.3. 嵌入式设备的特殊限制

  • 存储介质特性

    • Flash存储器:频繁的小块写入可能降低寿命,需结合lseek优化写入策略(如批量写入)。

    • 只读文件系统:在只读介质(如UBI/CRAMFS)上,lseek可正常使用,但写入操作会失败。

  • 文件系统支持:某些嵌入式文件系统(如JFFS2)可能对SEEK_END性能较差(需遍历所有数据)。

6.4. 大文件支持(LFS)

  • 32位系统限制:默认off_t为32位,最大支持2GB偏移。需在编译时启用大文件支持:

#define _FILE_OFFSET_BITS 64  // 在包含头文件前定义
#include <unistd.h>

6.5. 不可寻址设备的处理

  • 字符设备(如串口、GPIO):调用lseek可能返回ESPIPE错误。

  • 管道/FIFO:不支持寻址,需避免在此类文件描述符上使用lseek

6.6. 并发与原子性

  • 多进程/多线程竞争lseek + read/write操作非原子,可能被其他线程打断。需用文件锁(fcntl)或pread/pwrite替代。

// 非原子操作(不安全)
lseek(fd, offset, SEEK_SET);
read(fd, buf, len);

// 原子操作(推荐)
ssize_t bytes_read = pread(fd, buf, len, offset);

6.7. 偏移量溢出风险

  • 手动计算偏移时需避免溢出(尤其在循环中):

off_t offset = lseek(fd, 0, SEEK_CUR);
offset += new_increment;  // 可能溢出!
if (offset < 0) {
    // 处理溢出
}

6.8. 性能优化

  • 减少lseek调用次数:在低速存储介质(如SD卡)上,合并相邻的lseek + write操作为单次pwrite

  • 预读/缓存策略:结合lseek实现顺序访问预读,提升读取效率。

七、常见问题

7.1. lseek函数返回值判断

问题:如何正确判断lseek函数的返回值?

解答

  • lseek函数成功时返回新的文件偏移量(相对于文件开头的字节数)。
  • 失败时返回(off_t)-1,并设置errno以指示错误类型。
  • 注意:不能用<0来判断lseek是否成功,因为文件偏移量可能是负的(特别是在某些特殊文件或设备上)。正确的做法是检查返回值是否等于(off_t)-1,并查看errno的值来确定错误类型。

7.2. lseek与文件大小的关系

问题lseek函数能否改变文件的大小?

解答

  • lseek函数本身不会改变文件的内容,只是移动读写指针的位置。
  • 但是,如果将文件指针移动到文件末尾之外的位置,并随后进行写操作,那么文件大小将被扩展到新的偏移量。
  • 仅仅移动文件指针而不写入数据并不会真正扩展文件。

7.3. lseek在不同文件类型上的行为

问题lseek函数在所有文件类型上都能正常工作吗?

解答

  • lseek函数主要适用于普通文件、设备文件等支持随机访问的文件类型。
  • 对于管道、套接字等不支持随机访问的文件类型,lseek函数的行为可能是未定义的或返回错误。
  • 在使用lseek之前,需要了解目标文件类型的特性,以确保其适用性。

7.4. lseek与文件描述符的关系

问题lseek函数是否依赖于有效的文件描述符?

解答

  • 是的,lseek函数依赖于有效的文件描述符来操作文件。
  • 如果文件描述符无效或已关闭,lseek函数将返回错误。
  • 在使用lseek之前,需要确保文件描述符是有效的,并且具有相应的读写权限。

7.5. lseek与文件指针的同步问题

问题:在使用多个文件描述符访问同一文件时,lseek操作是否会影响其他文件描述符的文件指针?

解答

  • 每个文件描述符都有自己的文件指针和偏移量。
  • 对一个文件描述符执行lseek操作不会影响其他文件描述符的文件指针和偏移量。
  • 因此,在使用多个文件描述符访问同一文件时,需要分别对每个文件描述符进行lseek操作来设置其文件指针的位置。

综上所述,lseek函数在嵌入式Linux应用开发中具有广泛的应用场景和重要性。了解并掌握其用法和注意事项,对于开发高效、稳定的嵌入式应用程序至关重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byte轻骑兵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值