一、基础理论概述
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终端,系统死机。