跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

Linux 内核开发笔记:休眠唤醒、Proc 文件系统、调试与性能工具

档记录了 Linux 内核开发中的关键技术点,涵盖系统休眠与唤醒机制、Proc 文件系统接口创建、内核堆栈追踪、Printk 日志等级及动态调试方法、Trace 追踪工具以及多种性能分析工具的使用。内容包括电源管理操作、等待队列应用、内核调试接口实现细节及系统性能瓶颈排查技巧,旨在帮助开发者理解内核行为并进行有效调试。

信号故障发布于 2026/3/29更新于 2026/10/21101 浏览
Linux 内核开发笔记:休眠唤醒、Proc 文件系统、调试与性能工具

1. 休眠&唤醒

休眠状态指的是一种系统低功耗运行状态。在此状态下,各种支持休眠模式的外围设备也都全部进入休眠模式,CPU 挂起,所有用户态应用程序和内核态进程全部被冻结,内存处于自刷新模式。系统处于休眠状态会屏蔽所有除唤醒之外的所有命令,直到系统被某种原因唤醒才会解除此种状态。

// cat /sys/power/state【freeze: 可快速恢复,mem: 长时间省电】:支持哪些挂起模式,不是当前状态
// echo freeze/mem > /sys/power/state:触发休眠,命令敲完后,串口卡住没反应了(在/sys/power/autosleep【CONFIG_PM_AUTOSLEEP:自动休眠唤醒】是 off 状态才行)
// kernel/power/suspend.c: int pm_suspend(suspend_state_t state)
// autosleep 也是调用这行函数
{
    error = enter_state(state);
    // 进入 suspend 状态,具体的状态根据 state 值而定,如果执行成功的话,一直到系统退出 suspend 的状态后此函数才退出
}
// 如果外设没有实现 dev_pm_ops,那么外设对于休眠唤醒没有任何动作
struct dev_pm_ops {
    int (*suspend)(struct device *dev); // 休眠,下电,与下面成对出现
    int (*resume)(struct device *dev);   // 唤醒,上电
    ...
    int (*runtime_suspend)(struct device *dev); // 单设备:并不是要求设备一定要进入低功耗状态,而是要求设备在 suspend 后,不再处理数据,不再和 CPUs、RAM 进行任何的交互,直到设备的 runtime_resume 被调用
    int (*runtime_resume)(struct device *dev);
};

如整机休眠时调用下面外设的.suspend。

如下查看哪个驱动模块当前有活跃锁,导致整机休眠不下去。自动休眠唤醒 suspend 控制器发现如果 /sys/kernel/debug/wakeup_sources 中 active_since 一列都为 0 即没有持锁(即没有唤醒)就休眠。

如下 __pm_relax 用来释放先前通过 __pm_stay_awake 增加的唤醒请求即 active_since 一列。

如按触屏和播放视频都不能休眠。

/* 有时候进程在读设备时,发现设备数据还没准备好,没办法正常读取设备。或在写设备时,发现设备缓冲区满,没办法正常写设备。进程在操作设备时,如果条件不满足,就让它进入休眠等待,直到条件满足,就可唤醒进程进行后面操作。
初始化:DECLARE_WAIT_QUEUE_HEAD() // 初始化等待队列头,宏的静态方式 或 wait_queue_head_t wq; // 动态方式 init_waitqueue_head(&wq);
休眠:wait_event() // 不可被打断,死等,直到满足条件为止
        wait_event_interruptible() // 可使用信号打断它,常用
唤醒:wake_up() // 对应 wait_event()
      wake_up_interruptible() // 对应上面可中断方式休眠的进程 wait_event_interruptible()
*/
#include 
DECLARE_WAIT_QUEUE_HEAD(wq);
{
    
    printk(KERN_INFO , hc_dev);
    
    wait_event_interruptible(wq, hc_dev->c != );
}
{
    
    printk(KERN_INFO , current->comm);
    wake_up(&wq); 
    wake_up_interruptible(&wq);
     count;
}
<linux/wait.h>
// hc_read 函数中
"read hc_dev %p\n"
// wait_event(wq, hc_dev->c != NULL); // 第一个参数:等待队列头。第二个参数:等待条件:如果设备字符串区(即 echo 值进设备文件里)为空就进入等待(唤醒方法在 write 函数中)。
NULL
// hc_write 函数中
"%s write done"
// 当写入字符设备成功后调用 wakeup 函数唤醒等待队列上的进程
return

如下等待条件满足,如上 卡住的即休眠的进程 会正常退出(两个进程 cat 卡住休眠,执行如下一行,两个进程都不会卡住)。

2. Proc 文件系统

