0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

高負(fù)載處理及上下文切換

Linux閱碼場 ? 來源:Linux閱碼場 ? 作者:Linux閱碼場 ? 2022-11-10 09:37 ? 次閱讀

概述

本程序在謝寶友老師[1]所提供的高負(fù)載處理模塊的代碼[2]基礎(chǔ)上,根據(jù)5.15版內(nèi)核的變化,修改出的。本程序是一個內(nèi)核模塊,用于監(jiān)控系統(tǒng)負(fù)載,在平均負(fù)載超過4時,打印所有進(jìn)程的調(diào)用棧。

本程序分為三個文件:main.c、load.h、Makefile。其中,main.c是本內(nèi)核模塊的主程序;load.h中是該內(nèi)核模塊的擴(kuò)展代碼,這里放了一個獲取內(nèi)核中未被導(dǎo)出符號(變量或函數(shù))的一個函數(shù);Makefile用來編譯該內(nèi)核模塊。完整代碼在文章的最下面。

模塊的主要實(shí)現(xiàn)方式為:設(shè)置一個定時器,以固定的間隔訪問系統(tǒng)給出的1分鐘內(nèi)平均負(fù)載,如果超過負(fù)載閾值,則輸出運(yùn)行隊(duì)列全部進(jìn)程棧信息,并使程序休眠一段較長的時間。流程圖如下:

0451803c-608f-11ed-8abf-dac502259ad0.png

定時器

本模塊采用了hrtimer——高精度定時器,由linux/hrtimer.h引入,可精確到ns級。

平均負(fù)載

這里有所改動,原文中是通過kallsyms_lookup_name函數(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)符號

輸出進(jìn)程棧

這里改動很大,在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ù)相比變化很大,好在在源代碼中有詳細(xì)的接口說明,根據(jù)這我成功的修改了棧的輸出部分。

與此同時我發(fā)現(xiàn)了功能類似的另一個函數(shù)show_stack,定義于arch/x86/kernel/dumpstack.c

然而,這兩個函數(shù)的符號都沒有導(dǎo)出,也就無法通過引入相關(guān)頭文件來使用,原文章來獲取內(nèi)核中未導(dǎo)出符號的kallsyms_lookup_name函數(shù)也未被導(dǎo)出,這就要求我尋找一種新的方法來獲取未導(dǎo)出符號,我找到了kprobe技術(shù)。

