• 正文
  • 相关推荐
申请入驻 产业图谱

拿来就用,Linux内核里面有哪些实用代码模块?

04/13 09:25
268
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

我是老温,一名热爱学习的嵌入式工程师,关注我,一起变得更加优秀!

嵌入式开发的工程师都知道,Linux 作为开源领域的标杆操作系统,其内核代码包含了无数经过实战检验的实用代码模块。

这些模块包含着一系列优秀的软件工程设计思想,并且经过千万台设备的长期运行和稳定性验证,大多具备良好的可移植性,并且无需经过复杂的修改,就可以适配单片机、ESP32、MPU等各类嵌入式芯片

使用Linux内核这些优秀的代码模块,可以帮助嵌入式软件工程师免去重复造轮子的麻烦,提升项目开发效率。

以下分享几个实用的代码模块,各位嵌入式工程师老铁可以按需使用。

模块一:循环冗余校验(CRC)

核心源码文件是lib/crc32.c、lib/crc16.c,CRC校验主要用于数据传输和存储中的完整性校验,在串口或网络通信、Flash存储等场景中高频使用。

Linux内核里面的CRC模块支持CRC8/CRC16/CRC32等多种校验方式,代码简洁且执行高效,不需要依赖内核里面其他组件代码,以下是CRC32的代码示例。

#include <stdint.h>// CRC32核心校验函数uint32_t crc32(uint32_t crc, const uint8_t *buf, uint32_t len){    const uint32_t poly = 0xEDB88320;    uint32_t i;    while (len--) {        crc ^= *buf++;        for (i = 0; i < 8; i++) {            crc = (crc >> 1) ^ ((crc & 1) ? poly : 0);        }    }    return ~crc;}
// 简化调用接口uint32_t crc32_calc(const uint8_t *buf, uint32_t len){    return crc32(0xFFFFFFFF, buf, len);}

注意:对于低硬件资源的单片机芯片,在移植的时候只需要删除代码里面内核特有的头文件(如linux/kernel.h),并替换为单片机芯片对应的头文件,同时调整数据类型匹配即可。

模块二:字符串处理模块

核心源码文件是lib/string.c、include/linux/string.h,包含了字符串拷贝、比较、查找、拼接等常用函数。

嵌入式软件开发经常离不开字符串操作,而C标准库里面的字符串函数在部分嵌入式环境里面可能占用资源过多,Linux内核的字符串模块更加轻量化,更能适配嵌入式场景。

#include <stdint.h>#include <string.h>// 字符串拷贝(避免缓冲区溢出,比标准strcpy更安全)char *linux_strcpy(char *dest, const char *src, uint32_t dest_len){    if (!dest || !src || dest_len == 0)        return NULL;    char *d = dest;    const char *s = src;    while (*s && (d - dest) < (dest_len - 1)) {        *d++ = *s++;    }    *d = ''; // 确保字符串结束符    return dest;}// 字符串比较int linux_strcmp(const char *a, const char *b){    while (*a && *a == *b) {        a++;        b++;    }    return *(unsigned char *)a - *(unsigned char *)b;}// 字符串查找(查找substr在str中的位置,返回首地址,无则返回NULL)char *linux_strstr(const char *str, const char *substr){    if (!*substr)        return (char *)str;    while (*str) {        const char *s1 = str;        const char *s2 = substr;        while (*s1 && *s2 && *s1 == *s2) {            s1++;            s2++;        }        if (!*s2)            return (char *)str;        str++;    }    return NULL;}

模块的核心函数(如strcpy、strcmp、strstr)与C标准库接口兼容,移植难度非常低,对于单片机移植,只需要提取所需函数,并且删除内核依赖的内存管理代码,适配芯片的字符类型即可。

模块三:位运算模块

核心源码文件位于include/asm-generic/bitops/instrumented-non-atomic.h,包含了位设置、位清除、位判断等常用操作。

在嵌入式开发里面,GPIO口控制、寄存器操作、标志位管理等场景下,经常需要直接操作二进制位,Linux内核的位运算模块封装完善,避免了手动写位操作的繁琐,并且兼容性极强。

