在 ARM Cortex-a8 BeagleBone Black 上计算时钟周期数

Compute clock cycle count on ARM Cortex-a8 BeagleBone Black

我想计算将要编译的 c 代码中特定函数的时钟周期数,并在 BeagleBone Black 上 运行。我不知道我该怎么做。我在网上搜索并找到了这条指令:

Arndale板上的时钟读取方法:

步骤 1: 插入内核模块以允许用户 space 访问 PMU 计数器。 解压包含 Makefile 和 enableccnt.c 的附件“arndale_clockread.tar.bz2”。在 Makefile 中,将“KERNELDIR”更改为您的内核源目录,例如/usr/src/linux-kernel-version 然后 运行 命令。

linaro@linaro-server:~/enableccnt$ make

上面的命令应该输出enableccnt.ko,这是内核模块允许用户space访问PMU计数器。然后运行命令。

linaro@linaro-server:~/enableccnt$ sudo insmod enableccnt.ko

以下命令应该显示 enableccnt 模块正在 运行ning 内核中插入。

linaro@linaro-server:~/enableccnt$ lsmod

步骤 2: 从用户 space 应用程序读取计数器。 一旦设置了内核模块。以下函数可用于读取计数器

static void readticks(unsigned int *result)
{   
  struct timeval t;
  unsigned int cc;
  if (!enabled) {
   // program the performance-counter control-register:
    asm volatile("mcr p15, 0, %0, c9, c12, 0" :: "r"(17));
   //enable all counters.
    asm volatile("mcr p15, 0, %0, c9, c12, 1" :: "r"(0x8000000f));
   //clear overflow of coutners
    asm volatile("mcr p15, 0, %0, c9, c12, 3" :: "r"(0x8000000f));
    enabled = 1;
  }
  //read the counter value.
  asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(cc));
  gettimeofday(&t,(struct timezone *) 0);
  result[0] = cc;
  result[1] = t.tv_usec;
  result[2] = t.tv_sec;
}

我相信此说明应该适用于任何 ARMv7 平台。所以,我按照说明更改了内核源目录。 Makefile 是这样的:

KERNELDIR := /usr/src/linux-headers-3.8.13-bone70

obj-m := enableccnt.o
CROSS=arm-linux-gnueabihf-

all:
        CC=arm-cortex_a15-linux-gnueabihf-gcc $(MAKE) ARCH=arm -C $(KERNELDIR) M=`pwd`  CROSS_COMPILE=$(CROSS) -I/lib/arm-linux-gnueabihf/lib

现在,当我 运行 make 时,我遇到了这个错误,抱怨 arm-linux-gnueabihf-ar:

CC=arm-cortex_a08-linux-gnueabihf-gcc make ARCH=arm -C /usr/src/linux-headers-3.8.13-bone70 M=`pwd`  CROSS_COMPILE=arm-linux-gnueabihf- -I/lib/arm-linux-gnueabihf/
make[1]: Entering directory `/usr/src/linux-headers-3.8.13-bone70'
  LD      /root/crypto_project/Arndale_enableccnt/built-in.o
/bin/sh: 1: arm-linux-gnueabihf-ar: not found
make[2]: *** [/root/crypto_project/Arndale_enableccnt/built-in.o] Error 127
make[1]: *** [_module_/root/crypto_project/Arndale_enableccnt] Error 2
make[1]: Leaving directory `/usr/src/linux-headers-3.8.13-bone70'
make: *** [all] Error 2

我尝试安装 arm-linux-gnueabihf-ar 但它不起作用。所以,我不知道我现在该怎么办!

EDIT1- 正如评论中提到的,我使用以下方法将我的工具链路径添加到我的环境变量中:

export PATH=/path/to/mytoolchain/bin:$PATH

现在我没有得到以前的错误。但是,我遇到了这个语法错误,我认为它与内核头文件有关:

