概述
本程序在謝寶友老師[1]所提供的高負載處理模塊的代碼[2]基礎上,根據(jù)5.15版內(nèi)核的變化,修改出的。本程序是一個內(nèi)核模塊,用于監(jiān)控系統(tǒng)負載,在平均負載超過4時,打印所有進程的調(diào)用棧。
本程序分為三個文件:main.c、load.h、Makefile。其中,main.c是本內(nèi)核模塊的主程序;load.h中是該內(nèi)核模塊的擴展代碼,這里放了一個獲取內(nèi)核中未被導出符號(變量或函數(shù))的一個函數(shù);Makefile用來編譯該內(nèi)核模塊。完整代碼在文章的最下面。
模塊的主要實現(xiàn)方式為:設置一個定時器,以固定的間隔訪問系統(tǒng)給出的1分鐘內(nèi)平均負載,如果超過負載閾值,則輸出運行隊列全部進程棧信息,并使程序休眠一段較長的時間。流程圖如下:
定時器
本模塊采用了hrtimer——高精度定時器,由linux/hrtimer.h引入,可精確到ns級。
平均負載
這里有所改動,原文中是通過kallsyms_lookup_name函數(shù)獲取的,但我在瀏覽頭文件時發(fā)現(xiàn)了linux/sched/loadavg.h頭文件,里面已經(jīng)定義好了一些有關平均負載——loadavg的宏,并導出了avenrun——平均負載數(shù)組——1、5、15分鐘內(nèi)的平均負載,所以我這里直接引用了該頭文件、直接使用了相關符號
輸出進程棧
這里改動很大,在5.15版中,沒有了save_stack_trace_tsk,通過查看linux/stacktrace.h文件,發(fā)現(xiàn)這個函數(shù)被用于未配置CONFIG_ARCH_STACKWALK的系統(tǒng),而配置了CONFIG_ARCH_STACKWALK的系統(tǒng)中,有新的函數(shù):unsigned int stack_trace_save_tsk(struct task_struct *task, unsigned long *store, unsigned int size, unsigned int skipnr),定義于kernel/stacktrace.c中,與舊函數(shù)相比變化很大,好在在源代碼中有詳細的接口說明,根據(jù)這我成功的修改了棧的輸出部分。
與此同時我發(fā)現(xiàn)了功能類似的另一個函數(shù)show_stack,定義于arch/x86/kernel/dumpstack.c
然而,這兩個函數(shù)的符號都沒有導出,也就無法通過引入相關頭文件來使用,原文章來獲取內(nèi)核中未導出符號的kallsyms_lookup_name函數(shù)也未被導出,這就要求我尋找一種新的方法來獲取未導出符號,我找到了kprobe技術。
kprobes技術[3]是內(nèi)核開發(fā)者們專門為了便于跟蹤內(nèi)核函數(shù)執(zhí)行狀態(tài)所設計的一種輕量級內(nèi)核調(diào)試技術。利用kprobes技術,內(nèi)核開發(fā)人員可以在內(nèi)核的絕大多數(shù)指定函數(shù)中動態(tài)的插入探測點來收集所需的調(diào)試狀態(tài)信息而基本不影響內(nèi)核原有的執(zhí)行流程。我們可以通過注冊一個指定了函數(shù)名的kprobe來獲取函數(shù)的地址。
main.c
#include/* for stack_trace_print */ #include /* for module_*, MODULE_*, printk */ #include /* for hrtimer_*, ktime_* */ #include /* for avenrun, LOAD_* */ #include /* for struct task_struct */ #include /* for do_each_thread, while_each_thread */ #include "load.h" /* for find_kallsyms_lookup_name */ #define BACKTRACE_DEPTH 20 /* 最大棧深度 */ void (*ShowStack)(struct task_struct *task, unsigned long *sp, const char *loglvl); /* 將要指向stack_show函數(shù),可以直接輸出進程控制塊的調(diào)用棧 */ unsigned int (*StackTraceSaveTask)(struct task_struct *tsk, unsigned long *store, unsigned int size, unsigned int skipnr); /* 將要指向stack_trace_save_tsk */ static void print_all_task_stack(void) { /* 打印全部進程調(diào)用棧 */ struct task_struct *g, *p; /* 用于遍歷進程 */ unsigned long backtrace[BACKTRACE_DEPTH]; /* 用于存儲調(diào)用棧的函數(shù)地址 */ unsigned int nr_bt; /* 用于存儲調(diào)用棧的層數(shù) */ printk("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! "); printk("Load: %lu.%02lu, %lu.%02lu, %lu.%02lu ", /* 輸出近期平均負載 */ LOAD_INT(avenrun[0]), LOAD_FRAC(avenrun[0]), LOAD_INT(avenrun[1]), LOAD_FRAC(avenrun[1]), LOAD_INT(avenrun[2]), LOAD_FRAC(avenrun[2])); rcu_read_lock(); /* 為運行隊列上鎖 */ printk("dump running task. "); do_each_thread(g, p) { /* 遍歷運行隊列 */ if(p->__state == TASK_RUNNING) { printk("running task, comm: %s, pid %d ", p->comm, p->pid); // show_stack(p, NULL, ""); /* 可以取代下面兩個語句 */ nr_bt = StackTraceSaveTask(p, backtrace, BACKTRACE_DEPTH, 0); /* 保存棧 */ // 和下面一個語句一起可以取代上面一條語句 stack_trace_print(backtrace, nr_bt, 0); /* 打印棧 */ } } while_each_thread(g, p); printk("dump uninterrupted task. "); do_each_thread(g, p) { /* 和上面的遍歷類似 */ if(p->__state & TASK_UNINTERRUPTIBLE) { printk("uninterrupted task, comm: %s, pid %d ", p->comm, p->pid); // show_stack(p, NULL, ""); /* 可以取代下面兩個語句 */ nr_bt = StackTraceSaveTask(p, backtrace, BACKTRACE_DEPTH, 0); /* 保存棧 */ // 和下面一個語句一起可以取代上面一條語句 stack_trace_print(backtrace, nr_bt, 0); /* 打印棧 */ } } while_each_thread(g, p); rcu_read_unlock(); /* 為運行隊列解鎖 */ } struct hrtimer timer; /* 創(chuàng)建一個計時器 */ static void check_load(void) { /* 主要的計時器觸發(fā)后的程序 */ static ktime_t last; /* 默認值是0 */ u64 ms; int load = LOAD_INT(avenrun[0]); if(load < 4) /* 近1分鐘內(nèi)平均負載不超過4,沒問題 */ return; ms = ktime_to_ms(ktime_sub(ktime_get(), last)); /* 計算打印棧時間間隔 */ if(ms < 20*1000) /* 打印棧的時間間隔小于20s,不打印 */ return; last = ktime_get(); /* 獲取當前時間 */ print_all_task_stack(); /* 打印全部進程調(diào)用棧 */ } static enum hrtimer_restart monitor_handler(struct hrtimer *hrtimer) { /* 計時器到期后調(diào)用的程序 */ enum hrtimer_restart ret = HRTIMER_RESTART; check_load(); hrtimer_forward_now(hrtimer, ms_to_ktime(10)); /* 延期10ms后到期 */ return ret; } static void start_timer(void) { hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_PINNED); /* 初始化計時器為綁定cpu的自開機以來的恒定時鐘 */ timer.function = monitor_handler; /* 設定回調(diào)函數(shù) */ hrtimer_start_range_ns(&timer, ms_to_ktime(10), 0, HRTIMER_MODE_REL_PINNED); /* 啟動計時器并設定計時模式為綁定cpu的相對時間,計時10ms,松弛范圍為0 */ } static int load_monitor_init(void) { /* 模塊初始化 */ // ShowStack = find_kallsyms_lookup_name("show_stack"); /* 使用show_stack時將此三行取消注釋 */ // if(!ShowStack) // return -EINVAL; StackTraceSaveTask = find_kallsyms_lookup_name("stack_trace_save_tsk"); /* 使用stack_trace_save_tsk時將此三行取消注釋 */ if(!StackTraceSaveTask) return -EINVAL; start_timer(); printk("load-monitor loaded. "); return 0; } static void load_monitor_exit(void) { /* 模塊退出 */ hrtimer_cancel(&timer); /* 取消計時器 */ printk("load-monitor unloaded. "); } module_init(load_monitor_init); module_exit(load_monitor_exit); MODULE_DESCRIPTION("load monitor module"); MODULE_AUTHOR("Baoyou Xie "); MODULE_LICENSE("GPL");
load.h
#include/* for *kprobe* */ /* 調(diào)用kprobe找到kallsyms_lookup_name的地址位置 */ int noop_pre(struct kprobe *p, struct pt_regs *regs) { return 0; } /* 定義探針前置程序 */ void *find_kallsyms_lookup_name(char *sym) { /* 通過kprobe找到函數(shù)入口地址 */ int ret; void *p; /* 用于保存要返回的函數(shù)入口地址 */ struct kprobe kp = { /* 初始化探針 */ .symbol_name = sym, /* 設置要跟蹤的內(nèi)核函數(shù)名 */ .pre_handler = noop_pre /* 放置前置程序 */ }; if ((ret = register_kprobe(&kp)) < 0) { /* 探針注冊失敗就報告錯誤信息并返回空指針 */ printk(KERN_INFO "register_kprobe failed, error ", ret); return NULL; } /* 保存探針跟蹤地址,即函數(shù)入口;輸出注冊成功信息,注銷探針,返回地址 */ p = kp.addr; printk(KERN_INFO "%s addr: %lx ", sym, (unsigned long)p); unregister_kprobe(&kp); return p; }
Makefile
OS_VER := UNKOWN UNAME := $(shell uname -r) ifneq ($(findstring 4.15.0-39-generic,$(UNAME)),) OS_VER := UBUNTU_1604 endif ifneq ($(KERNELRELEASE),) obj-m += $(MODNAME).o $(MODNAME)-y := main.o ccflags-y := -I$(PWD)/ else export PWD=`pwd` endif ifeq ($(KERNEL_BUILD_PATH),) KERNEL_BUILD_PATH := /lib/modules/`uname -r`/build endif ifeq ($(MODNAME),) export MODNAME=load_monitor endif all: make CFLAGS_MODULE=-D$(OS_VER) -C /lib/modules/`uname -r`/build M=`pwd` modules clean: make -C $(KERNEL_BUILD_PATH) M=$(PWD) clean
運行結(jié)果
將三個文件放入一個單獨的文件夾中,運行make命令,編譯出可插入內(nèi)核的程序。編譯好后,運行sudo insmod load_monitor.ko命令將其插入內(nèi)核。
接下來是測試,運行stress -c 8命令(stress需要另外安裝),使平均負載快速到達4以上,這里可以在新的虛擬終端通過top命令實時觀測負載。當負載到達4之后,在運行著stress命令的窗口中按下ctrl+c終止程序,運行sudo dmesg命令就可以查看到內(nèi)核棧的輸出信息。
-
內(nèi)核
+關注
關注
3文章
1382瀏覽量
40372 -
定時器
+關注
關注
23文章
3255瀏覽量
115174 -
高負載
+關注
關注
0文章
4瀏覽量
5959
原文標題:概述
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論