kprobes技術(shù)[3]是內(nèi)核開發(fā)者們專門為了便于跟蹤內(nèi)核函數(shù)執(zhí)行狀態(tài)所設(shè)計(jì)的一種輕量級內(nèi)核調(diào)試技術(shù)。利用kprobes技術(shù),內(nèi)核開發(fā)人員可以在內(nèi)核的絕大多數(shù)指定函數(shù)中動態(tài)的插入探測點(diǎn)來收集所需的調(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ù),可以直接輸出進(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]; /* 用于存儲調(diào)用棧的函數(shù)地址 */
    unsigned int nr_bt; /* 用于存儲調(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, ""); /* 可以取代下面兩個語句 */
            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(); /* 為運(yùn)行隊(duì)列解鎖 */
}
struct hrtimer timer; /* 創(chuàng)建一個計(jì)時器 */
static void check_load(void) { /* 主要的計(jì)時器觸發(fā)后的程序 */
    static ktime_t last; /* 默認(rèn)值是0 */
    u64 ms;
    int load = LOAD_INT(avenrun[0]);
    if(load < 4) /* 近1分鐘內(nèi)平均負(fù)載不超過4,沒問題 */
        return;
    ms = ktime_to_ms(ktime_sub(ktime_get(), last)); /* 計(jì)算打印棧時間間隔 */
    if(ms < 20*1000) /* 打印棧的時間間隔小于20s,不打印 */
        return;
    last = ktime_get(); /* 獲取當(dāng)前時間 */
    print_all_task_stack(); /* 打印全部進(jìn)程調(diào)用棧 */
}
static enum hrtimer_restart monitor_handler(struct hrtimer *hrtimer) { /* 計(jì)時器到期后調(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ì)時器為綁定cpu的自開機(jī)以來的恒定時鐘 */
    timer.function = monitor_handler; /* 設(shè)定回調(diào)函數(shù) */
    hrtimer_start_range_ns(&timer, ms_to_ktime(10), 0, HRTIMER_MODE_REL_PINNED); /* 啟動計(jì)時器并設(shè)定計(jì)時模式為綁定cpu的相對時間,計(jì)時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); /* 取消計(jì)時器 */
    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, /* 設(shè)置要跟蹤的內(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

運(yùn)行結(jié)果

將三個文件放入一個單獨(dú)的文件夾中,運(yùn)行make命令,編譯出可插入內(nèi)核的程序。編譯好后,運(yùn)行sudo insmod load_monitor.ko命令將其插入內(nèi)核。

接下來是測試,運(yùn)行stress -c 8命令(stress需要另外安裝),使平均負(fù)載快速到達(dá)4以上,這里可以在新的虛擬終端通過top命令實(shí)時觀測負(fù)載。當(dāng)負(fù)載到達(dá)4之后,在運(yùn)行著stress命令的窗口中按下ctrl+c終止程序,運(yùn)行sudo dmesg命令就可以查看到內(nèi)核棧的輸出信息。

04656c78-608f-11ed-8abf-dac502259ad0.png

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1346

    瀏覽量

    40152
  • 定時器
    +關(guān)注

    關(guān)注

    23

    文章

    3227

    瀏覽量

    114146
  • 高負(fù)載
    +關(guān)注

    關(guān)注

    0

    文章

    4

    瀏覽量

    5940

原文標(biāo)題:概述

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    編寫一個任務(wù)調(diào)度程序,在上下文切換后遇到了一些問題求解

    大家好, 我正在編寫一個任務(wù)調(diào)度程序,在上下文切換后遇到了一些問題。 為下一個任務(wù)恢復(fù)上下文后: __builtin_tricore_mtcr_by_name(\"pcxi\"
    發(fā)表于 05-22 07:50

    關(guān)于進(jìn)程上下文、中斷上下文及原子上下文的一些概念理解

    : 進(jìn)程控制塊task_struct、內(nèi)存管理信息(mm_struct、vm_area_struct、pgd、pte)、內(nèi)核棧。 當(dāng)發(fā)生進(jìn)程調(diào)度時,進(jìn)行進(jìn)程切換就是上下文切換(context switch
    發(fā)表于 09-06 09:58

    BT堆棧上下文切換

    100ms就會產(chǎn)生一個上下文切換。上下文切換每秒似乎有點(diǎn)。我想我真的不能抱怨10個開關(guān),但是有什么東西嗎?在BT協(xié)議中真的需要這個嗎?不能處理中斷驅(qū)動嗎?(這是BT是可連接的,但既沒
    發(fā)表于 12-17 16:30

    多線程如何實(shí)現(xiàn)上下文切換

    處理系統(tǒng)中,CPU需要處理所有程序的操作,當(dāng)用戶來回切換它們時,需要記錄這些程序執(zhí)行到哪里。上下文切換就是這樣一個過程,他允許CPU記錄并恢復(fù)各種正在運(yùn)行程序的狀態(tài),使它能夠完成
    發(fā)表于 08-02 08:21

    上下文切換簡介

    處理系統(tǒng)中,CPU需要處理所有程序的操作,當(dāng)用戶來回切換它們時,需要記錄這些程序執(zhí)行到哪里。上下文切換就是這樣一個過程,他允許CPU記錄并恢復(fù)各種正在運(yùn)行程序的狀態(tài),使它能夠完成
    發(fā)表于 08-06 08:08

    上下文切換的情況發(fā)生

    處理系統(tǒng)中,CPU需要處理所有程序的操作,當(dāng)用戶來回切換它們時,需要記錄這些程序執(zhí)行到哪里。上下文切換就是這樣一個過程,他允許CPU記錄并恢復(fù)各種正在運(yùn)行程序的狀態(tài),使它能夠完成
    發(fā)表于 08-07 08:38

    ucos上下文該怎么切換?

    的值,那么在pendSV處理結(jié)束的時刻我們看到有條 BXLR 指令, 那豈不是又回到了任務(wù)A的這個地方呀,怎么能切換到別的任務(wù)上去啊??????---->問題2:參見附件:圖示中上下文切換為什么會回到任務(wù)B呀?不是IRQ一直懸起
    發(fā)表于 08-26 03:21

    基于cortex-m3的rt-thread系統(tǒng)如何實(shí)現(xiàn)線程上下文切換

    PendSV 異常處理函數(shù)里完成上下文切換 LDRr0, =NVIC_INT_CTRL LDRr1, =NVIC_PENDSVSET STRr1, [r0] BXLR流程圖如下
    發(fā)表于 05-05 15:00

    討論ARM mbed OS(RTX) 的上下文切換

    ;#125;4.2 第一次上下文切換在本文 3.2 節(jié)對系統(tǒng)調(diào)用處理函數(shù)的解析中,我們有意忽略了一個極其重要的點(diǎn)。因?yàn)樵?3.2 節(jié)中,我們聚焦的是系統(tǒng)調(diào)用的實(shí)現(xiàn)。本節(jié)中,我們補(bǔ)充被忽略的事實(shí):mbed 會在
    發(fā)表于 02-16 14:26

    rt-thread上下文切換函數(shù)的意義在哪?

    Cortex-M3內(nèi)核上下文切換函數(shù)rt_hw_context_switch()/ rt_hw_context_switch_interrupt()中有個判斷rt_thread_switch_interrupt_flag的地方,不知道意義在哪?
    發(fā)表于 03-10 11:28

    中斷中的上下文切換詳解

    的,那么根據(jù)上文分析,在出systick中斷處理函數(shù)時,也是需要調(diào)用tos_knl_irq_leave來使得此任務(wù)得到一次上下文切換的機(jī)會。  總結(jié)一下,對于可能會觸發(fā)某些任務(wù)進(jìn)入ready狀態(tài)的中斷處理
    發(fā)表于 03-23 17:18

    CPU上下文切換的詳細(xì)資料講解

    當(dāng)UCOS-III轉(zhuǎn)向執(zhí)行另一項(xiàng)新任務(wù)的時候,他保存了當(dāng)前任務(wù)的CPU寄存器到堆棧,并從新任務(wù)的堆棧CPU寄存器載入CPU,這個過程叫做上下文切換
    發(fā)表于 08-16 17:31 ?2次下載
    CPU<b class='flag-5'>上下文切換</b>的詳細(xì)資料講解

    如何分析Linux CPU上下文切換問題

    在我的上一篇文章:《探討 Linux CPU 的上下文切換》中,我談到了 CPU 上下文切換的工作原理??焖倩仡櫼幌?,CPU 上下文切換是保證 Linux 系統(tǒng)正常運(yùn)行的核心功能??煞譃檫M(jìn)程
    的頭像 發(fā)表于 05-05 20:11 ?1877次閱讀

    Linux技術(shù):什么是cpu上下文切換

    過多的上下文切換會消耗 CPU 的時間來保存和恢復(fù)寄存器、程序計(jì)數(shù)器、內(nèi)核棧和虛擬內(nèi)存等數(shù)據(jù),從而導(dǎo)致系統(tǒng)性能顯著下降。 既然上下文切換對系統(tǒng)性能的影響如此之大,那么我們?nèi)绾螜z查它呢?好了,你可以使用 vmstat 工具來查詢你系統(tǒng)的
    發(fā)表于 09-01 09:31 ?395次閱讀
    Linux技術(shù):什么是cpu<b class='flag-5'>上下文切換</b>

    FreeRTOS系列技術(shù)文章:上下文切換

    嵌入式實(shí)時操作系統(tǒng)(RTOS)中的上下文切換是指保存和恢復(fù)任務(wù)的狀態(tài),以使調(diào)度程序能夠切換到另一個任務(wù),從而促進(jìn)多任務(wù)處理
    的頭像 發(fā)表于 11-21 15:48 ?1022次閱讀