找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2252

积分

0

好友

291

主题
发表于 21 小时前 | 查看: 1| 回复: 0

本文将整理几个在嵌入式Linux项目中高频用到的C语言小片段,它们都属于“排障、打点、设备信息”的基础能力。掌握这些代码,能够帮助你快速将关键上下文信息打进日志,从而在问题定位时节省大量时间。

通用的编程思路是,无论从sysfs读取文件,还是使用ioctl获取网卡信息,其核心流程都可以概括为“选择数据源 → 读取 → 解析 → 格式化输出”。

获取系统信息流程图

1. 获取内存信息

内存信息常用来快速判断系统是否处于“内存紧张”状态。例如,在日志里定期记录 MemAvailable 的值,可以帮助你更容易地发现内存泄漏或缓存膨胀等问题的趋势。

代码片段:

#include<stdio.h>
#include<string.h>

#define PROC_MEMINFO "/proc/meminfo"

int get_meminfo_kb(long *total_kb, long *avail_kb)
{
    FILE *fp = fopen(PROC_MEMINFO, "r");
    if (NULL == fp)
    {
        printf("fopen error\n");
        return -1;
    }

    char line[256] = {0};
    long total = -1;
    long avail = -1;

    while (NULL != fgets(line, sizeof(line), fp))
    {
        if (0 == strncmp(line, "MemTotal:", 9))
        {
            sscanf(line, "MemTotal: %ld kB", &total);
        }
        else if (0 == strncmp(line, "MemAvailable:", 13))
        {
            sscanf(line, "MemAvailable: %ld kB", &avail);
        }

        if (total >= 0 && avail >= 0)
        {
            break;
        }
    }

    fclose(fp);

    if (total < 0 || avail < 0)
    {
        printf("parse error\n");
        return -1;
    }

    *total_kb = total;
    *avail_kb = avail;
    return 0;
}

int main(void)
{
    long total_kb = 0;
    long avail_kb = 0;
    get_meminfo_kb(&total_kb, &avail_kb);
    printf("mem_total = %ld kB, mem_available = %ld kB\n", total_kb, avail_kb);
    return 0;
}

注意点:

  • 不同内核版本的 /proc/meminfo 字段可能略有差异,但 MemTotalMemAvailable 这两个关键字段在绝大多数Linux发行版中都能获取到。

2. 获取CPU温度

在嵌入式设备中,定时获取CPU温度对于排查性能异常、频繁重启或降频等问题至关重要。温度往往是需要首先确认的指标之一。

代码片段:

#include<stdio.h>   
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>

#define CPU_TEMP_FILE0  "/sys/devices/virtual/thermal/thermal_zone0/temp"

struct cpu_temperature
{
    int integer_part;
    int decimal_part;
};

typedef struct cpu_temperature cpu_temperature_t;

cpu_temperature_t get_cpu_temperature(const char *_cpu_temp_file)
{
    FILE *fp = NULL;
    cpu_temperature_t cpu_temperature = {0};
    int temp = 0;

    fp = fopen(_cpu_temp_file, "r");
    if (NULL == fp)
    {
        printf("fopen file error\n");
        return cpu_temperature;
    }

    fscanf(fp, "%d", &temp);
    cpu_temperature.integer_part = temp / 1000;
    cpu_temperature.decimal_part = temp % 1000 / 100;

    fclose(fp);

    return cpu_temperature;
}

int main(int arc, char *argv[])
{
    cpu_temperature_t cpu_temperature = {0};

    cpu_temperature = get_cpu_temperature(CPU_TEMP_FILE0);
    printf("cpu_temperature = %d.%d ℃\n", cpu_temperature.integer_part, cpu_temperature.decimal_part);
    return 0;
}

运行结果:

获取CPU温度命令执行结果

注意点:

  • 路径不固定:不同硬件平台,温度传感器在sysfs中的路径(如 thermal_zoneX)可能不同。建议在实际项目中将此路径做成可配置项。
  • 单位:大多数sysfs接口提供的温度值是“毫摄氏度”,例如 42000 表示 42.0℃。
  • 采样频率:在日志中打点时,频率不宜过高(例如1~10秒一次),避免因频繁的I/O操作影响系统性能或产生干扰信号。

3. 获取文件大小

在进行文件发送、缓冲区预分配或制作进度条时,预先知道文件大小是非常必要的操作。

代码片段:

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

long get_file_size(const char *_file_name)
{
    FILE * fp = fopen(_file_name, "r");
    if (NULL == fp)
    {
        printf("fopen error\n");
        return -1;
    }

    fseek(fp, 0L, SEEK_END);
    long size = ftell(fp);
    fclose(fp);

    return size;
}

int main()
{
#define FILE_NAME  "./get_file_size"
    long file_size = get_file_size(FILE_NAME);
    printf("file_size = %ld\n", file_size);

    return 0;
}

