精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

高負載處理及上下文切換

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

概述

本程序在謝寶友老師[1]所提供的高負載處理模塊的代碼[2]基礎上,根據5.15版內核的變化,修改出的。本程序是一個內核模塊,用于監控系統負載,在平均負載超過4時,打印所有進程的調用棧。

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

模塊的主要實現方式為:設置一個定時器,以固定的間隔訪問系統給出的1分鐘內平均負載,如果超過負載閾值,則輸出運行隊列全部進程棧信息,并使程序休眠一段較長的時間。流程圖如下:

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

定時器

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

平均負載

這里有所改動,原文中是通過kallsyms_lookup_name函數獲取的,但我在瀏覽頭文件時發現了linux/sched/loadavg.h頭文件,里面已經定義好了一些有關平均負載——loadavg的宏,并導出了avenrun——平均負載數組——1、5、15分鐘內的平均負載,所以我這里直接引用了該頭文件、直接使用了相關符號

輸出進程棧

這里改動很大,在5.15版中,沒有了save_stack_trace_tsk,通過查看linux/stacktrace.h文件,發現這個函數被用于未配置CONFIG_ARCH_STACKWALK的系統,而配置了CONFIG_ARCH_STACKWALK的系統中,有新的函數:unsigned int stack_trace_save_tsk(struct task_struct *task, unsigned long *store, unsigned int size, unsigned int skipnr),定義于kernel/stacktrace.c中,與舊函數相比變化很大,好在在源代碼中有詳細的接口說明,根據這我成功的修改了棧的輸出部分。

與此同時我發現了功能類似的另一個函數show_stack,定義于arch/x86/kernel/dumpstack.c

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

kprobes技術[3]是內核開發者們專門為了便于跟蹤內核函數執行狀態所設計的一種輕量級內核調試技術。利用kprobes技術,內核開發人員可以在內核的絕大多數指定函數中動態的插入探測點來收集所需的調試狀態信息而基本不影響內核原有的執行流程。我們可以通過注冊一個指定了函數名的kprobe來獲取函數的地址。

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函數,可以直接輸出進程控制塊的調用棧 */
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) { /* 打印全部進程調用棧 */
    struct task_struct *g, *p; /* 用于遍歷進程 */
    unsigned long backtrace[BACKTRACE_DEPTH]; /* 用于存儲調用棧的函數地址 */
    unsigned int nr_bt; /* 用于存儲調用棧的層數 */
    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; /* 創建一個計時器 */
static void check_load(void) { /* 主要的計時器觸發后的程序 */
    static ktime_t last; /* 默認值是0 */
    u64 ms;
    int load = LOAD_INT(avenrun[0]);
    if(load < 4) /* 近1分鐘內平均負載不超過4,沒問題 */
        return;
    ms = ktime_to_ms(ktime_sub(ktime_get(), last)); /* 計算打印棧時間間隔 */
    if(ms < 20*1000) /* 打印棧的時間間隔小于20s,不打印 */
        return;
    last = ktime_get(); /* 獲取當前時間 */
    print_all_task_stack(); /* 打印全部進程調用棧 */
}
static enum hrtimer_restart monitor_handler(struct hrtimer *hrtimer) { /* 計時器到期后調用的程序 */
    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; /* 設定回調函數 */
    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* */
/* 調用kprobe找到kallsyms_lookup_name的地址位置 */
int noop_pre(struct kprobe *p, struct pt_regs *regs) { return 0; } /* 定義探針前置程序 */
void *find_kallsyms_lookup_name(char *sym) { /* 通過kprobe找到函數入口地址 */
    int ret;
    void *p; /* 用于保存要返回的函數入口地址 */
    struct kprobe kp = { /* 初始化探針 */
        .symbol_name = sym, /* 設置要跟蹤的內核函數名 */
        .pre_handler = noop_pre /* 放置前置程序 */
    };
    if ((ret = register_kprobe(&kp)) < 0) { /* 探針注冊失敗就報告錯誤信息并返回空指針 */
        printk(KERN_INFO "register_kprobe failed, error
", ret);
        return NULL;
    }
    /* 保存探針跟蹤地址,即函數入口;輸出注冊成功信息,注銷探針,返回地址 */
    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

運行結果

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

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

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

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 內核
    +關注

    關注

    3

    文章

    1366

    瀏覽量

    40236
  • 定時器
    +關注

    關注

    23

    文章

    3241

    瀏覽量

    114516
  • 高負載
    +關注

    關注

    0

    文章

    4

    瀏覽量

    5943

原文標題:概述

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

收藏 人收藏

    評論

    相關推薦

    編寫一個任務調度程序,在上下文切換后遇到了一些問題求解

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

    關于進程上下文、中斷上下文及原子上下文的一些概念理解

    : 進程控制塊task_struct、內存管理信息(mm_struct、vm_area_struct、pgd、pte)、內核棧。 當發生進程調度時,進行進程切換就是上下文切換(context switch
    發表于 09-06 09:58

    BT堆棧上下文切換

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

    多線程如何實現上下文切換

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

    上下文切換簡介

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

    上下文切換的情況發生

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

    ucos上下文該怎么切換

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

    基于cortex-m3的rt-thread系統如何實現線程上下文切換

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

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

    ;#125;4.2 第一次上下文切換在本文 3.2 節對系統調用處理函數的解析中,我們有意忽略了一個極其重要的點。因為在 3.2 節中,我們聚焦的是系統調用的實現。本節中,我們補充被忽略的事實:mbed 會在
    發表于 02-16 14:26

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

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

    中斷中的上下文切換詳解

    的,那么根據上文分析,在出systick中斷處理函數時,也是需要調用tos_knl_irq_leave來使得此任務得到一次上下文切換的機會。  總結一下,對于可能會觸發某些任務進入ready狀態的中斷處理
    發表于 03-23 17:18

    CPU上下文切換的詳細資料講解

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

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

    在我的上一篇文章:《探討 Linux CPU 的上下文切換》中,我談到了 CPU 上下文切換的工作原理。快速回顧一下,CPU 上下文切換是保證 Linux 系統正常運行的核心功能。可分為進程
    的頭像 發表于 05-05 20:11 ?1924次閱讀

    Linux技術:什么是cpu上下文切換

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

    FreeRTOS系列技術文章:上下文切換

    嵌入式實時操作系統(RTOS)中的上下文切換是指保存和恢復任務的狀態,以使調度程序能夠切換到另一個任務,從而促進多任務處理
    的頭像 發表于 11-21 15:48 ?1117次閱讀