每日一练:以修道者的方式修炼编程基础之C语言练气心法
·
心练场景一: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:修复
* 调整驱动卸载顺序,确保锁在使用前不被释放
*/
由丹田运行至自身诸穴
-
如果系统在压力测试时随机重启,没有任何日志,你会怎么排查?
-
你遇到过内存泄漏问题吗?怎么定位的?
-
在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仿真 * * 最终解决方案:改结构设计+增加软件滤波 */
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)