天天看點

linux程序排程算法cfs,第一次作業:Linux的程序模型及CFS排程器算法分析

1. 關于程序

1.1. 程序的定義

程序:指在系統中能獨立運作并作為資源配置設定的基本機關,它是由一組機器指令、資料和堆棧等組成的,是一個能獨立運作的活動實體。

程序是程式的一次執行

程序是可以和别的計算并行執行

程序是程式在一個資料集合上運作的過程,它是系統進行資源配置設定和排程的一個獨立機關

1.2. 程序的特征

1.動态性:程序的實質是程式的一次執行過程,程序是動态産生,動态消亡的。

2.并發性:任何程序都可以同其他程序一起并發執行。

3.獨立性:程序是一個能獨立運作的基本機關,同時也是系統配置設定資源和排程的獨立機關。

4.異步性:由于程序間的互相制約,使程序具有執行的間斷性,即程序按各自獨立的、不可預知的速度向前推進。

2. 關于程序的組織

task_struct 是Linux核心的一種資料結構,它被裝在到RAM裡并且包含着程序的資訊。每個程序都把它的資訊放在 task_struct 這個資料結構中, task_struct 包含了以下内容:

辨別符:描述本程序的唯一辨別符,用來差別其他程序。

狀态:任務狀态,退出代碼,退出信号等。

優先級:相對于其他程序的優先級。

程式計數器:程式中即将被執行的下一條指令的位址。

記憶體指針:包括程式代碼和程序相關資料的指針,還有和其他程序共享的記憶體塊的指針。

上下文資料:程序執行時處理器的寄存器中的資料。

I/O狀态資訊:包括顯示的I/O請求,配置設定給程序的I/O裝置和被程序使用的檔案清單。

記賬資訊:可以包括處理器時間總和,使用的時鐘數總和,時間限制,記賬号等。

儲存程序資訊的資料結構叫做 task_struct ,并且可以在 include/linux/sched.h 裡找到它。是以運作在系統裡的程序都以 task_struct 連結清單的形式存在于核心中。

2.1. 程序狀态

2.1.1. 程序狀态

volatile longstate;int exit_state;

2.1.2. state成員的可能取值

#define TASK_RUNNING 0

#define TASK_INTERRUPTIBLE 1

#define TASK_UNINTERRUPTIBLE 2

#define __TASK_STOPPED 4

#define __TASK_TRACED 8

#define EXIT_ZOMBIE 16

#define EXIT_DEAD 32

#define TASK_DEAD 64

#define TASK_WAKEKILL 128

#define TASK_WAKING 256

2.1.3. 程序的各個狀态

TASK_RUNNING

表示程序正在執行或者處于準備執行的狀态

TASK_INTERRUPTIBLE

程序因為等待某些條件處于阻塞(挂起的狀态),一旦等待的條件成立,程序便會從該狀态轉化成就緒狀态

TASK_UNINTERRUPTIBLE

意思與TASK_INTERRUPTIBLE類似,但是我們傳遞任意信号等不能喚醒他們,隻有它所等待的資源可用的時候,他才會被喚醒。

TASK_STOPPED

程序被停止執行

TASK_TRACED

程序被debugger等程序所監視。

EXIT_ZOMBIE

程序的執行被終止,但是其父程序還沒有使用wait()等系統調用來獲知它的終止資訊,此時程序成為僵屍程序

EXIT_DEAD

程序被殺死,即程序的最終狀态。

TASK_KILLABLE

當程序處于這種可以終止的新睡眠狀态中,它的運作原理類似于 TASK_UNINTERRUPTIBLE,隻不過可以響應緻命信号

2.1.4. 狀态轉換圖

linux程式排程算法cfs,第一次作業:Linux的程式模型及CFS排程器算法分析

2.2. 程序辨別符(pid)

2.2.1. 辨別符定義

pid_t pid; //程序的辨別符

2.2.2. 關于辨別符

pid是 Linux 中在其命名空間中唯一辨別程序而配置設定給它的一個号碼,稱做程序ID号,簡稱PID。

程式一運作系統就會自動配置設定給程序一個獨一無二的PID。程序中止後PID被系統回收,可能會被繼續配置設定給新運作的程式。

是暫時唯一的:程序中止後,這個号碼就會被回收,并可能被配置設定給另一個新程序。

2.3. 程序标記符

2.3.1. 标記符

unsigned int flags;

flags反應程序的狀态資訊,用于核心識别目前程序的狀态。

2.3.2. flags的取值範圍

#define PF_EXITING 0x00000004

#define PF_EXITPIDONE 0x00000008

