心练场景一:C语言与Linux内核基础

心练场:几个基础但容易踩的坑。

幻境第一轮:C语言陷阱与内存管理

#include <string.h>
#include <stdio.h>
​
void test_strcpy() {
    char *src = "Hello World";
    char dest[10];
    strcpy(dest, src);
    printf("dest: %s\n", dest);
}
​
void test_memcpy() {
    char buffer[20] = "0123456789";
    memcpy(buffer+2, buffer, 10);
    printf("buffer: %s\n", buffer);
}
​
int main() {
    test_strcpy();
    test_memcpy();
    return 0;
}

练气心法

/**
 * @brief 演示strcpy的缓冲区溢出隐患
 * 
 * 问题分析:
 * 1. src长度为11字节 + 1个'\0'结尾符 = 12字节
 * 2. dest只有10字节,发生缓冲区溢出
 * 3. 导致栈被破坏,可能引发程序崩溃或安全漏洞
 * 
 * 安全写法:应使用strncpy或strlcpy
 */
void test_strcpy() {
    char *src = "Hello World";      /* 源字符串: 11个字符 + '\0' = 12字节 */
    char dest[10];                   /* 目标缓冲区: 只有10字节,太小了! */
    
    /* BUG: 缓冲区溢出!12字节拷贝到10字节空间 */
    strcpy(dest, src);               /* 危险操作:没有长度检查 */
    
    printf("dest: %s\n", dest);      /* 可能打印正常,但栈已经被破坏 */
}
​
/**
 * @brief 演示memcpy的内存重叠问题
 * 
 * 问题分析:
 * 1. memcpy假设源和目标区域不重叠
 * 2. buffer+2 和 buffer 区域重叠 (buffer[2]~buffer[11] 与 buffer[0]~buffer[9])
 * 3. 结果是未定义行为,可能得到错误的结果
 * 
 * 正确做法:重叠区域应使用memmove
 */
void test_memcpy() {
    char buffer[20] = "0123456789";  /* 初始化: 0 1 2 3 4 5 6 7 8 9 \0 ... */
    
    /* BUG: 源和目标内存重叠!
     * 源: buffer[0]~buffer[9]   -> "0123456789"
     * 目标: buffer[2]~buffer[11] 
     * memcpy是逐字节拷贝,如果从前向后拷贝,会覆盖源数据
     */
    memcpy(buffer+2, buffer, 10);    /* 危险操作:内存重叠 */
    
    /**
     * 预期结果应该是: "010123456789"
     * 但memcpy可能得到: "010101010101"(被覆盖后的错误结果)
     */
    printf("buffer: %s\n", buffer);
}
​
/**
 * @brief 正确的实现方式
 * 
 * 设计模式:防御性编程
 * - 总是检查缓冲区长度
 * - 重叠内存使用memmove
 */
void test_correct_version() {
    char *src = "Hello World";
    char dest[20];                    /* 足够的空间:20 >= 12 */
    
    /* 安全写法1:strncpy - 指定最大拷贝长度 */
    strncpy(dest, src, sizeof(dest)-1);
    dest[sizeof(dest)-1] = '\0';      /* 确保字符串结尾 */
    
    /* 安全写法2:使用snprintf(推荐) */
    snprintf(dest, sizeof(dest), "%s", src);
    
    /* 重叠内存使用memmove */
    char buffer[20] = "0123456789";
    memmove(buffer+2, buffer, 10);     /* memmove能正确处理重叠 */
}

幻境第二轮:内核链表操作

心练场:链表操作container_of

// 这是Linux内核的链表结构
struct list_head {
    struct list_head *next, *prev;
};
​
// 这是一个具体的设备结构体
struct my_device {
    int id;
    char name[32];
    struct list_head list;  // 链表节点
    int irq;
    void __iomem *base;
};

练气心法

/**
 * @def container_of
 * @brief 通过结构体成员的指针获取整个结构体的指针
 * 
 * 设计模式:侵入式链表
 * - 不需要为每个数据结构单独实现链表操作
 * - 数据结构包含链表节点,而非链表节点包含数据
 * 
 * 实现原理:
 * 1. ((type *)0)->member:获取member在type中的偏移量
 * 2. (char *)(ptr) - offset:指针运算得到结构体起始地址
 * 3. (type *)( (char *)(ptr) - offsetof(type, member) )
 * 
 * @param ptr   成员指针
 * @param type  结构体类型
 * @param member 成员名称
 */
#define container_of(ptr, type, member) ({ \
    const typeof(((type *)0)->member) *__mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); \
})
​
/**
 * @brief 遍历设备链表的示例
 * 
 * 内核源码位置:include/linux/list.h
 * 使用场景:驱动probe时遍历已注册的设备,或处理所有设备的中断
 */
void iterate_devices(void) {
    struct my_device *dev;
    struct list_head *pos;
    
    /* 方法1:使用list_for_each_entry(推荐) */
    list_for_each_entry(dev, &device_list, list) {
        /**
         * 宏展开相当于:
         * for (pos = (device_list)->next; 
         *      pos != &(device_list); 
         *      pos = pos->next) {
         *     dev = container_of(pos, struct my_device, list);
         * }
         */
        pr_info("Device %d: %s, IRQ: %d\n", 
                dev->id, dev->name, dev->irq);
    }
}
​
/**
 * @brief 动态添加设备的函数
 * 
 * @param id 设备ID
 * @param name 设备名称
 * @return struct my_device* 创建的设备指针,失败返回NULL
 * 
 * 性能考虑:
 * - GFP_KERNEL 可能睡眠,不能在中断上下文使用
 * - 如果必须在原子上下文中,使用 GFP_ATOMIC
 */
struct my_device *create_device(int id, const char *name) {
    /* 1. 分配内存 */
    struct my_device *dev = kmalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev) {
        pr_err("Failed to allocate my_device\n");
        return ERR_PTR(-ENOMEM);
    }
    
    /* 2. 初始化成员 */
    dev->id = id;
    strncpy(dev->name, name, sizeof(dev->name) - 1);
    dev->name[sizeof(dev->name) - 1] = '\0';
    
    /* 3. 初始化链表节点 */
    INIT_LIST_HEAD(&dev->list);
    
    /* 4. 硬件资源暂未映射,先设为NULL */
    dev->base = NULL;
    dev->irq = -1;
    
    return dev;
}
​
/**
 * @brief 将设备添加到全局链表
 * 
 * @param dev 设备指针
 * @return 0成功,负值失败
 */