运行结果:

获取文件大小及ls命令对比结果

注意点:

  • 此示例使用了 fseekftell 的组合,对于获取常规文件的大小来说足够直观。更严谨和通用的方式是使用 stat() 系统调用。
  • 如果需要读取二进制文件,在Windows平台建议使用 "rb" 模式打开;而在Linux上,使用 "r" 模式通常也能正常工作。

4. 获取时间戳

给日志打上精确的时间戳是最常见的用途之一。通过将多线程或多进程的事件按时间顺序串联起来,可以显著提升故障排查的效率。

代码片段:

#include<stdio.h>   
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/time.h>
#include<time.h>

long long get_sys_time_ms(void)
{
    long long time_ms = 0;
    struct timeval sys_current_time;

    gettimeofday(&sys_current_time, NULL);
    time_ms = ((long long)sys_current_time.tv_sec*1000000 + sys_current_time.tv_usec) / 1000;

    return time_ms;
}

int main(int arc, char *argv[])
{
    long long cur_sys_time = get_sys_time_ms();

    printf("cur_sys_time = %lld ms\n", cur_sys_time);

    return 0;
}

运行结果:

获取当前系统时间戳

注意点:

  • gettimeofday() 获取的是“墙上时间”(wall-clock time),可能会被NTP服务或手动修改系统时间所影响。如果你需要测量程序耗时或时间间隔,更推荐使用 clock_gettime(CLOCK_MONOTONIC, ...)
  • 在日志系统中统一时间戳的单位(如毫秒)非常重要,这有助于直接对齐和排序日志事件。

5. 获取MAC地址

MAC地址经常被用作设备的唯一标识符,或参与生成设备ID。以下代码使用 ioctl(SIOCGIFHWADDR) 从指定的网络接口获取其MAC地址。

代码片段:

#include<stdio.h>
#include<stdint.h>
#include<net/if.h>
#include<sys/socket.h>
#include<sys/ioctl.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>

int get_netif_mac(const char *_ifr_name, char *_mac)
{
    int32_t             ret = -1;
    struct ifreq        m_ifreq;
    int32_t             sock = 0;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        printf("socket err\r\n");
        goto err;
    }

    strncpy(m_ifreq.ifr_name, _ifr_name, IFNAMSIZ);
    m_ifreq.ifr_name[IFNAMSIZ - 1] = 0;

    ret = ioctl(sock,SIOCGIFHWADDR, &m_ifreq);
    if (ret < 0)
    {
        printf("ioctl err:%d\r\n",ret);
        goto err;
    }

    snprintf((char *)_mac, 32, "%02x%02x%02x%02x%02x%02x", (uint8_t)m_ifreq.ifr_hwaddr.sa_data[0],
                                                                     (uint8_t)m_ifreq.ifr_hwaddr.sa_data[1],
                                                                     (uint8_t)m_ifreq.ifr_hwaddr.sa_data[2],
                                                                     (uint8_t)m_ifreq.ifr_hwaddr.sa_data[3],
                                                                     (uint8_t)m_ifreq.ifr_hwaddr.sa_data[4],
                                                                     (uint8_t)m_ifreq.ifr_hwaddr.sa_data[5]);

    return 0;
err:
    return -1;
}

int main(int argc, char **argv)
{
    char mac_str[32] = {0};
    get_netif_mac("wlan1", mac_str);
    printf("mac = %s\n", mac_str);

    return 0;
}

运行结果:

获取MAC地址并与ifconfig结果对比

注意点:

  • 网络接口名称(如 eth0wlan0)在不同系统上可能不同,现代Linux系统可能使用 enp0s3 这类“可预测的网络接口命名”。
  • ifr_name 字段有长度限制(IFNAMSIZ),在复制字符串时必须注意确保字符串以 \0 结尾。
  • 此处创建的 socket() 仅用于获取一个文件描述符(fd)供 ioctl 使用,并不需要真正发送或接收网络数据包。如果你对网络编程有更深入的兴趣,可以进一步探索。

6. 获取IP地址

获取本机IP地址常用于状态显示、信息上报或记录在启动日志中,这能为现场故障排查提供便利。以下代码使用 ioctl(SIOCGIFADDR) 获取指定网络接口的IPv4地址。

代码片段:

#include<stdio.h>
#include<net/if.h>
#include<sys/socket.h>
#include<sys/ioctl.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>

int get_local_ip(const char *_ifr_name, char *_ip)
{
    int ret = -1;
    int sockfd;
    struct sockaddr_in sin;
    struct ifreq ifr;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        printf("socket error\n");
        return ret;
    }

    strncpy(ifr.ifr_name, _ifr_name, IFNAMSIZ);
    ifr.ifr_name[IFNAMSIZ - 1] = 0;

    if (ioctl(sockfd, SIOCGIFADDR, &ifr) < 0)
    {
        printf("ioctl error\n");
        close(sockfd);
        return ret;
    }

    memcpy(&sin, &ifr.ifr_addr, sizeof(sin));
    int ip_len = snprintf(_ip, 32, "%s", inet_ntoa(sin.sin_addr));

    close(sockfd);
    ret = ip_len;

    return ret;
}

