1. 程式人生 > >linux核心之排程演算法(二)

linux核心之排程演算法(二)

 

上層排程,linux排程的核心函式為schedule,schedule函式封裝了核心排程的框架。細節實現上呼叫具體的排程類中的函式實現。schedule函式主要流程為:

1,將當前程序從相應的執行佇列中刪除;

2,計算和更新排程實體和程序的相關排程資訊;

3,將當前進重新插入到排程執行佇列中,對於CFS排程,根據具體的執行時間進行插入而對於實時排程插入到對應優先順序佇列的隊尾;

4,從執行佇列中選擇執行的下一個程序;

5,程序排程資訊和上下文切換;

當程序上下文切換後(關於程序切換在前面的文章中有介紹),排程就基本上完成了,當前執行的程序就是切換過來的程序了。

/*核心和其他部分用於呼叫程序排程器的入口,選擇
 哪個程序可以執行,何時將其投入執行。schedule通常
 都需要和一個具體的排程類相關聯,也就是說,他
 會找到一個最高優先順序的排程類,後者需要有自己的
 可執行佇列,然後問後者誰才是下一個該執行的程序
 該函式唯一重要的事情是,他回撥用pick_next_task*/
asmlinkage void __sched schedule(void)
{
	struct task_struct *prev, *next;
	unsigned long *switch_count;
	struct rq *rq;
	int cpu;

need_resched:
	preempt_disable();
	cpu = smp_processor_id();
	rq = cpu_rq(cpu);/*得到特定cpu的rq*/
	rcu_sched_qs(cpu);
	prev = rq->curr;/*當前的執行程序*/
	switch_count = &prev->nivcsw;/*程序切換計數*/

	release_kernel_lock(prev);
need_resched_nonpreemptible:

	schedule_debug(prev);

	if (sched_feat(HRTICK))
		hrtick_clear(rq);

	spin_lock_irq(&rq->lock);
	update_rq_clock(rq);/*更新rq的clock屬性*/
	clear_tsk_need_resched(prev);/*清楚prev程序的排程位*/

	if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
		if (unlikely(signal_pending_state(prev->state, prev)))
			prev->state = TASK_RUNNING;
		else/*從執行佇列中刪除prev程序,根據排程類的
			不同,實現不同*/
			deactivate_task(rq, prev, 1);
		switch_count = &prev->nvcsw;
	}
	/*現只對實時程序有用*/
	pre_schedule(rq, prev);

	if (unlikely(!rq->nr_running))
		idle_balance(cpu, rq);
	/*將當前程序,也就是被切換出去的程序重新
	插入到各自的執行佇列中,對於CFS演算法插入
	到合適的位置上,對於實時排程插入到同一個
	優先順序佇列的連結串列尾部*/
	put_prev_task(rq, prev);
	/*從各自的執行佇列中選擇下一個程序來執行*/
	next = pick_next_task(rq);

	if (likely(prev != next)) {
		/*更新切換出去和進來程序以及對應rq的相關變數*/
		sched_info_switch(prev, next);
		perf_event_task_sched_out(prev, next, cpu);

		rq->nr_switches++;/*切換記錄*/
		rq->curr = next;
		++*switch_count;
		/*上下文切換,在程序切換已經介紹*/
		context_switch(rq, prev, next); /* unlocks the rq */
		/*
		 * the context switch might have flipped the stack from under
		 * us, hence refresh the local variables.
		 */
		cpu = smp_processor_id();
		rq = cpu_rq(cpu);
	} else
		spin_unlock_irq(&rq->lock);
	/*對於實時程序有用到*/
	post_schedule(rq);

	if (unlikely(reacquire_kernel_lock(current) < 0))
		goto need_resched_nonpreemptible;

	preempt_enable_no_resched();
	if (need_resched())
		goto need_resched;
}

對於cpu_rq函式

/*通過向上加偏移的方式得到rq,這裡可以看出
runqueues為一個rq結構的陣列,cpu為陣列下標*/
#define cpu_rq(cpu)		(&per_cpu(runqueues, (cpu)))

deactivate_task函式實現