int register_device(struct my_device *dev) {
    if (!dev) {
        return -EINVAL;
    }
    
    /* 添加到链表尾部 */
    list_add_tail(&dev->list, &device_list);
    
    /**
     * 思考:为什么不直接用 list_add?
     * - list_add_tail 保持添加顺序,便于调试
     * - list_add 添加到头部,适合优先级队列
     */
    
    return 0;
}

幻境第三轮:内存对齐与大小端

心练场:内存对齐的问题sizeof

struct data_packet {
    char cmd;        // 1 byte
    int  length;     // 4 bytes
    short flag;      // 2 bytes
    char *buffer;    // 4 bytes (32位系统)
} __attribute__((packed));
​
struct data_packet_normal {
    char cmd;
    int  length;
    short flag;
    char *buffer;
};

练气心法

/**
 * @brief 内存对齐分析
 * 
 * 默认对齐规则(自然对齐):
 * - char: 按1字节对齐
 * - short: 按2字节对齐
 * - int: 按4字节对齐
 * - pointer: 按4字节对齐(32位)
 * 
 * 结构体总大小必须是最大对齐成员的整数倍
 */
struct data_packet_normal {
    char cmd;        /* offset 0, size 1 */
    /* 这里有3字节填充 (offset 1-3) 以保证length按4对齐 */
    int  length;     /* offset 4, size 4 */
    short flag;      /* offset 8, size 2 */
    /* 这里有2字节填充 (offset 10-11) 以保证buffer按4对齐 */
    char *buffer;    /* offset 12, size 4 */
    /* 总大小:16字节 (1+3+4+2+2+4 = 16) */
};
​
/**
 * @brief 打包的结构体(取消对齐)
 * 
 * __attribute__((packed)) 告诉编译器取消对齐优化
 * 按实际需要空间紧凑排列
 * 
 * 优点:节省内存,适合网络传输
 * 缺点:访问未对齐的成员可能导致性能下降或异常
 */
struct data_packet {
    char cmd;        /* offset 0, size 1 */
    int  length;     /* offset 1, size 4 (未对齐!) */
    short flag;      /* offset 5, size 2 */
    char *buffer;    /* offset 7, size 4 (未对齐!) */
    /* 总大小:11字节 (1+4+2+4 = 11) */
} __attribute__((packed));
​
/**
 * @brief 处理大小端转换
 * 
 * 使用场景:与硬件通信或网络协议时
 * 硬件通常是大端,x86是小端
 */
void handle_endianness(struct data_packet *pkt) {
    /**
     * 从硬件读取的数据可能是大端格式
     * 需要转换为主机字节序
     */
    
    /* 假设从寄存器读取的值是大端的 */
    u32 reg_val = readl(ioaddr);  /* 假设硬件返回 0x12345678 */
    
    /* 在大端机器上,内存布局是:12 34 56 78 */
    /* 在小端机器上,内存布局是:78 56 34 12 */
    
    /* 转换为主机字节序 */
    pkt->length = be32_to_cpu(reg_val);
    
    /**
     * 内核提供的转换函数:
     * cpu_to_le32()  - 主机转小端
     * cpu_to_be32()  - 主机转大端
     * le32_to_cpu()  - 小端转主机
     * be32_to_cpu()  - 大端转主机
     */
}
​
/**
 * @brief 最佳实践:定义明确的协议格式
 * 
 * 设计模式:明确指定字节序的对齐结构
 */
struct network_packet {
    __u8  cmd;           /* 1 byte,始终不变 */
    __be32 length;       /* 明确指定为大端32位 */
    __be16 flags;        /* 明确指定为大端16位 */
    __be32 buffer_addr;  /* 明确指定为大端32位 */
} __attribute__((packed));  /* 网络协议通常打包 */

幻境第四轮:内核上下文与锁机制

心练场:中断上下文和进程上下文

练气心法

/**
 * @file drivers/misc/sample_driver.c
 * @brief 演示中断上下文和进程上下文的区别
 * 
 * 内核源码位置:随意,这是示例代码
 */
​
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
​
struct my_driver_data {
    void __iomem *base;
    int irq;
    
    /* 保护共享数据的锁 */
    spinlock_t lock;           /* 用于中断上下文 */
    struct mutex mutex;        /* 用于进程上下文 */
    
    wait_queue_head_t wq;      /* 等待队列 */
    int data_ready;
};
​
/**
 * @brief 中断处理程序(上半部)
 * @param irq 中断号
 * @param dev_id 设备ID(注册时传入的data)
 * @return irqreturn_t
 * 
 * 执行上下文:中断上下文
 * 限制:
 * 1. 不能睡眠(不能调用schedule()、wait_event()等)
 * 2. 不能获取信号量(mutex)
 * 3. 不能访问用户空间内存
 * 4. 不能执行长时间操作
 * 
 * 设计模式:快速处理,延迟执行
 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_driver_data *data = dev_id;
    unsigned long flags;
    u32 status;
    
    /* 1. 读取硬件状态寄存器 */
    status = readl(data->base + REG_STATUS);
    
    /**
     * 2. 使用自旋锁保护共享数据
     * 为什么不能用mutex?因为mutex可能导致睡眠
     * 自旋锁在等待期间会忙等(但不会睡眠)
     */
    spin_lock_irqsave(&data->lock, flags);
    
    /* 更新共享数据 */
    if (status & INT_DATA_READY) {
        data->data_ready = 1;
        
        /* 3. 唤醒等待队列中的进程 */
        wake_up_interruptible(&data->wq);
    }
    
    spin_unlock_irqrestore(&data->lock, flags);
    
    /* 4. 清除中断(硬件相关) */
    writel(status, data->base + REG_STATUS);
    
    /* 如果工作量大,可以触发下半部 */
    if (status & INT_NEED_SLOW_PATH) {
        /* 触发tasklet或workqueue */
    }
    
    return IRQ_HANDLED;
}
​
/**
 * @brief 中断下半部 - tasklet
 * 
 * 执行上下文:软中断上下文
 * 限制:虽然可以睡眠,但仍有部分限制
 */
