Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

1.CSPL架构简述

CSPL的定位和目的:

  • CSPL位于业务与操作系统之间
  • 屏蔽操作系统的复杂接口,提供统一的操作方式,使得业务进程只需要关心本身业务,从而加速协议栈的开发
  • CSPL核心是以lib库(动态库、静态库)的形式提供给业务,同时为了支撑业务,提供多个进程支撑;DAEMON、LOG、CLI、HA

Alt text

CSPL Core Stack Porting Layer 核心系统接口层
OSAL Operating System Adapter Layer 操作系统适配层
任务管理 任务调度管理等功能,提供统一的运行调度机制
内存管理 提供内存池,高端内存等功能
消息通信 提供任务间的消息通信处理等功能,如UDP、TCP、消息队列等
系统调试 提供Log、信令跟踪、黑匣子、运行时调试,性能统计等功能
事件管理 提供事件服务器,支持事件订阅、发布操作
其他功能 提供定时器,数据库,CLI调试等功能

Alt text

2.CSPL设计思路

2.1模型抽象

2.1.1数据结构

QMANIFEST
每一个协议栈实例(模块)由QMANIFEST数据结构来表示,CSPL通过此数据结构来和协议栈交互, CSPL_RegisterModule函数使用此结构体向CSPL注册module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct QMANIFEST{
const char *name; //模块名称
unsigned long service; //模块ID
struct {
void *(*early)(void *); //模块早期初始化函数,不能发消息,不能启动定时器
void *(*late)(void *); //模块后期初始化函数,可以发消息,和启动定时器
} init; //初始化函数
struct {
int (*message)(void *, void *); //消息处理函数,需要返回1
void (*timer)(QTIMER, void *, void *); //定时器处理函数
} handler; //消息、定时器处理入口
struct {
void *(*pack)(QMODULE, void *, unsigned int *);
void *(*unpack)(QMODULE, void *);
void *(*alloc)(QMODULE, unsigned int);
} method; //消息编解码函数

const unsigned long *destinations; //外部交互模块
} QMANIFEST;

cspl_address_t
每一个协议栈路由信息用cspl_address_t数据结构来表示,CSPL_Open内部通过此数据结构来配置本地接收和发送的路由(即创建相应的fd, 并加入到epoll中)

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct{
route_type_et type; //接收或者发送0:recv 1:send 2:trace(暂时不支持)
trans_type_et transType; //传输类型0:UDP 1:TCP 2:msg queue消息队列为POSIX版本3:LINX
U8 isStripCspIHead; //该字段仅对外部模块起作用
U32 moduleId; //模块ID号,如果是接收,则当没有CSPL头的消息转发到该模块
union{
U8 ipAddress[CSPL_IP_ADDRESS_LENGTH]; //ip地址加端口号,类似127.0.0.1::6700
U8 mqName[CSPL_MQ_NAME_LENGTH]; //消息队列的名称。类似“/RrmToMac%d:1024” 后面的数字作为消息最大长度值,如果没有,则使用默认配置
U8 endPoint[CSPL_LINX_ENDPOINT_LENGTH]; //LINX endpoint名称的最大长度
U8 rawType[CSPL_IP_ADDRESS_LENGTH]; //原始套接字类型,可选值:UDP、TCP、ICMP、IGMP
}address;
}cspl_address_t; //open的时候传入该数组

qvars
qvars结构体用于描述driver,当模块未设置立即发送标志时,hold队列用于存放所有的发送消息,当本driver调度运行时,会将所有hold消息都发送出去。

1
2
3
4
5
6
7
8
9
10
typedef struct qvars{
YLNODE __header__; //driver节点,挂载到qcontext的driverloops双链表上
QMODULE self; //driver当前的处理的module指针
YSQUEUE queue; //接收消息优先级队列
YSQUEUE hold; //发送消息优先级队列
YLIST modules; //module双链表

void (*wakeup)(void *); //用于唤醒driver,对应于QWAIT wakeup
void *argwakeup;
}qvars;

