【嵌入式Linux应用开发基础】open函数与close函数

news2025/3/17 5:57:41

目录

一、open函数

1.1. 函数原型

1.2 参数说明

1.3 返回值

1.4. 示例代码

二、close函数

2.1. 函数原型

2.2. 示例代码

三、关键注意事项

3.1. 资源管理与泄漏防范

3.2. 错误处理的严谨性

3.3. 标志(flags)与权限(mode)的陷阱

3.4. 并发与原子操作

3.5. 信号中断(EINTR)处理

3.6. 嵌入式设备文件的特殊问题

3.7. 调试与工具

3.8. 最佳实践清单

四、典型应用场景

4.1. 设备驱动访问

4.2. 配置文件读写

4.3. 资源独占访问

4.4. 非易失性存储操作

4.5. 动态资源管理

4.6. 临时文件操作

五、常见问题

5.1. open函数常见问题

5.2. close函数常见问题

5.3. 解决方案与建议

六、总结


在嵌入式 Linux 应用开发中,open 函数和 close 函数是文件 I/O 操作里极为基础且关键的函数。借助这两个函数,程序能够打开文件、设备文件或者创建新文件,还能在操作完成后关闭相应的文件描述符。

一、open函数

open函数用于打开一个文件,并返回一个文件描述符,用于后续的文件操作。

1.1. 函数原型

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
 
int open(const char *pathname, int flags, mode_t mode);

1.2 参数说明

  • pathname:这是一个字符串,代表要打开或者创建的文件的路径名,可以是绝对路径,也可以是相对路径。
  • flags:用于指定文件的打开方式,是一个整数类型的参数,可使用以下常见标志:
    • O_RDONLY:以只读模式打开文件。
    • O_WRONLY:以只写模式打开文件。
    • O_RDWR:以读写模式打开文件。
    • O_CREAT:若文件不存在,则创建该文件。使用此标志时,需要第三个参数 mode 来指定文件的权限。
    • O_TRUNC:若文件已经存在,并且以写模式打开,会将文件长度截断为 0。
    • O_APPEND:以追加模式打开文件,每次写入数据时都会追加到文件末尾。
  • mode:当使用 O_CREAT 标志时,此参数用于指定新创建文件的权限。权限以八进制数表示,例如 0644 表示文件所有者有读写权限,组用户和其他用户有读权限。

1.3 返回值

  • 若成功打开或创建文件,open 函数会返回一个非负整数的文件描述符,用于后续对该文件的操作。
  • 若失败,返回 -1,并且会设置 errno 来指示具体的错误类型。

1.4. 示例代码

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

