提交 eaa8e38c 编写于 作者: xiaonuo911teamo's avatar xiaonuo911teamo

补充corelib中的README

上级 35ce4dd4
......@@ -2,6 +2,13 @@
这是一个零耦合的模块工程架构。模块间的交互操作通过查询在静态存储区存放的注册函数的地址,传入相应参数,完成调用交互,从而达到所有模块均不互相依赖的目的。该架构适用于多任务并行执行,并且多有交互的情景,多用于C++后台服务和界面程序的底层架构。
## 本文使用的术语
| 编号 | 术语和缩写 | 解释 |
| --- | --------- | -----|
| 1 | 模块 | 用于描述一个整体功能模块, 通常模块内都会有一个pulgin.cpp文件,去控制模块的启动和停止|
## 框架背景以及原理
有时候,我们会因为功能的划分,将一部分内容拆分成多个模块,但是由于其内部有具有一定的关联性,如相互之间的函数调用等。在单独编译各个模块的时候,仍然需要对方的头文件,以及运行时对so的依赖。这样的一种设计,我们称其为耦合设计。通常情况下,我们并不喜欢这种强制的耦合关系,它导致我们的系统依赖关系复杂,不够灵活。
......@@ -21,18 +28,26 @@
## 框架现有的功能
1. 服务机制
1. 服务机制&消息机制
服务机制和消息机制在messager中实现, [详细介绍](code/src/corelib/include/message/README.md)
2. 消息机制
同上.
2. 线程安装的变量封装
现已提供线程安全的变量封装以及vector/queue封装.[详细介绍](code/src/corelib/include/core/README.md)
3. 模块线程管理
每个模块使用独立线程,模块运行模式提供2+1的方案。触发式调度模式和定时循环式处理模式+任务池模式。[详细介绍](code/src/corelib/include/pipe/README.md)
4. 标准化log输出
提供六种等级的log输出模式,适用于不同场景,依次为DEBUG/INFO/WARNING/ERROR/FATAL/DIRECT。[详细介绍](code/src/corelib/include/log/README.md)
5. 进程CPU 内存实时记录
6. 时序ulog存储
7. 日志落盘
8. 类似与电信号中断机制的信号诊断功能
5. 类似与电信号中断机制的信号诊断功能
通过模拟电信号中**中断触发方式**, 对连续信号提供诊断支持.[详细介绍](code/src/corelib/include/diag/diagnose.hpp)
6. 框架配置介绍
框架内的各个模块可以通过AppConfig中的静态函数, 获取指定类型的配置. 这些配置统一通过 DlUtils::initial_path(argv) 进行获取读入.[详细介绍](code/src/corelib/include/utils/README.md)
7. 进程CPU 内存实时记录
server_proc模块可以通过实时记录/proc下的进程信息, 保存进程实时使用CPU和内存情况.[详细介绍](code/src/server_proc/README.md)
8. 时序ulog存储
Ulogger是一种高效的时间序列数据记录格式.可以实时存储数据,生成ulog文件.通过其他软件的解析,可以绘制出数据变化的曲线图.[详细介绍](code/src/corelib/include/ulg/README.md)
9. 日志落盘
server_log模块可以将INFO等日志信息输出到文件中.[详细介绍](code/src/server_log/README.md)
10. 使用protobuf作为通用通信格式
[详细介绍](code/src/proto_data/README.md)
## 框架后续的发展规划
......@@ -43,12 +58,6 @@
5. 线程调度方面有待完善
6. 获取上一条已输出日志的接口,用于自测时
## 本文使用的术语
| 编号 | 术语和缩写 | 解释 |
| --- | --------- | -----|
| 1 | 模块 | 用于描述一个整体功能模块, 通常模块内都会有一个pulgin.cpp文件,去控制模块的启动和停止|
## 框架使用建议
1. 尽量不要自启线程, 框架内包含对线程的调度部分, 可以满足大部分需求.
......@@ -63,8 +72,8 @@
3. `./build.sh -j2 -nbi` 编译,并产出到output目录
4. `cd output/linux-x86_64` 进入产出目录
5. `./run.sh -r test` 执行测试运行命令,等待10秒左右,**Ctrl+C** 退出。
6. `vim log/proc/*.proc` 查看生成的cpu信息和内存信息
6. `vim log/*.log` 查看输出的log信息, 有两类INFO和ERROR.
7. `vim log/proc/*.proc` 查看生成的cpu信息和内存信息
## 特殊情况使用指导
......@@ -73,4 +82,4 @@
有时,我们会使用到someip ros这类的通讯机制, 其主要通过回调函数完成, 但是都需要调用一个阻塞线程, 用于监听. 这时, 建议不要在模块内自启线程, 而是直接让阻塞线程在 thread_func 函数内阻塞住. 同时, 重载基类函数stop, 实现阻塞线程的退出.
[111](code/src/corelib/include/pipe/README.md)
......@@ -15,6 +15,58 @@
7. 查看数据更新状态函数 is_updated
8. 查看是否已载入数据函数 has_data
### DoubleBufferData 使用示例
> 使用经典的消费-生产的例子作为演示用法.
> 1. 生产线程每次产生一个蛋糕, 当蛋糕被取走后, 进行生产下一个.
> 2. 生产一个蛋糕用时2s.
> 3. 消费消费线程, 按计划来取蛋糕, 如此时没有蛋糕, 则等待其生产.
```
struct Cake {
enum Flavors {
Sponge,
Chiffon,
AngelFool,
Pound,
Cheese,
Mousse
};
Flavors flavor;
int size;
Cake() : flavor(Flavors::Sponge), size(6) {}
};
DoubleBufferData<Cake> cake; // 表示蛋糕
std::thread producer([&](){
while(1) {
if (!cake.is_update()) { // get_data 和 set_data会自动更新内部的is_updated状态
AppUtil::sleep_ms(2000);
cake.set_data(Cake()); // 内部带有线程锁, 无需额外设置
}
}
});
std::thread customer([&](){
int plan[6] = {1, 2, 5, 7, 8, 10}; // 计划第几秒去取蛋糕
for (int i = 0; i < 6;) {
static int64_t init_time = AppUtil::get_current_ms();
while (AppUtil::get_current_ms() - init_time >= a[i]*1000) {
if (cake.is_update()) {
Cake tmp = cake.get_data(); // 内部带有线程锁, 无需额外设置
INFO() << "get a cake. " << (int)tmp.flavor;
i++; // 切换到下一个人
} else {
INFO() << "waiting for cake ...";
}
}
}
});
```
### 仍需优化部分
1. DoubleBufferedQueue和DoubleBufferedVector, 对swap的实现部分, 存在争议, 具体参考定义部分, [double_buffered_queue](./double_buffered_queue.hpp), [double_buffered_vector](./double_buffered_vector.hpp)
2. vector queue还有很多操作没有支持, 像迭代器, 随机访问等.
## 诊断模块说明
这个目录下存放支持诊断功能的机制, 目前已经提供两类功能.
1. 模拟中断方式, 判断一个连续信号是否产生错误.
2. 计算连续信号的频率, 并支持跨模块获取信号频率.
# TODO: 等引入failsafe模块后再补充使用方式.
\ No newline at end of file
......@@ -21,6 +21,7 @@ log_debug/log_info/log_warning/log_error/log_fatal/log_direct,可以通过subs
- ERROR(): 用于一般错误的输出,即不影响程序运行的错误
- FATAL(): 用于严重错误的输出,即影响严重错误的错误
- DIRECT(): 最高级别的输出方式
- SET_LOG_LEVEL(level): 设置日志的输出等级
- DATAINFO(name, value): 用于输出一个变量的值。
- **_IF(condition): 如果 condition==true,则输出内容
- **_IF_NOT(condition): 如果 condition==false,则输出内容
......
......@@ -57,7 +57,7 @@ if (Messager::has_server("get_a_meco")) {
设置场景: 在马戏团中的一只海豚, 听驯兽师的指令, 做出规范动作. 模块A模拟驯兽师发出指令, 模块B模拟海豚响应指令.
```
// 模块A
// 驯兽师
// 设定一次 "play_ball" 指令持续ns, 听到"stop"指令或超过ns, 则停止.
Messager::publish("play_ball", 10);
......@@ -66,7 +66,17 @@ Messager::publish("stop");l
```
### message 模块接口详细说明
```
// 海豚
Messager::subcribe<int>("play_ball", [&](int sec) {
_is_stop = true;
int64_t start = AppUtil::get_current_ms();
while (AppUtil::get_current_ms() - start < sec * 1000 && (!_is_stop)) {
play_ball(); // 用时很短, ms级别
}
});
1. Messager构造函数, 已设置delete. Message为静态类结构, 不支持构造对象.
2.
\ No newline at end of file
Messager::subcribe("stop", [&](){
_is_stop = true;
});
```
\ No newline at end of file
......@@ -8,13 +8,119 @@
-- PipeElement: 并发模块, 可配置无处理间隔的循环处理,也可配置需要手动submit触发的处理方式
-- TaskPoolElement: 任务池模块
-- TimerElement: 定时处理模块
-- TimerTrigger: 定时触发模块
-- TimerTrigger: 定时触发模块, 仅用于触发 继承TimerElement的模块的内部检测, 此模块决定了TimerElement所属模块的定时精度
### 使用方式
### 类详细介绍
#### PipeController
模块控制器,可以同时控制多个模块的启动和停止。一般在各个模块中的plugin.cpp 文件中使用。
```
#include <pipe/pipe_controller.hpp>
PipeController g_controllor;
extern "C"{
void LOAD_PLUGIN(){
// 同时加载A B C D 四个模块
g_controllor.add_element<A>();
g_controllor.add_element<B>();
g_controllor.add_element<C>();
g_controllor.add_element<D>();
}
void RUN_PLUGIN(){
// 单一的启动命令,会依次启动A B C D 四个模块
g_controllor.start();
}
void UNLOAD_PLUGIN(){
// 单一的停止命令,会依次停止A B C D 四个模块
g_controllor.stop();
g_controllor.wait();
}
}
```
#### PipeElement
此模块是构成框架内模块的基础,所有模块的基类实现。囊括了无间隔处理、手动submit触发的两种工作模式,定时submit触发即定时处理。
##### 成员变量
```
std::thread thd; // 子线程
std::condition_variable func_cv; // 使用条件变量调度线程,可避免资源损耗
std::mutex cv_mutex; // 线程锁
std::atomic_bool running; // 运行状态
std::atomic_bool busy; // 是否在处理任务, 在处理thread_func中
std::atomic_bool wait_cond; // 处理thread_func之前是否需要等待submit,设置为false,则是无间隔处理模式即死循环模式。
std::atomic_bool submitted; // submit状态
std::string thr_name; // 线程名称
int priority; // 优先级
int policy; // 优先策略
```
##### 主要部分的伪代码描述
```
virtual void start() {
创建子线程thd{
线程初始化thread_initial
设置线程运行状态 running
查询绑定CPU
设置进程优先级和优先策略
设置线程名称
while(running){
if 需要等待submit & 没有进行过submit
等待
if 需要退出
break
更新忙碌和提交状态 & 调用线程处理函数thread_func
取消忙碌状态
}
调用线程退出的清理函数thread_closing
}
```
##### 使用建议
1. 若要创建无间隔处理的模块,继承时,第一个参数传入false即可。
2. 若要需要使用手动submit触发调用thread_func函数时,第一个参数传入true
#### TimerPipement
这个模块可作为所有需要定时处理模块的基类。如server_proc模块. TimerElement的原理是订阅timer_trigger信号,由该信号触发检测是否满足下次调用thread_func函数的时间限制,故timer_trigger的频率同时也决定着,定时处理的精度。
##### 详细介绍
模块很简单,不多介绍。[请看](./timer_element.hpp)
##### 使用建议
1. 创建模块时,传入时间间隔即可。
#### TaskPoolElement
有时,我们需要处理多种不同的任务,任务处理没有很大的规律性,而且很随时随意。这时可以考虑使用TaskPoolElement模块,将想要处理的任务,直接压入TaskPoolElemnet的处理队列中, 其会自动依次执行。
##### 详细介绍
这个模块继承于无间隔处理的PipeElement,也就是第一个参数为false。但是,其内部实现,在队列为空时,会自动进入睡眠,当再次压入任务时,再次激活处理。
1. 基于PipeElement或TimerElement,创建可以处理你想完成的任务的模块
2. 将这好多模块加入PipeController中,可以进行统一调度
### 注意事项
1. 在使用基于TimerElement时,需要同时创建TimerTrigger模块,用于触发定时模块的检测处理。
\ No newline at end of file
1. 在使用基于TimerElement时,需要同时创建TimerTrigger模块,用于触发定时模块的检测处理。
2. 框架内不推荐匿名线程,所以尽量给每一个子类线程都起一个特有的名字
### TODO
1. 使用AppUtil中函数, 增加CPU绑定功能
2. 线程调度方面存在冗余
......@@ -2,12 +2,17 @@
这个模块中集成多种工具函数,详细见如下。
### 工具函数
### 目录说明
-- app_preference: 单例AppPreference,用于保存整个程序使用的配置,按 std::map<group.key, value> 分类保存
-- app_config: APP配置相关的函数
-- app_util: 通用工具函数
-- dl_utils: 框架模块加载方式封装
-- timer_counter: 计时工具
-- math_util: 数学相关的工具函数, 平均值等
-- json_writer: json读写工具, 尚不完整, 后续补充
-- data_reader: 文件读取, 尚不完整, 后续补充
-- data_recoder: 文件写入, 尚不完整, 后续补充
### APP配置使用方式
......@@ -19,13 +24,119 @@
```
int32_t log_level = AppConfig::get_int32("Log.log_level");
```
### app_util通用工具使用说明
### TimerCounter 使用方式
1. __FOLDER__ 获取文件路径的文件夹目录, 返回值是std::string类型
2. FREQUENCE(v) 可以记录当前位置的调用频率, 频率保存在double变量v中. (v不需要外面定义, 宏定义中已有) 统计精度为3s, 即3秒内, 平均1s的调用次数.
```
while (1) {
static int i=0;
FREQUENCE(freq); // 注意: 上面是没有定义freq变量的
INFO() << "current freq: " << freq;
sleep_ms(i++);
}
```
3. TIME_LIMIT_EXEC(interval) 可以控制下属代码块的执行频率. 控制间隔至少为interval(ms)
```
while (1) {
static int i=0;
FREQUENCE(freq);
TIME_LIMIT_EXEC(3000) { // 上面的输出频率太高了.这里设置3s输出一次
INFO() << "current freq: " << freq;
}
sleep_ms(i++);
}
```
4. TIMESTAMP_LIMIT_EXEC(interval, timpstamp), 与TIME_LIMIT_EXEC类似, 可以自己设置时间等级. 如进行微秒上的精度控制.
```
while (1) {
static int i=0;
FREQUENCE(freq);
TIMESTAMP_LIMIT_EXEC(3e6, AppUtil::get_current_us()) { // 代码层次上可支持us级的输出频率控制
INFO() << "current freq: " << freq;
}
sleep_ms(i++);
}
```
<font color=red>注: TIME_LIMIT_EXEC和TIMESTAMP_LIMIT_EXEC, 任意组合都不能在同一个代码块中使用两次.</font>
5. sleep_ms(int value) 当前线程睡眠value毫秒; sleep_us(int value) 当前线程睡眠value微秒; 秒级的系统函数已提供
6. get_current_us/get_current_ms/get_current_sec 获取当前的(微秒/毫秒/秒)级时间戳.
7. get_file_text(const std::string& path) 读取文件path,以string输出,<font color=red>string格式限制大小<2G. </font>
8. string_trim(const std::string& str) 去掉str前后的空格和回车
9. string_split(const std::string& src, const std::string &delim) 按delim分割字符串src, <font color=red>delim是string类型的形参.</font>
10. string_sprintf(const char* format, ...) 解释格式化字符串到string
11. string_vsnprintf(const std::string& format, va_list args) 解释格式化字符串到string
12. _string_append(const std::string& file, const char* format, va_list ap) 解释格式化字符串后,追加到file中
13. string_append(const std::string& file, const char* format, ...) 解释格式化字符串后,追加到file中
14. string_load(const std::string& file, std::string& content) 读取file到content, <font color=blue>与get_file_text不同,string_load内部使用的是C语言的FILE相关函数实现, 而 get_file_text是通过C++中的ifstream实现
15. 格式转换部分, 具体查看[app_util.hpp](./app_util.hpp)
16. now_time() 返回当前时间,格式为 tm_hour-tm_min-tm_sec-ms
17. now_date() 返回当前日期,格式为 tm_year-tm_mon-tm_mday
18. get_file_list(const std::string& path, const std::string& filter = ".*") 获取当前文件夹的文件列表,忽略 . ..
19. get_file_size(const std::string& path) 返回文件大小
20. make_dir(const std::string& path) 创建文件夹, @return: 1 create success ; 2 exist ; -1 create failed
21. make_file(const std::string& file_name) 创建文件, <font color=red>内部没有任何错误检查, 创建失败返回-1</font>
22. remove_rf(const char *dir) 删除文件或文件夹, 成功返回0, 失败返回-1
23. check_file_num(const std::string& dir_name, const std::string& filter, int32_t num_limited) 检查dir_name文件夹下面的满足筛选条件filter的文件个数是否已经超过上限值num_limited,并将超过的文件删掉
. 如::check_file_num(".", ".*\\.ulg", 10); # 当前路径下只保存10个ulog文件,其他的都删除掉
24. copy_file(const char* src, const char* des) 拷贝文件
25. set_thread_affinity(const size_t cpu_index) 绑定当前线程到指定的CPU上, 比如将计算需求高的线程绑定在计算能力好的CPU上
### dl_utils 使用
> 具体使用方法参照 iv_task 即可.
1. get_plug_map 保存动态库名称和入口地址的map<name, ptr>
2. initial_path 初始化环境, argv[0]为运行目录, 该目录下应该有 "lib" "config/ini" 文件夹, 用于模块的读取与配置文件的装载.
3. try_load_plugin(const char* name, std::string& message) 加载name模块, 返回加载日志
4. load_plugin 对try_load_plugin 进行封装, 只输出加载错误的日志
5. run_plugin(const char* name) 调用动态库中的 run_$name 函数, 参照一般模块中plugin.cpp 文件的 RUN_PLUGIN() 函数
6. path_go_back 回退路径函数, 应该放到utils函数中
### math_util 使用
1. math::average 求数组内数组的平均值
2. math::variance 求数组内数组的方差
3. math::standar_deviation 求数组内数组的标准差
### TimerCounter 使用
TimerCounter 用于统计算法的用时,从构造函数开始计时,截止至析构函数,并输出中间运行时间
1. 定义 TimerCounter
```
// 计算输出一条log所需要的时间
{
TimerCounter counter("INFO", true, 1000);
INFO() << "Hello world!";
}
```
\ No newline at end of file
```
### 待优化项
1. json_writer json文件处理, 补充完整
2. data_reader 文件读取, 补充完整
3. data_recoder 文件写入, 补充完整
4. path_go_back 回退路径函数, 应该放到utils函数中
## proto_data 模块介绍
本模块中存放google protobuf文件和转译的h和c文件.
### 转译方式
在根目录下, 使用 `./build.sh -P`即可调用**protocol**自动将proto_data/proto目录下的文件进行转译.
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册