static void my_tasklet_handler(unsigned long data)
{
    struct my_driver_data *drv_data = (struct my_driver_data *)data;
    
    /* 可以调用一些可能睡眠的函数,但有限制 */
    /* 比如可以调用kmalloc(GFP_KERNEL)?不行!tasklet不能睡眠 */
    
    /* 实际处理耗时操作 */
}
​
/**
 * @brief read 函数(进程上下文)
 * @param filp 文件指针
 * @param buf 用户空间缓冲区
 * @param count 读取大小
 * @param offp 偏移量
 * @return ssize_t
 * 
 * 执行上下文:进程上下文
 * 特点:
 * 1. 可以睡眠
 * 2. 可以访问用户空间内存
 * 3. 可以持有互斥锁
 */
static ssize_t my_read(struct file *filp, char __user *buf,
                       size_t count, loff_t *offp)
{
    struct my_driver_data *data = filp->private_data;
    int ret;
    
    /**
     * 1. 使用mutex保护(可以睡眠)
     * 如果是中断上下文,这里就会死锁或崩溃
     */
    if (mutex_lock_interruptible(&data->mutex)) {
        return -ERESTARTSYS;
    }
    
    /* 2. 等待数据准备好(可以睡眠) */
    while (!data->data_ready) {
        mutex_unlock(&data->mutex);
        
        /* 进程在这里睡眠,让出CPU */
        ret = wait_event_interruptible(data->wq, data->data_ready);
        if (ret) {
            return ret;
        }
        
        if (mutex_lock_interruptible(&data->mutex)) {
            return -ERESTARTSYS;
        }
    }
    
    /* 3. 复制数据到用户空间 */
    if (copy_to_user(buf, &data->data, min(count, sizeof(data->data)))) {
        mutex_unlock(&data->mutex);
        return -EFAULT;
    }
    
    data->data_ready = 0;
    mutex_unlock(&data->mutex);
    
    return min(count, sizeof(data->data));
}
​
/**
 * @brief likely/unlikely 的使用
 * 
 * 设计模式:分支预测优化
 * 告诉编译器哪种情况更可能发生
 */
static int process_data(struct my_driver_data *data)
{
    u32 val = readl(data->base + REG_DATA);
    
    /**
     * 大多数情况下数据是有效的
     * likely() 告诉编译器条件为真的可能性大
     */
    if (likely(val & VALID_MASK)) {
        /* 正常处理路径 - 放在if块的前面,优化缓存行 */
        data->normal_count++;
        return process_normal_data(val);
    } else {
        /**
         * 极少出现的错误情况
         * unlikely() 告诉编译器条件为假的可能性大
         * 编译器会把这段代码放到远处,优化指令缓存
         */
        data->error_count++;
        pr_err_once("Invalid data: 0x%x\n", val);  /* 只打印一次 */
        return -EINVAL;
    }
}
​
/**
 * @brief 驱动初始化函数
 * @return 0成功,负值失败
 */
