【WALT】WALT入口 update_task_ravg() 程式碼詳解

2023-07-02 06:00:39

【WALT】WALT入口 update_task_ravg() 程式碼詳解

程式碼版本:Linux4.9 android-msm-crosshatch-4.9-android12

程式碼展示

void update_task_ravg(struct task_struct *p, struct rq *rq, int event,
						u64 wallclock, u64 irqtime)	{
	u64 old_window_start;
	
	// ⑴ 判斷是否進入 WALT 演演算法
	if (!rq->window_start || sched_disable_window_stats ||
	    p->ravg.mark_start == wallclock)
		return;

	lockdep_assert_held(&rq->lock);

	// ⑵ 獲取 WALT 演演算法中上一個視窗的開始時間
	old_window_start = update_window_start(rq, wallclock, event);

	// ⑶ 如果任務剛初始化結束,不進入 WALT 演演算法,進入 `done`
	if (!p->ravg.mark_start) {
		update_task_cpu_cycles(p, cpu_of(rq), wallclock);
		goto done;
	}

	// ⑷ 更新任務及 CPU 的 cycles
	update_task_rq_cpu_cycles(p, rq, event, wallclock, irqtime);
	// ⑸ 更新任務及 CPU 的 demand 及 pred_demand
	update_task_demand(p, rq, event, wallclock);
	// ⑹ 更新 CPU 的 busy time
	update_cpu_busy_time(p, rq, event, wallclock, irqtime);
	// ⑺ 更新任務的 pred_demand
	update_task_pred_demand(rq, p, event);

	// ⑻ 如果任務正在退出,進入 `done`
	if (exiting_task(p))
		goto done;

	// 兩個系統自帶的 tracepoint
	trace_sched_update_task_ravg(p, rq, event, wallclock, irqtime,
				rq->cc.cycles, rq->cc.time, &rq->grp_time);
	trace_sched_update_task_ravg_mini(p, rq, event, wallclock, irqtime,
				rq->cc.cycles, rq->cc.time, &rq->grp_time);

done:
	p->ravg.mark_start = wallclock;
	run_walt_irq_work(old_window_start, rq);
}

程式碼邏輯

WALT 演演算法以任務為主,當任務被喚醒、任務開始執行、任務停止執行、任務退出、視窗捲動、頻率變化、任務遷移、經過一個排程tick、在中斷結束時會呼叫update_task_ravg()

其中,視窗是 WALT 演演算法中的一個特殊的設定,將在 update_task_demand()update_cpu_busy_time() 中詳細解釋。

⑴ 判斷是否進入 WALT 演演算法

在進入 WALT 演演算法後首先會判斷當前任務所在的執行佇列(runqueue)是否進行初始化,以及是否禁用 CPU 的視窗統計:if(!rq->window_start || sched_disable_window_stats...)。如果沒有初始化,就不會記錄視窗的開始時間,任務負載就無法進行計算。有幾點需要注意:

  1. 該處是指 rq,而非 cfs->rq 或 rt->rq,即該處不區分實時任務或普通任務;
  2. 任務/CPU 視窗(sched_ravg_window)是自定義的,不同版本程式碼或不同裝置中設定的視窗大小是不一樣的,調整的位置也不盡相同。

然後會判斷視窗開始時間是否更新:if(...p->ravg.mark_start == wallclock)。如果執行佇列沒有初始化,或禁用了 CPU 的視窗統計,或視窗開始時間沒有更新,就會直接結束 WALT 演演算法。

⑵ 獲取 WALT 演演算法中上一個視窗的開始時間

然後通過函數update_window_start()獲取上一個視窗的開始時間,存在變數old_window_start中。

點選此處檢視 update_window_start() 程式碼詳解。

⑶ 如果任務剛初始化結束

如果任務剛初始化結束:if(!p->ravg.mark_start),還沒有標記過任務的開始時間,就先通過函數 update_task_cpu_cycles() 更新一下該任務的 cycles 值(p->cpu_cycles),然後進入 done

點選此處檢視 update_task_cpu_cycles() 程式碼詳解。

⑷ 更新任務及 CPU 的 cycles

update_task_cpu_cycles() 相似,但比其多更新了 CPU 的 cycles 值(rq->cc.cycles)。

點選此處檢視 update_task_rq_cpu_cycles() 程式碼詳解。

⑸ 更新任務及 CPU 的 demand 及 pred_demand

在任務滿足條件後,在不同情況下根據任務的開始時間、視窗的開始時間以及當前時間來計算任務在當前及之前M個視窗中的執行時間。在視窗結束時將執行時間進行歸一化,並統計進任務的歷史視窗中(sum_history[RAVG_HIST_SIZE])。

WALT 演演算法根據歷史視窗中的值計算任務的 demand,根據桶演演算法計算任務的 pred_demand,並將 demand 與 pred_demand 統計進任務所在 CPU 的 rq(runqueue)中。

注意:以上說的 demand 與 pred_demand 都是預測值。

點選此處檢視 update_task_demand() 程式碼詳解。

⑹ 更新 CPU 的 busy time

在任務滿足條件後,在不同情況下根據任務的開始時間、視窗的開始時間以及當前時間來計算任務在當前及上一個視窗中的執行時間,將不同視窗內的執行時間進行歸一化,並根據任務的狀態統計進任務的 curr_windowprev_window 中,以及任務所在 rq 的 curr_runnable_sumprev_runnable_sum 中。

在視窗翻滾的時候更新任務的 window 值 以及 rq 的 runnable_sum 的值。

注意:以上說的 window 以及 runnable_sum 都是真實值。

點選此處檢視 update_cpu_busy_time() 程式碼詳解。

⑺ 更新任務的 pred_demand

如果符合條件的任務在當前視窗中預測出來的 demand 值小於 curr_window,則再次使用桶演演算法計算 pred_demand。

點選此處檢視 update_task_pred_demand() 程式碼詳解。

⑻ 如果任務正在退出

#define EXITING_TASK_MARKER	0xdeaddead

static inline int exiting_task(struct task_struct *p)
{
	return (p->ravg.sum_history[0] == EXITING_TASK_MARKER);
}

當任務最近一個視窗的值為 0xdeaddead 時,意味著任務正在退出,進入 done

done 結束部分:

  1. 更新一下任務的開始時間:p->ravg.mark_start = wallclock
  2. 通過函數 irq_work_queue() 處理沒有 tick 的情況,迴圈呼叫 update_task_ravg()