Linux
timeコマンド

timeコマンドは、何をどのようにして計測しているのか?

はじめに

timeコマンドを使用して”プロセスの処理時間”を知ることができ、以下のような出力を得る事が出来ます。

real    0m0.000s
user    0m0.000s
sys 0m0.000s

(上は一例で、表示され方が異なる場合があります。シェルによってはbult-inコマンドで用意されていたりする。)
realは実際の時間で、userがユーザー空間での処理時間、sysがカーネル内部での処理時間となります。

普段何気なく使うtimeコマンドでは、これらの時間はどのように計測されるのかを追っていき、時間計測の理解を深めていきたい。
おそらく、知っている人はそんなこといまさら…って内容かとは思います。

** ( ただし、間違っている可能性も大いにあるので、間違いがある場合はご指摘よろしくお願いします… ) **

さっそく、結論…

(GNU timeは検索してソースコードが読むとわかるが、結論を言ってしまうと)プロセスの処理時間はカーネルの機能として計測されており、timeコマンドは、sys/timesを使用してプロセスの処理時間(tick単位)を取得し、時間換算した結果を表示しているだけです。
つまり、timeコマンドが取得している値の本質を探るにはTickを理解するのが手っ取り早いと思います。

Tickとは?

linuxでは、一定間隔毎に割り込みが行われ、この定期的な割り込みをTickと呼びます。(まさしく、時計のチックタック的なイメージ。)。tickの発生する間隔は(基本的に)カーネルをコンパイルする時の定数(HZ)の値によって決定され、1ms,5ms,10msが一般的です。

Tickの大きな役割の一つに、実行しているプロセスから別のプロセスへの切り替えがあります。このため、有限個のCPUで大量のプロセスを(一見すると同時に)処理することが出来ています。

また、Tickの処理内にて、カーネルは幾つかのデータを周期的に収集します。

  • 実行プロセスのCPU資源制限の確認
  • ローカルCPUの作業負荷の更新
  • システムの平均付加の算出
  • カーネルコードのプロファイルリング

Tickによるプロセス時間の計測

実際に、単純化したモデルを例としてTickによるプロセス時間の計測について説明をしたいと思います。

temp.png

図はCPUで実行されているプロセスを時系列的に図示したもので、プロセスA(緑色の矢印)とプロセスB(青色の矢印)が動作していることを示しています。図の最初ではプロセスAがCPUの実行権を得ることができ、CPUを使用しています。
プロセスAが実行されている途中でも、一定間隔で割り込みされ、Tickの処理が行われています。
Tick終了後は、通常は直前に実行していたプロセス(ここではプロセスA)が、あらかじめ許可された実行時間(クォンタム)を使い切るまで、そのプロセスにCPUが戻され、続きが実行されます。クォンタムを使い切ったり、その他の条件により再度のスケジューリングが必要であれば)Tick終了する際に、次に実行するプロセスを選定を行い、選定されたプロセスにCPUが渡されます。(この選定に関する詳細は、別の記事が大量にあると思うのでそちらを参考に… )

図では、4回目のTickの後でプロセスAからプロセスBにCPUが渡されています。その後、プロセスBは終了し、プロセスAにCPUが渡されています。さらに、その後、プロセスAも終了して実行待ちのプロセスが存在しなくなった例を示してします。

カーネルは、各プロセスの状態をテーブルとして管理しており、この中にstime,utimeと呼ばれる変数があります。
Tickが呼ばれるたびに、直前にCPUを使用していたプロセスがユーザー空間にいたときは、そのプロセスのutimeを、カーネル空間にいたときはそのプロセスのstimeをインクリメントします。
これにより、それぞれのプロセスが実行しているときに何回のtickがカーネル空間またはユーザー空間で発生したかがカウントされていきます。

この例では、最終的に、8回のTickが発生しており、その内訳は以下のようになります。

  • プロセスA
    • ユーザーモード:4回
    • カーネルモード:1回
  • プロセスB
    • ユーザーモード:1回
  • アイドル状態:2回

これらの蓄積された情報からCPUの使用率やtimeコマンドで表示される結果を導出します。プロセスAは、ユーザー空間で4回、カーネル空間では1回のtickが呼ばれたため、tickが10msのときはユーザー空間で40ms、カーネル空間で10msの処理時間を使用したことになります。このようにして、”プロセスの処理時間”を計測しています。

( このため、考え方によっては、カーネルが計測しているプロセス時間(特に、カーネル時間とユーザ時間)は、ある意味、統計的な意味を含んだ数値であると言えると思います。例えば、(運が悪くor運がよく?)Tickの割り込みが入るタイミング直前にシステムコールを発行して、Tick終了してプロセスに処理が戻ったときにカーネルを抜けることが重なれば、実際にカーネル内部にいる時間よりもカーネルでの実行時間が大きくなる結果となると思います。もちろん、その逆がユーザーモードでの時間計測においても言えると思います(※1) )

なお、この図の説明では、プロセス実行時間の割合に比べてTickの処理時間が長く見えるが実際はTickの処理時間はもっと短いです。

ちなみに、これまでの説明では、コアの1個の環境での動作となる。コアが複数ある場合は、各コアが各々の実行プロセスのキューを持っており、コア毎にTickの割り込みが実行され、これらの処理が行われます。
また、他のコアのプロセスキューを確認し、コア間でプロセスを移動させて負荷のバランシングなどの調整もtickにて行われます。(詳細は別の機会に書きたい)

(※1) 自信がないので、今後の検証してみたい。本当に厳密な計測が行えた場合と、tickカウントによる時間との差分どの程度になるのか気になる…