一、影響 Linux 性能的各種因素 1、系統硬體資源 (1)CPU 如何判斷多核 CPU 與超線程 消耗 CPU 的業務:動態 web 服務、mail 服務 (2)記憶體 物理記憶體與 swap 的取捨 選擇 64 位 Linux 操作系統 消耗記憶體的業務:記憶體資料庫(redis/hbase/mong ...
什麼是調度
調度器決定哪個線程被允許在任何時間點上執行;這個線程被稱為當前線程。
在不同的時間點有機會改變當前線程的身份。這些點被稱為重新安排點。一些潛在的重排點是:
- 從運行狀態過渡到暫停或等待狀態,例如通過k_sem_take()或k_sleep()。
- 過渡到準備狀態,例如通過k_sem_give()或k_thread_start()。
- 處理完中斷後返回到線程上下文
- 調用k_yield()
當線程主動發起將自身轉換為暫停或等待狀態的操作時,它就會進入睡眠狀態。
每當調度器改變了當前線程的身份,或者當前線程的執行被ISR所取代時,內核會首先保存當前線程的CPU寄存器值。當線程後來恢復執行時,這些寄存器的值會被恢復。
調度器是如何工作的?
內核的調度器選擇最高優先順序的就緒線程作為當前線程。當存在多個相同優先順序的就緒線程時,調度器會選擇等待時間最長的那個。
註意:ISR的執行要優先於線程的執行。除非中斷被屏蔽了,否則當前的線程可以在任何時候被ISR取代。
內核在構建時可以選擇幾種就緒隊列的實現方式之一。這種選擇是一種權衡:
- 代碼大小
- 恆定繫數的運行時間開銷
- 當涉及到許多線程時的性能擴展
你的Kconfig文件(prj.conf)應該包含以下內容之一(或者它將預設為簡單的鏈接列表)。
隊列類型:
- 簡單鏈接列表準備隊列(CONFIG_SCHED_DUMB)
- 簡單無序列表
- 對於單線程來說,具有非常快的恆定時間性能
- 代碼大小非常低
- 對有以下情況的系統有用:
- 有限的代碼大小
- 在任何時候都有少量的線程(<=3)。
- 紅/黑樹就緒隊列(CONFIG_SCHED_SCALABLE)
- 紅/黑樹
- 較慢的恆定時間插入和移除開銷
- 需要額外的2Kb代碼
- 可以乾凈利落地擴展到成千上萬的線程
- 適用於有以下情況的系統:許多併發的可運行線程(>20個左右)。
- 傳統的多隊列就緒隊列(CONFIG_SCHED_MULTIQ)
- 經典的列表陣列,每個優先順序一個(最多32個優先順序)。
- 與 "dumb "調度器相比,其代碼開銷很小
- 在0(1)時間內運行,恆定繫數很低
- 需要相當大的RAM預算來存儲列表頭
- 與截止日期調度和SMP親和性不相容
- 有少量線程的系統(但通常DUMB已經足夠好了)。
- 可擴展的wait_q實現(CONFIG_WAITQ_SCALABLE)。
- 簡單的鏈接列表wait_q (CONFIG_WAITQ_DUMB)
線程優先順序是如何工作的?
線程的優先順序是整數值,可以是負數,也可以是非負數。從數字上看,較低的優先順序優先於較高的值(-5>6)。
調度器根據每個線程的優先順序來區分兩類線程。
- 協作(cooperative)線程有負的優先順序值。一旦它成為當前線程,合作線程就一直是當前線程,直到它執行了使其unready的動作。
- 可搶占的線程有非負的優先權值。一旦成為當前線程,如果協作線程或更高或同等優先順序的可搶占線程準備就緒,可搶占線程可以在任何時候被取代。
線程的初始優先順序值可以在該線程啟動後被向上或向下改變。因此,可搶占的線程有可能成為協作線程,反之亦然。
內核支持幾乎無限數量的線程優先順序。配置選項CONFIG_NUM_COOP_PRIORITIES和CONFIG_NUM_PREEMPT_PRIORITIES為每一類線程指定了優先順序的數量,導致以下可用的優先順序範圍:
- 協作性線程:(-CONFIG_NUM_COOP_PRIORITIES)到-1
- 搶占式線程: 0到(CONFIG_NUM_PREEMPT_PRIOTIES-1)
什麼是協作式時間切分?
因此,如果協作線程執行冗長的計算,它可能會導致其他線程的調度出現不可接受的延遲,包括那些更高的優先順序。
為了剋服這樣的問題,協作線程可以不時地自願放棄CPU,以允許其他線程執行。線程可以通過兩種方式放棄CPU:
- 調用k_yield()將線程放在調度器的就緒線程優先順序列表的後面,然後調用調度器。所有優先順序高於或等於該線程的就緒線程被允許在該線程被重新安排之前執行。如果沒有這樣的線程存在,調度器會立即重新安排該線程,而不進行上下文切換。
- 調用k_sleep()使線程在指定的時間段內unready。然後,所有優先順序的就緒線程都被允許執行;然而,並不保證優先順序低於休眠線程的線程會在休眠線程再次變得就緒之前實際被調度。
什麼是搶占式時間切分?
圖中顯示了同等優先順序的線程相互搶占的情況
搶占式線程可以執行合作性的時間切分(如上所述),或者利用調度器的時間切分能力來允許其他相同優先順序的線程執行。
調度器將時間劃分為一系列的時間片,這裡的時間片是以系統時鐘刻度來衡量的。時間片的大小是可配置的,但這個大小可以在應用程式運行時改變。
在每個時間片結束時,調度器會檢查當前線程是否是可搶占的,如果是,就隱式地代表線程調用k_yield()。這給了其他相同優先順序的準備好的線程在當前線程再次被調度之前執行的機會。如果沒有相同優先順序的線程準備好了,那麼當前線程仍然保留。
優先順序高於指定限制的線程不受搶占式時間切分的影響,也不會被同等優先順序的線程搶占。這允許應用程式只在處理對時間不太敏感的低優先順序線程時使用搶占式時間切分。
註意:內核的時間切分演算法並不能確保一組同等優先順序的線程獲得公平的CPU時間,因為它並不衡量線程實際得到的執行時間的多少。然
不希望在執行關鍵操作時被搶占的可預選線程可以通過調用k_sched_lock()指示調度器暫時將其作為協作線程。這可以防止其他線程在執行關鍵操作時受到干擾。
一旦關鍵操作完成,可搶占的線程必須調用k_sched_unlock()來恢復其正常的、可搶占的狀態。
如果線程調用k_sched_lock(),並隨後執行了unready的操作,調度器將把鎖定的線程切換出來,並允許其他線程執行。當鎖定的線程再次成為當前線程時,它的不可搶占狀態將被保持。
註意:對於可搶占的線程來說,鎖定調度器是一種比將其優先順序改為負值更有效的防止搶占的方法。
實例:協作式時間切分
線程_1,即使優先順序較低,也不會讓給線程_2(直到完成)。
#include <zephyr/zephyr.h>
/* size of stack area used by each thread */
#define STACKSIZE 1024
#define PRIORITY_THREAD_1 (-1)
#define PRIORITY_THREAD_2 (-2)
K_THREAD_STACK_DEFINE(thread_1_stack_area, STACKSIZE);
static struct k_thread thread_1_data;
K_THREAD_STACK_DEFINE(thread_2_stack_area, STACKSIZE);
static struct k_thread thread_2_data;
void thread_1(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);
int i = 0;
printk("thread_1: thread started \n");
k_thread_start(&thread_2_data);
printk("thread_1: thead_2 started \n");
while (1)
{
i++;
printk("thread_1: thread loop %d\n", i);
if (i == 3)
{
printk("thread_1: thread abort\n");
k_thread_abort(&thread_1_data);
}
}
}
void thread_2(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);
int i = 0;
printk("thread_2: thread started \n");
while (1)
{
i++;
printk("thread_2: thread loop %d\n", i);
if (i == 3)
{
printk("thread_2: thread abort\n");
k_thread_abort(&thread_2_data);
}
}
}
void main(void)
{
k_thread_create(&thread_1_data, thread_1_stack_area,
K_THREAD_STACK_SIZEOF(thread_1_stack_area),
thread_1, NULL, NULL, NULL,
PRIORITY_THREAD_1, 0, K_FOREVER);
k_thread_name_set(&thread_1_data, "thread_1");
k_thread_create(&thread_2_data, thread_2_stack_area,
K_THREAD_STACK_SIZEOF(thread_2_stack_area),
thread_2, NULL, NULL, NULL,
PRIORITY_THREAD_2, 0, K_FOREVER);
k_thread_name_set(&thread_2_data, "thread_2");
k_thread_start(&thread_1_data);
}
執行結果:
$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: qemu32,+nx,+pae
SeaBIOS (version zephyr-v1.0.0-0-g31d4e0e-dirty-20200714_234759-fv-az50-zephyr)
Booting from ROM..
*** Booting Zephyr OS build zephyr-v3.4.0-554-g33b116407b03 ***
thread_1: thread started
thread_1: thead_2 started
thread_1: thread loop 1
thread_1: thread loop 2
thread_1: thread loop 3
thread_1: thread abort
thread_2: thread started
thread_2: thread loop 1
thread_2: thread loop 2
thread_2: thread loop 3
thread_2: thread abort
實例:搶占式(不同優先順序)
#include <zephyr/zephyr.h>
/* size of stack area used by each thread */
#define STACKSIZE 1024
#define PRIORITY_THREAD_1 (3)
#define PRIORITY_THREAD_2 (2)
#define PRIORITY_THREAD_3 (1)
K_THREAD_STACK_DEFINE(thread_1_stack_area, STACKSIZE);
static struct k_thread thread_1_data;
K_THREAD_STACK_DEFINE(thread_2_stack_area, STACKSIZE);
static struct k_thread thread_2_data;
K_THREAD_STACK_DEFINE(thread_3_stack_area, STACKSIZE);
static struct k_thread thread_3_data;
void thread_1(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);
int i = 0;
printk("thread_1: thread started \n");
k_thread_start(&thread_2_data);
while (1)
{
i++;
printk("thread_1: thread loop %d\n", i);
if (i == 3)
{
printk("thread_1: thread abort\n");
k_thread_abort(&thread_1_data);
}
}
}
void thread_2(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);
int i = 0;
printk("thread_2: thread started \n");
k_thread_start(&thread_3_data);
while (1)
{
i++;
printk("thread_2: thread loop %d\n", i);
if (i == 3)
{
printk("thread_2: thread abort\n");
k_thread_abort(&thread_2_data);
}
}
}
void thread_3(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);
int i = 0;
printk("thread_3: thread started \n");
while (1)
{
i++;
printk("thread_3: thread loop %d\n", i);
if (i == 3)
{
printk("thread_3: thread abort\n");
k_thread_abort(&thread_3_data);
}
}
}
void main(void)
{
k_thread_create(&thread_1_data, thread_1_stack_area,
K_THREAD_STACK_SIZEOF(thread_1_stack_area),
thread_1, NULL, NULL, NULL,
PRIORITY_THREAD_1, 0, K_FOREVER);
k_thread_name_set(&thread_1_data, "thread_1");
k_thread_create(&thread_2_data, thread_2_stack_area,
K_THREAD_STACK_SIZEOF(thread_2_stack_area),
thread_2, NULL, NULL, NULL,
PRIORITY_THREAD_2, 0, K_FOREVER);
k_thread_name_set(&thread_2_data, "thread_2");
k_thread_create(&thread_3_data, thread_3_stack_area,
K_THREAD_STACK_SIZEOF(thread_3_stack_area),
thread_3, NULL, NULL, NULL,
PRIORITY_THREAD_3, 0, K_FOREVER);
k_thread_name_set(&thread_3_data, "thread_3");
k_thread_start(&thread_1_data);
}
執行結果:
ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: qemu32,+nx,+pae
SeaBIOS (version zephyr-v1.0.0-0-g31d4e0e-dirty-20200714_234759-fv-az50-zephyr)
Booting from ROM..
*** Booting Zephyr OS build zephyr-v3.4.0-554-g33b116407b03 ***
thread_1: thread started
thread_2: thread started
thread_3: thread started
thread_3: thread loop 1
thread_3: thread loop 2
thread_3: thread loop 3
thread_3: thread abort
thread_2: thread loop 1
thread_2: thread loop 2
thread_2: thread loop 3
thread_2: thread abort
thread_1: thread loop 1
thread_1: thread loop 2
thread_1: thread loop 3
thread_1: thread abort
實例:搶占式(相同優先順序)
include <zephyr/zephyr.h>
/* size of stack area used by each thread */
#define STACKSIZE 1024
#define PRIORITY_THREAD_1 (1)
#define PRIORITY_THREAD_2 (1)
#define PRIORITY_THREAD_3 (1)
K_THREAD_STACK_DEFINE(thread_1_stack_area, STACKSIZE);
static struct k_thread thread_1_data;
K_THREAD_STACK_DEFINE(thread_2_stack_area, STACKSIZE);
static struct k_thread thread_2_data;
K_THREAD_STACK_DEFINE(thread_3_stack_area, STACKSIZE);
static struct k_thread thread_3_data;
void thread_1(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);
int i = 0;
printk("thread_1: thread started \n");
while (1)
{
i++;
printk("thread_1: thread loop %d\n", i);
if (i == 300)
{
printk("thread_1: thread abort\n");
k_thread_abort(&thread_1_data);
}
}
}
void thread_2(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);
int i = 0;
printk("thread_2: thread started \n");
while (1)
{
i++;
printk("thread_2: thread loop %d\n", i);
if (i == 300)
{
printk("thread_2: thread abort\n");
k_thread_abort(&thread_2_data);
}
}
}
void thread_3(void *dummy1, void *dummy2, void *dummy3)
{
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
ARG_UNUSED(dummy3);
int i = 0;
printk("thread_3: thread started \n");
while (1)
{
i++;
printk("thread_3: thread loop %d\n", i);
if (i == 300)
{
printk("thread_3: thread abort\n");
k_thread_abort(&thread_3_data);
}
}
}
void main(void)
{
k_thread_create(&thread_1_data, thread_1_stack_area,
K_THREAD_STACK_SIZEOF(thread_1_stack_area),
thread_1, NULL, NULL, NULL,
PRIORITY_THREAD_1, 0, K_FOREVER);
k_thread_name_set(&thread_1_data, "thread_1");
k_thread_create(&thread_2_data, thread_2_stack_area,
K_THREAD_STACK_SIZEOF(thread_2_stack_area),
thread_2, NULL, NULL, NULL,
PRIORITY_THREAD_2, 0, K_FOREVER);
k_thread_name_set(&thread_2_data, "thread_2");
k_thread_create(&thread_3_data, thread_3_stack_area,
K_THREAD_STACK_SIZEOF(thread_3_stack_area),
thread_3, NULL, NULL, NULL,
PRIORITY_THREAD_3, 0, K_FOREVER);
k_thread_name_set(&thread_3_data, "thread_3");
k_thread_start(&thread_1_data);
k_thread_start(&thread_2_data);
k_thread_start(&thread_3_data);
}
執行結果
$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: qemu32,+nx,+pae
SeaBIOS (version zephyr-v1.0.0-0-g31d4e0e-dirty-20200714_234759-fv-az50-zephyr)
Booting from ROM..
*** Booting Zephyr OS build zephyr-v3.4.0-554-g33b116407b03 ***
thread_1: thread started
thread_1: thread loop 1
...
thread_1: thread loop 66
threthread_2: thread started
thread_2: thread loop 1
...
thread_2: thread loop 101
ththread_3: thread started
thread_3: thread loop 1
...
thread_3: thread loop 101
thad_1: thread loop 67
thread_1: thread loop 68
...
thread_1: thread loop 164
threadread_2: thread loop 102
thread_2: thread loop 103
...
thread_2: thread loop 197
thread_2: threaread_3: thread loop 102
thread_3: thread loop 103
...
thread_3: thread loop 197
thread_3: threa_1: thread loop 165
thread_1: thread loop 166
...
thread_1: thread loop 260
thread_1: thread lood loop 198
thread_2: thread loop 199
...
thread_2: thread loop 293
thread_2: thread loop 2d loop 198
thread_3: thread loop 199
...
thread_3: thread loop 293
thread_3: thread loop 2p 261
thread_1: thread loop 262
...
thread_1: thread loop 300
thread_1: thread abort
94
thread_2: thread loop 295
...
thread_2: thread loop 300
thread_2: thread abort
94
thread_3: thread loop 295
...
thread_3: thread loop 300
thread_3: thread abort
參考資料
- 軟體測試精品書籍文檔下載持續更新 https://github.com/china-testing/python-testing-examples 請點贊,謝謝!
- 本文涉及的python測試開發庫 謝謝點贊! https://github.com/china-testing/python_cn_resouce
- python精品書籍下載 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
什麼是線程睡眠?
線程可以調用k_sleep()來延遲它的處理過程,直到指定的時間段。線上程休眠期間,CPU被放棄以允許其他準備好的線程執行。一旦指定的延遲時間過了,線程就會準備好,並有資格再次被調度。
沉睡的線程可以被另一個線程用k_wakeup()提前喚醒。這種技術有時可以用來允許輔助線程向沉睡的線程發出信號,告訴它有事情發生了,而不需要線程定義內核同步對象,比如semaphore。喚醒沒有睡眠的線程是允許的,但沒有任何效果。
什麼是忙於等待?
線程可以調用k_busy_wait()來執行繁忙等待,在指定的時間段內延遲其處理,而不把CPU讓給其他準備好的線程。
當所需的延遲時間太短,以至於調度器的上下文從當前線程切換到另一個線程,然後再返回時,通常會使用繁忙等待來代替線程睡眠。
協作式還是搶占式?
- 設備驅動和其他性能關鍵的工作 -> 協作線程
- 使用協作線程來實現互斥,而不需要內核對象,例如mutex。
- 使用搶占式線程,使時間敏感的處理優先於時間不敏感的處理。