QSHELL
QSHELL结构体用于适配不同的操作系统, 属于OSAL层。使用多路复用IO模型,内部使用epoll完成对多路事件的监控。

1
2
3
4
5
6
7
8
9
10
struct QSHELL{
void (*timedwait)(void *, const QTIME); //睡眠等待指定的时间,若所监控的描述符数组中有事件发生(epoll),则结束睡眠
void (*send)(void *, const void *, QMODULE, QMODULE, void *, unsigned int); //将消息发送到指定的外部模块
void (*receive)(void *, const void *, QMODULE *, QMODULE *, signed char *); //扫描监听的描述符数组,从fd可读事件中读取所有消息
//receive内部调用epoll_wait时传入的时间参数为0,是非阻塞调用
void *(*open)(void *); //依据形参中cspl_address_t信息创建发送、接收路由
//每一条路由信息实际都对应着一个fd,并加入到epoll的监听数组中
void (*close)(void *); //关闭路由
void *(*hash)(void *, void *);
};

QWAIT
QWAIT结构体用于适配不同的操作系统, 属于OSAL层。使用条件变量+互斥锁,完成driver的睡眠与唤醒操作。

1
2
3
4
5
6
struct QWAIT{
void *(*newchannel)(void); //新建一个条件变量+互斥锁
void (*sleep)(void *, const QTIME *); //driver睡眠指定的时间
void (*wakeup)(void *); //唤醒driver
void *(*walltime)(QTIME *); //获取当前系统启动后的累加时间
};
walltime内部使用clock_gettime(CLOCK_MONOTONIC, &ts)获取系统时间,此时间为单调递增时间,避免系统时间修改后, 对定时器造成影响。
newchannel中创建的条件变量使用函数pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);来避免系统时间修改所造成的影响

QSYSOP
QSYSOP结构体用于适配不同的操作系统, 属于OSAL层。使用linux线程局部存储设施来存放每个线程都具有的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct{
void *(*malloc)(unsigned int);
void (*free)(void *);
void *(*newkey)(void); //pthread_key_create
void *(*getkey)(void *); //pthread_getspecific
void (*setkey)(void *, void *); //pthread_setspecific
void (*destroylock)(void *);
void (*lock)(void *); //pthread_mutex_lock
void (*unlock)(void *); //pthread_mutex_unlock
int (*vprintf)(const char *, va_list);
int (*iprintf)(const char *, va_list);
void (*abort)(void);
}QSYSOP;

2.1.2运行模式

  • 单线程

    • 一个domain(进程)只有一个driver,所有module(协议栈模块)运行在一个driver内

    • driver驱动协议栈运行,同时负责对外通信(收发消息)

      Alt text

  • 多线程

    • 一个domain有多个driver,module(协议栈模块)分散运行在多个driver上

    • domain内有一个主driver负责对外通信(收发消息)

      Alt text

  • 多线程+IndependDriver

    • 一个domain有多个driver,module(协议栈模块)分散运行在多个driver上

    • domain内有一个主driver负责对外通信(收发消息)

    • domain内还有独立的driver可以单独接收外部数据

      Alt text

2.2通信机制

CSPL提供消息循环,各协议模块(module)之间基于消息进行通信:

  1. 内部模块(进程内)之间通过线程调度通信。
    基于链表实现,直接传递消息指针,消息处理支持零拷贝,引用计数等特性。
  2. 与外部模块通信基于OS的IPC机制,比如socket。
    采用钩子注册机制,可以选择UDP、TCP、Linx、消息队列等具体机制。
  3. 对每个外部模块可以单独注册通信函数。
    对每个外部模块可以选择不同的通信方式,比如板内选择消息队列,板间选择UDP socket。

CSPL通讯抽象为driver内通信,driver之间通信,domain(进程)之间通信。

driver有发送消息(hold)和接收消息(queue)优先级队列,qvSchdule运行时,从接收消息队列上取出消息进行处理(qmodule->messageservice), 从发送队列取出消息发送出去(qmodule->dispatch,仅在非立即发送模式下)

2.3消息格式

