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)を考慮していません。

カウンタの設定とカウンタの読み出しが別々のコアで実行されたらおかしくなります。

関連するページ

Cortex-A9のサイクルカウンタを利用した簡単な実行時間の計測方法



トラックバックURL

コメントする

名前
URL
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード