ちょっと思い立って、Raspberry Pi 3 model B+で、x86のRdtscみたいなサイクル数カウントをとろうとしたのです。Armにもサイクルカウンタはあるのですが、通常は特権アクセス。しかし、USERモードでもアクセス可にできるビットがあったので、これをONにしてUSERプログラムで測定すべし、と思ったところがまたまたハマリました。Pi 1 model B+であったならば顕在化しなかった問題であります。
※「トホホな疑問」投稿順Indexはこちら
まずは、今回やってみようと思った切っ掛けになったホームページのご紹介。
ある計算機屋さんの手帳 Raspberry Piでカーネルモジュールを作成
カーネルモジュールを書けるといいな、と前から思っていたのです。しかし、カーネルモジュールを書くには 該当のバージョンの Linuxのソースを見つけてきてビルドしないとならないんじゃなかったっけ?
- ビルドに何時間かかるの?
- ビルドで生成されるのだろう大量のファイル、小さなマイクロSD-CARDに収まるの?
というところが、チラチラ頭をよぎりRaspberry Pi 1 model B+ のみのころに一度検討したものの、沙汰やみとなっておりました。でも、CPUやIOのディープなところに踏み込んでいくためには、特権命令使えるカーネルモジュールは必須。ところが、Webを眺めていて見つけた上のページによれば、なんと、
ビルド不要、その上、勝手に自分のOSバージョンに適合するソースツリーをダウンロードし、カーネルモジュール作成用の準備をしてくれ、即カーネルモジュール書きに邁進できる
素晴らしいツールが公開されているとのこと。そちらのツールのGithubがこちら、
そこに書かれている方法をほぼそのままなぞり、サンプルのカーネルモジュールをビルドして、insmodするまで、30分もかかりませんでした。ホントに便利なものを公開してくれて、ありがとうです。なお「ある計算機屋さんの手帳」の方では、
$ sudo rpi-source
で実施しているのですが、こちらは、ご本家のWikiにならい
$ rpi-source
で処理。特権つけなくても rpi-sourceは動作するようで、その場合、ライブラリ用のソースツリーや、ビルドツリーは、自分のホームディレクトリ配下に作られ、/lib/modulesから、自分配下へシンボリックリンクが張られました。これなら、カーネルモジュールの編集からビルド作業までを全てユーザモードで行え、最後、insmodするところだけ、sudoするだけで済みます。
今や特権命令を発行できるようになったので怖いものなし。標的は、Armのコープロセッサ番号p15番のサイクル数カウンタ
PMCCNTR
です。とりあえずただカウンタを読み出すだけのサンプルプログラムを以下に示します。CPUクロックで動作するカウンタです。これを読み出せれば、プログラムの特定部分の実行サイクル数など数えることができる筈。困るのはインラインアセンブラの中のレジスタアクセス命令が特権であること。
何も手を打たないで、上のプログラムをビルドして動かすと、つれなく、
$ ./readPMCCNTR Illegal instruction
特権違反で弾かれます。なお、このレジスタをユーザモードからアクセスできるようにするためのレジスタがあるのですが、そちらはユーザーモードでも読み出しだけは可能。上とほぼ同じプログラムで、読み出し先を PMUSERENRに変更すれば、読み出せます。
$ ./readPMUSERENR PMUSERENR = 00000000
PMUSERENRに0が返ってきているままでは、PMCCNTRは読み出せません。これを読んだときに1が返らないとアクセス不可であります。また、PMC全体の機能そのものを許可するためにPMCCNTR、サイクルカウントを稼働状態に入れるためにPMCNTENSET(止めるためにはPMCCNTENCLR)レジスタも操作しないとなりません。レジスタ3個を設定するカーネルモジュールを書けば、後はUSERモードでカウンタを読みだして使えるわけです。(モジュールダウンロードするときにイネーブルにしていたレジスタをディセーブルにするようにもしました)
ビルドはOK、いよいよカーネルモジュールを insmodします
$ sudo insmod enablePMCCNTR.ko
ちゃんとインストールされているか確認
$ lsmod Module Size Used by enablePMCCNTR 16384 0 ~以下略~
printkで info で出力しているカーネルメッセージを確認
$ dmesg | tail -1 [ 1833.694783] Enable PMCCNTR access. Old PMCR = 0x41033000 Old PMUSERENR = 0
一応、インストール前のPMCRと PMUSERENRの値を表示させています。ちなみに PMCRの頭の方の0x41はArmのインプリメントのPMC、次の0x03はCortex A53(勿論 Raspberry Pi 3 model B+のCPUコア)、その次の0x3(頭から5ビットなので下に0が1ビット追加されて3ではなく6)は、PMC内のカウンタ総本数であります。ちゃんと動いていそう。
$ ./readPMUSERENR PMUSERENR = 00000001 $ ./readPMCCNTR PMCCNTR = 28173cf7
しかし、何度も動かしていると挙動がオカシイ。ときどきこんなことになる。
$ ./readPMUSERENR PMUSERENR = 00000000 $ ./readPMCCNTR Illegal instruction
うーんとかなり考え込みました。どうも、プログラムから見たら
裏で動いているCPUが交代している
のではないか、と思い至りました。そういえば、長らく使ってきたRaspberry Pi 1 model B+やPi Zero Wは、シングルコアですが、Raspberry Pi 3 model B+は堂々の4コアマシンです。当然、Linuxはしょっちゅう使用するコアを勝手に取り換えている筈。
先ほどのドライバインストールで設定したコアはその内の一つだけ
でないでしょうか。ええと、何て言ったっけ、プロセスに割り当てるコアを固定する方法があった筈、
CPUアフィニティ
です。taskset コマンドで特定プロセスには特定のコア(またはコア群)を割り当てできる。しかし、どのコアだか分からずに動かしてしまったものは致し方ない、コア4個について、総当たりで調べてみることにいたしました。
コア1番。。。駄目。
PMUSERENRは何度やっても0だし、PMCCNTRは Illegal instructionです。
コア2番。。。ビンゴ!
2個目であたりました。PMUSERENRは常に1だし、PMCCNTRは 常になんらかの値を返してきます。
まあね、PMC使うときは、使う前にCPUアフィニティで使用するコアを固定しておいてから、使おうね。今回のトホホであります。