Alt text

有效载荷长度:loadSize = payload + CSPL_HEADER_SIZE

2.4内存管理

CSPL_AllocMsg:用于申请cspl消息内存结构

  1. 接收cspl消息
    使用此函数申请内存,将接收到的cspl消息(cspl header + payload)打包后, 投递到目的module所对应的driver接收优先级队列中
  2. 发送cspl消息
    在此函数的返回值位置后面构造cspl header、填充消息payload后, 使用CSPL_SendMsg将消息发送到目的module

2.5启动流程及默认模块

Alt text

CSPL_Init初始化时,会创建一个独立的driver,注册到此driver上的模块及功能如下:

  1. monitorEntity模块
    初始化定时器,每隔5s遍历所有driver,获取每个driver上发送、接收队列中所有消息的数目;若有超时的消息,则回调msgTimeoutCallback接口函数;若业务有消息或者定时器消息阻塞,则回调userDeadLoopCallback接口函数;若接收队列消息数目>maxPendRecvMsg,则回调driverMsgOverLoadCallback接口函数;最后回调totalMsgOverLoadCallback接口函数。

  2. debugEntity模块
    调用以DebugInfo结尾的调试函数,并回传执行结果(重定向)到cliDebug进程;全局变量打印及修改(宏和const变量除外)。

  3. haEntity模块
    用于主备切换。

  4. eventEntity模块
    用于实现事件初始化、发布、订阅、分发功能。
    事件客户端、服务器代码运行于此模块中。

2.6版本信息

  • 查看版本号
    1
    2
    strings  libcspl_interface.a | grep V10
    V10R02CA1.B002
  • 查看编译时间
    1
    2
    strings  libcspl_interface.a | grep csplBuildTime
    csplBuildTime: 2017-03-28 14:16:07

3.CSPL组件

3.1事件服务

CSPL提供一种事件服务机制,任何业务都可以作为事件发布者和事件订阅者。

  1. 事件初始化
    业务使用CSPL_EventInit初始化事件,事件服务端记录所有的事件订阅信息。

  2. 事件订阅
    业务使用CSPL_EventSubscribe订阅事件,当事件发布者在某个时候发布此事件后,
    订阅者就会eventEntity模块中回调已注册的回调函数。

  3. 事件发布
    业务使用CSPL_EventPublish发布事件,当事件类型不为进程内类型时,事件会被发送到事件服务器,之后事件服务器再转发此事件到订阅此事件的所有业务进程中。

  4. 事件服务器
    位于lte_log进程中的eventEntity模块内,负责管理事件的订阅、取消订阅、事件转发操作。

心跳机制及订阅恢复机制: 事件服务器每隔3s就会向所有已经注册的事件客户端发送一次心跳事件;若客户端连续超过3次没有收到心跳,重新向事件服务端注册业务地址。当重新注册成功,恢复心跳,则遍历客户端中LocalDatalist链表,将已订阅的事件重新向服务端订阅。(事件客户端、服务器均运行在cspl中的eventEntity模块内)

3.2日志服务

  1. 日志和注册日志;日志信息保存为本地文件,支持日志文件加密,压缩
    CSPL_Log输出普通日志信息,保存到公用的日志文件中,如log.dat
    CSPL_Slog输出的注册日志信息,保存到指定的日志文件中,由cspl_config.xml中registerLogCfg标记配置

  2. 支持设置日志过滤条件
    可按照模块ID、日志级别、进程名称来过滤日志

  3. 实时输出到本地终端、tcp client显示(远端实时显示)
    PC端日志服务器可通过tcp连接到lte_log上,实时获取日志信息,并保存到磁盘上

  4. 自定义类型日志-内存日志
    CSPL_MemLog可自定义日志的消息格式,满足业务特殊的需求,由cspl_config.xml中memLogCfg标记配置

  5. 当日志文件大小超出配置值时,自动创建对应的压缩文件,并控制压缩文件的数量在配置值范围内

  6. 事件服务器目前部署在日志进程内