/*
 * deactivate_task - remove a task from the runqueue.
 */
static void deactivate_task(struct rq *rq, struct task_struct *p, int sleep)
{
	if (task_contributes_to_load(p))
		rq->nr_uninterruptible++;
	/*具體操作*/
	dequeue_task(rq, p, sleep);
	dec_nr_running(rq);/*rq中當前程序的執行數減一*/
}

我們看具體的操作

static void dequeue_task(struct rq *rq, struct task_struct *p, int sleep)
{
	if (sleep) {/*如果sleep不為0,更新se中相關變數*/
		if (p->se.last_wakeup) {
			update_avg(&p->se.avg_overlap,
				p->se.sum_exec_runtime - p->se.last_wakeup);
			p->se.last_wakeup = 0;
		} else {
			update_avg(&p->se.avg_wakeup,
				sysctl_sched_wakeup_granularity);
		}
	}
	/*更新程序的sched_info資料結構中相關屬性*/
	sched_info_dequeued(p);
	/*呼叫具體排程類的函式從他的執行佇列中刪除*/
	p->sched_class->dequeue_task(rq, p, sleep);
	p->se.on_rq = 0;
}

可見,呼叫了具體執行佇列的刪除函式,我們看最關鍵的選擇下一個程序的方式。

/*
 * Pick up the highest-prio task:
 */
 /*以優先順序為序,從高到低,一次檢查每個排程類
 並且從高優先順序的排程類中,選擇最高優先順序的程序
 */
static inline struct task_struct *
pick_next_task(struct rq *rq)
{
	const struct sched_class *class;
	struct task_struct *p;

	/*
	 * Optimization: we know that if all tasks are in
	 * the fair class we can call that function directly:
	 */
	if (likely(rq->nr_running == rq->cfs.nr_running)) {
		p = fair_sched_class.pick_next_task(rq);
		if (likely(p))
			return p;
	}

	class = sched_class_highest;
	for ( ; ; ) {/*對每一個排程類*/
		p = class->pick_next_task(rq);/*呼叫該排程類中的函式,找出下一個task*/
		if (p)
			return p;
		/*
		 * Will never be NULL as the idle class always
		 * returns a non-NULL p:
		 */
		 /*訪問下一個排程類*/
		class = class->next;
	}
}

可見,對於排程類的選擇,同樣以優先順序進行。

對於程序排程資訊的切換最終會呼叫__sched_info_switch

/*
 * Called when tasks are switched involuntarily due, typically, to expiring
 * their time slice.  (This may also be called when switching to or from
 * the idle task.)  We are only called when prev != next.
 */
static inline void
__sched_info_switch(struct task_struct *prev, struct task_struct *next)
{
	struct rq *rq = task_rq(prev);

	/*
	 * prev now departs the cpu.  It's not interesting to record
	 * stats about how efficient we were at scheduling the idle
	 * process, however.
	 */
	if (prev != rq->idle)/*如果被切換出去的程序不是idle程序*/
		sched_info_depart(prev);/*更新prev程序和他對應rq的相關變數*/

	if (next != rq->idle)/*如果切換進來的程序不是idle程序*/
		sched_info_arrive(next);/*更新next程序和對應佇列的相關變數*/
}
/*
 * Called when a process ceases being the active-running process, either
 * voluntarily or involuntarily.  Now we can calculate how long we ran.
 * Also, if the process is still in the TASK_RUNNING state, call
 * sched_info_queued() to mark that it has now again started waiting on
 * the runqueue.
 */
static inline void sched_info_depart(struct task_struct *t)
{
	/*計算在程序在rq中執行的時間長度*/
	unsigned long long delta = task_rq(t)->clock -
					t->sched_info.last_arrival;
	/*更新RunQueue中的Task所得到CPU執行
	時間的累加值.*/
	rq_sched_info_depart(task_rq(t), delta);
	
	/*如果被切換出去程序的狀態是執行狀態
	那麼將程序sched_info.last_queued設定為rq的clock
	last_queued為最後一次排隊等待執行的時間*/
	if (t->state == TASK_RUNNING)
		sched_info_queued(t);
}
/*
 * Called when a task finally hits the cpu.  We can now calculate how
 * long it was waiting to run.  We also note when it began so that we
 * can keep stats on how long its timeslice is.
 */
