1. 上下文相关原则
1.1 中断上下文限制
/* 中断上下文中禁止的操作 */
- 不能睡眠或调用可能睡眠的函数(如 kmalloc(GFP_KERNEL))
- 不能使用互斥锁(mutex)
- 不能执行耗时操作
- 不能访问用户空间内存
原因:中断上下文没有进程上下文,无法被调度,如果睡眠会导致系统死锁。
1.2 软中断和 tasklet 限制
- 不能睡眠
- 不能访问用户空间
- 同一个 tasklet 不会在多个 CPU 上同时运行
原因:软中断运行在中断上下文,具有相似的限制。
2. 锁和同步原则
2.1 锁的顺序
/* 必须按照固定顺序获取锁 */
spin_lock(&lock1);
spin_lock(&lock2); // 始终先获取 lock1,再获取 lock2
原因:避免死锁。
2.2 自旋锁使用原则
/* 持有自旋锁时禁止的操作 */
- 不能睡眠
- 不能调用可能睡眠的函数
- 持锁时间必须尽可能短
- 不能递归获取同一个自旋锁
原因:自旋锁会禁用抢占,长时间持有会影响系统响应。
2.3 RCU 使用原则
rcu_read_lock();
/* RCU 读侧临界区内:
- 不能睡眠
- 不能调用同步 RCU 函数
*/
rcu_read_unlock();
3. 内存管理原则
3.1 内存分配标志
/* 中断上下文 */
kmalloc(size, GFP_ATOMIC); // 不能使用 GFP_KERNEL
/* 进程上下文 */
kmalloc(size, GFP_KERNEL); // 可以睡眠
3.2 DMA 内存分配
/* DMA 内存必须使用专门的分配函数 */
dma_alloc_coherent();
/* 不能使用普通的 kmalloc */
原因:DMA 需要物理连续且满足硬件限制的内存。
3.3 栈空间限制
/* 内核栈很小(通常 8KB 或 16KB)*/
- 避免大的局部变量
- 避免深度递归
- 大数据使用动态分配
4. 抢占和调度原则
4.1 禁用抢占期间
preempt_disable();
/* 不能睡眠
不能调用 schedule()
必须尽快恢复抢占
*/
preempt_enable();
4.2 禁用中断期间
local_irq_disable();
/* 必须尽快恢复中断
不能睡眠
影响系统响应时间
*/
local_irq_enable();
5. 用户空间访问原则
5.1 必须使用专门的函数
/* 正确的方式 */
copy_from_user(kernel_buf, user_buf, size);
copy_to_user(user_buf, kernel_buf, size);
/* 错误的方式 */
memcpy(kernel_buf, user_buf, size); // 禁止!
原因:需要检查用户空间指针的有效性,处理页面故障。
5.2 访问前必须检查
if (!access_ok(user_ptr, size))
return -EFAULT;
6. 原子操作原则
6.1 原子变量操作
atomic_t counter;
/* 必须使用原子操作函数 */
atomic_inc(&counter);
/* 不能直接操作 */
counter++; // 错误!
6.2 位操作
/* 使用原子位操作 */
set_bit(nr, addr);
clear_bit(nr, addr);
test_and_set_bit(nr, addr);
7. 并发和 SMP 原则
7.1 共享数据保护
/* 所有共享数据必须保护 */
- 使用适当的锁机制
- 或使用无锁算法
- 或使用 per-CPU 变量
7.2 内存屏障
/* 在必要时使用内存屏障 */
smp_wmb(); // 写屏障
smp_rmb(); // 读屏障
smp_mb(); // 全屏障
8. 错误处理原则
8.1 资源释放
/* 获取的资源必须释放 */
- 使用 goto 进行错误处理
- 确保所有路径都能正确释放资源
- 考虑使用 devm_* 管理的资源
8.2 错误返回值
/* 使用标准错误码 */
return -ENOMEM; // 内存不足
return -EINVAL; // 无效参数
/* 不要自定义错误码 */
9. 模块和符号原则
9.1 符号导出
EXPORT_SYMBOL(function_name); // GPL 兼容模块可用
EXPORT_SYMBOL_GPL(function_name); // 仅 GPL 模块可用
9.2 模块引用计数
/* 使用模块时增加引用 */
try_module_get(THIS_MODULE);
module_put(THIS_MODULE);
10. 设备驱动原则
10.1 设备注册顺序
/* 正确的顺序 */
1. 分配设备号
2. 初始化 cdev
3. 创建设备类
4. 创建设备
10.2 电源管理
/* 实现必要的电源管理回调 */
.suspend = driver_suspend,
.resume = driver_resume,
11. 调试和日志原则
11.1 日志级别
pr_emerg() // 系统崩溃
pr_alert() // 必须立即处理
pr_crit() // 临界条件
pr_err() // 错误条件
pr_warning() // 警告条件
pr_notice() // 正常但重要
pr_info() // 信息消息
pr_debug() // 调试消息
11.2 动态调试
/* 使用 pr_debug 和 dev_dbg */
/* 可以在运行时通过 debugfs 控制 */
这些原则是 Linux 内核开发的基础,违反这些原则可能导致:
- 系统崩溃
- 死锁
- 数据竞争
- 安全漏洞
- 性能问题
始终记住:内核代码运行在特权级别,错误可能导致整个系统崩溃,因此必须格外谨慎。
本文版权归原作者zhaofujian所有,采用 CC BY-NC-ND 4.0 协议进行许可,转载请注明出处。