CC=arm-cortex_a15-linux-gnueabihf-gcc make ARCH=arm -C /usr/src/linux-headers-3.8.13-bone70 M=`pwd`  CROSS_COMPILE=arm-linux-gnueabihf- -I/lib/arm-linux-gnueabihf/bin
/root/gcc-linaro-arm-linux-gnueabihf-4.7-2012.11-20121123_linux/bin/arm-linux-gnueabihf-gcc: 1: /root/gcc-linaro-arm-linux-gnueabihf-4.7-2012.11-20121123_linux/bin/arm-linux-gnueabihf-gcc: Syntax error: "(" unexpected
make[1]: Entering directory `/usr/src/linux-headers-3.8.13-bone70'
  LD      /root/crypto_project/Arndale_enableccnt/built-in.o
/root/gcc-linaro-arm-linux-gnueabihf-4.7-2012.11-20121123_linux/bin/arm-linux-gnueabihf-ar: 1: /root/gcc-linaro-arm-linux-gnueabihf-4.7-2012.11-20121123_linux/bin/arm-linux-gnueabihf-ar: Syntax error: "(" unexpected
make[2]: *** [/root/crypto_project/Arndale_enableccnt/built-in.o] Error 2
make[1]: *** [_module_/root/crypto_project/Arndale_enableccnt] Error 2
make[1]: Leaving directory `/usr/src/linux-headers-3.8.13-bone70'
make: *** [all] Error 2

我想到的唯一合理的解决方案是下载带有头文件的内核源代码,然后再尝试制作。有没有人知道如何解决这个问题?

由于一路上会遇到很多障碍,下面是完整的指南,说明如何构建该内核模块和用户-space 应用程序。

工具链

首先,您需要下载并安装2个工具链:

  1. 构建内核(和内核模块)的工具链:bare-metal (EABI)工具链
  2. 用于构建用户的工具链-space 应用程序:GNU/Linux 工具链

我建议您使用 Linaro ARM 工具链,因为它们是 free, reliable and well optimized for ARM. Here 您可以选择所需的工具链(在 "Linaro Toolchain" 部分)。在 BeagleBone Black 上,默认情况下你有小端架构(就像在大多数 ARMv7 处理器上一样),所以下载接下来的两个档案:

  1. linaro-toolchain-binaries (little-endian) Bare Metal
  2. linaro-toolchain-binaries (little-endian) Linux

下载后,将这些档案解压缩到 /opt 目录中。

内核源码

首先,您需要找出 确切 内核源代码用于构建闪存到您的主板的内核。您可以尝试从 here 中找出(通过您的董事会修订版)。或者您可以构建自己的内核,将其闪存到您的主板上,现在您可以确切知道正在使用的内核版本。

无论如何,您需要下载正确的内核源代码(与您主板上的内核相对应)。这些资源将进一步用于构建内核模块。如果内核版本不正确,您将在模块加载时出现 "magic mismatch" 错误或类似错误。

我将使用 stable kernel sources from kernel.org 仅供参考(至少应该足以构建模块)。

构建内核

运行 终端中的下一个命令为内核构建配置 shell 环境(裸机工具链):

$ export PATH=/opt/gcc-linaro-5.1-2015.08-x86_64_arm-eabi/bin:$PATH
$ export CROSS_COMPILE=arm-eabi-
$ export ARCH=arm

使用 defconfig 为您的开发板配置内核(来自 arch/arm/configs/)。我将使用 omap2plus_defconfig 例如:

$ make omap2plus_defconfig

现在构建整个内核:

$ make -j4

或准备构建外部模块所需的内核文件:

$ make prepare
$ make modules_prepare

在第二种情况下,模块将没有依赖项列表,您可能需要在加载它时使用 "force" 选项。所以首选是构建整个内核。

内核模块

注意:我将进一步使用的代码来自this answer

首先您需要为用户space 访问启用ARM 性能计数器(详细信息here)。它只能在 kernel-space 中完成。这是模块代码,Makefile 您可以使用它:

perfcnt_enable.c:

#include <linux/module.h>

