1. syncd应用启动
1.1 syncd应用启动参数
syncd应用启动初始化时可以设置参数,如下图所示:
: enable dialoh shell
-d
: “profile”,指定
-p file
,形如:
sai.profile
: 启动方式,存在以下几种: – 区别????
-t startType
STRING_SAI_START_TYPE_COLD_BOOT
STRING_SAI_START_TYPE_WARM_BOOT
STRING_SAI_START_TYPE_FAST_BOOT
STRING_SAI_START_TYPE_FASTFAST_BOOT
STRING_SAI_START_TYPE_UNKNOWN
: enable useTempView – ?
-u
: disableExistSleep – ?
-S
: enableUnittests
-U
: enableConsistencyCheck – ?
-C
: “syncMode”,表示允许启动同步模式
-s
: “redisCommunicationMode”,表示启用的通信模式,包括
-z mode
、
SAI_REDIS_COMMUNICATION_MODE_REDIS_ASYNC
、
SAI_REDIS_COMMUNICATION_MODE_REDIS_SYNC
SAI_REDIS_COMMUNICATION_MODE_ZMQ_SYNC
: enableSaiBulkSupport – ?
-l
: “globalContext”,指定全局上下文的序号,一般为0,配合
-g index
使用
-x
: “contextConfig”,指定上下文配置文件,形如:
-x file
: 只有在broadcom中配置了,文件内容为:
-b breakConfig
– ?
SAI_OBJECT_TYPE_ACL_TABLE
: “rpcserver”,启用”rpcserver”,只有允许使用thrift机制才会启用,默认不启用
-r
: “port_config.ini”,形如:
-m file
: “help”
-h
最终barefoot的启动命令为:
/usr/bin/syncd -u -l -p /usr/share/sonic/hwsku/sai.profile
不支持热重启没有指定上下文配置不允许同步模式
1.2 syncd应用启动初始化
初始化启动位于
:
syncd_main.cpp
设置syncd应用的日志存放检查syncd应用是否支持热重启 – 不支持解析syncd应用启动时设置的参数
默认信息
设置的参数信息
m_enableTempView = true
m_enableSaiBulkSupport = true
是否配置了port map文件,如果有则解析并保存到全局变量
m_profileMapFile = "/usr/share/sonic/hwsku/sai.profile"
– 没有配置是否启用了rpc server,如果启用了,则创建thrift rpc server – 没有启用创建
gPortMap
对象 – 负责实际调用sdk – 重点初始化
VendorSai
对象,并启用循环监听, – 是syncd应用的重点对象,处理发布/订阅事件,建立与orchagent应用的通信通道
Syncd
1.3
Syncd
对象构造
Syncd
初始化启动位于
,看一下它的构造过程:
syncd.cpp
传入参数:
对象,启动参数
VendorSai
,是否允许热重启
CommandLineOptions
isWarmStart
成员变量赋初值
–
m_commandLineOptions
: 参数传入
CommandLineOptions
–
m_isWarmStart
: 参数传入,
bool
,是通过启动参数配置的
false
:
m_firstInitWasPerformed
false
:
m_asicInitViewMode
false
–
m_vendorSai
: 参数传入
VendorSai
:
m_veryFirstRun
false
:
m_enableSyncMode
false
–
m_timerWatchdog
: 30 * 1000000 – 30s
TimerWatchdog
根据api类型分别设置Log级别 –
SAI_LOG_LEVEL_NOTICE
根据启动参数获取上下文信息,如果参数没有设置,则获取默认的:
{"guid":0,"name":"syncd","dbAsic":"ASIC_DB","dbCounters":"COUNTERS_DB","dbFlex":"FLEX_COUNTER_DB","dbState":"STATE_DB","zmqEnable":"false","zmqEndpoint":"ipc:///tmp/zmq_ep","zmqNtfEndpoint":"ipc:///tmp/zmq_ntf_ep"}
如果上下文中允许使用ZeroMQ机制(
),并且启动参数中允许同步模式(
m_contextConfig->m_zmqEnable=true
),更新启动参数信息:
m_commandLineOptions->m_enableSyncMode=true
m_commandLineOptions->m_enableSyncMode = false;
m_commandLineOptions->m_redisCommunicationMode = SAI_REDIS_COMMUNICATION_MODE_ZMQ_SYNC;
不执行,因为
&
m_contextConfig->m_zmqEnable=false
m_commandLineOptions->m_enableSyncMode=false
如果启动参数中允许同步模式(
),更新配置信息:
m_commandLineOptions->m_enableSyncMode=true
m_enableSyncMode = true;
m_contextConfig->m_zmqEnable = false;
m_commandLineOptions->m_redisCommunicationMode = SAI_REDIS_COMMUNICATION_MODE_REDIS_SYNC;
不执行,因为
m_commandLineOptions->m_enableSyncMode=false
如果启动参数中设置的通信模式是基于ZeroMQ机制的同步模式(
),更新配置信息:
m_commandLineOptions->m_redisCommunicationMode = SAI_REDIS_COMMUNICATION_MODE_ZMQ_SYNC
m_contextConfig->m_zmqEnable = true;
m_enableSyncMode = true;
不执行,因为
,默认配置,配置的是基于redis机制的异步模式。
m_commandLineOptions->m_redisCommunicationMode=SAI_REDIS_COMMUNICATION_MODE_REDIS_ASYNC
上述三个判断说明如果支持ZeroMQ机制,那么将优先使用基于ZeroMQ机制的同步模式进行通信。
初始化成员变量
: 管理
std::shared_ptr<FlexCounterManager> m_manager
中的相关信息,如每个端口关联的计数器/统计信息
COUNTERS_DB
如果启动参数中设置了·
,那么加载配置文件(
-p file
)存入成员变量
sai.profile
– 保存应用启动的配置信息:
m_profileMap
: 0
SAI_VS_GLOBAL_CONTEXT
: NULL
SAI_KEY_VS_CONTEXT_CONFIG
:
SAI_KEY_WARM_BOOT_WRITE_FILE
var/warmboot/sai-warmboot.bin
:
SAI_KEY_WARM_BOOT_READ_FILE
从
/var/warmboot/sai-warmboot.bin
获取信息: 当前设置下就是
sai.profile
以及
SAI_KEY_WARM_BOOT_WRITE_FILE
的默认设置
SAI_KEY_WARM_BOOT_READ_FILE
初始化与
的连接对象
asic_db
判断是否允许启动ZeroMQ机制,初始化成员变量
以及
std::shared_ptr<NotificationProducerBase> m_notifications
:
std::shared_ptr<sairedis::SelectableChannel> m_selectableChannel
允许(
),构建ZeroMQ通信通道:
m_contextConfig->m_zmqEnable=true
使用
子类初始化
ZeroMQNotificationProducer
使用
m_notifications
子类初始化
ZeroMQSelectableChannel
,对应orchagent应用中的
m_selectableChannel
通信
ZeroMQChannel
不允许(
m_enableSyncMode = true;
,构建redis通信通道:
m_contextConfig->m_zmqEnable=false
使用
子类初始化
RedisNotificationProducer
使用
m_notifications
子类初始化
RedisSelectableChannel
,对应orchagent应用中的
m_selectableChannel
通信
RedisChannel
– false
m_enableSyncMode = m_commandLineOptions->m_redisCommunicationMode == SAI_REDIS_COMMUNICATION_MODE_REDIS_SYNC;
: 一个通知发布对象
m_notifications
: 监听来自orchagent应用的事件并且处理,与orchagent应用中的
m_selectableChannel
对应
m_communicationChannel
初始化notification相关成员对象,启动notification处理线程,写入asic_db: switch相关事件通知,比如交换机状态变化、端口状态变化、fdb事件等,有两个来源:
syncd应用启动的时候: 根据配置文件得到热重启能力,然后更新启动类型接收来自orchagent应用的通知: 如果通知中的object类型是
,并且是create或者update操作,需要发布通知,写入asic_db接收来自
SAI_OBJECT_TYPE_SWITCH
的通知并处理
ASIC_DB RESTARTQUERY CHANNEL
初始化
相关表的订阅与监听:
FLEX_COUNTER_DB
: 保存的数据指什么??
FLEX_COUNTER_TABLE
: 保存的数据指什么?
FLEX_COUNTER_GROUP_TABLE
初始化成员对象
,是一个通过asic_db中一个计数器进行虚拟oid管理的对象,属于sonic层面的object id,而不是实际在asic的oid
std::shared_ptr<sairedis::VirtualObjectIdManager> m_virtualObjectIdManager
初始化成员对象
– 管理vid与rid的对应关系:
std::shared_ptr<VirtualOidTranslator> m_translator
vid: 表示虚拟的object id,用于在orchagent以及syncd之间作为参数传递rid: 表示调用sdk真正创建了对象之后得到的object id判断是否是第一次运行,赋值标志位
:
m_veryFirstRun
,存在信息则不是第一次运行
HGETALL HIDDEN
根据当前启动参数判断启动类型,并且更新
对象中的
m_profileMap
信息 –
SAI_BOOT_TYPE
,根据当前设置是冷启动,所以:
performStartupLogic()
–
m_profileMap[SAI_KEY_BOOT_TYPE] = "SAI_START_TYPE_COLD_BOOT";
SAI_START_TYPE_COLD_BOOT = 0
调用
:
vendorSai->initialize()
: – ??
sai_api_initialize()
: –?
sai_metadata_apis_query()
初始化成员
: – ??
std::shared_ptr<BreakConfig> m_breakConfig
启动循环
Syncd::run()
Syncd::run()
初始化热重启table对象:
设置关机类型参数: 初始化设置为冷启动类型设置循环标志位:
STATE_DB:WARM_RESTART_TABLE
初始化
runMainLoop = true
的一个共享指针对象
Select
: 用于管理当前应用进程的事件通知对象
s
: 对switch info的热重启与硬重启处理,目前启动参数配置为不允许热重启,所以会进行一次新的冷启动,创建switch相关信息,更新数据库中的switch、fdb、neighbors等信息 – ???启动notification线程,处理各种通知,写入asic_db – 重点将构造函数中初始化的消费者对象加入
onSyncdStart()
开启循环,
s
首先通过
s->select()
获取内核中的事件通知,然后每一个消费对象读取对应的事件通知,按照时间戳顺序轮询选择消费者对象进行处理,一共存在四个消费者对象:
epoll
处理来自
的
asic_db
的通知:
RESTARTQUERY
处理来自
NotificationConsumer
的
FLEX_COUNTER_DB
的通知:
FLEX_COUNTER_TABLE
处理来自
ConsumerTable
的
FLEX_COUNTER_DB
的通知:
FLEX_COUNTER_GROUP_TABLE
处理来自
ConsumerTable
应用通过
orchagent
对
asic_db
应用的通知 – 重点 如果循环标志位被置为
syncd
,那么syncd应用也结束了:
false
如果是热重启,将结束信息以及一些标志位写入
的
state_db
中清空计数器移除switch实例 – ???结束通知线程取消sai初始化 –
WARM_RESTART_TABLE
VendorSai::uninitialize()
2 事件通知预处理
2.1 通知消息读取
上述过程是读取内核中的事件通知过程:
epoll_wait查询当前存在的连接是否有events遍历得到的fd,针对每个fd:
调用
读取内核中events – 具体怎么处理,看用的是哪一个子类将fd存入一个有序集合
Selectable::readData()
– 该集合表示当前已经获取到events的连接,按照连接上次使用的时间戳进行排序,时间戳小排在前面 – 即处理等待时间长的events 遍历该有序集合
m_ready
:获取集合最前面的订阅对象
m_ready
从集合中删除,并且更新时间戳如果该订阅对象没有events,继续循环如果该订阅对象有events,返回该对象作为待处理的events对象;如果不只一条event,重新将该fd插入到有序集合中更新订阅对象中待处理的消息数量,退出循环
在添加消费者的过程中实际上存在两类消费者,一类是
,一类是
NotificationConsumer
,对于事件监听与读取的过程不完全相同。
ConsumerTable
事件通知读取 – readData():
首先
会判断当前是否有事件通知,如果有,针对有事件通知的每一个消费者对象从内核中读取事件通知:
s->select()
如果是
,此处与orchagent应用不同,因为这里订阅使用的是redis的list机制,实际上通知详细信息是存储在list中,而发布的通知只是针对对应频道发布了一个空的事件通知。所以这里只是读取了事件通知的数量,以便判断当前消费者对象是否存在需要处理的事件通知:
ConsumerTable
如果是
PUBLISH ASIC_STATE_CHANNEL G
,将直接读取通知消息存储到成员
NotificationConsumer
队列中
m_queue
2.2 事件通知预处理 – pop()
以单个事件处理为例。首先分析一下从队列中取出一个事件的过程:
事件通知预处理 – pop():
如果是
,以acl为例,成员
ConsumerTable
是待处理的事件通知队列,如果为空,则首先访问redis数据库获取详细的事件通知 –
m_buffer
:
ConsumerTable::pops()
使用
命令调用
EVALSHA
脚本: 当前配置是读取redis的
consumer_table_pops.lua
队列的事件通知(与RedisRemoteSaiInterface章节中对应),然后将信息写入
ASIC_STATE_KEY_VALUE_OP_QUEUE
的
ASIC_DB
表中,记录的
ASIC_STATE
值形如
key
然后解析事件通知,每一个事件通知格式为
ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE:oid:0x%
,存储为一个
<key,op,values>
– 存储的是序列化的值如果选择了这个消费者对象,从
vector
中取出事件通知进行处理 如果是
m_buffer
: 如果选择了这个消费者对象,从
NotificationConsumer
中取出事件通知进行处理
m_queue
上图是事件通知的通用处理操作:
从key值反序列出对应的object type以及object id:
:
key
反序列化其实就是拆解字符串:
SAI_OBJECT_TYPE_ACL_TABLE:oid:0x%
{"object type":"SAI_OBJECT_TYPE_ACL_TABLE", "oid":"oid:0x%"}
判断object type是否有效
构造属性列表对象,主要是检查属性的有效性,以及针对属性value的反序列化
如果事件类型是
,需要更新每个通知信息对应的callback函数
SAI_OBJECT_TYPE_SWITCH
isInitViewMode(): 当前配置信息是false,所以不执行
如果不是get操作,需要将所有属性的vid转换为rid
调用sdk库获取object type对应的meta信息
根据meta信息判断调用哪一个处理方法,以acl为例,调用
处理事件通知,写入asic: 根据不同的操作继续向下调用不同的处理方法,每个操作对于不同object type的对象处理方法是同样的 – 在下一个小节”acl事件处理”中会进行详细介绍
Syncd::processOid()
sai_status_t processOid(
_In_ sai_object_type_t objectType,
_In_ const std::string &strObjectId,
_In_ sai_common_api_t api,
_In_ uint32_t attr_count,
_In_ sai_attribute_t *attr_list);
: 对象类型,如
objectType
SAI_OBJECT_TYPE_ACL_TABLE
: 如
strObjectId
oid:0x%
: 操作类型,如
api
SAI_COMMON_API_CREATE
: 对象的属性个数
attr_count
: 对象的属性信息列表
attr_list
当前操作如果是GET操作,需要向asic_db写入响应;如果是其他操作,如果是同步模式则需要写入响应,如果是异步模式,则直接返回成功 – 响应的写入与读取也是基于redis的订阅/发布机制,不再赘述
3 acl事件处理
在处理各种操作之前,调用sdk库获取object type对应的meta信息 –
。
info
3.1 CREATE
调用
方法处理
processOidCreate()
操作:
create
sai_status_t processOidCreate(
_In_ sai_object_type_t objectType,
_In_ const std::string &strObjectId,
_In_ uint32_t attr_count,
_In_ sai_attribute_t *attr_list);
主要步骤只有两步:
调用
调用sai接口的具体实现如果创建成功,会得到rid,将vid与rid的对应关系写入asic_db
VendorSai::create()
主要看一下每一个方法向下调用过程中的参数变化与处理:
VendorSai::create()
virtual sai_status_t create(
_In_ sai_object_type_t objectType,
_Out_ sai_object_id_t* objectId,
_In_ sai_object_id_t switchId,
_In_ uint32_t attr_count,
_In_ const sai_attribute_t *attr_list)
: 对象类型,如
objectType
SAI_OBJECT_TYPE_ACL_TABLE
: 传递指针,将创建成功以后的oid赋值给该参数
objectId
switchId
: 对象的属性个数
attr_count
: 对象的属性信息列表
attr_list
处理:
调用sdk库获取object type对应的meta信息
,包括类型的基本属性信息、sai对应的crud方法信息等确认
info
中存在
info
方法调用
create
得到
info->create()
赋值给参数
object id
objectId
info->create()
最终调用的是sdk中
中的
saimetadata.c
方法:
sai_metadata_generic_create_SAI_OBJECT_TYPE_ACL_TABLE()
sai_status_t sai_metadata_generic_create_SAI_OBJECT_TYPE_ACL_TABLE(
_Inout_ sai_object_meta_key_t *meta_key,
_In_ sai_object_id_t switch_id,
_In_ uint32_t attr_count,
_In_ const sai_attribute_t *attr_list)
{
return sai_metadata_sai_acl_api->create_acl_table(&meta_key->objectkey.key.object_id, switch_id, attr_count, attr_list);
}
:
meta_key
– 最终
{ .objecttype = objectType, .objectkey = { .key = { .object_id = 0 } } }
保存创建完成以后的oid
.objectkey = { .key = { .object_id = 0 } }
switchId
: 对象的属性个数
attr_count
: 对象的属性信息列表
attr_list
然后与orchagent应用中调用sairedis api的过程类似,
是一个全局变量,通过
sai_metadata_sai_acl_api
&
sai_api_initialize()
方法赋值,最终赋值为
sai_api_query()
中的全局变量
saiacl.app
。所以上述最终调用的方法是:
acl_api
sai_status_t sai_create_acl_table(_Out_ sai_object_id_t *acl_table_id,
_In_ sai_object_id_t switch_id,
_In_ uint32_t attr_count,
_In_ const sai_attribute_t *attr_list)
其中
: 是
acl_table_id
中
meta_key
的引用,有以下几个步骤:
.objectkey
检查属性列表与table类型是否匹配将属性列表信息转化为符合switch store的对应属性信息最后调用
方法
bf_switch_object_create()
bf_switch_object_create()
switch_status_t bf_switch_object_create(const switch_object_type_t object_type,
const std::set<smi::attr_w> &attrs,
switch_object_id_t &object_handle) {
return smi::api::smi_object_create(object_type, attrs, object_handle);
}
: 如
object_type
SAI_OBJECT_TYPE_ACL_TABLE
: 转换之后的属性列表
attrs
: 将得到的oid赋值给该参数
object_handle
smi::api::smi_object_create()
switch_status_t smi_object_create(const switch_object_type_t object_type,
const std::set<attr_w> &attrs,
switch_object_id_t &object_handle);
检查当前类型的table是否支持这些属性调用
创建table
switch_store::object_create()
– 写入switch store,也是写入asic的最终执行
switch_store::object_create()
switch_status_t object_create(const switch_object_type_t object_type,
const std::set<attr_w> &attrs,
switch_object_id_t &object_id);
检查object type以及属性信息 – 比上一步骤中的检查更底层一点??检查triggers,如果存在create之前执行的trigger,执行
: 先分配object id
oid_create()
: 参数为object id,在switch store中创建这个hash值将属性信息写入switch store对应hash值中
db::object_create()
: – ???检查triggers,如果存在create之后执行的trigger,执行
create_keys()
: – ??
create_membership()
未完待续…