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

目录

一、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应用开发中文件操作的基础。通过合理使用这两个函数,可以实现对文件的读写操作,并有效管理文件资源。在开发过程中,务必注意错误处理和资源释放,以确保程序的稳定性和安全性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW国内外开发的区别

LabVIEW作为全球领先的图形化编程平台&#xff0c;在国内外工业测控领域均占据重要地位。本文从开发理念、技术生态、应用深度及自主可控性四个维度&#xff0c;对比分析国内外LabVIEW开发的差异&#xff0c;并结合国内实际应用场景&#xff0c;探讨其未来发展趋势。 ​ 一、开…

【大模型】阿里云百炼平台对接DeepSeek-R1大模型使用详解

目录 一、前言 二、DeepSeek简介 2.1 DeepSeek 是什么 2.2 DeepSeek R1特点 2.2.1 DeepSeek-R1创新点 2.3 DeepSeek R1应用场景 2.4 与其他大模型对比 三、阿里云百炼大平台介绍 3.1 阿里云百炼大平台是什么 3.2 阿里云百炼平台主要功能 3.2.1 应用场景 3.3 为什么选…

【DuodooBMS】给PDF附件加“受控”水印的完整Python实现

给PDF附件加“受控”水印的完整Python实现 功能需求 在实际工作中&#xff0c;许多文件需要添加水印以标识其状态&#xff0c;例如“受控”“机密”等。对于PDF文件&#xff0c;添加水印不仅可以增强文件的可识别性&#xff0c;还可以防止未经授权的使用。本代码的功能需求是…

linux的三剑客和进程处理

Linux三剑客&#xff1a; grep&#xff1a;查找 sed&#xff1a;编辑 awk&#xff1a;分析 grep - 正则表达式 [rootlocalhost ~]# grep ^a hello.txt abc grep - 忽略大小写&#xff0c;还有一些场景需要查询出来对应字符串所在的行号&#xff0c;方便我们快速在文件中定位字…

ASUS/华硕飞行堡垒9 FX506H FX706H 原厂Win10系统 工厂文件 带ASUS Recovery恢复

华硕工厂文件恢复系统 &#xff0c;安装结束后带隐藏分区&#xff0c;带一键恢复&#xff0c;以及机器所有的驱动和软件。 支持型号&#xff1a;FX506HC, FX506HE, FX506HM, FX706HC, FX706HE, FX706HM, FX506HHR, FX706HMB, FX706HEB, FX706HCB, FX506HMB, FX506HEB, FX506HC…

13.StringTable

String的基本特性 String&#xff1a;字符串&#xff0c;使用一对 ”” 引起来表示 String s1 "mogublog" ; // 字面量的定义方式String s2 new String("moxi"); string声明为final的&#xff0c;不可被继承String实现了Serializable接口&#xff1a;表…

JavaSE基本知识补充 -Map集合

