プロセスアフィニティマスクをアプリケーションで設定する

前回のエントリではimagecfgというユーティティを使用してプロセスアフィニティを設定したが、この方法ではバイナリのイメージヘッダを直接書き換えてしまうため変更は恒久的なものになってしまう。
動的に変更することはできないのだろうかと調べるとWin32にはプロセスアフィニティマスクを取得、設定するAPIが用意されており、例えば.NET C#等からなら簡単に操作することができることが判った。

使うAPIは以下の二つ。

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetProcessAffinityMask(IntPtr hProcess, out UIntPtr lpProcessAffinityMask, out UIntPtr lpSystemAffinityMask);

[DllImport("kernel32.dll")]
static extern bool SetProcessAffinityMask(IntPtr hProcess, UIntPtr dwProcessAffinityMask);

GetProcessAffinityMaskは現在のプロセスアフィニティマスクと元々のシステムアフィニティマスクを取得し、SetProcessAffinityMaskは現在のプロセスに対してプロセスアフィニティマスクを設定することができる。

以下、WindowsFormsを用いて簡単なアプリケーションを書いてみた。

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace AffinityTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            IntPtr vHandle;
            UIntPtr maskProcess;
            UIntPtr maskSystem;

            vHandle = Process.GetCurrentProcess().Handle;
            SetProcessAffinityMask(vHandle, new UIntPtr(0x0e));
            GetProcessAffinityMask(vHandle, out maskProcess, out maskSystem);
            this.label1.Text = "Process Affinity Mask : "
                               + Convert.ToString(maskProcess.ToUInt32(), 2);
            this.label2.Text = "System Affinity Mask  : "
                               + Convert.ToString(maskSystem.ToUInt32(), 2);
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool GetProcessAffinityMask(IntPtr hProcess,
           out UIntPtr lpProcessAffinityMask, out UIntPtr lpSystemAffinityMask);

        [DllImport("kernel32.dll")]
        static extern bool SetProcessAffinityMask(IntPtr hProcess,
           UIntPtr dwProcessAffinityMask);
    }
}

Form1にはデザイナからラベルコントロールを二つ配置している。
プロセスアフィニティマスクを設定するだけであれば、SetProcessAffinityMaskだけを使っても良いのだが、搭載されているCPUの種類や個数によってセット可能なビットマスクの組み合わせは変わるはずなので、通常のプログラミングではまずGetProcessAffinityMaskを使って現状の値とシステムの値を取得して、その中で設定できるマスク値を制限して使うことになるだろう。

私の環境(物理CPU×2+HTによる論理CPU×2=4CPU)でこのアプリケーションを実行すると、以下のように表示された。LSBからのビットがそのまま関係付けられるCPU番号を表す。

プロセスアフィニティマスクが変更されることで、本当に関係付けられるプロセッサに変化があったかどうかを確かめてみよう。

タスクマネジャ -> AffinityTest.exe 選択 -> 関係の設定で現在の値を取得、設定できる。

見事にCPU0の割り当てだけチェックが外れているのが解るだろう。

以上、プロセスアフィニティマスクの変更に関しては、元々TV-Tunerの視聴ソフトの不具合への対策だったがこのような用途以外でもマルチCPUのプラットホームではいろいろと使い道があるのではないだろうかと思う。


追記:
Windows Vista上、Visual Studio 2005を使ってテストする場合、デバッガが使用する〜vshost.exeに対してタスクマネジャから関係の設定を取得するには管理者権限が必要となるので注意が必要だ。〜vshost.exeではなく、ビルドして出来た通常のアセンブリを使えば問題無い。