Linux内核 -- 汇编结合ko案例之PMU获取周期技术

ARMv7汇编实现周期计数读取与清空

本文档详细描述了如何在ARMv7平台上使用汇编语言编写周期计数器读取与清空函数,如何在内核模块中导出这些函数供其他模块调用,以及如何使用Netlink接口供用户态程序进行调用。

1. 汇编函数实现

首先,编写汇编函数以实现周期计数器的读取与清空。创建文件cpu_cycle.S

assembly 复制代码
.global clear_cycle_counter
.global get_cycle_count

// 清空周期计数器
clear_cycle_counter:
    // 将0写入周期计数器寄存器
    MCR p15, 0, r0, c9, c12, 2
    MOV pc, lr

// 获取周期计数
get_cycle_count:
    // 从周期计数器寄存器读取值
    MRC p15, 0, r0, c9, c13, 0
    MOV pc, lr

2. 内核模块实现

接下来,编写内核模块代码,以导出汇编函数并通过Netlink提供接口。创建文件cm_cpu_cycle.c

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <net/sock.h>

#define NETLINK_USER 31

// 声明汇编函数
extern void clear_cycle_counter(void);
extern unsigned int get_cycle_count(void);

// 导出汇编函数
EXPORT_SYMBOL(clear_cycle_counter);
EXPORT_SYMBOL(get_cycle_count);

static struct sock *nl_sk = NULL;

static void nl_recv_msg(struct sk_buff *skb) {
    struct nlmsghdr *nlh;
    int pid;
    struct sk_buff *skb_out;
    int msg_size;
    char *msg = NULL;
    int res;

    nlh = (struct nlmsghdr*)skb->data;
    pid = nlh->nlmsg_pid; // 获取发送者的PID

    if (strncmp((char*)nlmsg_data(nlh), "clear", 5) == 0) {
        clear_cycle_counter();
        msg = "Cycle counter cleared";
    } else if (strncmp((char*)nlmsg_data(nlh), "get", 3) == 0) {
        unsigned int cycle_count = get_cycle_count();
        msg_size = snprintf(NULL, 0, "Cycle count: %u", cycle_count);
        msg = kmalloc(msg_size + 1, GFP_KERNEL);
        snprintf(msg, msg_size + 1, "Cycle count: %u", cycle_count);
    } else {
        msg = "Invalid command";
    }

    msg_size = strlen(msg);
    skb_out = nlmsg_new(msg_size, 0);

    if (!skb_out) {
        pr_err("Failed to allocate new skb
");
        return;
    }

    nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
    NETLINK_CB(skb_out).dst_group = 0;
    strncpy(nlmsg_data(nlh), msg, msg_size);

    res = nlmsg_unicast(nl_sk, skb_out, pid);

    if (res < 0)
        pr_err("Error while sending back to user
");

    if (msg && strncmp(msg, "Cycle count: ", 13) == 0)
        kfree(msg);
}

static int __init cm_cpu_cycle_init(void) {
    struct netlink_kernel_cfg cfg = {
        .input = nl_recv_msg,
    };

    nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
    if (!nl_sk) {
        pr_err("Error creating socket.
");
        return -10;
    }

    pr_info("cm_cpu_cycle module loaded.
");
    return 0;
}

static void __exit cm_cpu_cycle_exit(void) {
    netlink_kernel_release(nl_sk);
    pr_info("cm_cpu_cycle module unloaded.
");
}

module_init(cm_cpu_cycle_init);
module_exit(cm_cpu_cycle_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Cycle Count Management Module");
MODULE_AUTHOR("Your Name");

3. Makefile

创建Makefile以编译内核模块:

makefile 复制代码
obj-m += cm_cpu_cycle.o
cm_cpu_cycle-objs := cm_cpu_cycle_main.o cpu_cycle.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

4. 用户态程序

编写用户态程序,通过Netlink接口与内核模块通信。创建文件user_program.c

c 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NETLINK_USER 31

struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
struct msghdr msg;

void send_msg(const char *cmd) {
    nlh->nlmsg_len = NLMSG_SPACE(1024);
    nlh->nlmsg_pid = getpid();
    nlh->nlmsg_flags = 0;
    strcpy(NLMSG_DATA(nlh), cmd);

    iov.iov_base = (void *)nlh;
    iov.iov_len = nlh->nlmsg_len;
    msg.msg_name = (void *)&dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    sendmsg(sock_fd, &msg, 0);

    recvmsg(sock_fd, &msg, 0);
    printf("Received message payload: %s
", (char *)NLMSG_DATA(nlh));
}

int main() {
    sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
    if (sock_fd < 0) {
        return -1;
    }

    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = getpid();

    bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));

    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0;   // For Linux Kernel
    dest_addr.nl_groups = 0; // unicast

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(1024));
    memset(nlh, 0, NLMSG_SPACE(1024));

    // 发送清空周期计数器命令
    send_msg("clear");

    // 发送获取周期计数器值命令
    send_msg("get");

    close(sock_fd);
    return 0;
}

5. 编译与加载模块

编译内核模块:

bash 复制代码
make

加载内核模块:

bash 复制代码
sudo insmod cm_cpu_cycle.ko

运行用户态程序:

bash 复制代码
gcc user_program.c -o user_program
./user_program

卸载内核模块:

bash 复制代码
sudo rmmod cm_cpu_cycle

通过以上步骤,可以实现一个在内核态使用汇编语言编写的周期计数读取与清空功能,并通过Netlink接口供用户态程序进行调用的完整示例。

相关推荐
AlfredZhao4 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户97183563346610 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪12 小时前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩1 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
古城小栈1 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
凡人叶枫1 天前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
2601_961875241 天前
决战申论100题2026|最新|范文
linux·容器·centos·debian·ssh·fabric·vagrant