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接口供用户态程序进行调用的完整示例。

相关推荐
技术不好的崎鸣同学3 分钟前
x64汇编之堆栈工作原理理论篇(继上篇内容讲解)
汇编
Peace2 小时前
【Prometheus】
linux·运维·prometheus
LZZ and MYY3 小时前
RTS 在windows和Linux之间ShareMem
linux·运维·服务器
aningx3 小时前
openSUSE Leap 16.0 运行 sunshine 报错的解决方法
linux
爱学习的徐徐3 小时前
Linux 基础IO
linux·服务器
xiaobobo33303 小时前
面向对象:linux内核中函数转数据的用法
linux·面向对象·隔离·函数指针绑定
姓刘的哦4 小时前
C++软件架构设计思路
linux
ModestCoder_4 小时前
windows/ubuntu解决挂梯子但是codex reconnecting五次的问题
linux·windows·ubuntu
禹凕4 小时前
Linux基础——环境
linux·运维·服务器·ubuntu
好好风格4 小时前
【一行代码】查看本机公网 IP
linux·命令行