static int __init perfcnt_enable_init(void)
{

    /* Enable user-mode access to the performance counter */
    asm ("mcr p15, 0, %0, C9, C14, 0\n\t" :: "r"(1));

    /* Disable counter overflow interrupts (just in case) */
    asm ("mcr p15, 0, %0, C9, C14, 2\n\t" :: "r"(0x8000000f));

    pr_debug("### perfcnt_enable module is loaded\n");
    return 0;
}

static void __exit perfcnt_enable_exit(void)
{
}

module_init(perfcnt_enable_init);
module_exit(perfcnt_enable_exit);

MODULE_AUTHOR("Sam Protsenko");
MODULE_DESCRIPTION("Module for enabling performance counter on ARMv7");
MODULE_LICENSE("GPL");

生成文件:

ifneq ($(KERNELRELEASE),)

# kbuild part of makefile

CFLAGS_perfcnt_enable.o := -DDEBUG
obj-m := perfcnt_enable.o

else

# normal makefile

KDIR ?= /lib/modules/$(shell uname -r)/build

module:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

.PHONY: module clean

endif

构建内核模块

使用上一步配置的shell环境,让我们再导出一个环境变量:

$ export KDIR=/path/to/your/kernel/sources/dir

现在 运行:

$ make

模块已构建(perfcnt_enable.ko 文件)。

用户-space应用程序

一旦在kernel-space中启用了ARM性能计数器(通过内核模块),您就可以在user-space应用程序中读取它的值。这是此类应用程序的示例。

perfcnt_test.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static unsigned int get_cyclecount(void)
{
    unsigned int value;

    /* Read CCNT Register */
    asm volatile ("mrc p15, 0, %0, c9, c13, 0\t\n": "=r"(value));

    return value;
}

static void init_perfcounters(int32_t do_reset, int32_t enable_divider)
{
    /* In general enable all counters (including cycle counter) */
    int32_t value = 1;

    /* Peform reset */
    if (do_reset) {
        value |= 2; /* reset all counters to zero */
        value |= 4; /* reset cycle counter to zero */
    }

    if (enable_divider)
        value |= 8; /* enable "by 64" divider for CCNT */

    value |= 16;

    /* Program the performance-counter control-register */
    asm volatile ("mcr p15, 0, %0, c9, c12, 0\t\n" :: "r"(value));

    /* Enable all counters */
    asm volatile ("mcr p15, 0, %0, c9, c12, 1\t\n" :: "r"(0x8000000f));

    /* Clear overflows */
    asm volatile ("mcr p15, 0, %0, c9, c12, 3\t\n" :: "r"(0x8000000f));
}

int main(void)
{
    unsigned int overhead;
    unsigned int t;

    /* Init counters */
    init_perfcounters(1, 0);

    /* Measure the counting overhead */
    overhead = get_cyclecount();
    overhead = get_cyclecount() - overhead;

    /* Measure ticks for some operation */
    t = get_cyclecount();
    sleep(1);
    t = get_cyclecount() - t;

    printf("function took exactly %d cycles (including function call)\n",
            t - overhead);

    return EXIT_SUCCESS;
}

生成文件:

CC = gcc
APP = perfcnt_test
SOURCES = perfcnt_test.c
CFLAGS = -Wall -O2 -static

default:
    $(CROSS_COMPILE)$(CC) $(CFLAGS) $(SOURCES) -o $(APP)

clean:
    -rm -f $(APP)

.PHONY: default clean

请注意,我添加了 -static 选项,以防万一您使用 Android 等。如果您的发行版具有常规 libc,您可以删除该标志以减小结果二进制文件的大小。

构建用户-space应用程序

准备shell环境(Linux工具链):

$ export PATH=/opt/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin:$PATH
$ export CROSS_COMPILE=arm-linux-gnueabihf-

构建应用程序:

$ make

输出二进制是perfcnt_test.

测试

  1. 将内核模块和用户-space 应用程序上传到您的主板。
  2. 加载模块:

    # insmod perfcnt_enable.ko
    
  3. 运行申请者:

    # ./perfcnt_test