static int __init my_driver_init(void)
{
    struct my_driver_data *data;
    
    /* 分配内存 */
    data = kzalloc(sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    
    /* 初始化锁 */
    spin_lock_init(&data->lock);     /* 自旋锁初始化 */
    mutex_init(&data->mutex);         /* 互斥锁初始化 */
    
    init_waitqueue_head(&data->wq);   /* 等待队列初始化 */
    
    /* 映射硬件寄存器 */
    data->base = ioremap(0x10000000, 0x1000);
    if (!data->base) {
        kfree(data);
        return -ENOMEM;
    }
    
    /* 注册中断 */
    if (request_irq(data->irq, my_irq_handler,
                    IRQF_SHARED, "my_device", data)) {
        iounmap(data->base);
        kfree(data);
        return -EBUSY;
    }
    
    return 0;
}
​
module_init(my_driver_init);

幻境第五轮:Linux驱动框架实战

心练场:I2C触摸屏驱动

练气心法

/**
 * @file drivers/input/touchscreen/my_ts.c
 * @brief I2C触摸屏驱动
 * 
 * 设计模式分析:
 * 1. 平台驱动模式 (Platform Driver) - 将设备与驱动分离
 * 2. 输入子系统 (Input Subsystem) - 标准输入事件上报
 * 3. 设备树匹配 (Device Tree) - 硬件描述与驱动代码分离
 * 4. 中断处理 + 工作队列 - 快速响应+慢速处理分离
 */
​
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/pm.h>
​
/* 寄存器定义 */
#define MYTS_REG_STATUS      0x00
#define MYTS_REG_TOUCH_DATA  0x01
#define MYTS_REG_RESET       0x80
#define MYTS_REG_FW_VERSION  0xF0
​
/* 最大支持触摸点数 */
#define MYTS_MAX_TOUCHES     10
​
/**
 * @struct myts_data
 * @brief 触摸屏设备私有数据结构
 * 
 * 设计模式:面向对象思想,封装所有设备相关的数据
 */
struct myts_data {
    struct i2c_client *client;      /**< I2C客户端 */
    struct input_dev *input;        /**< 输入设备 */
    struct device *dev;              /**< 设备指针(用于调试) */
    
    int irq;                         /**< 中断号 */
    struct work_struct work;         /**< 工作队列(处理触摸数据) */
    
    /* 硬件参数 */
    u32 max_x;                       /**< X轴最大值 */
    u32 max_y;                       /**< Y轴最大值 */
    bool swap_xy;                    /**< 是否需要交换XY */
    bool invert_x;                    /**< 是否需要翻转X */
    bool invert_y;                    /**< 是否需要翻转Y */
    
    /* 状态信息 */
    u8 fw_version[4];                 /**< 固件版本 */
    int touch_count;                  /**< 当前触摸点数 */
    
    /* 同步保护 */
    struct mutex lock;                /**< 互斥锁 */
    bool suspended;                    /**< 是否已挂起 */
};
​
/**
 * @brief 从设备树解析配置参数
 * @param ts 触摸屏设备数据
 * 
 * 设备树示例:
 * my_touchscreen@38 {
 *     compatible = "vendor,my-ts";
 *     reg = <0x38>;
 *     interrupt-parent = <&gpio1>;
 *     interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
 *     max-x = <800>;
 *     max-y = <480>;
 *     swap-xy;
 * };
 */
static void myts_parse_dt(struct myts_data *ts)
{
    struct device_node *np = ts->client->dev.of_node;
    u32 val;
    
    if (!np) {
        dev_warn(ts->dev, "No device tree node, using defaults\n");
        ts->max_x = 800;
        ts->max_y = 480;
        return;
    }
    
    /**
     * of_property_read_u32 - 读取32位整型属性
     * 返回值:0成功,负值失败
     */
    if (of_property_read_u32(np, "max-x", &val) == 0) {
        ts->max_x = val;
        dev_info(ts->dev, "max-x = %d\n", ts->max_x);
    }
    
    if (of_property_read_u32(np, "max-y", &val) == 0) {
        ts->max_y = val;
        dev_info(ts->dev, "max-y = %d\n", ts->max_y);
    }
    
    /**
     * of_property_read_bool - 读取布尔属性
     * 存在即为true
     */
    ts->swap_xy = of_property_read_bool(np, "swap-xy");
    ts->invert_x = of_property_read_bool(np, "invert-x");
    ts->invert_y = of_property_read_bool(np, "invert-y");
    
    dev_info(ts->dev, "swap=%d, invert_x=%d, invert_y=%d\n",
             ts->swap_xy, ts->invert_x, ts->invert_y);
}
​
/**
 * @brief 通过I2C读取触摸数据
 * @param ts 设备数据
 * @param buf 数据缓冲区
 * @param len 读取长度
 * @return 0成功,负值失败
 * 
 * 性能考虑:
 * - I2C传输可能较慢,但触摸数据量小
 * - 使用重试机制应对硬件不稳定
 */
static int myts_i2c_read(struct myts_data *ts, u8 *buf, int len)
{
    struct i2c_msg msg;
    int ret;
    int retry = 3;
    
    while (retry--) {
        /**
         * i2c_master_recv - 从I2C设备接收数据
         * 封装了i2c_transfer
         */
        ret = i2c_master_recv(ts->client, buf, len);
        if (ret == len) {
            return 0;  /* 成功 */
        }
        
        dev_dbg(ts->dev, "I2C read failed (%d), retry %d\n", ret, retry);
        msleep(10);  /* 等待10ms后重试 */
    }
    
    dev_err(ts->dev, "I2C read failed after retry\n");
    return -EIO;
}
​
/**
 * @brief 处理原始触摸数据
 * @param ts 设备数据
 * @param data 原始数据缓冲区
 * 
 * 设计模式:数据转换与归一化
 * 将硬件原始数据转换为标准输入事件
 */
static void myts_process_touch(struct myts_data *ts, u8 *data)
{
    int i;
    int touch_count = data[0] & 0x0F;  /* 前4位是触摸点数 */
    u8 *touch_data = data + 1;          /* 触摸数据起始位置 */
    
    /**
     * 输入协议:MT Protocol B
     * - 使用input_mt_slot()区分多个触摸点
     * - 使用input_mt_report_slot_state()报告触摸状态
     */
    for (i = 0; i < touch_count && i < MYTS_MAX_TOUCHES; i++) {
        u8 *point = touch_data + i * 5;  /* 每个点5字节 */
        u16 x, y;
        u8 id = point[0] & 0x0F;         /* 触摸ID */
        u8 pressure = point[4];           /* 压力值 */
        
        /* 解析坐标(假设协议:2字节X,2字节Y) */
        x = (point[1] << 8) | point[2];
        y = (point[3] << 8) | point[4];
        
        /**
         * 坐标转换
         * 根据设备树配置进行翻转/交换
         */
        if (ts->swap_xy) {
            swap(x, y);
        }
        if (ts->invert_x) {
            x = ts->max_x - x;
        }
        if (ts->invert_y) {
            y = ts->max_y - y;
        }
        
        /**
         * input_mt_slot - 选择当前触摸点槽位
         * input_mt_report_slot_state - 报告触摸状态
         * input_report_abs - 报告绝对坐标
         */
        input_mt_slot(ts->input, id);
        input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
        input_report_abs(ts->input, ABS_MT_POSITION_X, x);
        input_report_abs(ts->input, ABS_MT_POSITION_Y, y);
        input_report_abs(ts->input, ABS_MT_PRESSURE, pressure);
    }
    
    /* 上报同步事件 */
    input_mt_sync_frame(ts->input);
    input_sync(ts->input);
}
​
/**
 * @brief 工作队列处理函数
 * @param work 工作项
 * 
 * 设计模式:中断下半部处理
 * 为什么需要工作队列?
 * - I2C传输可能较慢,不适合在中断上下文中进行
 * - 工作队列允许睡眠,可以调用I2C API
 */
static void myts_work_handler(struct work_struct *work)
{
    struct myts_data *ts = container_of(work, struct myts_data, work);
    u8 buffer[64];  /* 足够存储所有触摸数据 */
    int ret;
    
    mutex_lock(&ts->lock);
    
    if (ts->suspended) {
        mutex_unlock(&ts->lock);
        return;
    }
    
    /* 读取触摸数据 */
    ret = myts_i2c_read(ts, buffer, sizeof(buffer));
    if (ret == 0) {
        /* 处理触摸数据 */
        myts_process_touch(ts, buffer);
    }
    
    mutex_unlock(&ts->lock);
    
    /* 重新使能中断 */
    enable_irq(ts->irq);
}
​
/**
 * @brief 中断处理函数
 * @param irq 中断号
 * @param dev_id 设备数据指针
 * @return IRQ_HANDLED
 * 
 * 设计模式:快速中断处理
 * 只做必要的最小工作,然后触发下半部
 */
static irqreturn_t myts_irq_handler(int irq, void *dev_id)
{
    struct myts_data *ts = dev_id;
    
    /**
     * 为什么要先disable_irq?
     * - 防止在处理过程中又触发中断
     * - 工作队列完成后再enable_irq
     */
    disable_irq_nosync(irq);
    
    /* 调度工作队列 */
    schedule_work(&ts->work);
    
    return IRQ_HANDLED;
}
​
/**
 * @brief 输入设备初始化
 * @param ts 设备数据
 * @return 0成功,负值失败
 */
static int myts_input_init(struct myts_data *ts)
{
    struct input_dev *input;
    int ret;
    
    /* 分配输入设备 */
    input = devm_input_allocate_device(&ts->client->dev);
    if (!input) {
        dev_err(ts->dev, "Failed to allocate input device\n");
        return -ENOMEM;
    }
    
    ts->input = input;
    
    /* 设置设备信息 */
    input->name = "My TouchScreen";
    input->id.bustype = BUS_I2C;
    input->dev.parent = &ts->client->dev;
    
    /* 设置支持的输入事件 */
    __set_bit(EV_ABS, input->evbit);
    __set_bit(EV_KEY, input->evbit);
    
    /* 设置单点触摸相关ABS */
    input_set_abs_params(input, ABS_X, 0, ts->max_x, 0, 0);
    input_set_abs_params(input, ABS_Y, 0, ts->max_y, 0, 0);
    input_set_abs_params(input, ABS_PRESSURE, 0, 255, 0, 0);
    
    /* 设置多点触摸相关ABS */
    input_set_abs_params(input, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0);
    input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0);
    input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0);
    
    /**
     * input_mt_init_slots - 初始化多点触摸槽位
     * 参数:设备、最大点数、标志
     */
    ret = input_mt_init_slots(input, MYTS_MAX_TOUCHES, 0);
    if (ret) {
        dev_err(ts->dev, "Failed to init MT slots: %d\n", ret);
        return ret;
    }
    
    /* 注册输入设备 */
    ret = input_register_device(input);
    if (ret) {
        dev_err(ts->dev, "Failed to register input device: %d\n", ret);
        return ret;
    }
    
    return 0;
}
​
/**
 * @brief I2C设备probe函数
 * @param client I2C客户端
 * @param id 设备ID
 * @return 0成功,负值失败
 * 
 * 设计模式:设备驱动分离
 * 当设备树中匹配的设备和驱动绑定时调用
 */