目录 Map(key&#xff0c;value键值对呈现&#xff09; 1.1 Map的映射的特点 1. 2.HashMap &#xff08;键值对的业务偏多&#xff0c;而且hashmap在jdk1.7和1.8之间有所不同&#xff0c;性能做了提升&#xff0c;面试高频考点&#xff09; 1.3 Map接口的方法 方法 HashMap遍…

JAVA学习第二天

ArryList的构造方法和添加方法 01。构造方法的<>里面可以放数据类型 02. add&#xff08;&#xff09;可以直接在后面加入数据&#xff0c;也可以指定下标的插入元素。 ArrayList的常用方法 ArrayList存储对象 在Java中&#xff0c;System.out.println()可以打印基本数据…

基于窄带物联网的矿车追踪定位系统(论文+源码+实物)

1.功能设计 鉴于智能物联网的大趋势&#xff0c;本次基于窄带物联网的矿车追踪定位系统应具备以下功能&#xff1a; &#xff08;1&#xff09;实现实时定位&#xff0c;真正实现矿车随时随地定位; &#xff08;2&#xff09;定位精度高&#xff0c;采用该系统可以实现矿车在…

如何把邮件批量导出到本地

最近遇到邮箱满了的问题&#xff0c;需要把邮件批量导出到本地&#xff0c;然后清空邮箱。 问题是这个邮箱的官网&#xff0c;没有批量导出按钮&#xff0c;比较麻烦&#xff1b;总不能一封一封下载到本地&#xff0c;上万的。 找到了一个好用的工具&#xff0c;Mozilla Thun…

ICLR 2025 oral|用nuPlan + 200h物流小车数据集测试!SOTA扩散模型轨迹规划器来了

导读&#xff1a; 本文介绍了清华大学联合毫末智行、自动化所、港中文、上海交大、上海人工智能实验室最新研究成果《Diffusion-based Planning for Autonomous Driving with Flexible Guidance》——荣获ICLR 2025 Oral Presentation(仅1.8%接受率)。 该算法创新性地设计了基…

dify.ai 怎么配置链接火山引擎等云厂商的deepseek模型

要将 dify.ai 配置链接到火山引擎等云厂商的 DeepSeek 模型. 申请火山引擎的key&#xff0c;创建endpoint 添加模型 测试模型

SAP-ABAP:dialog界面中的数据块Event Block详解举例

在SAP的Dialog程序开发中&#xff0c;Event Block&#xff08;事件块&#xff09;是屏幕流逻辑&#xff08;Flow Logic&#xff09;中的关键部分&#xff0c;用于定义屏幕在特定事件触发时执行的逻辑。Event Block通常与ABAP模块&#xff08;Module&#xff09;结合使用&#x…

2025年怎么选择SEO发布工具

在如今竞争激烈的互联网时代&#xff0c;网站的流量和曝光率直接决定着一个品牌或企业的市场影响力。无论是个人博客&#xff0c;还是企业官网&#xff0c;能够有效提升SEO&#xff08;搜索引擎优化&#xff09;排名的工具&#xff0c;已成为许多网站管理者和营销人员的必备良器…

Java 进阶day14XML Dom4j 工厂模式 Base64

目录 知识点1、XML 概念XML约束 知识点2、XML解析 Dom4j&#xff08;Dom for java&#xff09;XPath 知识点3、工厂模式知识点4、Base64 知识点1、XML 概念 XML的全称为&#xff08;eXtensible Markup Language&#xff09;&#xff0c;是一种可扩展的标记语言。 XML的作用…

数据结构实验——排序算法的实现与分析

前言 到目前为止&#xff0c;8个数据结构实验在这里就全部更完啦&#xff08;撒花&#xff09;&#xff01;我那一段难忘的周二晚课时光也告一段落&#xff0c;整体来说&#xff0c;有赶课的折腾&#xff0c;有调错的崩溃&#xff0c;也有故意迟到五分钟的惬意&#xff0c;用G…

【Antv G2 5.x】饼图添加点击事件,获取当前坐标数据

// 监听 tooltip:show 事件this.chart.on(tooltip:show, (event) => {this.currentShowTooltipName = event.data.items[0].name})// 监听绘图区plot的点击事件this.chart.on(interval:click, ev => {this.$emit(chartClick, this.currentShowTooltipName);})// 监听绘图…

Oracle常用导元数据方法

1 说明 前两天领导发邮件要求导出O库一批表和索引的ddl语句做国产化测试&#xff0c;涉及6个系统&#xff0c;6千多张表&#xff0c;还好涉及的用户并不多&#xff0c;要不然很麻烦。 如此大费周折原因&#xff0c;是某国产库无法做元数据迁移。。。额&#xff0c;只能我手动导…

anolis os 8.9安装jenkins

一、系统版本 # cat /etc/anolis-release Anolis OS release 8.9 二、安装 # dnf install -y epel-release # wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo # rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.…

Python办公自动化之PDF

python版本&#xff1a;3.13.1 开发工具&#xff1a;pycharm 安装三方库&#xff1a;pypdf2 、pdfplumber、pymupdf 一、从PDF中提取文字 用Python从PDF中提取文字-CSDN博客 二、从PDF中提取表格 用Python从PDF中提取表格-CSDN博客 三、拆分和合并PDF文件 用Python拆…