Linux O(1)スケジューラ

以前、O(n)スケジューラーとそのコアアルゴリズムの設計について学びました。ここで確認してください。

O(n)スケジューラコア:

O(n)スケジューラーは、runqueue実行キューを使用してすべての実行可能なプロセスを管理します。メインのスケジュールスケジュール機能では、最高の優先順位、つまり最大のタイムスライスを持つプロセスが実行対象として選択されます。そのようなプロセスのタイムスライスを増やすためにいくつかの補正を行います。runqueue実行キューに選択するプロセスがない場合、システム内のすべてのプロセスのタイムスライスが再計算され、残りのタイムスライスプロセスも補正されます。

O(n)スケジューラーの欠陥:

  1. 時間の複雑さはO(n)
  2. SMPシステムは適切にスケーリングされず、runqueueへのアクセスにはロックが必要です
  3. リアルタイムプロセスは時間内にスケジュールできません
  4. CPUアイドリング現象
  5. CPU間のプロセスのジャンプ、パフォーマンスへの影響

 

O(1)スケジューラーの紹介

Linuxカーネルコミュニティは、O(n)スケジューラのさまざまな問題に基づいて、2.6カーネルバージョンでO(1)スケジューラを導入しましたが、この導入の目的は、O(n)スケジューラが直面する問題を解決することです。私たちの記事はLinux 2.6.2バージョンに基づいています。Linuxカーネルのドキュメントには、O(1)スケジューラの目的、その設計方法、およびその実装に関する詳細な紹介があります。sched-design.txtドキュメントが重要です。あなたはそれを読むことができます。

 

O(1)スケジューラーの仕組み

  • システムの実行キューはPER_CPU変数です。つまり、各CPUがこの実行キューを維持するため、SMPシステムでは、複数のCPUが同じ実行キューにアクセスするのを効果的に回避できます。
  • 各runqueue実行キューは、2つのリンクリストを保持します。1つはアクティブなリンクリストです。つまり、実行中のプロセスはアクティブなリンクリストにマウントされます。もう1つは、期限切れのリンクリストです。つまり、タイムスライスを使い果たしたすべてのプロセスは、期限切れのリンクリストにマウントされます。
  • O(n)のすべてのプロセスがrunqueueに無秩序に配置されることを解決するために、O(1)アルゴリズムの勝利プロセスは優先順位に従って配置され、同じ優先順位が等しい優先順位のリストに掛けられます。
  • 同時に、優先的に実行できるプロセスを格納するために使用されるビットマップ構造を提供します。pciknextがビットマップに固執する必要がある場合は常に、対応する優先度リストに移動して、優先度戦略に従ってプロセスを選択します。
  • acitveで実行するプロセスがない場合は、システム内のすべてのプロセスのタイムスライスが使い果たされていることを意味します。この時点で調整する必要があるのは、アクティブポインターと期限切れポインターのみです。

以上のことから、O(n)アルゴリズムの問​​題に応じて、O(1)アルゴリズムの改良が変更されていることがわかる。

O(1)スケジューラーに含まれるデータ構造

struct runqueue {
	spinlock_t lock;
	unsigned long nr_running, nr_switches, expired_timestamp,
		      nr_uninterruptible, timestamp_last_tick;
	task_t *curr, *idle;
	struct mm_struct *prev_mm;
	prio_array_t *active, *expired, arrays[2];
	int best_expired_prio, prev_cpu_load[NR_CPUS];
	
	task_t *migration_thread;
	struct list_head migration_queue;

	atomic_t nr_iowait;
};

static DEFINE_PER_CPU(struct runqueue, runqueues);

struct runqueueはPER_CPUの変数であり、SMPシステムの各CPUに対応してstruct runqueue構造を維持していることがわかります。

/*
 * These are the runqueue data structures:
 */

#define BITMAP_SIZE ((((MAX_PRIO+1+7)/8)+sizeof(long)-1)/sizeof(long))

typedef struct runqueue runqueue_t;

struct prio_array {
	int nr_active;
	unsigned long bitmap[BITMAP_SIZE];
	struct list_head queue[MAX_PRIO];
};

struct prio_arrayは2つの優先配列を表し、nr_activeは現在アクティブまたは使用されているプロセスの数を表します。検索プロセスを容易にするためにビットマップが導入されているため、プロセスを検索するときにビットマップのみを照会する必要があり、ビットマップのサイズは固定されているため、アルゴリズムの時間の複雑さはO(1)です。

O(1)スケジューラのコアアルゴリズム

O(1)コアアルゴリズム。スケジュール関数を直接見ることができます。

array = rq->active;	
idx = sched_find_first_bit(array->bitmap);
queue = array->queue + idx;
next = list_entry(queue->next, task_t, run_list);

