前回、Raspberry Pi 3のカーネルモジュールがビルドできるようになったので、つい嬉しくなって、ユーザモードで ArmコアのPMCCNTRレジスタを読み取れるようにするモジュールを書いてみました。しかし、マルチコアのことを棚にあげて書いたせいで、使い勝手が悪すぎました。今回は、マルチコア問題を回避しつつ、多少使いでを改良してみました。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
前回の問題を整理すると、以下のようでしょうかね。
- Raspberry Pi 3にはCPUコアが4個あり、それぞれにPMCレジスタが存在する。デフォルトで動作しておらず、また、PMCCNTRへのユーザモードアクセスは禁止されている。
- 作成したカーネルモジュール(PMCの動作を許可し、PMCCNTRへのユーザアクセスを許可する)は、insmodした時に呼び出される初期化処理内でその許可をやっていた。そのため該当の処理を実行した時点のコアは許可に設定されるが、他のコアには設定がされない。
- よって、実際にユーザモードアクセスをしたとき、たまたまinsmodした時と同じコアであればうまく行くが、そうでないコアが割り当てられているとPMCCNTRレジスタへのアクセスに失敗する。
- カーネルモジュールのrmmodで不許可に戻しているが、上と同じ理由で別なコアに不許可設定している可能性もある。意味なし。
- これを回避するためには、プロセスに対して taskset コマンドなどで「CPUアフィニティ」を設定し、CPUコアの割当を制約した上で、そのプロセス上で、insmod, PMCCNTRへのユーザーモードアクセス、rmmodを行わないとならない。
だいたいユーザーモードでカウンタを「お手軽に読みたい」ための設定なので、これでは、毎回 sudo しないとならず、とても使い勝手が悪いです。そこで、変更することにいたしました。方針としては、以下のような感じ。
- インストールしただけで効果を発揮するような形を止め、カーネルモジュールにちゃんとデバイスファイルを紐づけて、キャラクタデバイスとしてアクセスできるようにする
- デバイスファイルにある「御印」を書き込んだら、PMCCNTRへのユーザアクセスを許可し、別な「御印」を書き込んだら不許可になるようにする
こうすれば、カーネルモジュール自体は普通のデバイスドライバとしていつ読み込んでも良くなります。PMCCNTRにアクセスするような場合、該当プロセス(作業用のbashのプロセスが適当)とその子孫にCPUコアの呪縛をかけておいてから、デバイスファイルに書き込み(当然、ユーザーモードで書き込み可としておく)すれば、確実に実行を担当するコアのPMCが操作されるという目論見。
長々と書きましたが、やりたいことは単純です。デバイスファイル名は pmcCnt としました。許可の御印はEという一文字として、
$ echo E > /dev/pmcCnt
とすれば以降、アクセス可となり、不許可はDとしたので
$ echo D > /dev/pmcCnt
とすれば、アクセス不可にるようにする。勿論、echoしなくてもプログラムの中でもデバイスファイルに書き込めば許可、不許可できるので、測定用(多分、PMCCNTRなど読みたいと思うのは何か測定にきまっているでしょう)のプログラム内で簡単にできる筈。
なお、4個のCPUコアがある場合、bit 0 = core 1, bit 1 = core 2, bit 2 = core 3, bit 4 = core 4みたいなマスクで taskset する必要があるので
$ taskset -a -p 2 対象プロセスのPID
などとすれば、そのプロセスはcore 2でのみ実行されることになるようです。当然 -p 15 などとすれば、全部のコアが使われるので意味がありません。なお、-aとすればそこから起動される子スレッド達にも同じ縛りがかかるのでサイクル数などの測定上は都合が良いです。
結局、今日の「低レベルプログラミング」は、デバイスドライバにEを送ったとき、以下のようなコードを実行させるだけであります。Arm Cortex A53のコープロセッサ15番のレジスタのフラグをON/OFFするだけのもの。なお、読み取ったレジスタの値をregPMUSERENRとかregPMCRとか変数にも書き込んでいるのは、操作前の値を読み取っておいて、printkで念のため出力しておきたいがためです。
__asm__ ( "mrc p15, 0, %[Rd2], c9, c12, 0 \n\t" "mov r0, %[Rd2] \n\t" "orr r0, r0, #1 \n\t" "mcr p15, 0, r0, c9, c12, 0 \n\t" "mrc p15, 0, %[Rd], c9, c14, 0 \n\t" "mov r0, %[Rd] \n\t" "orr r0, r0, #1 \n\t" "mcr p15, 0, r0, c9, c14, 0 \n\t" "mrc p15, 0, r0, c9, c12, 1 \n\t" "orr r0, r0, #0x80000000 \n\t" "mcr p15, 0, r0, c9, c12, 1 \n\t" : [Rd] "=r" (regPMUSERENR), [Rd2] "=r" (regPMCR) : : "r0" );
Dを送ったときは、イネーブルにしたフラグをディセーブルに戻すだけなので、上と逆の以下のようなコード
__asm__ ( "mrc p15, 0, r0, c9, c12, 2 \n\t" "and r0, r0, #0x7fffffff \n\t" "mcr p15, 0, r0, c9, c12, 2 \n\t" "mrc p15, 0, %[Rd], c9, c14, 0 \n\t" "mov r0, %[Rd] \n\t" "and r0, r0, #0xfffffffe \n\t" "mcr p15, 0, r0, c9, c14, 0 \n\t" "mrc p15, 0, %[Rd2], c9, c12, 0 \n\t" "mov r0, %[Rd2] \n\t" "and r0, r0, #0xfffffffe \n\t" "mcr p15, 0, r0, c9, c12, 0 \n\t" : [Rd] "=r" (regPMUSERENR), [Rd2] "=r" (regPMCR) : : "r0" );
なお、Raspberry Pi 3 上で、簡単にカーネルモジュールをビルドするための方法(やってみたが、20~30分あれば十分できる)へのリンクは前回の投稿に貼ってあるのでそちらを御覧くださいまし。
カーネルモジュールが出来たらば、どこのCPUコアなんてことは気にせず
$ sudo insmod pmcCnt.ko
その後インストール済のカーネルモジュールのメジャー番号を調べ(成り行きで番号が決まる書き方したので。ここでは239でした。)、デバイスファイルを作成すれば使えるようになります。
$ sudo mknod -m 666 /dev/pmcCnt c 239 0
キャラクタデバイスのモジュールなので、c であります。前後いたしましたが、デバドラが一度インストールされたならば、その後いつでも、CPUアフィニティ使ってコアをご指定になり、その上で実行すればちゃんとユーザーモードでカウンタが読める筈。
ぐだぐだだなあ、今日も。