#define PF_VCPU 0x00000010

#define PF_WQ_WORKER 0x00000020

#define PF_FORKNOEXEC 0x00000040

#define PF_MCE_PROCESS 0x00000080

#define PF_SUPERPRIV 0x00000100

#define PF_DUMPCORE 0x00000200

#define PF_SIGNALED 0x00000400

#define PF_MEMALLOC 0x00000800

#define PF_NPROC_EXCEEDED 0x00001000

#define PF_USED_MATH 0x00002000

#define PF_USED_ASYNC 0x00004000

#define PF_NOFREEZE 0x00008000

#define PF_FROZEN 0x00010000

#define PF_FSTRANS 0x00020000

#define PF_KSWAPD 0x00040000

#define PF_MEMALLOC_NOIO 0x00080000

#define PF_LESS_THROTTLE 0x00100000

#define PF_KTHREAD 0x00200000

#define PF_RANDOMIZE 0x00400000

#define PF_SWAPWRITE 0x00800000

#define PF_NO_SETAFFINITY 0x04000000

#define PF_MCE_EARLY 0x08000000

#define PF_MUTEX_TESTER 0x20000000

#define PF_FREEZER_SKIP 0x40000000

#define PF_SUSPEND_TASK 0x80000000

struct task_struct __rcu *parent;

struct list_head children;

struct list_head sibling;

struct task_struct *group_leader;

可以用下面這些通俗的關系來了解它們:real_parent是該程序的”親生父親“,不管其是否被“寄養”;parent是該程序現在的父程序,有可能是”繼父“;這裡children指的是該程序孩子的連結清單,可以得到所有孩子的程序描述符,但是需使用list_for_each和list_entry,list_entry其實直接使用了container_of,同理,sibling該程序兄弟的連結清單,也就是其父親的所有孩子的連結清單。用法與children相似;struct task_struct *group_leader這個是主線程的程序描述符,也許你會奇怪,為什麼線程用程序描述符表示,因為linux并沒有單獨實作線程的相關結構體,隻是用一個程序來代替線程,然後對其做一些特殊的處理;struct list_head thread_group;這個是該程序所有線程的連結清單。

3. 程序的排程

3.1. 完全公平的排程器CFS

CFS(完全公平排程器)是Linux核心2.6.23版本開始采用的程序排程器,它從RSDL/SD中吸取了完全公平的思想,不再跟蹤程序的睡眠時間,也不再企圖區分互動式程序。它将所有的程序都統一對待,這就是公平的含義。它的基本原理如下:設定一個排程周期( sched_latency_ns),目标是為了讓每個程序在這個周期内至少有機會運作一次,也可以說就是每個程序等待CPU的時間最長不超過這個排程周期;然後根據程序的數量,所有程序平分這個排程周期内的CPU使用權,由于程序的優先級即nice值不同,分割排程周期的時候要權重;每個程序的累計運作時間儲存在自己的vruntime字段裡,哪個程序的vruntime最小就獲得本輪運作的權利。CFS的算法和實作都相當簡單,衆多的測試表明其性能也非常優越。

宏 SCHED_NOMAL 和 SCHED_BATCH 主要用于CFS排程。這幾個宏的定義可以在 include/linux/sched.h 中找到。檔案 kernel/sched.c 包含了核心排程器及相關系統調用的實作。排程的核心函數為 sched.c 中的 schedule() , schedule 函數封裝了核心排程的架構。細節實作上調用具體的排程算法類中的函數實作,如 kernel/sched_fair.c 或 kernel/sched_rt.c 中的實作。

3.2. 程序排程的算法

在CFS中,當産生時鐘tick中斷時,sched.c中scheduler_tick()函數會被時鐘中斷(定時器timer的代碼)直接調用,我們調用它則是在禁用中斷時。注意在fork的代碼中,當修改父程序的時間片時,也會導緻sched_tick的調用。sched_tick函數首先更新排程資訊,然後調整目前程序在紅黑樹中的位置。調整完成後如果發現目前程序不再是最左邊的葉子,就标記need_resched标志,中斷傳回時就會調用scheduler()完成程序切換,否則目前程序繼續占用CPU。注意這與以前的排程器不同,以前是tick中斷導緻時間片遞減,當時間片被用完時才觸發優先級調整并重新排程。sched_tick函數的代碼如下:

void scheduler_tick(void)

