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

1867

积分

0

好友

259

主题
发表于 3 天前 | 查看: 10| 回复: 0

在嵌入式Linux开发中,用户态应用程序常常需要复用一些基础功能。手动实现这些“轮子”不仅能加深对系统机制的理解,更能提升开发效率。本文将分享10个经过实战检验的C语言代码模块,涵盖从内存、文件到网络、调试等多个方面,可直接集成到你的项目中。

1. 快速获取结构体成员大小及偏移量

在涉及内存操作或协议解析时,我们常需要知道结构体成员的具体大小和偏移量。除了使用 offsetof 宏,我们也可以自己定义更直观的宏来实现。

核心代码:

// 获取结构体成员大小
#define  GET_MEMBER_SIZE(type, member)   sizeof(((type*)0)->member)

// 获取结构体成员偏移量
#define  GET_MEMBER_OFFSET(type, member)  ((size_t)(&(((type*)0)->member)))

typedef struct _test_struct0
{
    char x;
    char y;
    char z;
}test_struct0;

typedef struct _test_struct1
{
    char a;
    char c;
    short b;
    int d;
    test_struct0 e;
}test_struct1;

int main(int arc, char *argv[])
{
    printf("GET_MEMBER_SIZE(test_struct1, a) = %ld\n", GET_MEMBER_SIZE(test_struct1, a));
    printf("GET_MEMBER_SIZE(test_struct1, c) = %ld\n", GET_MEMBER_SIZE(test_struct1, c));
    printf("GET_MEMBER_SIZE(test_struct1, b) = %ld\n", GET_MEMBER_SIZE(test_struct1, b));
    printf("GET_MEMBER_SIZE(test_struct1, d) = %ld\n", GET_MEMBER_SIZE(test_struct1, d));
    printf("GET_MEMBER_SIZE(test_struct1, e) = %ld\n", GET_MEMBER_SIZE(test_struct1, e));
    printf("test_struct1 size = %ld\n", sizeof(test_struct1));

    printf("GET_MEMBER_OFFSET(a): %ld\n", GET_MEMBER_OFFSET(test_struct1, a));
    printf("GET_MEMBER_OFFSET(c): %ld\n", GET_MEMBER_OFFSET(test_struct1, c));
    printf("GET_MEMBER_OFFSET(b): %ld\n", GET_MEMBER_OFFSET(test_struct1, b));
    printf("GET_MEMBER_OFFSET(d): %ld\n", GET_MEMBER_OFFSET(test_struct1, d));
    printf("GET_MEMBER_OFFSET(e): %ld\n", GET_MEMBER_OFFSET(test_struct1, e));

    return 0;
}

理解结构体对齐与内存布局是C/C++编程中的高级技巧,对于性能优化和跨平台兼容至关重要。

运行结果:
结构体成员大小与偏移量输出示例

2. 获取CPU温度

在嵌入式系统中,监控CPU温度有助于排查因过热导致的异常或进行系统状态展示。温度信息通常通过 sysfs 文件系统暴露给用户态。

核心代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.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温度程序输出

3. 获取文件大小

在进行文件传输或内容读取前,获取文件大小是常见的需求。通过标准文件操作API可以轻松实现。

核心代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}

4. 获取时间戳

为日志添加时间戳是定位问题的关键。使用 gettimeofday 函数可以获取到微秒精度的时间。

核心代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.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;
}

运行结果:
获取系统时间戳毫秒数

5. 获取MAC地址

MAC地址常作为网络设备的唯一标识。通过 ioctl 系统调用配合 SIOCGIFHWADDR 命令可以获取指定网络接口的MAC地址。

核心代码:

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

int get_netif_mac(const char *_ifr_name, uint8_t *_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;
    }
    strcpy(m_ifreq.ifr_name, _ifr_name);
    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_ifr.ifr_hwaddr.sa_data[1],
            (uint8_t)m_ifr.ifr_hwaddr.sa_data[2],
            (uint8_t)m_ifr.ifr_hwaddr.sa_data[3],
            (uint8_t)m_ifr.ifr_hwaddr.sa_data[4],
            (uint8_t)m_ifr.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;
}

6. 获取本地IP地址

与获取MAC地址类似,获取本地IP地址也依赖于 ioctl 系统调用,但使用的是 SIOCGIFADDR 命令。

核心代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.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;
}

网络信息获取是网络/系统编程的基础,理解 ioctl 和 Socket 接口对于开发网络应用至关重要。

7. 封装基础文件操作

直接使用 fread/fwrite 有时不够便捷,根据实际业务需求进行一层封装,可以统一错误处理,提升代码健壮性。

核心代码:

#include <stdio.h>

static int file_opt_write(const char *filename, void *ptr, int size)
{
    FILE *fp;
    size_t num;

    fp = fopen(filename, "wb");
    if(NULL == fp)
    {
        printf("open %s file error!\n", filename);
        return -1;
    }

    num = fwrite(ptr, 1, size, fp);
    if(num != size)
    {
        fclose(fp);
        printf("write %s file error!\n", filename);
        return -1;
    }

    fclose(fp);

    return num;
}

static int file_opt_read(const char *filename, void *ptr, int size)
{
    FILE *fp;
    size_t num;

    fp = fopen(filename, "rb");
    if(NULL == fp)
    {
        printf("open %s file error!\n", filename);
        return -1;
    }

    num = fread(ptr, 1, size, fp);
    if(num != size)
    {
        fclose(fp);
        printf("write %s file error!\n", filename);
        return -1;
    }
    fclose(fp);

    return num;
}