これらの3行はアルゴリズムのコアです。まず、runqueueのアクティブキューでビットマップから添え字を見つけます。この添え字は対応する優先度であり、対応する優先度のリンクリストを取得してから、次のプロセスを取得します。後者の操作は、プロセスの切り替えとスケジューリングを実行することです。

システムに実行中のプロセスがない場合

システムで実行するプロセスがない場合、つまり、プロセスのタイムスライスが使い果たされた場合、プロセスのタイムスライスを再度設定する必要があります。

	array = rq->active;
	if (unlikely(!array->nr_active)) {
		/*
		 * Switch the active and expired arrays.
		 */
		rq->active = rq->expired;
		rq->expired = array;
		array = rq->active;
		rq->expired_timestamp = 0;
		rq->best_expired_prio = MAX_PRIO;
	}

操作も非常に簡単で、アクティブポインターと有効なポインターを切り替えるだけです。

O(1)スケジューラ優先度設定

プロセスの優先度は、静的優先度と動的優先度に分けられます。通常の優先度は、プロセスの作成時にデフォルトで設定される優先度であり、動的優先度はプロセスの実行時に動的に調整されます。

通常のプロセスの静的優先順位はstatic_prioです。

リアルタイムプロセスの静的優先度はrt_priorityです

O(1)スケジューラーのすべてのプロセスの動的優先順位は、p-> prioです。

#define CURRENT_BONUS(p) \
	(NS_TO_JIFFIES((p)->sleep_avg) * MAX_BONUS / \
		MAX_SLEEP_AVG)

static int effective_prio(task_t *p)
{
	int bonus, prio;

	if (rt_task(p))
		return p->prio;

	bonus = CURRENT_BONUS(p) - MAX_BONUS / 2;

	prio = p->static_prio - bonus;
	if (prio < MAX_RT_PRIO)
		prio = MAX_RT_PRIO;
	if (prio > MAX_PRIO-1)
		prio = MAX_PRIO-1;
	return prio;
}

システム操作中、この機能はプロセスの動的優先順位を再計算するために追跡されます。リアルタイムプロセスは、対応するp-> prioを返すだけで済みます。通常のプロセスは、報われ、罰せられる必要があります。プロセスsleep_avgのスリープ時間を通じて、プロセスに報酬とペナルティが必要かどうかを計算します。プロセスが頻繁にスリープ状態になると、優先順位が高くなります。プロセスがCPUを頻繁に占有する場合、その優先度を下げるために罰せられる必要があります

タイムスライスの更新

システムティックが来ると、それはschedule_tick関数に行きます

	if (!--p->time_slice) {
		dequeue_task(p, rq->active);
		set_tsk_need_resched(p);
		p->prio = effective_prio(p);
		p->time_slice = task_timeslice(p);
		p->first_time_slice = 0;

		if (!rq->expired_timestamp)
			rq->expired_timestamp = jiffies;
		if (!TASK_INTERACTIVE(p) || EXPIRED_STARVING(rq)) {
			enqueue_task(p, rq->expired);
			if (p->static_prio < rq->best_expired_prio)
				rq->best_expired_prio = p->static_prio;
		} else
			enqueue_task(p, rq->active);
	}

タイムスライスが使い果たされると、プロセスをアクティブキューから削除する必要があり、再スケジュールする必要があるフラグTIF_NEED_RESCHEDを設定する必要があります。このプロセスの優先順位、タイムスライスも再計算する必要があります。O(1)スケジューラアルゴリズムはO(n)よりも暴力的ではありません。また、対話型プロセスであるかどうか、またはこのプロセスが空腹であるかどうかを判別する必要があります。そうである場合、アクティブキューに追加されます。それ以外の場合は、expriedに追加されますキュー。

要約:

  • O(1)スケジューラーの導入は主にO(n)スケジューラーの不足を解決することです
  • O(1)スケジューラは、報酬およびペナルティメカニズムでO(n)スケジューラよりも多くの要素を考慮します。O(1)のようにタイムスライスのサイズを直接スケジュールするのはもはや時間ではありません。
  • ただし、O(n)およびO(1)スケジューリングアルゴリズムの中核は、プロセス報酬メカニズムへのスリープ報酬、優先度を上げるためのラブスリープ、より実行されるようにタイムスライスを増やすためのメカニズムなど、プロセスの動作を判断します。時間。
  • O(1)スケジューラの実装を見ると、O(n)アルゴリズムほど単純で明確ではありません。O(1)は時間のかかる判断を追加し、さまざまな状況を考慮するとコードの可読性が低下します。とても激しい。
  • もちろん、時代はまだ先に進む必要がありますO(n)およびO(1)スケジューラーは、CFSスケジューラーの出現に適した環境を提供します。
187件の元の記事を公開 108 件を獲得 37万回表示

おすすめ

転載: blog.csdn.net/longwang155069/article/details/104457109