static int myts_probe(struct i2c_client *client)
{
    struct myts_data *ts;
    int ret;
    
    dev_info(&client->dev, "MyTouchScreen probe started\n");
    
    /* 1. 分配私有数据 */
    ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
    if (!ts) {
        return -ENOMEM;
    }
    
    ts->client = client;
    ts->dev = &client->dev;
    i2c_set_clientdata(client, ts);
    
    /* 2. 初始化锁和工作队列 */
    mutex_init(&ts->lock);
    INIT_WORK(&ts->work, myts_work_handler);
    
    /* 3. 解析设备树配置 */
    myts_parse_dt(ts);
    
    /* 4. 硬件初始化(复位、读取固件版本等) */
    ret = i2c_smbus_write_byte_data(client, MYTS_REG_RESET, 0x01);
    if (ret) {
        dev_err(ts->dev, "Failed to reset device\n");
        return ret;
    }
    msleep(50);  /* 等待复位完成 */
    
    /* 5. 读取固件版本 */
    ret = i2c_smbus_read_i2c_block_data(client, MYTS_REG_FW_VERSION,
                                        sizeof(ts->fw_version), ts->fw_version);
    if (ret > 0) {
        dev_info(ts->dev, "Firmware version: %02x.%02x.%02x.%02x\n",
                 ts->fw_version[0], ts->fw_version[1],
                 ts->fw_version[2], ts->fw_version[3]);
    }
    
    /* 6. 初始化输入子系统 */
    ret = myts_input_init(ts);
    if (ret) {
        return ret;
    }
    
    /* 7. 注册中断 */
    ts->irq = client->irq;
    if (!ts->irq) {
        dev_err(ts->dev, "No IRQ specified\n");
        return -EINVAL;
    }
    
    ret = devm_request_irq(&client->dev, ts->irq, myts_irq_handler,
                           IRQF_TRIGGER_FALLING, "my_ts", ts);
    if (ret) {
        dev_err(ts->dev, "Failed to request IRQ %d: %d\n", ts->irq, ret);
        return ret;
    }
    
    dev_info(&client->dev, "MyTouchScreen probe success\n");
    return 0;
}
​
/**
 * @brief 设备匹配表
 * 
 * 设计模式:设备树匹配
 * compatible属性必须与设备树中的一致
 */
static const struct of_device_id myts_of_match[] = {
    { .compatible = "vendor,my-ts", },
    { .compatible = "vendor,my-ts-v2", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myts_of_match);
​
/**
 * @brief I2C设备ID表
 */
static const struct i2c_device_id myts_id[] = {
    { "my-ts", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, myts_id);
​
/**
 * @brief I2C驱动结构体
 * 
 * 设计模式:驱动程序的核心
 * 向内核注册probe、remove、电源管理等方法
 */
static struct i2c_driver myts_driver = {
    .driver = {
        .name = "my_ts",
        .of_match_table = myts_of_match,
        .pm = &myts_pm_ops,  /* 电源管理操作 */
    },
    .probe = myts_probe,
    .remove = myts_remove,
    .id_table = myts_id,
};
​
/**
 * @brief 模块初始化
 */
module_i2c_driver(myts_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My TouchScreen Driver");
MODULE_LICENSE("GPL");

心练场景二:C语言实战问题与调试工具

心练场:ftrace触摸屏响应延迟

练气心法


心法实战案例:触摸屏响应延迟的ftrace追踪

/**
 * @brief 问题背景
 * 
 * 项目:某款基于RK3588平台的Android平板
 * 现象:用户反馈触摸操作有时会有明显的延迟(约200-300ms)
 * 环境:Android 12,内核5.10,Goodix GT9271触摸屏
 * 复现:快速连续滑动时最容易出现
 */
​
/**
 * ======================================================
 * 第一阶段:问题复现与初步定位
 * ======================================================
 */
​
/**
 * 1. 首先通过printk添加时间戳,确认延迟发生的阶段
 */
static irqreturn_t gt9271_irq_handler(int irq, void *dev_id)
{
    struct gt9271_data *ts = dev_id;
    
    /* 添加时间戳 */
    ktime_t start = ktime_get();
    pr_info("TS: IRQ received at %lld us\n", ktime_to_us(start));
    
    disable_irq_nosync(irq);
    schedule_work(&ts->work);
    
    return IRQ_HANDLED;
}
​
static void gt9271_work_handler(struct work_struct *work)
{
    struct gt9271_data *ts = container_of(work, struct gt9271_data, work);
    ktime_t start = ktime_get();
    
    /* 读取触摸数据 */
    gt9271_read_touch_data(ts);
    
    /* 上报输入事件 */
    input_sync(ts->input);
    
    ktime_t end = ktime_get();
    pr_info("TS: Work completed in %lld us\n", 
            ktime_to_us(ktime_sub(end, start)));
    
    enable_irq(ts->irq);
}
​
/**
 * 日志分析发现:
 * [   45.123456] TS: IRQ received at 45123456 us
 * [   45.123789] TS: Work completed in 333 us  <- 处理很快,但用户感觉延迟
 * 
 * 说明问题不在驱动本身,可能在系统调度或上层
 */
​
/**
 * ======================================================
 * 第二阶段:使用ftrace追踪调度延迟
 * ======================================================
 */
​
/**
 * 启用ftrace追踪调度器事件
 * 
 * # 挂载tracefs
 * mount -t tracefs none /sys/kernel/tracing
 * 
 * # 设置追踪器为function_graph
 * echo function_graph > /sys/kernel/tracing/current_tracer
 * 
 * # 只追踪触摸相关的函数
 * echo gt9271_* > /sys/kernel/tracing/set_ftrace_filter
 * 
 * # 开启调度器事件追踪
 * echo 1 > /sys/kernel/tracing/events/sched/enable
 * 
 * # 开始追踪
 * echo 1 > /sys/kernel/tracing/tracing_on
 */
​
/**
 * ftrace输出分析:
 * 
 * 正常情况:
 *  2)               |  gt9271_irq_handler() {
 *  2)   0.225 us    |    disable_irq_nosync();
 *  2)   0.125 us    |    schedule_work();
 *  2)   2.891 us    |  }
 *  2)               |  gt9271_work_handler() {
 *  2)   0.125 us    |    gt9271_read_touch_data();
 *  2)   0.125 us    |    input_sync();
 *  2)   0.125 us    |    enable_irq();
 *  2)   3.256 us    |  }
 * 
 * 异常情况(有延迟时):
 *  2)               |  gt9271_irq_handler() {
 *  2)   0.225 us    |    disable_irq_nosync();
 *  2)   0.125 us    |    schedule_work();
 *  2)   2.891 us    |  }
 *  2)   <idle>-0    |  (schedule_work返回后,work没有立即执行)
 *  2)               |  /* 等了约200ms */
 *  2)   <...>-1234  |  gt9271_work_handler() {
 *  2)   0.125 us    |    gt9271_read_touch_data();
 *  2)   0.125 us    |    input_sync();
 *  2)   0.125 us    |    enable_irq();
 *  2)   3.256 us    |  }
 */
