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

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

【Linux】カーネル関数「account_user_time()」は何をしているのか

概要

謎にCPUのユーザ時間ってなんだって疑問が出てきたので調べた。

CPU使用率とは

CPUは、アプリケーション(プログラム)が使うときと、OS(システム)が使うときがあります。「CPUをフルに使っている状態が100%だとすると、今やっているお仕事に使っているのは何パーセントくらい?」を表す数値

https://wa3.i-3-i.info/word18297.html

プロセスごとに見えるユーザ時間とは

CPU使用率について調べていくとpsとかのコマンドを用いてカーネルが持っているプロセスごとのCPU使用率を観測することが可能です。ここでこのコマンドを使うとプロセスが使用したCPUの使用時間についてどのような操作を行っていたかを確認することができます。

topとかを打つと出てくるプロセス後の使用率はmanにはこのように書いています。

項目 概要
us ユーザプロセス(プログラムを実行することによって生成されるプロセス)の使用時間
sy システムプロセス(OSによって生成されるプロセス)の使用時間

どうやって時間を足しているのか

linuxはタスクをtasuk_structという構造体でプロセスを管理しています。プロセスごとのCPU使用率はこの構造体に紐づいておりどの程度使用したかを管理しています。以下の関数でアプリケーションがCPUを使用していた場合にCPU使用時間をインクリメントする関数です。

/* ユーザーのCPU時間をプロセスディスクリプタに割り当てる関数 */
void account_user_time(struct task_struct *p, cputime_t cputime,
               cputime_t cputime_scaled)
{
    int index;

    /* ユーザー時間を追加 */
    p->utime += cputime;
    p->utimescaled += cputime_scaled;
    account_group_user_time(p, cputime);

    index = (task_nice(p) > 0) ? CPUTIME_NICE : CPUTIME_USER;

    /* cpustatにユーザー時間を追加 */
    task_group_account_field(p, index, (__force u64) cputime);

    /* 使用されたユーザー時間のアカウント */
    acct_account_cputime(p);
}

task_structに紐づくプロセスの使用時間のタイプ別は以下のように定義されています。上記の前段の関数でCPU使用率の更新時にアプリの場合は上記の関数でutimeを更新。カーネル使用時間を追加する場合はそれに対応した関数でstimeをインクリメントしていました。

struct task_struct {
    u64             utime;
    u64             stime;

前段の関数は以下。user_tickなどのフラグでどの値をインクリメントするかを判定してユーザ時間/システム時間の関数を振り分けています。stealだけ特殊で所謂仮想化関連の値になるのでこれだけは例外のようです。

void account_process_tick(struct task_struct *p, int user_tick)
{
    u64 cputime, steal;

    if (vtime_accounting_enabled_this_cpu())
        return;

    if (sched_clock_irqtime) {
        irqtime_account_process_tick(p, user_tick, 1);
        return;
    }

    cputime = TICK_NSEC;
    steal = steal_account_process_time(ULONG_MAX);

    if (steal >= cputime)
        return;

    cputime -= steal;

    if (user_tick)
        account_user_time(p, cputime);
    else if ((p != this_rq()->idle) || (irq_count() != HARDIRQ_OFFSET))
        account_system_time(p, HARDIRQ_OFFSET, cputime);
    else
        account_idle_time(cputime);
}

strealの値が有効的に確認可能になるのは「ハイパーバイザ型の仮想環境の場合、ゲストOSがプログラムを実行していたと思っているにもかかわらず、ホスト上で他のVMとのCPUの取り合い(競合)が発生し、実際にはハイパーバイザが物理CPUの時間を与えていないので、実世界では実行されていなかった」みたいな場合です。

ハイパーバイザーが絡むCPU使用率は知見がない + 今回知りたい情報ではないので一旦追うのはやめます。前提として以下の記事あたりを読み込んだ時にでも追ってみたいと思います。

qiita.com

まとめ

  • CPUの使用時間はtask_structで管理されている
  • インクリメントは専用の関数で行われていて簡単なフラグ制御のみで行われている。

感想/所感

CPUバウンドなアプリケーションの原因調査のエントリポイント的な感じで使っているCPUの使用時間についてカーネルがどうやって算出しているのかを知れたのでよかったです。

ボトルネック調査なんかはこの感覚的な部分があるかないかで調査に対する工数は全然変わってくる物だと思うので今後もLinuxカーネルの曖昧な部分はきちんとソースレベルで理解していきたいと思いました。