dataProcess 此线程处理tcp的链接,客户端请求获得日志实时数据
cfgProcess 此线程处理tcp的链接,解析传递过来的命令,实现查询日志记录;查询、设置当前过滤规则;查询、设置实时开关等配置功能
loopProcess 处理CSPL_Log、CSPL_Slog等相关函数产生的日志、注册日志,并依据实时开关配置决定是否实时输出日志信息;保存日志信息到磁盘上
mem_log_proc 处理CSPL_MemLog内存日志信息;保存内存日志到磁盘
log_tarSystemCmd 当日志文件大小大于配置值时,压缩日志文件,并记录压缩文件个数,删除超出maxSaveCounts(压缩保存文件最大个数)之外的压缩文件
cspl默认线程 处理cspl相关的一些操作, 如事件服务器等
clidebug默认线程 处理clidebug相关的一些操作,启动clidebug服务器

3.3cliDebug

clidebug用于在被调试进程运行时调试此进程,获取被调试进程的调试信息、内部运行状态等相关信息;clidebug配置文件为cspl_cliDebug_config.xml

  1. clidebug在连接被调试进程时,被调试进程必须已经在运行状态,必须明确被调试进程函数名称、调试ip、端口信息(若未指定,则使用配置文件中的配置值)
    1
    clidebug -t 127.0.0.1:62001 -p lte_oam
  2. 调用基本命令
    1
    如ps、ls、ifconfig、pwd、cd、ping、quit、file、kill、help 、log_tool等
  3. 历史记录,可调试状态下支持20条命令记录,上下键切换
  4. 全局变量打印及修改(宏和const变量除外)
  5. 调用cspl自身维测接口(尚在开发中)
    1
    cspl_timer、cspl_event等
  6. 调用业务进程通过CSPL_CliDebug_RegCmd自行注册的函数或程序中以DebugInfo结尾的函数
  7. clidebug可以以多实例的方式

3.4黑盒子

主要用于业务进程异常崩溃时记录现场堆栈信息,为定位问题提供分析依据。

支持的异常信号如下:

信号 类型
SIGSEGV 段错误
SIGFPE 运算错误
SIGILL 非法指令
SIGABRT 终止
SIGBUS 总线访问异常,部分CPU在访问未对齐地址报错

输出内容如下:

  • 异常原因(信号名)。
  • 进程标示(进程pid,进程名)
  • 异常发生时间
  • 异常指令地址,异常访问的非法地址(仅对段错误有效)
  • 寄存器数据,寄存器显示寄存器名字,如EAX,EBX等
  • 异常调用栈,以及函数行号(优先使用dladdr 解析,若dladdr 解析失败,则使用addr2line解析行号)
  • 栈中部分数据

3.5守护进程

lte_daemon进程配置文件为cspl_confing.xml, 其功能如下:

  1. 启动所有启动类型不为START_BY_SELF的SYS_APP(如lte_log)、USER_APP(业务)进程

  2. 等待子进程进入运行状态(最长等待时间: 500 ms * 120)
    收集所有进程的CSPL_SIG_RUN_SYNC信号,进而判断进程是否已经进入运行态(APP_RUNNING);并发送CSPL_SIG_RUN_SYNC给所有进程,完成进程状态双向确认。

  3. 进程保活(心跳信号)状态监控
    注册daemonEntity模块监控进程心跳信号,若超出配置3次没有连续接收到心跳信号(CSPL_SIG_KEEPALIVE),则依据restartMode的配置进行相应处理。

  4. 喂狗
    看门狗使能的前提下创建喂狗线程daemon_feedWatchDog(高优先级线程), 每隔2 s进行一次喂狗操作:bsp_watchdog_feed(g_daemonConfig.feedDogInternel);

  5. 监控所有运行的进程
    创建监控线程daemon_monitorApp,执行daemon_WaitApp函数,一旦有业务进程退出,依据restartMode的配置采取动作:

    1. 重启此进程
    2. 重启所有进程
    3. 重启系统
    4. 忽略不做处理。
      记录重启进程的次数,当重启次数 > maxRestartNum时, 强制重启系统

