You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

读取sysfs(hwmon)传感器数据偶发阻塞调用耗时过长问题排查

问题背景

我有一台运行Linux(BusyBox)的ARM设备,需要频繁读取存储电压值的文件 /sys/class/hwmon/hwmon0/device/in7_input,该文件位于 /sys/class/hwmon/ 路径下的虚拟文件系统 sysfs 中。

通过SSH连接到设备读取该文件,返回的数据格式示例如下:

root:~# cat /sys/class/hwmon/hwmon0/device/in7_input
10345
root:~# cat /sys/class/hwmon/hwmon0/device/in7_input
10250

通常读取该文件的耗时约为 1 ms,但偶发读取耗时会达到 1 sec。由于该问题仅在现场环境低概率出现,测试台架上始终无法复现。问题发生时核查过CPU利用率,通常低于 60%,无法确定为何偶发读取该文件会出现类似阻塞调用的现象,导致函数执行时长远超预期。

目前不确定该问题是源于虚拟文件系统 sysfs 的底层异常,还是编写的 readVoltage() 函数未实现完全非阻塞。
以下是调整后的代码片段:

/******************************************************************************

                              Online C++ Compiler.
               Code, Compile, Run and Debug C++ program online.
Write your code in this editor and press "Run" button to compile and execute it.

*******************************************************************************/

#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

uint64_t GetClockCount(void);
float calculateLoopTime(uint32_t tUsec1, uint32_t tUsec2);

void readVoltage()
{

    static const char VoltageFile[] = "/sys/class/hwmon/hwmon0/device/in7_input";
    static int VoltageFD = -1;

    if (-1 == VoltageFD)
    {
        VoltageFD = open(VoltageFile, O_RDONLY | O_NONBLOCK);
    }

        if (-1 == VoltageFD)
        {
            std::cout << "couldn't open FD for " << VoltageFile << std::endl;
        }
        else
        {
            static const size_t bufSize = 15;
            char buffer[bufSize];

            fd_set input;
            FD_ZERO(&input);
            FD_SET(VoltageFD, &input);

            struct timeval to;
            to.tv_sec = 0;
            to.tv_usec = 0;
            int n = 0;
            n = select(VoltageFD + 1, &input, NULL, NULL, &to);

            if (n > 0)
            {
                ssize_t bytes_read = pread(VoltageFD, buffer, bufSize, 0);

                if (bytes_read > 0)
                {
                    float voltage = (atof(buffer) / 1000.0f);

                    std::cout << "voltage= " << voltage << std::endl;
                }
            }
        }
    }


int main()
{
    uint32_t start_time = GetClockCount();
    
    readVoltage();
    
    uint32_t end_time = GetClockCount();
    
    float time_diff = calculateLoopTime(start_time, end_time);
    
    std::cout << "function took " << time_diff << " ms to execute" << std::endl;
    
    
    return 0;
}

uint64_t GetClockCount(void)
{
    struct timespec now;
    if (clock_gettime(CLOCK_MONOTONIC, &now))
        return 0;
    return static_cast<uint64_t>(now.tv_sec) * 1000000 + now.tv_nsec / 1000;
}


float calculateLoopTime(uint32_t tUsec1, uint32_t tUsec2)
{
    float time_diff = 0;

    if (tUsec1 != tUsec2)
    {
        uint32_t time_diff_temp = 0;

        if (tUsec2 > tUsec1)
        {
            time_diff_temp = (tUsec2 - tUsec1);
        }
    
        // Scale from microseconds to milliseconds
        time_diff = static_cast<float>(time_diff_temp) / 1000;
    }

    return time_diff;
}

该代码可在在线IDE运行,但无法读取到有效电压值,因为在线IDE环境中不存在 /sys/class/hwmon/hwmon0/device/in7_input 文件。


根因分析

你写的非阻塞逻辑对sysfs文件完全无效,偶发1秒延迟不是用户态代码阻塞,是内核态硬件访问被卡住:

  • O_NONBLOCK、select() 这套逻辑只对真正支持非阻塞操作的文件类型生效,比如socket、管道、普通块设备文件。sysfs是虚拟文件系统,文件读操作回调直接对接硬件驱动,根本不实现非阻塞语义。你给open加O_NONBLOCK、调用零超时的select()没有任何实际作用,只要调用pread触发读操作,内核就会直接执行驱动代码和ADC硬件通信,整个过程完全同步,不会因为非阻塞标记提前返回。
  • 1秒级延迟基本都是驱动超时重试逻辑触发的:hwmon对应的ADC驱动读取电压值时,如果I2C/SPI总线出现偶发电磁干扰、仲裁失败、硬件应答超时,驱动会按照预设超时时间重试,常见单次超时就是1秒量级。这个过程中进程处于内核态睡眠状态,CPU利用率不会升高,和你观察到的CPU低于60%的现象完全吻合。现场环境比测试台架电磁干扰强,硬件通信偶发出错,才会出现低概率复现的情况。
修复方案
  • 删掉无意义的非阻塞冗余逻辑:不用给sysfs文件加O_NONBLOCK,也不用调用select()判断可读,这两步对sysfs文件没有实际作用,直接调用pread读取即可,减少无意义的系统调用开销。
  • 不要在时序敏感的高优先级业务线程里直接读sysfs:单独开一个低优先级后台线程轮询读取电压值,结果存在全局原子变量中,业务线程需要电压值时直接读原子变量的缓存值,就算驱动读取时卡1秒,也不会阻塞主业务流程。
  • 定位根因直接抓内核日志:现场复现时抓取dmesg日志,查看是否有ADC驱动、I2C/SPI总线的超时报错信息,就能确认是否为硬件通信重试导致的延迟。如果是总线干扰问题,从硬件加屏蔽、调整驱动重试参数、降低总线速率几个方向优化即可,用户态代码无法解决内核驱动层的硬件访问阻塞。
  • 修复现有代码的类型bug:GetClockCount()返回值是uint64_t类型,但存储时间的start_time、end_time定义为uint32_t,运行时间长了会出现整数截断溢出,导致耗时计算错误,需要把这两个变量的类型改成uint64_t。

内容的提问来源于stack exchange,提问作者Programmer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 08:51:26