int main() {
    int fd;
    // 以读写模式打开文件,如果文件不存在则创建,权限为 0644
    fd = open("test.txt", O_RDWR | O_CREAT, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    printf("文件打开成功,文件描述符: %d\n", fd);
    // 后续可以使用 fd 进行读写操作
    // ...
    return 0;
}

 

二、close函数

close函数用于关闭一个打开的文件描述符。

2.1. 函数原型

#include <unistd.h>

int close(int fd);

参数说明

  • fd:要关闭的文件描述符。

返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误。

2.2. 示例代码

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

int main() {
    int fd;
    // 以读写模式打开文件,如果文件不存在则创建,权限为 0644
    fd = open("test.txt", O_RDWR | O_CREAT, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    printf("文件打开成功,文件描述符: %d\n", fd);
    // 关闭文件
    if (close(fd) == -1) {
        perror("close");
        return 1;
    }
    printf("文件关闭成功\n");
    return 0;
}

 

三、关键注意事项

3.1. 资源管理与泄漏防范

① 严格配对open()close()

  • 嵌入式系统资源有限:文件描述符(fd)是内核管理的稀缺资源,默认上限通常较小(如1024)。未关闭的fd会导致耗尽问题,引发EMFILE错误。

  • 最佳实践

int fd = open(...);
if (fd == -1) { /* 错误处理 */ }
// ...操作文件...
if (close(fd) == -1) { /* 记录错误,但可能无法恢复 */ }
  • 使用RAII模式(如C++封装类,在析构时自动关闭)

  • 避免长生命周期持有fd:操作完成后立即关闭,而非延迟到程序退出。 

② 避免重复关闭

  • close()后的fd可能被复用:若重复关闭已关闭的fd,可能意外关闭其他合法资源。

  • 解决方案

if (fd != -1) {  // 确保fd有效后再关闭
    close(fd);
    fd = -1;     // 标记为无效,防止二次关闭
}

3.2. 错误处理的严谨性

① open()失败必须处理

  • 典型错误场景

    • ENOENT:路径不存在(如设备未加载驱动)

    • EACCES:权限不足(需检查用户/组权限或SELinux策略)

    • EBUSY:设备被占用(如另一个进程已打开)

int fd = open("/dev/i2c-0", O_RDWR);
if (fd == -1) {
    if (errno == EACCES) {
        // 提示用户需要root权限或调整udev规则
    } else if (errno == ENODEV) {
        // 检查内核是否加载了对应驱动
    }
    perror("open failed");
    exit(EXIT_FAILURE);
}

② close()失败不可忽视

  • 虽然罕见,但可能发生

    • EBADF:传入无效的fd(通常因编程错误)

    • EINTR:被信号中断(需重试关闭)

if (close(fd) == -1) {
    if (errno == EINTR) {
        // 重试关闭(极少数情况需循环处理)
        close(fd);
    }
    // 记录日志,但通常无法恢复
}

3.3. 标志(flags)与权限(mode)的陷阱

① O_CREAT必须指定mode

  • 未设置mode时权限随机:若省略mode参数,创建的文件权限由未初始化的栈数据决定。

  • 正确用法

// 创建用户可读写、组和其他只读的文件
int fd = open("log.txt", O_RDWR | O_CREAT, 0644);
    • 注意umask的影响:实际权限为mode & ~umask。若需精确控制,可在程序开始时调用umask(0)

② 设备文件的特殊标志

  • 串口设备需要O_NOCTTY:防止终端控制(防止成为控制终端): 

int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
  • 块设备的O_SYNC:确保数据写入物理存储(但降低性能)。 

3.4. 并发与原子操作

① O_EXCL防竞态条件

  • 安全创建文件:结合O_CREAT | O_EXCL可确保文件由当前进程创建,避免多进程竞争。

int fd = open("lockfile", O_RDWR | O_CREAT | O_EXCL, 0644);
if (fd == -1 && errno == EEXIST) {
    // 文件已存在,其他进程正在运行
}

② O_APPEND的多进程写入

  • 追加写入的原子性:使用O_APPEND时,内核保证每次write()前自动定位到文件末尾,避免多进程覆盖。 

// 进程A和进程B同时写日志
int fd = open("app.log", O_WRONLY | O_APPEND);

3.5. 信号中断(EINTR)处理

系统调用可能被信号打断

  • open()close()可能返回EINTR:需判断错误类型并重试。

retry:
int fd = open("/dev/sensor", O_RDWR);
if (fd == -1) {
    if (errno == EINTR) {
        goto retry;  // 重试被信号中断的调用
    }
    // 处理其他错误
}

3.6. 嵌入式设备文件的特殊问题

① 权限与udev规则

  • 默认设备文件权限受限:如/dev/gpio通常只有root可访问。

    • 解决方案

      • 以root权限运行程序(不推荐)

      • 修改udev规则,赋予普通用户访问权限:

# /etc/udev/rules.d/99-gpio.rules
SUBSYSTEM=="gpio", MODE="0666"

② 设备初始化延迟

  • 驱动加载或设备未就绪:在open()前增加重试机制。

int retries = 5;
while (retries--) {
    int fd = open("/dev/camera", O_RDWR);
    if (fd != -1) break;
    sleep(1);  // 等待驱动初始化
}

3.7. 调试与工具

① 监控文件描述符

  • 查看进程打开的fd

ls -l /proc/<PID>/fd  # 嵌入式系统可能需busybox支持
  • 检测泄漏:通过lsofcat /proc/sys/fs/file-nr观察系统级fd使用情况。

② 使用valgrind检测泄漏

  • 动态分析工具(需交叉编译): 

valgrind --track-fds=yes ./embedded_app

3.8. 最佳实践清单

  • 始终检查返回值open()close()都可能失败。

  • 使用O_CLOEXEC标志:避免fork后子进程继承fd(防止意外操作):

int fd = open("file", O_RDWR | O_CLOEXEC);
  • 最小化fd持有时间:操作完成后立即关闭。

  • 多线程环境加锁:若共享fd,确保read()/write()原子性。

  • 文档化设备依赖:记录设备路径、所需flags和权限要求。

掌握这些细节能显著提升嵌入式Linux应用的健壮性,尤其在资源紧张和高可靠性的场景中。

四、典型应用场景

4.1. 设备驱动访问

嵌入式系统通过设备文件(如/dev/gpio/dev/i2c-1)与硬件交互,open()用于获取设备句柄,close()用于释放资源。

// 示例:打开GPIO设备
int fd = open("/dev/gpiochip0", O_RDWR);
if (fd < 0) {
    perror("Failed to open GPIO device");
    return -1;
}

// 操作GPIO...
write(fd, &value, sizeof(value));

close(fd); // 必须关闭以释放内核资源

设备文件可能需要root权限(O_RDWR)。 

4.2. 配置文件读写

嵌入式设备常通过配置文件(如/etc/config.cfg)存储参数,需用open()获取文件描述符进行读写。

// 读取配置文件
int fd = open("/etc/config.cfg", O_RDONLY);
char buffer[256];
read(fd, buffer, sizeof(buffer));
close(fd);

// 写入配置
fd = open("/etc/config.cfg", O_WRONLY | O_TRUNC);
write(fd, new_config, strlen(new_config));
close(fd);

关键参数

  • O_TRUNC:清空文件内容后写入。

  • O_CREAT:文件不存在时创建(需指定权限,如0644)。 

4.3. 资源独占访问

通过O_EXCL标志确保设备或文件的独占访问,避免多进程冲突。

// 创建并独占访问一个锁文件
int fd = open("/var/run/app.lock", O_CREAT | O_EXCL | O_RDWR, 0644);
if (fd < 0) {
    if (errno == EEXIST) {
        printf("Another instance is running.\n");
        exit(1);
    }
}
// 程序运行期间保持文件打开

4.4. 非易失性存储操作

嵌入式设备频繁操作Flash或EEPROM时,需确保数据完整性。

// 写入数据到Flash(强制同步写入)
int fd = open("/mnt/flash/data.bin", O_WRONLY | O_SYNC);
write(fd, data, data_size);
close(fd); // 确保数据落盘

关键参数O_SYNC:每次写操作等待物理写入完成(防止断电丢失数据)。

4.5. 动态资源管理

在资源受限的嵌入式系统中,及时close()释放文件描述符避免泄漏。

while (1) {
    int fd = open("/dev/sensor", O_RDONLY);
    if (fd < 0) break;
    // 读取传感器数据...
    close(fd); // 每次循环必须关闭!
}

陷阱:忘记close()会导致文件描述符耗尽,系统崩溃。

4.6. 临时文件操作

配合unlink()实现临时文件自动清理。

int fd = open("/tmp/temp_data.tmp", O_CREAT | O_RDWR, 0600);
unlink("/tmp/temp_data.tmp"); // 删除文件链接
// 文件内容仍可通过fd访问...
close(fd); // 文件实际被删除

五、常见问题

5.1. open函数常见问题

  • 权限不足(Permission denied):当尝试打开一个文件或设备时,如果没有足够的权限,open函数将返回错误。这通常发生在尝试以写模式打开只读文件或尝试访问受保护的设备文件时。
  • 文件或目录不存在(No such file or directory):如果提供的文件路径不正确或文件/目录确实不存在,open函数将返回此错误。
  • 文件是一个目录(File is a directory):尝试以文件的方式打开一个目录时,open函数将返回此错误。在Linux中,目录不是以普通文件的方式打开的,而是使用特定的系统调用(如opendir)来访问。
  • 打开文件数量超过系统限制(Too many open files):每个进程在Linux系统中都有一个打开文件数量的限制。如果尝试打开的文件数量超过了这个限制,open函数将返回错误。
  • 文件正在被其他进程占用(File is in use by another process):如果尝试打开一个已经被其他进程以独占方式打开的文件,可能会遇到此问题。这通常发生在尝试写入一个被其他进程锁定的文件时。
  • 无效参数(Invalid argument):如果传递给open函数的参数无效(如无效的文件路径、不正确的标志组合等),函数将返回此错误。
  • 只读文件系统(Read-only file system):尝试在只读文件系统上写入文件时,将返回此错误。

5.2. close函数常见问题

  • 没有句柄(No such file or directory,但表现为close函数错误):尝试关闭一个无效或已经关闭的文件描述符时,close函数将返回错误。通常发生在文件描述符被误用或重复关闭时。
  • 文件描述符超出范围:如果尝试关闭一个超出当前进程文件描述符范围的文件描述符,close函数将返回错误。
  • 资源忙碌(Resource busy):在极少数情况下,如果尝试关闭一个仍在被使用的资源(如一个正在被其他线程或进程访问的文件),可能会遇到此问题。然而,这种情况在标准的close函数使用中较为罕见,更多发生在底层资源管理和驱动程序开发中。

5.3. 解决方案与建议

  • 检查权限:确保在尝试打开文件或设备时具有足够的权限。可以使用ls -l命令查看文件权限,并使用chmodchown命令调整权限和所有权。
  • 验证文件路径:在调用open函数之前,验证文件路径的正确性。可以使用绝对路径而不是相对路径来避免路径错误。
  • 避免误用文件描述符:仔细管理文件描述符的使用,避免重复打开和关闭同一个文件描述符。可以使用文件描述符表来跟踪打开的文件。
  • 处理错误:在调用openclose函数时,始终检查返回值以处理可能的错误。可以使用errno变量来获取更详细的错误信息。
  • 优化资源使用:监控和管理打开文件的数量,避免超过系统限制。在不再需要文件时及时关闭它们以释放资源。
  • 调试与测试:使用调试工具(如gdb)和日志记录来跟踪和诊断openclose函数使用中的问题。

六、总结

openclose函数是嵌入式Linux应用开发中文件操作的基础。通过合理使用这两个函数,可以实现对文件的读写操作,并有效管理文件资源。在开发过程中,务必注意错误处理和资源释放,以确保程序的稳定性和安全性。

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

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

相关文章

在实体机和wsl2中安装docker、使用GPU

正常使用docker和gpu&#xff0c;直接命令行安装dcoker和&#xff0c;nvidia-container-toolkit。区别在于&#xff0c;后者在于安装驱动已经cuda加速时存在系统上的差异。 1、安装gpu驱动 在实体机中&#xff0c;安装cuda加速包&#xff0c;我们直接安装 driver 和 cuda 即可…

Unity3D实现显示模型线框(shader)

系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、效果展示👉二、第一种方式👉二、第二种方式👉壁纸分享👉总结👉前言 在 Unity 中显示物体线框主要基于图形渲染管线和特定的渲染模式。 要显示物体的线框,通常有两种常见的方法:一种是利用内置的渲染…

VAD端到端系列梳理以及阅读

0. 简介 最近VAD v2论文出来了&#xff0c;又掀起了一波该系列模型的热点。我们先看一下蒋博的文章&#xff0c;然后再来看一下《VADv2: End-to-End Vectorized Autonomous Driving via Probabilistic Planning》这篇文章&#xff0c;代码目前还没开源&#xff0c;可以期待一波…

在vmd中如何渲染透明水分子

1.设置背景为白色 依次点击Graphics>>Colors... 2. 改变渲染模式 依次点击Display>>rendermode>>GLSL 3. 渲染水分子 选中水分子&#xff0c;显色方式改为ColorID, 编号10的颜色&#xff1b; 选择材质为GlassBubble; 绘图方式为QuickSurf. 若水盒子显示效…

MybatisPlus常用增删改查

记录下MybatisPlus的简单的增删改查 接口概述 Service和Mapper区别 Mapper简化了单表的sql操作步骤&#xff08;CRUD&#xff09;&#xff0c;而Serivce则是对Mapper的功能增强。 Service虽然加入了数据库的操作&#xff0c;但还是以业务功能为主&#xff0c;而更加复杂的SQL…

常用电路(过压保护、电流/电压采集)

过压保护电路 输入电压使用电源&#xff08;36V&#xff09;或者typec&#xff08;20V&#xff09;&#xff0c;需要过压保护电路处理输入再连接到CH224K&#xff0c;保证输入不高于最大获取电压20V MOS管导通条件为栅源极有压差&#xff0c;一般为5-10V 三极管导通条件为基极…

干部监督系统“三色”预警的构建与应用

在新时代背景下&#xff0c;强化干部监督、提升管理水平已成为推动国家治理体系和治理能力现代化的关键一环。干部监督系统“三色”预警机制作为一种创新的管理工具&#xff0c;通过智能化、可视化的手段&#xff0c;实现了对干部行为的高效管理。本文将详细探讨干部监督系统“…

Zabbix-Trigger中的time函数坑

问题描述 由于功能需求&#xff0c;需要限制trigger的报警时间&#xff0c;所以加了如下的报警限制 and (time()>010000 and time()<045959)但是事与愿违&#xff0c;报警的时间总是对不上 但是&#xff0c;Zabbix设置的时区就是北京时间&#xff0c;应该是没有问题的…

9 数据流图

9 数据流图 9.1数据平衡原则 子图缺少处理后的数据操作结果返回前端应用以及后端数据库返回操作结果到数据管理中间件。 9.2解题技巧 实件名 存储名 加工名 数据流

python项目相关

遇到的问题 解决 Python 模块导入路径问题 问题描述 在运行 Python 文件时&#xff0c;可能会遇到以下错误&#xff1a; ModuleNotFoundError: No module named utils原因&#xff1a; Python 的模块导入机制依赖于当前工作目录和 sys.path 中的路径。当直接运行某个文件时…

基于轨道角动量自由度在空间频域中的可选择特性

将光的轨道角动量自由度应用到全息领域&#xff0c;证实了轨道角动量全息&#xff1b;实现了高维轨道角动量复用全息技术&#xff0c;获得了高安全的全息加密和超高容量全息信息系统。 1、轨道角动量自由度在全息中的引入 如图1所示&#xff0c;当全息图中没有携带轨道角动量的…

机器人学的AGI实现路径:从专用智能到通用认知的跨越

文章目录 引言:机器人学的范式革命一、AGI与机器人学的融合现状1.1 传统机器人系统的局限1.2 AGI技术为机器人学带来的变革1.3 关键里程碑案例二、AGI机器人的核心技术栈2.1 多模态感知融合2.2 认知架构设计2.3 具身认知实现路径三、AGI机器人的实现路径3.1 阶段式发展路线3.2…

香港中文大学 Adobe 推出 MotionCanvas:开启用户掌控的电影级图像视频创意之旅。

简介&#xff1a; 亮点直击 将电影镜头设计引入图像到视频的合成过程中。 推出了MotionCanvas&#xff0c;这是一种简化的视频合成系统&#xff0c;用于电影镜头设计&#xff0c;提供整体运动控制&#xff0c;以场景感知的方式联合操控相机和对象的运动。 设计了专门的运动条…

基于STM32的学习环境控制系统设计

&#x1f91e;&#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是学习环境控制。 设备的详细功能见网盘中的文章《21、基于STM32的学习环境控制系统设计》&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1uWSZX2zbZwy9sY…

snort3.0-ubuntu18.04 64入侵检测安装与使用ailx10ailx10​​知乎知识会员

在日常生活中&#xff0c;很多人怀疑自己的手机、电脑被监控了&#xff0c;担心自己的隐私泄漏&#xff0c;实际上最佳的检测方式就是终端检测&#xff0c;也就是EDR&#xff0c;但是就是有那么多的人在网上大放厥词&#xff0c;说任何EDR杀毒软件都检测不到监控&#xff0c;毕…

使用亚马逊针对 PyTorch 和 MinIO 的 S3 连接器进行模型检查点处理

2023 年 11 月&#xff0c;Amazon 宣布推出适用于 PyTorch 的 S3 连接器。适用于 PyTorch 的 Amazon S3 连接器提供了专为 S3 对象存储构建的 PyTorch 数据集基元&#xff08;数据集和数据加载器&#xff09;的实现。它支持用于随机数据访问模式的地图样式数据集和用于流式处理…

408-数据结构

数据结构在学什么&#xff1f; 1.用代码把问题信息化 2.用计算机处理信息 ch1 数据&#xff1a;数据是信息的载体&#xff0c;是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。 ch2 //假设线性表…

spring cloud 使用 webSocket

1.引入依赖,(在微服务模块中) <!-- Spring WebSocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency> 2.新建文件 package com.ruoyi.founda…

安科瑞 Acrel-2000ES:解锁储能管理新高度,引领能源未来!

安科瑞 崔丽洁 在能源转型的关键时期&#xff0c;高效的储能管理成为众多企业和项目的核心需求。今天&#xff0c;就给大家介绍一款储能管理的 “神器”—— 安科瑞 Acrel-2000ES 储能能量管理系统。 安科瑞电气可是行业内的 “明星企业”&#xff0c;2003 年成立&#xff0c;2…

基于Django以及vue的电子商城系统设计与实现

基于Django以及vue的电子商城系统设计与实现 引言 随着电子商务的快速发展&#xff0c;越来越多的企业和个人选择搭建线上商城&#xff0c;以提供更加便捷的购物体验。本文基于Python开发了一套电子商城系统&#xff0c;后端采用Django框架&#xff0c;前端使用Vue.js&#x…