typedef struct _test_struct
{
    char a;
    char c;
    short b;
    int d;
}test_struct;

int main(int arc, char *argv[])
{
#define FILE_NAME  "./test_file"

    test_struct write_data = {0};
    write_data.a = 1;
    write_data.b = 2;
    write_data.c = 3;
    write_data.d = 4;
    printf("write_data.a = %d\n", write_data.a);
    printf("write_data.b = %d\n", write_data.b);
    printf("write_data.c = %d\n", write_data.c);
    printf("write_data.d = %d\n", write_data.d);
    file_opt_write(FILE_NAME, (test_struct*)&write_data, sizeof(test_struct));

    test_struct read_data = {0};
    file_opt_read(FILE_NAME, (test_struct*)&read_data, sizeof(test_struct));
    printf("read_data.a = %d\n", read_data.a);
    printf("read_data.b = %d\n", read_data.b);
    printf("read_data.c = %d\n", read_data.c);
    printf("read_data.d = %d\n", read_data.d);

    return 0;
}

运行结果:
文件读写封装功能测试输出

8. 终端进度条

在长时间操作(如文件下载、数据处理)时,一个简单的终端进度条能极大提升用户体验。

核心代码:

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

typedef struct _progress
{
    int cur_size;
    int sum_size;
}progress_t;

void progress_bar(progress_t *progress_data)
{
    int percentage = 0;
    int cnt = 0;
    char proc[102];

    memset(proc, '\0', sizeof(proc));

    percentage = (int)(progress_data->cur_size * 100 / progress_data->sum_size);
    printf("percentage = %d %%\n", percentage);

    if (percentage <= 100)
    {
        while (cnt <= percentage)
        {
            printf("[%-100s] [%d%%]\r", proc, cnt);
            fflush(stdout);
            proc[cnt] = '#';
            usleep(100000);
            cnt++;
        }
    }
    printf("\n");
}

int main(int arc, char *argv[])
{
    progress_t progress_test = {0};
    progress_test.cur_size = 65;
    progress_test.sum_size = 100;
    progress_bar(&progress_test);

    return 0;
}

9. 格式化日志输出

一个带文件名、行号、函数名的日志宏,能快速定位问题源头,是调试必备工具。

核心代码:

#include <stdio.h>

#define LOG_D(fmt, args...) do\
                            {\
                                printf("<<File:%s Line:%d Function:%s>> ", __FILE__, __LINE__, __FUNCTION__);\
                                printf(fmt, ##args);\
                            }while(0)

int main(int arc, char *argv[])
{
    char ch = 'a';
    char str[10] = "ZhengN";
    float float_val = 10.10;
    int num = 88;
    double double_val = 10.123456;
    LOG_D("字符为 %c \n", ch);
    LOG_D("字符串为 %s \n" , str);
    LOG_D("浮点数为 %f \n", float_val);
    LOG_D("整数为 %d\n" , num);
    LOG_D("双精度值为 %lf \n", double_val);
    LOG_D("八进制值为 %o \n", num);
    LOG_D("十六进制值为 %x \n", num);

    return 0;
}

运行结果:
格式化日志输出示例

10. 启用Core Dump以辅助调试

程序异常崩溃时,Core Dump文件记录了崩溃瞬间的完整内存状态,是分析段错误等严重问题的利器。

核心代码:

#include <stdio.h>
#include <sys/resource.h>
#include <stdlib.h>
#include <string.h>

#define SHELL_CMD_CONF_CORE_FILE    "echo /var/core-%e-%p-%t > /proc/sys/kernel/core_pattern"
#define SHELL_CMD_DEL_CORE_FILE     "rm -f /var/core*"

static int enable_core_dump(void)
{
    int ret = -1;
    int resource = RLIMIT_CORE;
    struct rlimit rlim;

    rlim.rlim_cur = 1 ? RLIM_INFINITY : 0;
    rlim.rlim_max = 1 ? RLIM_INFINITY : 0;

    system(SHELL_CMD_DEL_CORE_FILE);

    if (0 != setrlimit(resource, &rlim))
    {
        printf("setrlimit error!\n");
        return -1;
    }
    else
    {
        system(SHELL_CMD_CONF_CORE_FILE);
        printf("SHELL_CMD_CONF_CORE_FILE\n");
        return 0;
    }

    return ret;
}

int main(int argc, char **argv)
{
    enable_core_dump();

    printf("==================segmentation fault test==================\n");

    int *p = NULL;
    *p = 1234;

    return 0;
}

以上10个代码模块涵盖了嵌入式Linux用户态开发的常见需求,它们结构清晰、独立性强,可以根据项目需要灵活裁剪和集成。将这些基础功能封装成可靠的“轮子”,是提升开发效率和代码质量的有效途径。更多深入的技术讨论和实战分享,欢迎访问云栈社区




上一篇:Rust嵌入式KV存储Fjall 3.0发布,新增Keyspace与原子写入
下一篇:Claude Code Workflow Studio:开源 VS Code 扩展,可视化拖拽编排AI工作流
您需要登录后才可以回帖 登录 | 立即注册

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

GMT+8, 2026-1-9 18:04 , Processed in 0.294215 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

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