一、实现原理机制介绍
内核利用 TracePoint 机制实现内核和芯片特性功能解耦,根据 TracePoint 的代码实现来看,其主要原理是在内核代码中插入一个桩点,芯片厂家自己功能代码实现,然后注册到内核中即可,具体的流程可见下图:
1、在内核中定义一个桩点的宏,该宏的展开是产生三个函数:钩子执行函数、钩子注册函数和钩子注销函数
2、芯片厂家需要做的事是在功能实现代码函数中将需要完成的功能写进去,然后调用上一步的钩子注册函数将实现代码函数注册进去,最后将该模块加载到内核中
3、内核在特定的调用点加入插桩函数即步骤一中的钩子执行函数
4、测试用例:想办法调用内核接口函数,验证运行插桩函数是否执行了芯片厂家的功能实现代码的函数
二、宏定义
宏定义的展开过程
#define DECLARE_TRACE(name, proto, args) \
__DECLARE_TRACE(name, PARAMS(proto), PARAMS(args), \
cpu_online(raw_smp_processor_id()), \
PARAMS(void *__data, proto))
#define DECLARE_HOOK DECLARE_TRACE
#define PARAMS(args...) args
#define TP_PROTO(args...) args
#define TP_ARGS(args...) args
DECLARE_HOOK(vendor_tracepoint_demo,
TP_PROTO(void *arg),
TP_ARGS(arg));
|||||
|||||
|||||
\|||/
\|/
|
__DECLARE_TRACE(vendor_tracepoint_demo, \
PARAMS(void *arg), \
PARAMS(arg), \
cpu_online(raw_smp_processor_id()), \
PARAMS(void *__data, void *arg))
进一步解析
#define raw_smp_processor_id() (current_thread_info()->cpu) // 获取运行当前线程的CPUID
#define cpu_online(cpu) cpumask_test_cpu((cpu), cpu_online_mask) // 测试 CPU 掩码中的 CPU
#define __DECLARE_TRACE(name, proto, args, cond, data_proto) \
extern int __traceiter_##name(data_proto); \
DECLARE_STATIC_CALL(tp_func_##name, __traceiter_##name); \
extern struct tracepoint __tracepoint_##name; \
static inline void trace_##name(proto) \
{ \
if (static_key_false(&__tracepoint_##name.key)) \
__DO_TRACE(name, \
TP_ARGS(args), \
TP_CONDITION(cond), 0); \
if (IS_ENABLED(CONFIG_LOCKDEP) && (cond)) { \
rcu_read_lock_sched_notrace(); \
rcu_dereference_sched(__tracepoint_##name.funcs); \
rcu_read_unlock_sched_notrace(); \
} \
} \
__DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args), \
PARAMS(cond)) \
static inline int \
register_trace_##name(void (*probe)(data_proto), void *data) \
{ \
return tracepoint_probe_register(&__tracepoint_##name, \
(void *)probe, data); \
} \
static inline int \
register_trace_prio_##name(void (*probe)(data_proto), void *data, \
int prio) \
{ \
return tracepoint_probe_register_prio(&__tracepoint_##name, \
(void *)probe, data, prio); \
} \
static inline int \
unregister_trace_##name(void (*probe)(data_proto), void *data) \
{ \
return tracepoint_probe_unregister(&__tracepoint_##name, \
(void *)probe, data); \
} \
static inline void \
check_trace_callback_type_##name(void (*cb)(data_proto)) \
{ \
} \
static inline bool \
trace_##name##_enabled(void) \
{ \
return static_key_false(&__tracepoint_##name.key); \
}
最后展开结果
__DECLARE_TRACE( \
vendor_tracepoint_demo, \
PARAMS(void *arg), \
PARAMS(arg), \
cpu_online(raw_smp_processor_id()), \
PARAMS(void *__data, void *arg) \
)
|||||
|||||
|||||
\|||/
\|/
|
extern int __traceiter_vendor_tracepoint_demo(
void *__data, void *arg);
DECLARE_STATIC_CALL(tp_func_vendor_tracepoint_demo, __traceiter_vendor_tracepoint_demo);
extern struct tracepoint __tracepoint_vendor_tracepoint_demo;
static inline void trace_vendor_tracepoint_demo(
void *arg)
{
if (static_key_false(&__tracepoint_vendor_tracepoint_demo.key))
__DO_TRACE(vendor_tracepoint_demo,
TP_ARGS(arg),
TP_CONDITION(cpu_online(raw_smp_processor_id())), 0);
if (IS_ENABLED(CONFIG_LOCKDEP) && (cpu_online(raw_smp_processor_id()))) {
rcu_read_lock_sched_notrace();
rcu_dereference_sched(__tracepoint_vendor_tracepoint_demo.funcs);
rcu_read_unlock_sched_notrace();
}
}
__DECLARE_TRACE_RCU( \
vendor_tracepoint_demo, \
PARAMS(void *arg), \
PARAMS(arg), \
PARAMS(cpu_online(raw_smp_processor_id()))
)
static inline int register_trace_vendor_tracepoint_demo(
void (*probe)(void *__data, void *arg),
void *data)
{
return tracepoint_probe_register(&__tracepoint_vendor_tracepoint_demo, (void *)probe, data);
}
static inline int register_trace_prio_vendor_tracepoint_demo(
void (*probe)(void *__data, void *arg),
void *data, int prio)
{
return tracepoint_probe_register_prio(&__tracepoint_vendor_tracepoint_demo, (void *)probe, data, prio);
}
static inline int unregister_trace_vendor_tracepoint_demo(
void (*probe)(void *__data, void *arg),
void *data)
{
return tracepoint_probe_unregister(&__tracepoint_vendor_tracepoint_demo, (void *)probe, data);
}
static inline void check_trace_callback_type_vendor_tracepoint_demo(
void (*cb)(void *__data, void *arg))
{
}
static inline bool trace_vendor_tracepoint_demo_enabled(void)
{
return static_key_false(&__tracepoint_vendor_tracepoint_demo.key);
}
2.1 宏展开的结果说明
通过 DECLARE_HOOK(vendor_tracepoint_demo, TP_PROTO(void *arg), TP_ARGS(arg)); 展开最核心是为了得到以下函数:
●钩子执行函数:register_trace_vendor_tracepoint_demo
调用地方使用
●钩子注册函数:register_trace_prio_vendor_tracepoint_demo
芯片厂家实现功能函数后要调用注册函数
●钩子注销函数:unregister_trace_vendor_tracepoint_demo
同注册函数
●钩子回调函数:check_trace_callback_type_vendor_tracepoint_demo
如果没有调用过注册函数,那么执行钩子函数的时候默认执行该回调函数
●钩子使能函数:trace_vendor_tracepoint_demo_enabled
暂时不清楚,没来的及查看
可通过如下命令看出符号表是否加载成功
cat /proc/kallsyms | grep vendor_tracepoint_demo
2.2 实现代码
●drivers/hooks 里面的实现
●include/trace/hooks 里面的代码实现
三、芯片厂家实现的功能
芯片厂家需要定义一个和钩子回调函数类型相同的函数来实现自己的功能代码,然后通过调用钩子注册函数将实现代码注册进去。
为了将芯片部分代码成功加载到内核,我们一般使用内核模块实现,即通过module_init和module_exit实现。
DECLARE_HOOK宏的第二个参数是回调函数除了第一个以外的参数,可以一个,也可以两个三个等多个,第三个参数是要传入的参数值,从宏定义上来看应该很好理解的。
1.模块是否加载成功可通过命令
cat /proc/kallsyms | grep vendor_demo_init 查看
3.1 参考实现代码
// drivers/test_hook/test_hook.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <trace/hooks/demohook.h>
static void vendor_foo(void *data, void *arg)
{
printk("******%s************\n", __func__);
}
static int __init vendor_demo_init(void)
{
return register_trace_vendor_tracepoint_demo(&vendor_foo, NULL);
}
static void __exit vendor_demo_exit(void)
{
unregister_trace_vendor_tracepoint_demo(&vendor_foo, NULL);
}
module_init(vendor_demo_init);
module_exit(vendor_demo_exit);
MODULE_LICENSE("GPL");
四、内核调用参考 demo
这里是自己实现一个简单符号表中去调用插桩点,目的是为了测试方便,内核加载后可通过如下命令查看该符号表是否加载成功
cat /proc/kallsyms | grep test_symbol
// drivers/test_symbol/test_symbol.c
#include <linux/module.h>
#include <linux/init.h>
#include <trace/hooks/demohook.h>
#include <trace/hooks_test/test_symbol.h>
void test_symbol(void)
{
printk("-----%s-----\n", __FUNCTION__);
trace_vendor_tracepoint_demo(NULL);
}
EXPORT_SYMBOL(test_symbol);
MODULE_LICENSE("GPL");
五、测试用例
5.1 测试代码
先实现一个设备驱动调用符号表,然后在应用层打开设备从而调用符号表,这里主要考虑到 RK3568 编译 ko 文件不方便
// drivers/test_demo/test_demo.c
// 驱动侧代码实现
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <trace/hooks_test/test_symbol.h>
#define CHRNAME "chrdev_demo"
#define CLASSNAME "demo_class"
#define DEVICENAME "demo_device"
struct demo_desc {
unsigned int dev_major;
struct class *pcls;
struct device *pdev;
};
static struct demo_desc *demo_dev = NULL;
int demo_open(struct inode *inode, struct file *filep)
{
printk("-----%s-----\n", __FUNCTION__);
/**********************************************************/
test_symbol();
/**********************************************************/
return 0;
}
int demo_release(struct inode *inode, struct file *filep)
{
printk("-----%s-----\n", __FUNCTION__);
return 0;
}
static const struct file_operations demo_ops = {
.open = demo_open,
.release = demo_release,
};
static int __init test_demo_init(void)
{
int ret;
printk("-----%s-----\n", __FUNCTION__);
demo_dev = kzalloc(sizeof(struct demo_desc), GFP_KERNEL);
if (IS_ERR(demo_dev)) {
printk(KERN_ERR "kmalloc error!\n");
ret = -ENOMEM;
goto err0;
}
demo_dev->dev_major = register_chrdev(0, CHRNAME, &demo_ops);
if (demo_dev->dev_major < 0) {
printk(KERN_ERR "register chrdev faidemo!\n");
ret = -ENODEV;
goto err1;
}
demo_dev->pcls = class_create(THIS_MODULE, CLASSNAME);
if (IS_ERR(demo_dev->pcls)) {
printk(KERN_ERR "create demo class faidemo!\n");
ret = PTR_ERR(demo_dev->pcls);
goto err2;
}
demo_dev->pdev = device_create(demo_dev->pcls, NULL, MKDEV(demo_dev->dev_major, 0), NULL, DEVICENAME);
if (IS_ERR(demo_dev->pdev)) {
printk(KERN_ERR "create demo device faidemo!\n");
ret = PTR_ERR(demo_dev->pdev);
goto err3;
}
return 0;
err3:
class_destroy(demo_dev->pcls);
err2:
unregister_chrdev(demo_dev->dev_major, CHRNAME);
err1:
kfree(demo_dev);
err0:
return ret;
}
static void __exit test_demo_exit(void)
{
printk("-----%s-----\n", __FUNCTION__);
device_destroy(demo_dev->pcls, MKDEV(demo_dev->dev_major, 0));
class_destroy(demo_dev->pcls);
unregister_chrdev(demo_dev->dev_major, CHRNAME);
kfree(demo_dev);
}
module_init(test_demo_init);
module_exit(test_demo_exit);
MODULE_LICENSE("GPL");
// kernel/linux/build/test/unittest/accesstokenid
// 应用层代码实现,这里借助内核的unittest测试框架,主要不用自己去增加配置文件,方便
#include "accesstokenid_test.h"
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <ctime>
#include <climits>
#include <pthread.h>
#include <sys/syscall.h>
#include <grp.h>
namespace {
}
using namespace testing::ext;
using namespace std;
void AccesstokenidTest::SetUp() {}
void AccesstokenidTest::TearDown() {}
void AccesstokenidTest::SetUpTestCase() {}
void AccesstokenidTest::TearDownTestCase() {}
/**
* @tc.name: CheckInitToken
* @tc.desc: Test init value of tokenid and ftokenid
* @tc.desc: tokenid equals to the father(hdcd) and ftokenid equals to 0
* @tc.type: FUNC
*/
HWTEST_F(AccesstokenidTest, CheckInitToken, Function | MediumTest | Level1)
{
printf("----------------------------trace point test-----------------------------------------\n");
int fd = open("/dev/demo_device", O_RDWR);
if (fd < 0) {
printf("open demo_device failed\n");
return;
}
close(fd);
}
5.2 用例测试
将版本编译后烧录可以看到在/dev 目录下有个 demo_device 设备,然后将编译好的 accesstokenid_test 执行后内核中会有如下打印,表示我们插桩点成功
# shell终端运行结果(不重要)
# ./accesstokenid_test
Running main() from ../../third_party/googletest/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from AccesstokenidTest
[ RUN ] AccesstokenidTest.CheckInitToken
----------------------------trace point test-----------------------------------------
[ OK ] AccesstokenidTest.CheckInitToken (0 ms)
[----------] 1 test from AccesstokenidTest (0 ms total)
[----------] Global test environment tear-down
Gtest xml output finished
[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 1 test.
# 内核侧的log打印很重要
[52744.227764] -----demo_open-----
[52744.227800] -----test_symbol-----
[52744.227809] ******vendor_foo************
[52744.227827] -----demo_release-----
写在最后
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)用来跟着学习是非常有必要的。
这份鸿蒙(HarmonyOS NEXT)文档包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
希望这一份鸿蒙学习文档能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档
鸿蒙(HarmonyOS NEXT)5.0最新学习路线
有了路线图,怎么能没有学习文档呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档
《鸿蒙 (OpenHarmony)开发入门教学视频》
《鸿蒙生态应用开发V3.0白皮书》
《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
《鸿蒙开发基础》
●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……
《鸿蒙开发进阶》
●Stage模型入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●通知与窗口管理
●多媒体技术
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来设计
●鸿蒙系统移植和裁剪定制
……
《鸿蒙进阶实战》
●ArkTS实践
●UIAbility应用
●网络案例
……