/* 新调试方法:利用 proc 文件系统在 pro 文件夹下创建接口,读写这个接口就可实现对内核的调试
struct proc_ops // pro 文件夹下创建接口第一种方式 proc_create()
struct seq_operations // 第二种方式 proc_create_seq()
remove_proc_entry // 移除接口
*/
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#define PROC_DEBUG
#ifdef PROC_DEBUG
#include <linux/proc_fs.h> // 传统第一种方式
#include <linux/seq_file.h> // seq 第二种方式
#endif
char* str = "hello proc\n";
#ifdef PROC_DEBUG
int hp_open(struct inode *inode, struct file *filp) {
    printk(KERN_INFO "open %ld\n", strlen(str));
    return 0;
}
ssize_t hp_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos) {
    ssize_t retval = 0;
    int n = strlen(str); // 内容总长度(11 字节,含\n)
    if (*f_pos >= n) goto out;
    if (*f_pos + count > n) count = n - *f_pos; // 防止越界 count = n -*f_pos;调整读取长度
    if (copy_to_user(buff, str, count)) { // 将字符串 str("hello proc\n") 赋值到 buff 用户空间
        retval = -EFAULT;
        goto out;
    }
    *f_pos += count; // 更新偏移量
    return count;
out:
    return retval;
}
struct proc_ops hp_ops = {
    // 下面 __init 函数中 proc_create 调用 hp_ops 创建接口文件
    .proc_open = hp_open,
    .proc_read = hp_read,
};
void* hp_seq_start(struct seq_file *m, loff_t *pos) // pos 表示当前读到哪个位置或写到哪个位置了
{
    printk(KERN_INFO "seq start\n");
    if (*pos >= strlen(str)) // pos 指当前读到或写到哪个位置,索引
        return NULL;
    return &str[*pos]; // 拿出字符串中字符,将地址返回,这返回值将来作为下面其他函数的 v 传入
}
void hp_seq_stop(struct seq_file *m, void *v) {
    printk(KERN_INFO "seq stop\n"); // 清除 start 函数一些工作,start 里开辟一些空间或申请一些锁,这里清除
}
void* hp_seq_next(struct seq_file *m, void *v, loff_t *pos) // 改变索引值
{
    printk(KERN_INFO "seq next\n");
    (*pos)++;
    if (*pos >= strlen(str)) return NULL;
    return &str[*pos];
}
int hp_seq_show(struct seq_file *m, void *v) {
    printk(KERN_INFO "seq show\n");
    seq_putc(m, *(char*)v); // 将获得到的字符一个一个打印出
    return 0;
}
const struct seq_operations seq_ops = {
    .start = hp_seq_start,
    .stop = hp_seq_stop,
    .next = hp_seq_next,
    .show = hp_seq_show,
};
#endif
static int __init hello_init(void) {
    printk(KERN_INFO "HELLO LINUX MODULE\n");
#ifdef PROC_DEBUG
    proc_create("hello_proc", 0, NULL, &hp_ops); // 第一个参数即显示在 pro 目录下文件名称,第二个参数默认 0 只读权限。第三个参数父节点,null 默认 pro 目录。最后一个参数是操作的结构体地址
    proc_create_seq("hello_seq_proc", 0, NULL, &seq_ops); // 就可在 pro 目录下创建对应节点
#endif
    return 0;
}
static void __exit hello_exit(void) {
#ifdef PROC_DEBUG
    remove_proc_entry("hello_proc", NULL); // 第二个参数是父节点
    remove_proc_entry("hello_seq_proc", NULL);
#endif
    printk(KERN_INFO "GOODBYE LINUX\n");
}
module_init(hello_init);
module_exit(hello_exit);

show next 调用了 11 次,最后一次返回 null 会调用 stop。cat 又会再调用一次,调 start 后直接返回 null 到 stop。

3. 调用堆栈内核函数

WARN_ON(condition) 函数实际也是调用 dump_stack,如 WARN_ON(1) 条件判断成功,类似 BUG_ON(1)。

4. Printk

cat /proc/cmdline 查看 console=ttyS0,如下图左边分支是远程登录,缺点是 log_buf 是环形 buff(会循环覆盖,日志不全)。左右分支都有 dmesg,左分支 dmesg 不受下面 x 控制,所以 dmesg 是最全的。