​
/**
 * ======================================================
 * 第三阶段:使用trace-cmd更详细分析
 * ======================================================
 */
​
/**
 * # 使用trace-cmd记录所有调度事件
 * trace-cmd record -e sched_switch -e sched_wakeup -e irq_handler_entry \
 *   -e irq_handler_exit -p function_graph -l gt9271_*
 * 
 * # 分析结果
 * trace-cmd report
 */
​
/**
 * 关键发现:
 * 
 * irq_handler_entry: irq=67 name=gt9271
 * sched_wakeup: comm=kworker/u8:2 pid=1234 prio=120成功 wakee
 * sched_switch: prev_comm=swapper/2 prev_prio=120 prev_state=R 
 *               next_comm=kworker/u8:2 next_prio=120
 * 
 * 但是!在sched_wakeup和sched_switch之间有一个200ms的间隔
 * 原因是CPU2当时在运行一个实时进程(rt_prio=49):
 * 
 * sched_switch: prev_comm=android.hardware.audio.service 
 *               prev_prio=49 prev_state=S
 */
​
/**
 * ======================================================
 * 第四阶段:根因定位 - 音频服务抢占CPU
 * ======================================================
 */
​
/**
 * 问题根本原因:
 * 1. 触摸屏中断触发 -> 唤醒kworker
 * 2. 但CPU正在运行音频服务(实时优先级49)
 * 3. kworker (优先级120) 被抢占,等待音频服务让出CPU
 * 4. 音频服务有时会连续处理大量数据,导致kworker长时间得不到调度
 */
​
/**
 * ======================================================
 * 第五阶段:解决方案
 * ======================================================
 */
​
/**
 * 方案一:提高kworker优先级(临时方案)
 */
static int gt9271_probe(struct i2c_client *client)
{
    struct gt9271_data *ts;
    
    /* ... 其他初始化 ... */
    
    /* 创建高优先级workqueue */
    ts->high_pri_wq = alloc_workqueue("gt9271_wq", 
                                      WQ_HIGHPRI | WQ_UNBOUND, 0);
    if (!ts->high_pri_wq) {
        dev_err(&client->dev, "Failed to create high pri wq\n");
        return -ENOMEM;
    }
    
    INIT_WORK(&ts->work, gt9271_work_handler);
    
    return 0;
}
​
static irqreturn_t gt9271_irq_handler(int irq, void *dev_id)
{
    struct gt9271_data *ts = dev_id;
    
    disable_irq_nosync(irq);
    /* 使用高优先级workqueue */
    queue_work(ts->high_pri_wq, &ts->work);
    
    return IRQ_HANDLED;
}
​
/**
 * 方案二:使用 threaded IRQ(最终方案)
 * 
 * 设计模式:threaded IRQ 自动处理优先级
 * 内核会在irq线程中运行handler,可设置优先级
 */
static irqreturn_t gt9271_threaded_handler(int irq, void *dev_id)
{
    struct gt9271_data *ts = dev_id;
    
    /* 直接在这里处理,已经是线程上下文 */
    gt9271_read_touch_data(ts);
    input_sync(ts->input);
    
    return IRQ_HANDLED;
}
​
static int gt9271_probe(struct i2c_client *client)
{
    /* ... */
    
    /* 使用request_threaded_irq */
    ret = devm_request_threaded_irq(&client->dev, client->irq,
                                    NULL,  /* hardirq handler (NULL表示只使用threaded) */
                                    gt9271_threaded_handler,
                                    IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                                    "gt9271", ts);
    
    /* ... */
}
​
/**
 * ======================================================
 * 最终效果验证
 * ======================================================
 */
​
/**
 * 使用ftrace验证改进后效果:
 * 
 *  2)               |  gt9271_irq_handler() {
 *  2)   0.225 us    |    /* 硬中断部分几乎立即返回 */
 *  2)   2.891 us    |  }
 *  2)               |  irq/67-gt9271-1234 [实时优先级50] {
 *  2)   0.125 us    |    gt9271_read_touch_data();
 *  2)   0.125 us    |    input_sync();
 *  2)   3.256 us    |  }
 * 
 * 延迟从200ms降低到<5ms,用户体验明显改善
 */

一周天运气周转:调试工具实战经验总结

系统性的问题分析方法。

/**
 * @file debugging_cheatsheet.c
 * @brief BSP调试工具实战经验总结
 */
​
/**
 * ======================================================
 * 1. printk - 最基础但最有效的调试手段
 * ======================================================
 */