{int cpu =smp_processor_id();struct rq *rq =cpu_rq(cpu);struct task_struct *curr = rq->curr;

sched_clock_tick();

spin_lock(&rq->lock);

update_rq_clock(rq);

update_cpu_load(rq);

curr->sched_class->task_tick(rq, curr, 0);

spin_unlock(&rq->lock);

perf_event_task_tick(curr, cpu);

#ifdef CONFIG_SMP

rq->idle_at_tick =idle_cpu(cpu);

trigger_load_balance(rq, cpu);#endif }

它先擷取目前CPU上的運作隊列中的目前運作程序,更新runqueue級變量clock,然後通過sched_class中的接口名task_tick,調用CFS的tick處理函數task_tick_fair(),以處理時鐘中斷。我們看kernel/sched_fair.c中的CFS算法實作。

具體的排程類如下:

static const struct sched_class fair_sched_class ={

.next= &idle_sched_class,

.enqueue_task=enqueue_task_fair,

.dequeue_task=dequeue_task_fair,

.yield_task=yield_task_fair,

.check_preempt_curr=check_preempt_wakeup,

.pick_next_task=pick_next_task_fair,

.put_prev_task=put_prev_task_fair,

#ifdef CONFIG_SMP

.select_task_rq=select_task_rq_fair,

.load_balance=load_balance_fair,

.move_one_task=move_one_task_fair,

.rq_online=rq_online_fair,

.rq_offline=rq_offline_fair,

.task_waking=task_waking_fair,#endif .set_curr_task=set_curr_task_fair,

.task_tick=task_tick_fair,

.task_fork=task_fork_fair,

.prio_changed=prio_changed_fair,

.switched_to=switched_to_fair,

.get_rr_interval=get_rr_interval_fair,

#ifdef CONFIG_FAIR_GROUP_SCHED

.task_move_group=task_move_group_fair,#endif };

task_tick_fair函數用于輪詢排程類的中一個程序。實作如下:

static void task_tick_fair(struct rq *rq, struct task_struct *curr, intqueued)

{struct cfs_rq *cfs_rq;struct sched_entity *se = &curr->se;

for_each_sched_entity(se) {cfs_rq=cfs_rq_of(se);

entity_tick(cfs_rq, se, queued);

}

}

該函數擷取各層的排程實體,對每個排程實體擷取CFS運作隊列,調用entity_tick程序進行處理。kernel/sched_fair.c中的函數entity_tick源代碼如下:

static voidentity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, intqueued)

{update_curr(cfs_rq);

#ifdef CONFIG_SCHED_HRTICK

if(queued) {

resched_task(rq_of(cfs_rq)->curr);return;

}

if (!sched_feat(DOUBLE_TICK) &&hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))return;#endif

if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))

check_preempt_tick(cfs_rq, curr);

}

該函數用kernel/sched_fair.c:update_curr()更新目前程序的運作時統計資訊,然後調用kernel/sched_fair.c:check_preempt_tick(),檢測是否需要重新排程,用下一個程序來搶占目前程序。update_curr()實作記賬功能,由系統定時器周期調用,實作如下:

static inline void__update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,

unsignedlongdelta_exec)

{

unsignedlongdelta_exec_weighted;

schedstat_set(curr->exec_max, max((u64)delta_exec, curr->exec_max));

curr->sum_exec_runtime += delta_exec; schedstat_add(cfs_rq, exec_clock, delta_exec);

delta_exec_weighted= calc_delta_fair(delta_exec, curr);

4. 對作業系統程序模型的看法

作業系統(Operation System)從本質上說,并不是指我們平時看到的那些視窗、菜單、應用程式。那些隻是天邊的浮雲。作業系統其實是隐藏後面,我們根本看不到的部分。作業系統一般來說,工作就是:程序管理、記憶體管理、檔案管理、裝置管理等等。作業系統中最核心的概念是程序, 程序也是并發程式設計中的一個最重要、 最基本的概念。程序是一個動态的過程, 即程序有生命周期, 它擁有資源, 是程式的執行過程, 其狀态是變化的。所謂的排程器,就是程序管理的一部分。

Linux一開始的排程器是複雜度為O(n)的始排程算法, 這個算法的缺點是當核心中有很多任務時,排程器本身就耗費不少時間,是以,從linux2.5開始引入赫赫有名的O(1)排程器。然而,O(1)排程器又被另一個更優秀的排程器取代了,它就是CFS排程器Completely Fair Scheduler. 這個也是在2.6核心中引入的,具體為2.6.23,即從此版本開始,核心使用CFS作為它的預設排程器,O(1)排程器被抛棄了。但其實目前任何排程器算法都還無法滿足所有應用的需要,CFS也有一些負面的測試報告。相信随着Linux的發展,還會有新的排程算法,我們拭目以待。

轉載于:https://www..com/cloudbai/p/8973180.html