2011年10月12日
ARM LinuxのユーザプログラムからCortex-A9のサイクルカウンタを利用する
以前、「Cortex-A9のサイクルカウンタを利用した簡単な実行時間の計測方法」という記事を書きました。このときには特権モードで使用することを前提としていました。この記事の最後にも書いたとおり、ユーザーモードからパフォーマンスモニタのレジスタにアクセスするためにはあらかじめUser Enable Register(PMUSERENR)にてアクセスを許可しておく必要があります。簡単なカーネルモジュールを作ってこれをやってみました。
カーネルモジュールの作成
ユーザーモードからのPerformance Monitoring Unitへのアクセスを許可するには、あらかじめ特権モードでPMUSERENRレジスタのbit0を立てておく必要があります。詳しくはARMのArchitecture Reference Manualを見てください。
書籍に載っているカーネルモジュール版のHelloWorldのソースを少し変更して以下を作成しました。(参考にしたのは「Linux デバイスドライバプログラミング」)
pmuser.c
#include <linux/module.h> #include <linux/init.h> #define PMUSERENR_EN 0 MODULE_LICENSE("Dual BSD/GPL"); static int pmuser_init(void) { unsigned long x; asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r" (x)); x |= 1 << PMUSERENR_EN; asm volatile("mcr p15, 0, %0, c9, c14, 0" :: "r" (x)); printk(KERN_ALERT "Enabled accessing Performance Monitoring Unit from user space\n"); return 0; } static void pmuser_exit(void) { unsigned long x; asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r" (x)); x &= ~(1 << PMUSERENR_EN); asm volatile("mcr p15, 0, %0, c9, c14, 0" :: "r" (x)); printk(KERN_ALERT "Disabled accessing Performance Monitoring Unit from user space\n"); } module_init(pmuser_init); module_exit(pmuser_exit);
Makefile
obj-m := pmuser.o KERNEL_DIR = ../../kernel all: make -C $(KERNEL_DIR) M=$(PWD) modules clean: make -C $(KERNEL_DIR) M=$(PWD) clean
Makefile内のKERNEL_DIRはカーネル本体のソースのディレクトリを指すように修正してください。
通常のカーネルのビルドのように、ARCH=arm, CROSS_COMPILE=... の環境変数をセットしてから
$ make make -C ../../kernel M=/opt/koba/kzm/android/work/kernel_modules/pmuser modules make[1]: Entering directory `/opt/koba/kzm/work/kernel' CC [M] /opt/koba/kzm/work/kernel_modules/pmuser/pmuser.o Building modules, stage 2. MODPOST 1 modules LD [M] /opt/koba/kzm/work/kernel_modules/pmuser/pmuser.ko make[1]: Leaving directory `/opt/koba/kzm/work/kernel' $
これで、pmuser.ko ができました。
テストプログラム
以前の記事と同じものを使います。
pmon_ca9.h
#ifndef __KMC_PMON_CA9_H #define __KMC_PMON_CA9_H /* Performance Monitor Control Register of Cortex A9*/ #define PMCR_D 3 #define PMCR_C 2 #define PMCR_E 0 #define PMCNTENSET_C 31 volatile __inline__ static unsigned long __attribute__((always_inline)) pmon_start_cycle_counter() { unsigned long x; x = 1 << PMCNTENSET_C; asm volatile("mcr p15, 0, %0, c9, c12, 1" :: "r" (x)); asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (x)); x |= ((1 << PMCR_D) | (1 << PMCR_C) | (1 << PMCR_E)); asm volatile("mcr p15, 0, %0, c9, c12, 0" :: "r" (x)); asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (x)); return x; } volatile __inline__ static unsigned long __attribute__((always_inline)) pmon_read_cycle_counter() { unsigned long x; asm volatile ("mrc p15, 0, %0, c9, c13, 0": "=r" (x)); return x; } #endif /* __KMC_PMON_CA9_H */
test.c
#include <stdio.h> #include "pmon_ca9.h" int func(int x) { int sum = 0; int i; for (i = 1; i <= x; i++) { sum += i; } return sum; } int main() { unsigned long start, end; int x; start = pmon_start_cycle_counter(); x = func(1000); end = pmon_read_cycle_counter(); printf("time = %ld, x = %d\n", end - start, x); return x; }
実行例
ARM Ubuntuの上でこのテストプログラムをビルドして実行してみます。
user@arm-maverick:~/work/pmon$ gcc -o test test.c user@arm-maverick:~/work/pmon$ ls pmon_ca9.h pmuser.ko test test.c user@arm-maverick:~/work/pmon$ ./test Illegal instruction
普通に実行するとこのように例外で異常終了してしまいます。ユーザーモードでのPMUのアクセスが禁止されているからです。
今回作ったカーネルモジュールをロードしてから、再度実行してみます。
user@arm-maverick:~/work/pmon$ sudo insmod ./pmuser.ko user@arm-maverick:~/work/pmon$ ./test time = 146, x = 500500
それらしく動いているようです。
2011.10.13 追記
この例はSMP(Symmetric Multiprocessing)を考慮していません。
カウンタの設定とカウンタの読み出しが別々のコアで実行されたらおかしくなります。