一、基础理论概述

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终端,系统死机。