PX4模块设计之十七:ModuleBase模块

1. ModuleBase模块介绍

ModuleBase模块是一个基础类,主要完成各个模块需要的基础功能,比如:start, stop, status命令,但是目前尚不支持多实例(类似mavlink模块多实例应用)。

当继承自该类的模块:

  • 必须完成以下函数实现:
  1. static int task_spawn(int argc, char *argv[])
  2. static T *instantiate(int argc, char *argv[])
  3. static int custom_command(int argc, char *argv[])
  4. static int print_usage(const char *reason = nullptr)
  • 根据情况重构以下函数实现:
  1. virtual int print_status()
  2. virtual void run()
  3. virtual void request_stop()

2. ModuleBase类介绍

这是一个UML绘图工具绘制的ModuleBase类定义(给出一个整体的picture)。

ModuleBase类 UML图
注:由于PX4的代码已经都完成了实现,从代码上逆向回来的UML图,所以感觉很细(很具体化);设计上的一些内容(注释,考虑,场景分析等)看不太出来。如果自上而下的UML设计,会注意到有非常多的描述。这里只是用了UML画个图,做个整体的了解。知道这个类有哪些函数、参量,以及public/protected/private属性。所以不再去考虑和分析设计这个类的时候设计人员的想法(也无从得知)。

3. ModuleBase类功能介绍

3.1 模块入口

ModuleBase::main
 ├──> <argc <= 1 且 命令参数非 "-h"/"help"/"info"/"usage">
 │   └──> return T::print_usage();                                //【必须实现】的函数
 ├──> <命令参数 "start">
 │   └──> return start_command_base(argc - 1, argv + 1);
 ├──> <命令参数 "status">
 │   └──> return status_command();
 ├──> <命令参数 "stop">
 │   └──> return stop_command();
 ├──> lock_module(); // Lock here, as the method could access _object.
 ├──> int ret = T::custom_command(argc - 1, argv + 1);            //【必须实现】的函数
 └──> unlock_module();

在PX4飞控系统代码中模块入口采用的是_main来作为app应用入口的。这里只是ModuleBase::main,在实际符号链接之前要做转换。我们以logger模块为例:

int logger_main(int argc, char *argv[])
{
	// logger currently assumes little endian
	int num = 1;

	if (*(char *)&num != 1) {
		PX4_ERR("Logger only works on little endian!\n");
		return 1;
	}

	return Logger::main(argc, argv);
}

此时logger_main是logger模块的应用入口,然后【PX4模块设计之十一:Built-In框架】通过CMakelist将logger模块的应用入口编译和链接到系统中。

注:main的写法与【Linux应用程序之Helloworld入门】里面的入口main一致,符合常规习惯。

3.2 模块启动

检查是否已在运行,如果尚未运行,则调用T::task_spawn()。

ModuleBase::start_command_base
 ├──> lock_module();
 ├──> <is_running()>
 │   └──> PX4_ERR("Task already running");
 ├──> <else>
 │   └──> ret = T::task_spawn(argc, argv);                    //【必须实现】的函数
 └──> unlock_module();

3.3 模块停止

检查模块是否正在运行,如果正在运行,则请求模块停止并等待任务完成。

ModuleBase::stop_command
 ├──> lock_module();
 ├──> <is_running()>
 │   ├──> T *object = _object.load();
 │   ├──> <object>
 │   │   ├──> object->request_stop();  // 发布stop应用指令
 │   │   └──> do {} while (_task_id != -1);
 │   │       ├──> unlock_module();
 │   │       ├──> px4_usleep(10000); // 10 ms, 定期循环查询_task_id是否==-1(满足条件表明模块自己已经优雅退出)
 │   │       ├──> lock_module();
 │   │       └──> <++i > 500 && _task_id != -1>  // wait at most 5 sec
 │   │           ├──> PX4_ERR("timeout, forcing stop");
 │   │           ├──> <_task_id != task_id_is_work_queue>  // 如果已经创建task,需要强制删除任务
 │   │           │   └──> px4_task_delete(_task_id);
 │   │           ├──> _task_id = -1;
 │   │           ├──> delete _object.load();
 │   │           ├──> _object.store(nullptr);
 │   │           └──> break
 │   └──> <else>
 │       └──> _task_id = -1;
 └──> unlock_module();

3.4 状态查询

检查是否正在运行,如果正在运行,则调用print_status()。

ModuleBase::status_command
 ├──> lock_module();
 ├──> <is_running() && _object.load()>
 │   ├──> T *object = _object.load();
 │   └──> ret = object->print_status();
 ├──> <else>
 │   └──> PX4_ERR("failed to instantiate object");
 └──> unlock_module();

3.5 任务回调

创建任务以后,ModuleBase通过px4_task_spawn_cmd封装了任务入口函数run_trampoline。