#include <stdint.h>// 置位(将addr地址的第bit位设为1,bit从0开始)static inline void set_bit(uint8_t bit, volatile uint32_t *addr){    *addr |= (1UL << bit);}// 清除位(将addr地址的第bit位设为0,bit从0开始)static inline void clear_bit(uint8_t bit, volatile uint32_t *addr){    *addr &= ~(1UL << bit);}// 翻转位(将addr地址的第bit位翻转,0变1,1变0)static inline void toggle_bit(uint8_t bit, volatile uint32_t *addr){    *addr ^= (1UL << bit);}// 判断位(返回addr地址的第bit位的值,1返回非0,0返回0)static inline int test_bit(uint8_t bit, const volatile uint32_t *addr){    return (*addr & (1UL << bit)) != 0;}

单片机的位运算逻辑与Linux内核一致,可以直接提取位操作函数(如set_bit、clear_bit),替换头文件即可。

模块四:kfifo环形缓冲区模块

核心源码文件为include/linux/kfifo.h,用于数据的临时缓存,适用于串口、SPIIIC异步通信的场景下,可以解决数据读写不同步的问题,从而避免数据丢失。
移植时,需要提取kfifo的核心结构体和操作函数(如初始化、写入、读取、清空),删除内核特有的内存分配函数即可。

#include <stdint.h>#include <string.h>// kfifo环形缓冲区结构体typedef struct {    uint8_t *buf;      // 缓冲区数据指针    uint32_t size;     // 缓冲区大小(建议为2的幂,提升效率)    uint32_t in;       // 写入指针    uint32_t out;      // 读取指针} kfifo_t;// 初始化环形缓冲区(buf为外部定义的数组,size为缓冲区大小)int kfifo_init(kfifo_t *fifo, uint8_t *buf, uint32_t size){    if (!fifo || !buf || size == 0)        return -1;    memset(fifo, 0, sizeof(kfifo_t));    fifo->buf = buf;    fifo->size = size;    return 0;}// 写入数据到环形缓冲区,返回实际写入长度uint32_t kfifo_in(kfifo_t *fifo, const uint8_t *data, uint32_t len){    if (!fifo || !data || len == 0)        return 0;    len = (len > (fifo->size - fifo->in + fifo->out)) ? (fifo->size - fifo->in + fifo->out) : len;
    // 分两段写入(避免缓冲区溢出)    uint32_t left = fifo->size - (fifo->in % fifo->size);    if (len <= left) {        memcpy(fifo->buf + (fifo->in % fifo->size), data, len);    } else {        memcpy(fifo->buf + (fifo->in % fifo->size), data, left);        memcpy(fifo->buf, data + left, len - left);    }    fifo->in += len;    return len;}// 从环形缓冲区读取数据,返回实际读取长度uint32_t kfifo_out(kfifo_t *fifo, uint8_t *data, uint32_t len){    if (!fifo || !data || len == 0 || fifo->in == fifo->out)        return 0;    len = (len > (fifo->in - fifo->out)) ? (fifo->in - fifo->out) : len;
    // 分两段读取    uint32_t left = fifo->size - (fifo->out % fifo->size);    if (len <= left) {        memcpy(data, fifo->buf + (fifo->out % fifo->size), len);    } else {        memcpy(data, fifo->buf + (fifo->out % fifo->size), left);        memcpy(data + left, fifo->buf, len - left);    }    fifo->out += len;    return len;}// 清空环形缓冲区void kfifo_reset(kfifo_t *fifo){    if (fifo) {        fifo->in = 0;        fifo->out = 0;    }}

注意:在移植的时候,删除内核特有的内存分配函数(如kmalloc),替换对应的芯片内存分配方式(比如静态数组或者malloc)。

对于内存资源有限的单片机,可以将环形缓冲区的大小改为固定值,减少内存占用,而ESP32和MPU可以根据需求动态调整缓冲区大小,并适配不同的通信速率。

====== 我是分割线 ======

最后总结一下,以上4个代码模块都是来自Linux内核,其核心代码非常简洁,无需复杂的依赖,移植难度很低,并且适配绝大多数嵌入式芯片。

移植的核心原则是:删除内核特有的依赖(如头文件、内存分配、进程调度相关代码),适配目标芯片的数据类型和内存管理方式,保留核心的功能逻辑。

这些代码模块经过Linux内核的长期迭代优化,稳定性和执行效率远超手动编写的代码,既能节省开发时间,又能提升项目的可靠性。

无论是单片机的简单控制项目,还是ESP32、MPU等复杂的物联网项目,都可以直接“拿来就用”,真正实现嵌入式软件的高效开发!

相关推荐