​
/**
 * printk日志级别:
 * KERN_EMERG   "<0>" 系统崩溃前使用
 * KERN_ALERT   "<1>" 需要立即处理
 * KERN_CRIT    "<2>" 严重情况
 * KERN_ERR     "<3>" 错误情况(驱动中最常用)
 * KERN_WARNING "<4>" 警告
 * KERN_NOTICE  "<5>" 正常但重要
 * KERN_INFO    "<6>" 信息性消息
 * KERN_DEBUG   "<7>" 调试信息
 */
​
/* 实际项目中的printk使用技巧 */
#define dev_dbg_once(dev, fmt, ...) \
({ \
    static bool __printed; \
    if (!__printed) { \
        __printed = true; \
        dev_printk(KERN_DEBUG, dev, fmt, ##__VA_ARGS__); \
    } \
})
​
/* 动态控制printk级别 */
static int my_driver_param = 0;
module_param(my_driver_param, int, 0644);
​
#define my_dbg(fmt, ...) \
do { \
    if (my_driver_param > 0) \
        printk(KERN_DEBUG "my_drv: " fmt, ##__VA_ARGS__); \
} while (0)
​
/**
 * ======================================================
 * 2. devmem2/devmem - 直接访问物理内存
 * ======================================================
 */
​
/**
 * 使用场景:验证硬件寄存器配置是否正确
 * 
 * # 读取0x10000000地址的值
 * devmem2 0x10000000
 * 
 * # 写入32位值到寄存器
 * devmem2 0x10001000 w 0x12345678
 * 
 * # 实际案例:调试I2C控制器
 * # 查看I2C控制器状态寄存器
 * devmem2 0xff3d0000  # I2C控制器基址
 * devmem2 0xff3d0004  # 控制寄存器
 * devmem2 0xff3d0008  # 状态寄存器
 */
​
/**
 * ======================================================
 * 3. ftrace - 内核函数调用追踪
 * ======================================================
 */
​
/**
 * 常用ftrace技巧:
 * 
 * # 追踪特定函数的调用栈
 * echo function_graph > /sys/kernel/tracing/current_tracer
 * echo my_driver_function > /sys/kernel/tracing/set_ftrace_filter
 * echo 1 > /sys/kernel/tracing/tracing_on
 * 
 * # 追踪中断和调度延迟
 * echo 1 > /sys/kernel/tracing/events/irq/enable
 * echo 1 > /sys/kernel/tracing/events/sched/enable
 * 
 * # 使用trace-cmd更友好
 * trace-cmd record -p function_graph -l my_driver_* -e irq -e sched
 */
​
/**
 * ======================================================
 * 4. kgdb/kdb - 内核源码级调试
 * ======================================================
 */
​
/**
 * 配置内核支持kgdb:
 * CONFIG_KGDB=y
 * CONFIG_KGDB_SERIAL_CONSOLE=y
 * CONFIG_KGDB_KDB=y
 * 
 * 启动参数添加: kgdboc=ttyS0,115200 kgdbwait
 * 
 * 调试会话示例:
 * (gdb) target remote /dev/ttyS0
 * (gdb) b my_driver_function
 * (gdb) c
 * 
 * 或者使用kdb(不需要远程gdb):
 * # echo g > /proc/sysrq-trigger  # 进入kdb
 * kdb> bt  # 查看调用栈
 * kdb> rd  # 查看寄存器
 * kdb> md 0x10000000  # 查看内存
 */
​
/**
 * ======================================================
 * 5. 逻辑分析仪/示波器 - 硬件时序分析
 * ======================================================
 */
​
/**
 * 实战案例:I2C通信问题排查
 * 
 * 问题:触摸屏有时无法响应
 * 
 * 使用逻辑分析仪抓取I2C波形发现:
 * SCL __----____----____----__
 * SDA __--__--__--__--__--__--
 * 
 * 正常应该是:
 * SDA在SCL低电平时变化,SCL高电平时稳定
 * 
 * 发现问题:某次通信中SDA在SCL高电平时变化,导致从设备采样错误
 * 
 * 解决方案:在驱动中增加延时,确保setup/hold时间满足规格
 */
​
static int i2c_write_with_delay(struct i2c_client *client, u8 *buf, int len)
{
    int ret;
    int i;
    
    for (i = 0; i < len; i++) {
        ret = i2c_smbus_write_byte(client, buf[i]);
        if (ret < 0)
            return ret;
        
        /* I2C规范要求至少4.7us的setup时间 */
        udelay(5);  /* 增加微秒级延时 */
    }
    
    return 0;
}
​
/**
 * ======================================================
 * 6. perf - 性能分析
 * ======================================================
 */
​
/**
 * 使用场景:分析系统响应延迟、CPU使用率
 * 
 * # 记录调度事件
 * perf record -e sched:sched_switch -a sleep 10
 * 
 * # 分析结果
 * perf script
 * 
 * # 查看CPU周期分布
 * perf stat -e cycles,instructions,cache-misses ./my_program
 */
​
/**
 * ======================================================
 * 7. 内存调试工具
 * ======================================================
 */
​
/**
 * KASAN (Kernel Address SANitizer) - 检测内存错误
 * 
 * 内核配置:
 * CONFIG_KASAN=y
 * CONFIG_KASAN_INLINE=y
 * 
 * KASAN能检测:
 * - Use-after-free
 * - Out-of-bounds access
 * - Double-free
 */
​
/**
 * kmemleak - 检测内存泄漏
 * 
 * # 开启kmemleak
 * echo scan > /sys/kernel/debug/kmemleak
 * cat /sys/kernel/debug/kmemleak
 */
​
/**
 * ======================================================
 * 8. 实际项目中的调试流程(套路总结)
 * ======================================================
 */
​
/**
 * 问题排查SOP(Standard Operating Procedure):
 * 
 * 1. 复现问题,收集信息
 *    - dmesg日志
 *    - /proc/interrupts(查看中断计数)
 *    - /proc/meminfo(查看内存使用)
 * 
 * 2. 初步定位
 *    - 使用printk缩小范围
 *    - 检查硬件状态(devmem2)
 *    - 确认不是硬件问题(换板子、量波形)
 * 
 * 3. 深入分析
 *    - ftrace追踪函数调用
 *    - perf分析性能瓶颈
 *    - kgdb调试崩溃点
 * 
 * 4. 验证修复
 *    - 压力测试
 *    - 长期稳定性测试
 *    - 回归测试
 */
​
/**
 * 最后分享一个真实案例的完整调试记录:
 * 
 * 问题:系统随机死机,无规律
 * 
 * 步骤1:开启内核崩溃转储
 * echo c > /proc/sysrq-trigger  # 手动触发crash测试转储是否工作
 * 
 * 步骤2:配置kdump
 * # 在启动参数中添加crashkernel=128M
 * 
 * 步骤3:分析vmcore
 * crash vmlinux vmcore
 * crash> bt
 * crash> log
 * crash> ps
 * 
 * 发现:在某个驱动卸载时,使用了一个已经释放的spinlock
 * 
 * 步骤4:使用KASAN验证
 * 开启KASAN后,问题立即被捕获:
 * [   45.123456] ==================================================================
 * [   45.123456] BUG: KASAN: use-after-free in spin_lock+0x20/0x40
 * [   45.123456] Read of size 4 at addr ffff0000c1234567
 * 
 * 步骤5:修复
 * 调整驱动卸载顺序,确保锁在使用前不被释放
 */

由丹田运行至自身诸穴

  1. 如果系统在压力测试时随机重启,没有任何日志,你会怎么排查?

  2. 你遇到过内存泄漏问题吗?怎么定位的?

  3. 在Android系统上,如何区分是HAL层问题还是内核驱动问题?

心练技法:
/**
 * 问题1:随机重启无日志的排查方法
 * 
 * 这种情况通常是硬件看门狗复位,或者内核panic但console来不及输出
 */
​
/**
 * 方案1:使用pstore/ramoops保留最后日志
 */
/* 内核配置 */
CONFIG_PSTORE=y
CONFIG_PSTORE_RAM=y
/* 启动参数添加:ramoops.mem_address=0x30000000 ramoops.mem_size=0x100000 */
​
/* 重启后查看 */
cat /sys/fs/pstore/console-ramoops
​
/**
 * 方案2:使用硬件调试器(JTAG)
 * 
 * 案例:某项目遇到低温环境下随机重启
 * 用JTAG挂住,发现是PMIC在低温下输出电压波动
 * 最终修改:调整PMIC的LDO输出电压补偿参数
 */
​
/**
 * 问题2:内存泄漏定位
 * 
 * 现象:系统运行几天后变慢,free命令看到可用内存越来越少
 */
​
/**
 * 使用slabtop查看内核内存分配
 */
# slabtop -o | head -20
# 发现有某个缓存持续增长
​
/**
 * 使用kmemleak
 */
# echo scan > /sys/kernel/debug/kmemleak
# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffffffc0a23b5c00 (size 256):
  comm "kworker/u16:5", pid 1234, jiffies 4294891234
  backtrace:
    [<ffffff8008123456>] kmem_cache_alloc+0x123/0x234
    [<ffffff8008456789>] my_driver_ioctl+0x45/0x123  /* 这里定位到具体函数 */
​
/**
 * 修复代码示例
 */
static long my_driver_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct my_data *data;
    
    switch (cmd) {
    case MY_IOCTL_ALLOC:
        data = kmalloc(sizeof(*data), GFP_KERNEL);
        if (!data)
            return -ENOMEM;
        
        /* BUG: 以前忘记保存指针,导致无法释放 */
        filp->private_data = data;  /* 修复:保存指针 */
        break;
        
    case MY_IOCTL_FREE:
        data = filp->private_data;
        if (data) {
            kfree(data);            /* 修复:正确释放 */
            filp->private_data = NULL;
        }
        break;
    }
}
​
/**
 * 问题3:Android HAL层与内核驱动的区分
 * 
 * 分层调试方法论:
 */
​
/**
 * 1. 验证内核层(使用shell直接测试)
 */
# 查看输入设备
cat /proc/bus/input/devices
# 直接读取事件
hexdump /dev/input/event2
​
/**
 * 2. 验证HAL层(使用Android调试命令)
 */
# 查看HAL服务状态
lshal
# 测试传感器
sensors-test
# 测试摄像头
camera2test
​
/**
 * 3. 区分案例:触摸屏失灵
 * 
 * 步骤1:验证内核层
 * getevent -lt  # 看是否有输入事件
 * 
 * 如果getevent有事件,但上层无反应:问题在HAL/Framework
 * 如果getevent无事件,但中断有计数:问题在内核驱动
 * 如果中断计数也不增加:问题在硬件
 */

诸穴调匀自通

技法

/**
 * 跨团队问题协作方法论
 */
​
/**
 * 1. 问题定界(First Things First)
 * 
 * 例会上的沟通模板:
 * "目前遇到的问题是屏幕闪烁。我们先快速定界:
 *  - 硬件:请帮忙量一下LCD的时序,特别是VSYNC信号是否稳定
 *  - 内核:我会加trace点确认drm驱动刷新是否正常
 *  - 应用:能否提供一个最小复现的测试apk?
 * 
 * 2小时后我们碰一下各自的结果。"
 */
​
/**
 * 2. 信息同步(Use Data, Not Feelings)
 * 
 * 发给团队的邮件/文档格式:
 * 
 * 问题描述:xxxxxxxx
 * 复现步骤:
 * 1. 
 * 2.
 * 
 * 内核日志(关键部分):
 * [timestamp] drm: vblank wait timed out
 * 
 * 硬件测量数据:
 * - VSYNC周期:16.68ms(标准16.67ms,OK)
 * - 像素时钟:74.25MHz(标准74.25MHz,OK)
 * - DE信号:有毛刺(发现问题!)
 * 
 * 下一步计划:
 * 硬件:检查DE信号走线,看是否有干扰
 * 驱动:尝试在驱动中增加DE信号滤波
 */
​
/**
 * 3. 组织攻关(War Room)
 * 
 * 当问题严重影响项目进度时:
 * 
 * "我们需要组建一个攻关小组:
 * - 硬件:王工负责抓波形、检查原理图
 * - 驱动:我负责加调试代码、做压力测试
 * - 系统:张工负责监控系统负载、看看是否有资源抢占
 * 
 * 每天下午4点站会同步进展,问题追踪用Jira"
 */
​
/**
 * 4. 根本原因分析(5 Whys)
 * 
 * 问题:屏幕闪烁
 * 为什么?因为DE信号有毛刺
 * 为什么DE信号有毛刺?因为LCD模组的FPC线屏蔽层接地不良
 * 为什么接地不良?因为结构设计时FPC走线靠近天线
 * 为什么天线会干扰?因为没有做EMC仿真
 * 
 * 最终解决方案:改结构设计+增加软件滤波
 */
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