ModuleBase::run_trampoline
 ├──> T *object = T::instantiate(argc, argv);                //【必须实现】的函数
 ├──> _object.store(object);
 ├──> <object>
 │   └──> object->run();
 ├──> <else>
 │   └──> PX4_ERR("failed to instantiate object");
 └──> exit_and_cleanup

3.6 辅助函数

static bool is_running()                                       //判断模块是否已经启动
bool should_exit()                                             //判断模块是否需要退出
static void exit_and_cleanup()                                 //清除当前模块对象(清空指针)
static int wait_until_running(int timeout_ms = 1000)           //等待实例初始化完成,可以在task_spawn最初阶段
static T *get_instance()                                       // 获取当前模块实例

4. 总结

ModuleBase类总体上看类似一个简单封装,将模块设计的基本框架搭建好了,完成最为基本的start/stop/status功能。关于具体是否创建任务(线程),还是使用工作队列,ModuleBase类都没有具体给出。

关于模块是否采用任务(T::task_spawn内部实现),还是采用WorkQueue(继承WorkItem实现),在后续章节中再做深入。

5. 参考资料

【1】PX4开源软件框架简明简介
【2】Linux应用程序之Helloworld入门
【3】PX4模块设计之十一:Built-In框架
【4】PX4模块设计之十三:WorkQueue设计

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个驱动程序获取CmpTraceRoutine函数地址的示例代码,仅供参考。 ```cpp #include <ntddk.h> NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); // 获取ntoskrnl.exe模块的基址 PVOID ntoskrnlBase = GetKernelBase(); if (ntoskrnlBase == NULL) { return STATUS_UNSUCCESSFUL; } // 获取CmpTraceRoutine函数的地址 PVOID cmpTraceRoutine = GetFunctionAddress(ntoskrnlBase, "CmpTraceRoutine"); if (cmpTraceRoutine == NULL) { return STATUS_UNSUCCESSFUL; } // 使用CmpTraceRoutine函数 // ... return STATUS_SUCCESS; } PVOID GetKernelBase() { NTSTATUS status; ULONG size = 0; PVOID base = NULL; // 获取系统信息的长度 status = ZwQuerySystemInformation(SystemModuleInformation, NULL, 0, &size); if (status != STATUS_INFO_LENGTH_MISMATCH) { return NULL; } // 分配足够大的内存缓冲区 PVOID buffer = ExAllocatePoolWithTag(NonPagedPoolNx, size, 'KDBG'); if (buffer == NULL) { return NULL; } // 获取系统信息 status = ZwQuerySystemInformation(SystemModuleInformation, buffer, size, &size); if (NT_SUCCESS(status)) { PSYSTEM_MODULE_INFORMATION modules = (PSYSTEM_MODULE_INFORMATION)buffer; base = modules->Module[0].ImageBase; } // 释放内存缓冲区 ExFreePoolWithTag(buffer, 'KDBG'); return base; } PVOID GetFunctionAddress(PVOID ModuleBase, PCHAR FunctionName) { PIMAGE_NT_HEADERS ntHeaders = RtlImageNtHeader(ModuleBase); if (ntHeaders == NULL) { return NULL; } // 获取导出表RVA和大小 PIMAGE_DATA_DIRECTORY exportDirectory = &ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (exportDirectory->VirtualAddress == 0 || exportDirectory->Size == 0) { return NULL; } // 获取导出表的指针 PIMAGE_EXPORT_DIRECTORY exportTable = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)ModuleBase + exportDirectory->VirtualAddress); // 获取导出表中函数名称的指针表和地址表 PDWORD nameTable = (PDWORD)((ULONG_PTR)ModuleBase + exportTable->AddressOfNames); PDWORD addressTable = (PDWORD)((ULONG_PTR)ModuleBase + exportTable->AddressOfFunctions); PWORD ordinalTable = (PWORD)((ULONG_PTR)ModuleBase + exportTable->AddressOfNameOrdinals); // 在导出表中查找函数名称 for (DWORD i = 0; i < exportTable->NumberOfNames; i++) { PCHAR name = (PCHAR)((ULONG_PTR)ModuleBase + nameTable[i]); if (_stricmp(name, FunctionName) == 0) { WORD ordinal = ordinalTable[i]; ULONG_PTR address = (ULONG_PTR)ModuleBase + addressTable[ordinal]; return (PVOID)address; } } return NULL; } ``` 这个驱动程序使用`ZwQuerySystemInformation`函数获取系统模块的信息,然后遍历模块列表找到`ntoskrnl.exe`模块的基址。接着,使用`RtlImageNtHeader`函数读取模块的PE头,然后在导出表中查找`CmpTraceRoutine`函数的地址。最后,使用`CmpTraceRoutine`函数执行相关操作。 请注意,这个示例代码仅供参考,如果要在实际环境中使用,请根据具体情况进行适当修改和优化,并遵循内核编程的最佳实践和安全原则,以确保操作的正确性和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值