int main(int argc, char **argv)
{
    char ip_str[32] = {0};
    get_local_ip("wlan1", ip_str);
    printf("ip = %s\n", ip_str);

    return 0;
}

运行结果:

获取IP地址并与ifconfig结果对比

注意点:

  • 此方法获取的是“网络接口当前已配置的IPv4地址”。如果该接口尚未通过DHCP或静态配置获得地址,调用将会失败。
  • 更全面(支持IPv6、多IP地址)的方法是使用 getifaddrs(),但 ioctl 版本在嵌入式环境中更为常见,且依赖的库更少。

7. 获取磁盘剩余空间

磁盘空间不足可能导致写文件失败、系统升级失败、日志丢失等一系列难以排查的问题。在写入大量数据前,先检查一次剩余空间,可以有效避免许多陷阱。这是系统编程中的一个基础但重要的环节。

代码片段:

#include<stdio.h>
#include<sys/statvfs.h>

unsigned long long get_fs_free_bytes(const char *path)
{
    struct statvfs st;
    if (0 != statvfs(path, &st))
    {
        printf("statvfs error\n");
        return 0;
    }

    return (unsigned long long)st.f_bsize * (unsigned long long)st.f_bavail;
}

int main(void)
{
    unsigned long long free_bytes = get_fs_free_bytes("/");
    printf("free_bytes = %llu\n", free_bytes);
    return 0;
}

注意点:

  • statvfs() 可以获取文件系统的块大小(f_bsize)和可用块数(f_bavail),两者相乘即可得到以字节为单位的可用空间。
  • 在嵌入式系统中,常见的挂载点包括 //data/userdata 等,调用时需要根据实际情况传入正确的路径。

8. 获取CPU使用率(采样计算)

/proc/stat 中提供的CPU时间是从系统启动开始累计的,因此计算CPU使用率的标准方法是:间隔一小段时间采样两次,然后计算差值。

代码片段:

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

#define PROC_STAT "/proc/stat"

static int read_cpu_stat(unsigned long long *idle, unsigned long long *total)
{
    FILE *fp = fopen(PROC_STAT, "r");
    if (NULL == fp)
    {
        printf("fopen error\n");
        return -1;
    }

    unsigned long long user=0,nice=0,system=0,idle_v=0,iowait=0,irq=0,softirq=0,steal=0;
    int ret = fscanf(fp, "cpu  %llu %llu %llu %llu %llu %llu %llu %llu",
                     &user,&nice,&system,&idle_v,&iowait,&irq,&softirq,&steal);
    fclose(fp);
    if (8 != ret)
    {
        printf("fscanf error\n");
        return -1;
    }

    *idle = idle_v + iowait;
    *total = user + nice + system + idle_v + iowait + irq + softirq + steal;
    return 0;
}

int main(void)
{
    unsigned long long idle1=0,total1=0,idle2=0,total2=0;
    read_cpu_stat(&idle1, &total1);
    usleep(200 * 1000);
    read_cpu_stat(&idle2, &total2);

    unsigned long long idle_delta = idle2 - idle1;
    unsigned long long total_delta = total2 - total1;
    double cpu_usage = 0;
    if (total_delta > 0)
    {
        cpu_usage = (double)(total_delta - idle_delta) * 100.0 / (double)total_delta;
    }

    printf("cpu_usage = %.2f %%\n", cpu_usage);
    return 0;
}

注意点:

  • /proc/stat 文件中 cpu 行的各项数值是从系统启动至今累计的CPU时间片(tick),因此必须间隔读取两次,利用差值来计算特定时间段内的使用率。

总结与建议

以上分享的八个C语言代码片段,涵盖了嵌入式Linux开发中获取系统关键信息的常见场景。熟练运用这些基础能力,能够极大地提升开发调试和线上问题排查的效率。

在实际工程中,建议将这些功能封装成一个统一的小工具库,并为其配备统一的错误码和日志输出宏。这样,在项目的任何地方都可以方便地复用这些经过验证的代码,减少重复劳动并提高代码质量。

希望这些实用的代码片段能对你的嵌入式开发工作有所帮助。如果你对这类C/C++底层编程技巧感兴趣,欢迎在云栈社区与其他开发者一起交流探讨,共同进步。




上一篇:从京东背调失败谈Python多线程面试:交替打印字符串详解
下一篇:云服务器付费模式选择:按量计费与包年包月的成本分析与适用场景
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-16 21:46 , Processed in 0.235317 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表