複数のプロセスまたはスレッドを使用するプログラムを作成する方法を理解できます。新しいプロセスをfork()してIPCを使用するか、複数のスレッドを作成してこれらの種類の通信メカニズムを使用します。
コンテキストスイッチングも理解しています。つまり、1回のCPUで、オペレーティングシステムは各プロセスの時間をスケジュールします(そして、そこには大量のスケジューリングアルゴリズムがあります)。それにより、複数のプロセスを同時に実行できます。
そして、マルチコアプロセッサ(またはマルチプロセッサコンピュータ)ができたので、2つの別々のコアで2つのプロセスを同時に実行できます。
私の質問は、最後のシナリオについてです:プロセスが実行されるコアをカーネルがどのように制御しますか?特定のコアでプロセスをスケジュールするシステム呼び出し(LinuxまたはWindowsでも)はどれですか?
私が求めている理由:私は学校のプロジェクトに取り組んでいます。そこでは、コンピューティングの最近のトピックを探求しています。そして、マルチコアアーキテクチャを選択しました。この種の環境でのプログラミング方法(デッドロックまたは競合状態の監視方法)については多くの資料があるようですが、個々のコア自体の制御についてはあまりありません。いくつかのデモプログラムを作成し、アセンブリ命令またはCコードを「2番目のコアで無限ループを実行しています。CPU使用率の急上昇を見てくださいその特定のコア "。
コード例はありますか?またはチュートリアル?
編集:明確にするために-これはOSの目的であり、OSにこれを任せるべきだと多くの人が言っています。同意します!しかし、その後、私が求めている(または感じてみようとしている)ことは、オペレーティングシステムが実際にこれを行うことです。スケジューリングアルゴリズムではなく、「コアを選択したら、そのコアが命令のフェッチを開始するためにどの命令を実行する必要がありますか?」
他の人が述べたように、プロセッサアフィニティはオペレーティングシステム固有です。オペレーティングシステムの範囲外でこれを実行したい場合は、多くの楽しみがあります。
そうは言っても、他の人は SetProcessAffinityMask
をWin32に言及しています。プロセッサアフィニティを設定するLinuxカーネルの方法については誰も言及していません。 sched_setaffinity
関数を使用する必要があります。これが ニースのチュートリアル 方法です。
通常、アプリを実行するコアの決定はシステムが行います。ただし、特定のコアにアプリケーションの「アフィニティ」を設定して、そのコアでのみアプリを実行するようにOSに指示することができます。通常、これは良いアイデアではありませんが、それが理にかなっている可能性のあるまれなケースがあります。
Windowsでこれを行うには、タスクマネージャーを使用し、プロセスを右クリックして、[アフィニティの設定]を選択します。 SetThreadAffinityMask、SetProcessAffinityMask、SetThreadIdealProcessorなどの関数を使用して、Windowsでプログラムで実行できます。
ETA:
OSが実際にスケジューリングを行う方法に興味がある場合は、次のリンクをチェックしてください。
最新のOSのほとんどでは、OSはスレッドを短時間でコア上で実行するようにスケジュールします。タイムスライスが期限切れになるか、スレッドが自発的にコアを生成するIO操作を行うと、OSは別のスレッドがコアで実行されるようにスケジュールします(準備中のスレッドがある場合) run)。正確にスケジュールされるスレッドは、OSのスケジューリングアルゴリズムによって異なります。
コンテキストスイッチが発生する方法の正確な実装の詳細は、CPUとOSに依存します。通常、カーネルモードへの切り替え、OSによる前のスレッドの状態の保存、新しいスレッドの状態の読み込み、ユーザーモードへの切り替え、新たに読み込まれたスレッドの再開が含まれます。上記にリンクしたコンテキストスイッチングの記事では、これについてもう少し詳しく説明しています。
コアに「今、このプロセスの実行を開始する」ことは何もありません。
コア表示されないプロセス。実行可能コードとさまざまな実行レベル、および実行可能な命令の関連制限についてのみ認識します。
コンピューターが起動するとき、簡単にするために、1つのコア/プロセッサーのみがアクティブで、実際にコードを実行します。 OSがMultiProcessorに対応している場合、システム固有の命令で他のコアをアクティブにします。他のコアは、他のコアとまったく同じ場所からピックアップして実行する可能性が高いです。
そのため、スケジューラーはOSの内部構造(タスク/プロセス/スレッドキュー)を調べて1つを選択し、コアで実行中としてマークします。その後、他のコアで実行されている他のスケジューラインスタンスは、タスクが再び待機状態になるまで(および特定のコアに固定されているとしてマークされないまで)タッチしません。タスクが実行中としてマークされた後、スケジューラーはユーザーランドへの切り替えを実行し、タスクは以前中断された時点で再開します。
技術的には、コアがまったく同じコードをまったく同時に実行するのを止めることは何もありません(そして、多くのロック解除された関数はそうします)。
よりエキゾチックなメモリモデル(上記の「通常の」線形シングルワーキングメモリ空間を想定)でシナリオが奇妙になります。コアは必ずしもすべてが同じメモリを参照するわけではなく、他のコアのクラッチからコードを取得する必要があるかもしれませんが、単純に簡単に処理できますタスクをコアに固定したままにします(SPUを使用したSony PS3アーキテクチャはそのようなものです)。
OpenMPI プロジェクトには プロセッサアフィニティを設定するライブラリ onLinuxが移植可能です。
しばらく前に、私はプロジェクトでこれを使用しましたが、うまくいきました。
警告:オペレーティングシステムがコアに番号を付ける方法を見つけるのに問題があったことを私はぼんやりと覚えています。それぞれ4コアの2 Xeon CPUシステムでこれを使用しました。
cat /proc/cpuinfo
役立つかもしれません。私が使用した箱では、それはかなり奇妙です。煮詰められた出力は最後にあります。
明らかに、偶数番号のコアは最初のCPUにあり、奇数番号のコアは2番目のCPUにあります。しかし、正しく覚えていれば、キャッシュに問題がありました。これらのIntel Xeonプロセッサでは、各CPUの2つのコアがL2キャッシュを共有します(プロセッサにL3キャッシュがあるかどうかは覚えていません)。仮想プロセッサ0と2は1つのL2キャッシュを共有し、1と3は1つを共有し、4と6は1つを共有し、5と7は1つを共有すると思います。
この奇妙なため(1.5年前、Linuxのプロセス番号付けに関するドキュメントは見つかりませんでした)、この種の低レベルのチューニングを慎重に行う必要があります。ただし、明らかにいくつかの用途があります。コードが少数の種類のマシンで実行される場合、この種のチューニングを行う価値があるかもしれません。別のアプリケーションは、 StreamIt のようなドメイン固有の言語であり、コンパイラはこの汚い作業を行い、スマートスケジュールを計算できます。
processor : 0
physical id : 0
siblings : 4
core id : 0
cpu cores : 4
processor : 1
physical id : 1
siblings : 4
core id : 0
cpu cores : 4
processor : 2
physical id : 0
siblings : 4
core id : 1
cpu cores : 4
processor : 3
physical id : 1
siblings : 4
core id : 1
cpu cores : 4
processor : 4
physical id : 0
siblings : 4
core id : 2
cpu cores : 4
processor : 5
physical id : 1
siblings : 4
core id : 2
cpu cores : 4
processor : 6
physical id : 0
siblings : 4
core id : 3
cpu cores : 4
processor : 7
physical id : 1
siblings : 4
core id : 3
cpu cores : 4
/ proc/cpuinfoを使用する代わりにプロセッサの数を調べるには、次を実行します。
nproc
特定のプロセッサのグループでプロセスを実行するには:
taskset --cpu-list 1,2 my_command
私のコマンドはCPU 1または2でしか実行できないと言うでしょう。
4つの異なる処理を行う4つのプロセッサでプログラムを実行するには、パラメーター化を使用します。プログラムへの引数は、何か違うことをするように指示します:
for i in `seq 0 1 3`;
do
taskset --cpu-list $i my_command $i;
done
これの良い例は、0〜(2mil-1)がプロセッサ1に、2mil〜(4mil-1)がプロセッサ2に、というように配列で800万の操作を処理することです。
Apt-get/yumを使用してhtopをインストールし、コマンドラインで実行すると、各プロセスの負荷を確認できます。
htop
他の人が述べたように、それはオペレーティングシステムによって制御されます。 OSに応じて、特定のプロセスが実行されるコアに影響を与えるシステムコールを提供する場合と提供しない場合があります。ただし、通常はOSにデフォルトの動作をさせるだけです。 37のプロセスが実行されている4コアシステムがあり、それらのプロセスのうち34がスリープしている場合、残りの3つのアクティブプロセスを別々のコアにスケジュールします。
非常に特殊化されたマルチスレッドアプリケーションでコアアフィニティを使用した場合の速度向上のみが見られるでしょう。たとえば、2つのデュアルコアプロセッサを搭載したシステムがあるとします。 3つのスレッドを備えたアプリケーションがあり、2つのスレッドが同じデータセットを頻繁に操作し、3番目のスレッドが異なるデータセットを使用するとします。この場合、同じプロセッサで相互作用する2つのスレッドと、他のプロセッサで3番目のスレッドを使用すると、キャッシュを共有できるため、最もメリットがあります。 OSは、各スレッドがアクセスする必要のあるメモリを把握していないため、スレッドをコアに適切に割り当てることができません。
オペレーティングシステムにhow興味がある場合は、 scheduling を参照してください。 x86でのマルチプロセッシングの詳細は、 Intel 64およびIA-32アーキテクチャソフトウェア開発者向けマニュアル に記載されています。ボリューム3A、第7章および第8章には関連情報が含まれていますが、これらのマニュアルは非常に技術的なものであることに注意してください。
OSはこれを行う方法を知っているので、その必要はありません。どのコアで実行するかを指定すると、あらゆる種類の問題に遭遇する可能性があり、その中には実際にプロセスを遅くするものもあります。 OSにそれを理解させ、新しいスレッドを開始するだけです。
たとえば、プロセスにコアxで開始するように指示したが、コアxがすでに高負荷になっている場合、OSにそれを処理させた場合よりも悪化します。
アセンブリの手順がわかりません。ただし、Windows API関数は SetProcessAffinityMask です。 例 1つだけコアでPicasaを実行するために少し前に一緒に作ったものを見ることができます。
Linux _sched_setaffinity
_ Cの最小限の実行可能な例
この例では、アフィニティを取得して変更し、 sched_getcpu()
で有効になったかどうかを確認します。
_#define _GNU_SOURCE
#include <assert.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void print_affinity() {
cpu_set_t mask;
long nproc, i;
if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
perror("sched_getaffinity");
assert(false);
} else {
nproc = sysconf(_SC_NPROCESSORS_ONLN);
printf("sched_getaffinity = ");
for (i = 0; i < nproc; i++) {
printf("%d ", CPU_ISSET(i, &mask));
}
printf("\n");
}
}
int main(void) {
cpu_set_t mask;
print_affinity();
printf("sched_getcpu = %d\n", sched_getcpu());
CPU_ZERO(&mask);
CPU_SET(0, &mask);
if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
perror("sched_setaffinity");
assert(false);
}
print_affinity();
/* TODO is it guaranteed to have taken effect already? Always worked on my tests. */
printf("sched_getcpu = %d\n", sched_getcpu());
return EXIT_SUCCESS;
}
_
コンパイルして実行:
_gcc -std=c99 main.c
./a.out
_
サンプル出力:
_sched_getaffinity = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
sched_getcpu = 9
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
sched_getcpu = 0
_
つまり:
taskset
を介してこのプログラムを実行するのも楽しいです:
_taskset -c 1,3 ./a.out
_
次の形式の出力が得られます。
_sched_getaffinity = 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
sched_getcpu = 2
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
sched_getcpu = 0
_
そのため、最初からアフィニティが制限されていたことがわかります。
これは、アフィニティがtaskset
がフォークしている子プロセスに継承されているために機能します: 子フォークプロセスによるCPUアフィニティの継承を防ぐ方法
Ubuntu 16.04でテスト済み、 GitHubアップストリーム 。
x86ベアメタル
あなたがその筋金入りの場合: マルチコアアセンブリ言語はどのように見えますか?
Linuxでの実装方法