// Linux 内核为 printk 定义了 8 个打印等级,KERN_EMERG 等级最高,KERN_DEBUG 等级最低。在内核配置时,有一个宏来设定系统默认的打印等级 CONFIG_MESSAGE_LOGLEVEL_DEFAULT,通常该值设置为 4,那么只有打印等级高于 4 时才会打印到终端或者串口。
// kern_levels.h
#define KERN_EMERG KERN_SOH "0" /* system is unusable 紧急事件,一般是系统崩溃之前的提示消息 */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately 必须立即采取行动 */
#define KERN_CRIT  KERN_SOH "2" /* critical conditions 临界状态,通常涉及严重的硬件或者软件操作失败 */
#define KERN_ERR   KERN_SOH "3" /* error conditions 报告错误状态,经常用来报告硬件错误 */
#define KERN_WARNING KERN_SOH "4" /* warning conditions 对可能出现问题的情况进行警告,通常不会对系统造成严重问题 */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition 有必要的提示,通常用于安全相关的状况汇报 */
#define KERN_INFO  KERN_SOH "6" /* informational 提示信息,驱动程序常用来打印硬件信息 */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages 用于调试信息 */
// printk("[me]%s[%d].\n", __func__, __LINE__);
// printk 调用 printk_safe_enter_irqsave --> local_irq_save【把当前的中断状态(开或关)保存到 flags 中,然后禁用当前处理器上的中断】,所以:板卡 --> 没有办法跟外界通信(I2C、UART、SPI 都用到中断)

5. 动态打印

printk 是全局的且只能设打印等级,动态打印可控制选择模块的打印,在内核配置打开 CONFIG_DYNAMIC_DEBUG: printk 会关中断影响性能,如果在 usb 的 read/write 里 printk,那么 usb 就没法直接用了。所以用动态打印,调试时才打开,control 节点默认不输出,如下操作才输出,+p 是将动态打印语句【pr_debug/dev_dbg()】转为 printk 语句 (相当于下面的 define dev_dbg …)。

6. Trace

ltrace,strace 参考 notes8 epoll,这俩底层实现是 ptrace(如下)。

6.1 Tracefs

7. 性能工具

7.1 磁盘 io: iostat

7.2 内核热点函数:perf

7.3 网络

ifconfig 和 ip 分别属于 net-tools 和 iproute2: 上面是统计信息,下面是网络吞吐:

7.4 找到 cpu 瓶颈

7.5 Valgrind 内存泄漏排查

7.6 主频设置

可 echo 上面支持的策略 > scaling_governor。

7.7 驱动稳定性

access_ok:判断 arg 地址的数据长度是不是和 len 一样大: likely 一般用在 if 判断里,cpu 会把当前指令后面指令预取出来,等到执行时就去执行,效率提高,但是也要判断后面那条指令大概率执不执行,执行的话取出来,不执行则跳过。

7.8 平均负载

8. Debugfs

debugfs.h 中 api 建立目录/sys/kernel/debug

内核开发会遇到内核崩溃,如空指针异常,内存访问越界,如下会打印出异常调用栈信息定位。

目录

  1. 1. 休眠&唤醒
  2. 2. Proc 文件系统
  3. 3. 调用堆栈内核函数
  4. 4. Printk
  5. 5. 动态打印
  6. 6. Trace
  7. 6.1 Tracefs
  8. 7. 性能工具
  9. 7.1 磁盘 io: iostat
  10. 7.2 内核热点函数:perf
  11. 7.3 网络
  12. 7.4 找到 cpu 瓶颈
  13. 7.5 Valgrind 内存泄漏排查
  14. 7.6 主频设置
  15. 7.7 驱动稳定性
  16. 7.8 平均负载
  17. 8. Debugfs

更多推荐文章

查看全部
  • ThinkPHP 5.1 环境安装与配置指南
  • 面试高频缓存算法:LRU 与 LFU 原理及 Java 实现
  • MCP 实战:Figma 设计稿自动转前端代码
  • 5 款国产免费 AI 代码助手评测对比
  • 基于扣子平台搭建 AI 智能体与变现路径指南
  • Qwen2.5-VL 系列模型正式开源及实测分析
  • 网络安全工程师获取高薪的核心能力与素质要求
  • Java 核心面试知识点与实战解析
  • Neo4j Desktop 2 安装配置与图数据库实战
  • 大模型时代:重构技术开发的生产与组织方式
  • 我用 Nexent 做了个 AI 大厨:基于 Nexent 知识库与 MCP 生态打造智能烹饪顾问实战
  • GitHub Copilot 接入第三方 OpenAI 接口无需扩展
  • MixAIHub 镜像站使用指南:快速访问主流 AI 模型
  • 无人机避障方案 TOP 5 算法对比及选型指南
  • 链式二叉树递归实现与遍历详解
  • 深入理解 C++ 多态:虚函数、重写与底层原理
  • Spring Boot 与 Vue 3 实时游戏匹配实战:WebSocket 前后端对接
  • Python 数学可视化实战:显函数、隐函数与曲线交互绘图
  • Java JDK 安装与环境配置教程
  • 低代码表单设计避坑指南:PHP 工程师需掌握的 5 大核心原则

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online