地方エンジニアの学習日記

技術記事だったり興味ある技術に関してのメモだったりを書いてくブログ。たまに日記とガジェット紹介。

プロセススケジューラの話 メモ

メモ

Linuxのプロセススケジューラではプロセスが実際にCPUを割り当てられる際に負荷の低いプロセスに割り当てられる仕組みがある。

簡単なperlプログラムを使って実験。

第一引数に並列数、第二引数に1以上の数値を与えるとシステムコールを発行、0の場合は単純にループしてプロセッサを使い続ける仕様

#!/bin/perl
my $nprocs = $ARGV[0] || 1;
for( my $i=0; $i<$nprocs; $i++ ){
        my $pid = fork;
        die $! if( $pid < 0 );
        if( $pid == 0 ){
                while(1){
                        if( $ARGV[1] ){
                                open(IN, ">/dev/null");
                                close(IN);
                        }
                }
        }
}
wait;

基本状態のdstat(プログラむ実行なし)

[root@imp02 ~]# dstat -a -C 0,1
-------cpu0-usage--------------cpu1-usage------ -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq:usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw
  0   0 100   0   0   0:  0   0 100   0   0   0| 348B 7363B|   0     0 |   0     0 |  35    52
  0   0 100   0   0   0:  0   0 100   0   0   0|   0     0 |  66B 1070B|   0     0 |  52    71
  0   0 100   0   0   0:  0   0 100   0   0   0|   0     0 |  66B  422B|   0     0 |  42    63

このプログラムをまずは第一引数1、第二引数0で実行してみる。その結果をdstatでコアごとにcpu使用率を確認してみる。

[root@imp02 ~]# dstat -a -C 0,1,total
-------cpu0-usage--------------cpu1-usage-----------total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq:usr sys idl wai hiq siq:usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw
  0   0 100   0   0   0:  0   0 100   0   0   0:  0   0 100   0   0   0| 348B 7361B|   0     0 |   0     0 |  35    52
100   0   0   0   0   0:  0   1  99   0   0   0: 50   1  50   0   0   0|   0     0 |  66B 1318B|   0     0 |1058    79
100   0   0   0   0   0:  0   0 100   0   0   0: 50   0  50   0   0   0|   0     0 |  66B  494B|   0     0 |1043    67
100   0   0   0   0   0:  0   0 100   0   0   0: 50   0  50   0   0   0|   0     0 |  66B  494B|   0     0 |1050    75
100   0   0   0   0   0:  0   0 100   0   0   0: 51   0  50   0   0   0|   0     0 |  66B  494B|   0     0 |1041    63
100   0   0   0   0   0:  0   0 100   0   0   0: 50   0  50   0   0   0|   0     0 |  66B  494B|   0     0 |1035    58

cpu0のusr使用率が0%になっていることがわかった。

この状態では単純にユーザ空間でループしてるだけ。

今度はシステムコールを呼び出してみる。 システムコールを呼び出すことで今度はカーネル空間での処理が必要になるためcpu使用率はusrとsysで半分半分になる。

[root@imp02 ~]# dstat -a -C 0,1,total
-------cpu0-usage--------------cpu1-usage-----------total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq:usr sys idl wai hiq siq:usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw
 14  17  63   0   0   6: 17  26  54   0   0   3: 16  22  58   0   0   4|   0     0 | 402B  698B|   0     0 |1636   944
 20  26  47   0   0   7: 22  27  47   0   0   4: 21  26  47   0   0   5|   0     0 |  66B  494B|   0     0 |2054  1172
 21  27  46   0   0   6: 21  26  48   0   0   6: 21  27  47   0   0   5|   0     0 |  66B  494B|   0     0 |2093  1204
 18  22  53   0   0   7: 23  32  42   0   0   3: 20  27  47   0   0   5|   0     0 |  66B  494B|   0     0 |2085  1205
 21  27  47   0   0   5: 21  26  47   0   0   6: 21  26  47   0   0   5|   0     0 |  66B  494B|   0     0 |2054  1155
 27  30  39   0   0   4: 18  21  54   0   0   7: 22  26  47   0   0   5|   0     0 |  66B  494B|   0     0 |2100  1205
 18  23  52   0   0   7: 24  31  41   0   0   4: 20  27  47   0   0   5|   0     0 | 140B  548B|   0     0 |2038  1170
 17  25  52   0   0   5: 22  30  43   0   0   5: 20  28  47   0   0   5|   0     0 |  66B  494B|   0     0 |2104  1219
 24  28  44   0   0   5: 21  23  50   0   0   7: 22  25  47   0   0   5|   0     0 |  66B  494B|   0     0 |2052  1194
 15  16  64   0   0   4: 15  20  62   0   0   3: 15  18  63   0   0   4|   0     0 | 300B  738B|   0     0 |1504   874

こんな感じ。

今度はカーネルでの処理が発生するためにcpu使用率がcpu0とcpu1で処理されていることがわかった。

つまり

プログラム実行

cpu0でループ開始

システムコール発行

プロセススケジューラによってcpu1の空きが多いことを確認、cpu1を使ってカーネルでの処理

ユーザ空間へ(プロセススケジューラでcpu0とcpu1の空き状況を確認して割り当て)

って感じ。

プログラム上はシングルプロセスしか使ってないように見えても実はlinux側でいい感じにマルチコアを割り当ててくれています。

ただしこんな頻度でユーザーモードカーネルモード間のコンテキストスイッチを発生させるプログラムがあるかと言われるとないかなって思いますが。。w

(dstatをみてもなんというコンテキストの数がとんでもないことがわかりますねーw)

ちなみにコンテキストスイッチの時に発生するレジスタ退避はカーネルマクロのインラインアセンブリで記述されている。

 #define switch_to(prev,next,last) do { \
 unsigned long esi,edi; \
 asm volatile("pushfl\n\t" \ ――<3>
         "pushl %%ebp\n\t" \ ――<4>
         "movl %%esp,%0\n\t" \ ――<5>
         "movl %5,%%esp\n\t" \ ――<6>
         "movl $1f,%1\n\t" \ ――<7>
         "pushl %6\n\t" \ ――<8>
         "jmp __switch_to\n" \ ――<9>
         "1:\t" \ ――<10>
         "popl %%ebp\n\t" \ ――<11>
         "popfl" \ ――<12>
         :"=m" (prev->thread.esp),"=m" (prev->thread.eip), \
         "=a" (last),"=S" (esi),"=D" (edi) \
         :"m" (next->thread.esp),"m" (next->thread.eip), \
         "2" (prev), "d" (next)); \
 } while (0)

マルチプロセスのスケジューリング

ユーザ空間で動作する高負荷プロセスが他コアのアイドリングしてるコアに移らなかった理由として考えられるのはCPUがコアごとに持ってるキャッシュへのヒット率だと考えられる。

CPU1コアごとにL1,L2と共有のL3キャッシュが存在する。

f:id:ryuichi1208:20191201225115j:plain

引用:http://www.pasonisan.com/customnavi/z1012_cpu/05baseht.html

仮に特定のプロセスがCPUを跨いで実行されるとこのL1、L2のキャッシュが利用されなくて新たに生成するコストが必要となる。

スケジューラではこのような現象を防ぐために1プロセスはできるだけ同一のCPUで実行されるような仕組みとなっている(実装は読んでません><)

https://ja.osdn.net/projects/linux-kernel-docs/wiki/1.5%E3%80%80%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%82%B9%E3%82%B1%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%A9

参考

github.com