Linux 内核开发的重要原则和规范

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 协议进行许可,转载请注明出处。

发表评论