static void sched_info_arrive(struct task_struct *t)
{
	unsigned long long now = task_rq(t)->clock, delta = 0;

	if (t->sched_info.last_queued)/*如果被切換進來前在執行程序中排隊*/
		delta = now - t->sched_info.last_queued;/*計算排隊等待的時間長度*/
	sched_info_reset_dequeued(t);/*因為程序將被切換進來執行,設定last_queued為0*/
	t->sched_info.run_delay += delta;/*更新程序在執行佇列裡面等待的時間*/
	t->sched_info.last_arrival = now;/*更新最後一次執行的時間*/
	t->sched_info.pcount++;/*cpu上執行的次數加一*/
	/*更新rq中rq_sched_info中的對應的變數*/
	rq_sched_info_arrive(task_rq(t), delta);
}

對於schedule排程函式框架的分析基本是這樣了,對於具體的CFS和實時排程的實現在後面分析。

相關推薦

linux核心排程演算法

  上層排程,linux排程的核心函式為schedule,schedule函式封裝了核心排程的框架。細節實現上呼叫具體的排程類中的函式實現。schedule函式主要流程為: 1,將當前程序從相應的執行佇列中刪除; 2,計算和更新排程實體和程序的相關排程資訊; 3,將當前進重

linux核心分析排程演算法

linux排程演算法在2.6.32中採用排程類實現模組式的排程方式。這樣,能夠很好的加入新的排程演算法。 linux排程器是以模組方式提供的,這樣做的目的是允許不同型別的程序可以有針對性地選擇排程演算法。這種模組化結構被稱為排程器類,他允許多種不同哦可動態新增的排程演算法並

【轉】【Linux 核心】記憶體管理夥伴演算法

        通常情況下,一個高階作業系統必須要給程序提供基本的、能夠在任意時刻申請和釋放任意大小記憶體的功能,就像malloc 函式那樣,然而,實現malloc 函式並不簡單,由於程序申請記憶體的大小是任意的,如果作業系統對malloc 函式的實現方法不對,將直接導致

Linux核心初始化步驟

參考了http://www.360doc.com/content/16/0626/14/19351147_570866376.shtml和 https://blog.csdn.net/qing_ping/article/details/17351541的內容 setup_arch()是sta

..linux開發uboot移植——網路命令ping開發搭建使用&tftp伺服器的安裝&nfs網路伺服器的安裝

2018/01/05 19:48 - 網路命令搭建開發板uboot和虛擬機器ubuntu互相ping通記錄 1. uboot可以通過網路來傳輸檔案到開發

Linux學習程序通訊

