调度器35—DEBUG方法汇总-lockdep/hw_breakpoint
一、基础理论概述
1. 有三种 preemption model 和适用场景
非强占式内核(服务器)
强占式内核(低延迟桌面)
voluntary kernel preemption(桌面)
2. 动态功耗 = C * Freq * Voltage^2 其中Freq 是CPU运行频率,Voltage是CPU核心的电压
3. RT和DL视角下的负载均衡:n个runnable的RT task平均分配在n个处理器上。当一个CPU上有2个以上的RT task的时候被称为overload。
4. 任务虚拟时间 = 任务物理时间 * nice=0的权重/任务权重
5. /proc/sys/kernel/sched_rt_runtime_us 中显示的950ms表示1s内允许每个CPU上的RT任务运行950ms,若一个CPU上的时间耗尽后可以借用其它CPU的时间。
6. CPU idle子系统通过 lpm governor 来选择idle governor
二、Lockup_detector
背景:LOCKUP_DETECTOR 是内核的一个调试选项,其可以探测到soft lockup与hard lockup,其主要涉及到kernel线程、时钟中断、NMI中断。
soft lockup: 进程抢占被长时间关闭而导致的进程无法调度。
hard lockup: 中断被长时间关闭而导致的更严重的问题。
注:hard lockup的监测依赖硬件支持,目前Qcom平台还不支持NMI,所以无法使用 CONFIG_HARDLOCKUP_DETECTOR 来调试hard lockup。而soft lockup是软件机制,Qcom内核中为每个CPU创建一个叫watchdog的线程,其优先级非常高,若是一定时间没有被调到到,就认为CPU卡死了。
需要使能:CONFIG_LOCKUP_DETECTOR=y
直接看dmesg中检索 "soft lockup" 查看即可。
三、死锁检测 Lockdep
1. 作用:用于调试内核空间中的死锁问题。
需要使能:
CONFIG_PROVE_LOCKING=y CONFIG_DEBUG_LOCK_ALLOCK=y CONFIG_DEBUG_LOCKDEP=y CONFIG_LOCK_STAT=y CONFIG_DEBUG_LOCKING_API_SELFTESTS=y
出现死锁后,监测到死锁后,在kernel log中可以看到死锁类型,死锁描述(欲持锁点和已持锁点),死锁时尝试持锁位置和之前被持有的位置。
2. 内核还提供了一些其它debug死锁的方法:
CONFIG_DEBUG_MUTEXES: 可以用来调试mutex死锁。
CONFIG_DEBUG_SPINLOCK: 可以用来调试spinlock死锁。
CONFIG_DEBUG_LOCK_ALLOC: 可以用来检查锁是否被异常释放(包含mutex spin rwlock rwsem)。
CONFIG_DEBUG_ATOMIC_SLEEP: 可以用来检查是否在原子上下文中使用might_sleep相关操作。
CONFIG_SCHED_STACK_END_CHECK: 可以用来检查栈是否溢出。
CONFIG_DETECT_HUNG_TASK: 可有用来检查是否有进程长时间处于阻塞状态。
四、hw_breakpoint
1. hw_breakpoint 简介
hw_breakpoint 是由处理器提供专门断点寄存器来保存一个地址,是需要处理器支持的。处理器在执行过程中会不断去匹配,当匹配上后则会产生中断。
用于内核空间调试。可以支持读、写断点(支持1-8字节),也支持指令执行断点。
一段可以在断点irq函数中打印 dump_stack(),或直接 BUG() 让系统进dump来分析。一般ARM处理器支持4-8个断点。
实现文件:
kernel/events/hw_breakpoint.c
arch/arm64/kernel/hw_breakpoint.c
例子:
samples/hw_breakpoint/data_breakpoint.c
使能和使用例子需要配置:
CONFIG_HAVE_HW_BREAKPOINT=y
CONFIG_SAMPLE_HW_BREAKPOINT=y
但是一般 HAVE_HW_BREAKPOINT 是默认使能的,而 SAMPLES 是没有使能的。看Kconfig文件,测试的 data_breakpoint.c 必须要编译成模块。因此改为如下配置:
CONFIG_SAMPLES=y
CONFIG_SAMPLE_HW_BREAKPOINT=m
2. 实验
使用内核自带的例子 data_breakpoint.c,将 ksym_name[KSYM_NAME_LEN] = "sysctl_hw_breakpoint_debug"; 来进行测试,代码如下:
/* * data_breakpoint.c - Sample HW Breakpoint file to watch kernel data address * * usage: insmod data_breakpoint.ko ksym=<ksym_name> * * This file is a kernel module that places a breakpoint over ksym_name kernel * variable using Hardware Breakpoint register. The corresponding handler which * prints a backtrace is invoked every time a write operation is performed on * that variable. */ #include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_INFO */ #include <linux/init.h> /* Needed for the macros */ #include <linux/kallsyms.h> #include <linux/perf_event.h> #include <linux/hw_breakpoint.h> struct perf_event * __percpu *sample_hbp; /* //在/proc/sys/kernel下加一个节点,确保只有我们会写它,也可以在insmod的时候指定模块参数 */ static char ksym_name[KSYM_NAME_LEN] = "sysctl_hw_breakpoint_debug"; module_param_string(ksym, ksym_name, KSYM_NAME_LEN, S_IRUGO); MODULE_PARM_DESC(ksym, "Kernel symbol to monitor; this module will report any" " write operations on the kernel symbol"); static void sample_hbp_handler(struct perf_event *bp, struct perf_sample_data *data, struct pt_regs *regs) { printk(KERN_INFO "%s value is changed\n", ksym_name); dump_stack(); printk(KERN_INFO "Dump stack from sample_hbp_handler\n"); } extern unsigned int sysctl_hw_breakpoint_debug; static int __init hw_break_module_init(void) { int ret; struct perf_event_attr attr; /* 由于这个文件编译成了模块,内核中的符号必须要EXPORT_SYMBOL()导出,这里才能访问到 */ void *addr = __symbol_get(ksym_name); /* 为了不影响实验进度,再加一道 */ if (!addr) { printk("breakpoint:!addr, get directly\n"); addr = (void*)&sysctl_hw_breakpoint_debug; } if (!addr) return -ENXIO; hw_breakpoint_init(&attr); attr.bp_addr = (unsigned long)addr; attr.bp_len = HW_BREAKPOINT_LEN_4; attr.bp_type = HW_BREAKPOINT_W; sample_hbp = register_wide_hw_breakpoint(&attr, sample_hbp_handler, NULL); if (IS_ERR((void __force *)sample_hbp)) { ret = PTR_ERR((void __force *)sample_hbp); goto fail; } printk(KERN_INFO "HW Breakpoint for %s write installed\n", ksym_name); return 0; fail: printk(KERN_INFO "Breakpoint registration failed\n"); return ret; } static void __exit hw_break_module_exit(void) { unregister_wide_hw_breakpoint(sample_hbp); symbol_put(ksym_name); printk(KERN_INFO "HW Breakpoint for %s write uninstalled\n", ksym_name); } module_init(hw_break_module_init); module_exit(hw_break_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("K.Prasad"); MODULE_DESCRIPTION("ksym breakpoint");
在probe函数中注册了断点,实测在写的时候会卡住,而且只有写这个地址的任务会被卡住,系统照常正常运行。需要另外起一个终端,执行rmmod操作,因为只有rmmod的时候才会unregister断点,被卡住的任务只有在rmmod时才能退出,随即,系统立即死机。写这个地址的进程被卡住时,后台不停的打印栈回溯,如下:
Call trace: dump_stack_lvl+0xc4/0x140 perf_swevent_event+0xa0/0x1dc watchpoint_handler+0x214/0x61c el1_sync_handler+0x40/0x88 proc_dointvec+0x38/0x48 //写地址的函数 vfs_write+0x300/0x37c el0_svc_common+0xd4/0x270 el0_sync_handler+0x8c/0xf0 CPU: 4 PID: 11736 Comm: sh Tainted: P W O 5.10.66-android12-9-gab6a9773e258 #1 //写地址的pid和comm
执行 insmod /vendor/lib/modules/data_breakpoint.ko ksym=jiffies 时,整改系统hang住,另外开一个cmd终端,系统死机。
posted on 2022-05-01 13:06 Hello-World3 阅读(1329) 评论(0) 编辑 收藏 举报