lte_daemon中的信号及日志输出

信号 说明
CSPL_SIG_KEEPALIVE 进程保活心跳信号;
CSPL_SIG_START_SYNC 进程启动同步信号; 若进程配置项startSync打开,则lte_damon在拉起此进程后,会循环检测此进程的startFinishFlag标志是否置位; 被拉起的进程在启动后通过向lte_damon进程发送CSPL_SIG_START_SYNC信号来置位此标志(CSPL_SendStartSyncAck函数)
CSPL_SIG_RUN_SYNC 进程运行同步信号; lte_daemon进程与所拉起的进程在启动后,互相发送此信号,同步双方进程运行状态
CSPL_SIG_DAEMON_REBOOT daemon重启信号; lte_daemon接收到此信号后,调用daemon_reboot()函数重启系统

3.6主备倒换

Opensaf简介: Opensaf是一个高可靠性分布式系统中间件,介于操作系统和用户应用层之间,屏蔽下层操作系统接口,向上提供各种服务。

CSPL集成Opensaf

  1. Amf作为CSPL进程中的一个线程执行,对业务代码不可见。
  2. CSPL只向业务提供有限的对外API和数据结构来实现Amf功能。
  3. CSPL需要重新封装Amf和checkPoint功能,将两者结合起来使用,使用状态机的方式来处理。

基站主备倒换场景
Alt text

主控板通过浮动IP与基带板交互,如上图,主控板的浮动IP为10.11.1.130,开始在CB1上设置,当发生主备倒换,CB1上关闭浮动IP,在CB2上设置浮动IP。相应的OAM会通知基带板的薄平台,基带板上有两个网卡,薄平台需要控制始终与当前主用主控板连接的网卡设置同一个IP地址,比如开始时设置的Eth2网卡IP地址为192.168.1.4,此时Eth3关闭,当收到OAM的切换通知,关闭Eth2,在Eth3上设置IP 192.168.1.4。

3.7常用算法

CSPL中常用算法库位于目录cspl/src/core/ylib/src中:

  1. 位图算法
    位于ylib-bitmap.c文件中;cspl中定时器的超时时间排序使用此算法,用于获取距离当前时间最短的定时器超时时间间隔。

  2. crc算法
    位于ylib-crc.c文件中;8位、16位、32位crc算法

  3. 哈希算法
    位于ylib-hash.c文件中;根据关键码值(Key value)而直接进行访问的数据结构。即通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。

  4. 双链表算法
    位于ylib-list.c文件中;cspl内部大量使用,如在qvars.modules双链表用于遍历此driver下所有的module信息

  5. 单链表算法
    位于ylib-pool.c文件中;cspl中内存池qpool. list单链表用于链接所有qbuf结构体

  6. 优先级队列算法
    位于ylib-squeue.c文件中;cspl中driver中的接收、发送队列中压入、弹出cspl消息时均使用此算法。

  7. 红黑树算法
    位于ylib-tree.c文件中;一种自平衡二叉查找树,典型的用途是实现关联数组,获得较高的查找性能。cspl中QMODULE qvGetService( unsigned long name )函数使用了此算法,通过对qvcontext. services(YTREE)红黑树遍历来获取name所指向的qmodule结构体指针

3.8数据库

基于SQlite数据库。 SQLite是一个嵌入式数据库,和其他嵌入式DB(FastDB, extremeDB) 相比,DML、DDL操作均支持SQL语句,即最终提供给SQLite执行的是一个SQL语句,而不是一个数据结构。

SQlite数据类型:NULL、INTEGER、REAL、TEXT、BLOB
基站数据类型:8/16/32/64位有无符号整型、时间/IP/数组/枚举多选/密码/数据/文本类型、枚举/布尔型

数据库标识:数据库链接
表标识:表名和表ID(映射关系)
字段标识:字段名和字段ID(映射关系)

应用程序和DBS的数据传递一条记录的时候,仅仅包括应用指定的字段值。
包括以下场景:插入,查询,删除,更新。

Alt text

评论