言之者無罪,聞之者足以戒。 ——《詩序》 命令:kill -l   可以檢視核心可以傳送多少種訊號 命令:ps -axj 可以檢視程序的狀態 訊號: 訊號通訊,其實就是核心向用戶空間程序傳送訊號,只有核心才能發訊號,使用者空間程序不能傳送訊號 訊號通訊的框架: (

演算法學習排序演算法直接插入排序法

1、插入法排序原理 直接插入排序(Insertion Sort)的基本思想是:每次將一個待排序的記錄,按其關鍵字大小插入到前面已經排好序的子序列中的適當位置,直到全部記錄插入完成為止。 設陣列為a[0…n-1]。 1. 初始時,a[0]自成1

排程演算法

目錄 線性規劃 使用鬆弛法設計近似演算法 \(R||C_{max}\)問題 \(1|r_j|\sum C_j\)問題 \(1|r_j,prec|\sum w_jC_j\)問題

linux 核心模組程式設計hello word

我們的目的是要編譯個hello.ko的檔案,然後安裝到核心中。 先來看下需要的程式碼,hello.c檔案如下 #include <linux/module.h> #include <linux/init.h> static int hello_init(vo

linux核心排程演算法3--多核系統的負載均衡

多核CPU現在很常見,那麼問題來了,一個程式在執行時,只在一個CPU核上執行?還是交替在多個CPU核上執行呢?LINUX核心是如何在多核間排程程序的呢?又是核心又是CPU核,兩個核有點繞,下面稱CPU處理器來代替CPU核。 實際上,如果你沒有對你的程序做過特殊處理的話,L

linux核心排程演算法2--CPU時間片如何分配

核心在微觀上,把CPU的執行時間分成許多分,然後安排給各個程序輪流執行,造成巨集觀上所有的程序彷彿同時在執行。雙核CPU,實際上最多隻能有兩個程序在同時執行,大家在top、vmstat命令裡看到的正在執行的程序,並不是真的在佔有著CPU哈。 所以,一些設計良好的高效能程序,比如nginx,都是實際上有幾顆C

Linux 核心程式設計檔案系統

1.為了方便查詢,VFS引入了 目錄 項,每個dentry代表路徑中的一個特定部分。目錄項也可包括安裝點。 2.目錄項物件由dentry結構體表示 ,定義在檔案linux/dcache.h 標頭檔案中。   89struct dentry {  90        atomic_t d_count;     

Linux文件系統學習重要數據結構1

class targe html evel 系統結構 會有 集合 spec lan 轉載自:https://blog.csdn.net/wudongxu/article/details/6436894 《Linux內核設計與實現》 http://www.ibm.com/

演算法排序

排序演算法很多,常用的排序演算法有:氣泡排序、插入排序、選擇排序、歸併排序、快速排序、計數排序、基數排序、桶排序。 接下來一一介紹幾種排序的時間複雜度及優缺點。 插入排序與氣泡排序的時間複雜度相同O(n^2),開發中我們更傾向插入排序,而不是氣泡排序 排序演算法執行效率: 1.最好、最壞、平均情況時間

Logistic迴歸梯度上升優化演算法

Logistic迴歸之梯度上升優化演算法(二) 有了上一篇的知識儲備,這一篇部落格我們就開始Python3實戰 1、資料準備 資料集:資料集下載 資料集內容比較簡單,我們可以簡單理解為第一列X,第二列Y,第三列是分類標籤。根據標籤的不同,對這些資料點進行分類。  

嵌入式核心及驅動開發學習筆記 實現應用控制驅動

Linux系統根據驅動程式實現的模型框架將裝置驅動分成字元裝置驅動、塊裝置驅動、網路裝置驅動三大類。這裡簡單理解一下概念 字元裝置:裝置按位元組流處理資料,通常用的串列埠裝置、鍵盤裝置都是這種。 塊裝置:裝置按塊單位對資料處理,通常是儲存裝置。 網路裝置:顧名思義,建立在soc

linux基本服務系列智慧DNS

一、前言 上一期講了利用BIND實現智慧DNS,是一個主域名伺服器,負責維護這個區域的所有域名資訊,是特定的所有資訊的權威資訊源。看過的人知道,儲存該區域的正本資料,是依靠zone檔案,維護zone檔案是比較繁瑣的,每次都要進入系統修改檔案並需要重啟bind服務,今天就來說說bind+mysql

資料結構與演算法-線性表單鏈表順序儲存和鏈式儲存

前言:前面已經介紹過資料結構和演算法的基本概念,下面就開始總結一下資料結構中邏輯結構下的分支——線性結構線性表 一、簡介 1、線性表定義   線性表(List):由零個或多個數據元素組成的有限序列;   這裡有需要注意的幾個關鍵地方:     1.首先他是一個序列,也就是說元素之間是有個先來後到的。

linux學習筆記shell程式設計條件判斷

條件判斷 檔案存在與否 -d 是否存在為目錄 -e 是否是檔案 -f 是否存在為檔案 [-d /root && echo "yes" || echo "no"] -檔案讀寫執行許可權#### -r 讀許可權 -w 寫

多執行緒原子變數CAS演算法

上篇博文,我們介紹了多執行緒之記憶體可見性Volatile(一),但是也遺留了一個問題,如何保證變數的”原子性操作(Atomic operations)”? Volatile保證部分型別的原子性 上篇博文,我們說Voloatile不能保證原子性,有一點侷