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