5.1 概述
Sysrepo的每一类订阅(*_subscribe)调用都会返回一个相应的订阅的结构。用户可以对一个已知订阅进行订阅,然后将多个订阅事件由单个订阅做处理。Sysrepo在默认情况下,是由一个专门的线程订阅结构中的所有事件,所以在使用sysrepo时,无需做进一步处理。当然,如果不想使用默认处理,需要做一些特殊处理,可以在订阅时的ops选项中增加标志SR_SUBSCR_NO_THREAD。请参考sysrepo.h中结构sr_subscr_flag_e的定义。
5.2 change订阅
Change订阅是sysrepo中最常用的】的订阅,用户可以对module变化的订阅,可以对container变化的订阅,也可以对leaf,leaf-list变化的订阅,订阅的粒度由用户自己定义。不管是何种变化的订阅,该版本sysrepo的chage订阅都统一调任sr_module_change_subscribe做订阅的处理。
/*函数功能:在指定的module中订阅发生的change,如果在多个不同的Modul存在多个changes时,module响
* 应changes的顺序是由所保持的优先级的顺序。
*参数说明:
* sr_session_ctx_t *session:
* const char *module_name:订阅感兴趣的模块
* const char *xpath: 订阅模块下的更深的节点路径。
* sr_module_change_cb callback:数据发生变化时需要执行的回调函数
* void *private_data:
* uint32_t priority:执行callback时的优先级,值越低,优先级越高
* sr_subscr_options_t opts: 订阅的可选参数,用来改写默认的订阅行为
* sr_subscription_ctx_t **subscription: 订阅的上下文,在取消订阅时,由
* sr_unsubscribe释放
*
*/
API int
sr_module_change_subscribe(sr_session_ctx_t *session,
const char *module_name,
const char *xpath,
sr_module_change_cb callback,
void *private_data,
uint32_t priority, sr_subscr_options_t opts,
sr_subscription_ctx_t **subscription)
{
sr_error_info_t *err_info = NULL;
const struct lys_module *ly_mod;
sr_conn_ctx_t *conn;
sr_subscr_options_t sub_opts;
sr_mod_change_sub_t *shm_sub;
sr_mod_t *shm_mod;
uint16_t i;
/*参数有效性检测,在API入口做参数检测,从开始就保证参数在合理的范围*/
SR_CHECK_ARG_APIRET(!session || !module_name || !callback
|| ((opts & SR_SUBSCR_PASSIVE) && (opts & SR_SUBSCR_ENABLED)) || !subscription, session, err_info);
if ((opts & SR_SUBSCR_CTX_REUSE) && !*subscription) {
/* invalid option, remove */
opts &= ~SR_SUBSCR_CTX_REUSE;
}
conn = session->conn;
/* only these options are relevant outside this function and will be stored */
sub_opts = opts & (SR_SUBSCR_DONE_ONLY | SR_SUBSCR_PASSIVE | SR_SUBSCR_UPDATE | SR_SUBSCR_UNLOCKED);
/* is the module name valid? */
/*通过yang的上下文判断module_name是否有效,在sysrepoctl安装时,会将每个module的name
* 保存在连接的上下文,如果无效,直接返回。*/
ly_mod = ly_ctx_get_module(conn->ly_ctx, module_name, NULL, 1);
if (!ly_mod) {
sr_errinfo_new(&err_info, SR_ERR_NOT_FOUND, NULL, "Module \"%s\" was not found in sysrepo.", module_name);
return sr_api_ret(session, err_info);
}
/* check write/read perm */
/*检查module对应的startup库的读/写权限*/
if ((err_info = sr_perm_check(module_name, (opts & SR_SUBSCR_PASSIVE) ? 0 : 1))) {
return sr_api_ret(session, err_info);
}
/* call the callback with the current running configuration so that it is properly applied */
if ((session->ds == SR_DS_RUNNING) && (opts & SR_SUBSCR_ENABLED)) {
/* SHM LOCK (accessing ext SHM) */
if ((err_info = sr_shmmain_lock_remap(conn, SR_LOCK_NONE, 0, 0, __func__))) {
return sr_api_ret(session, err_info);
}
/* do not hold write lock here, would block callback from calling API functions */
err_info = sr_module_change_subscribe_running_enable(session, ly_mod, xpath, callback, private_data, opts);
/* SHM UNLOCK */
sr_shmmain_unlock(conn, SR_LOCK_NONE, 0, 0, __func__);
if (err_info) {
return sr_api_ret(session, err_info);
}
}
/* SHM LOCK (modifying subscriptions) */
if ((err_info = sr_shmmain_lock_remap(conn, SR_LOCK_WRITE, 1, 0, __func__))) {
return sr_api_ret(session, err_info);
}
/* find module */
shm_mod = sr_shmmain_find_module(&conn->main_shm, conn->ext_shm.addr, module_name, 0);
SR_CHECK_INT_GOTO(!shm_mod, err_info, error_unlock);
if (opts & SR_SUBSCR_UPDATE) {
/* check that there is not already an update subscription with the same priority */
shm_sub = (sr_mod_change_sub_t *)(conn->ext_shm.addr + shm_mod->change_sub[session->ds].subs);
for (i = 0; i < shm_mod->change_sub[session->ds].sub_count; ++i) {
if ((shm_sub[i].opts & SR_SUBSCR_UPDATE) && (shm_sub[i].priority == priority)) {
sr_errinfo_new(&err_info, SR_ERR_INVAL_ARG, NULL, "There already is an \"update\" subscription on"
" module \"%s\" with priority %u for %s DS.", module_name, priority, sr_ds2str(session->ds));
goto error_unlock;
}
}
}
/*创建一个新的子订阅,这处是一个核心的实现,是在sysrepo中创建监听事件变化的线程
*是全部订阅监听事件变化的入口吧,其整个流程可看一下,是怎么处理不同事件的。*/
if (!(opts & SR_SUBSCR_CTX_REUSE)) {
/* create a new subscription */
if ((err_info = sr_subs_new(conn, opts, subscription))) {
goto error_unlock;
}
}
/* add module subscription into main SHM */
/*添加模块的子订阅到共享内存中,先移动已存在的订阅,后添加新的订阅。这个移动主原是共享内存的映射上,原订阅占的地址+新订阅所需要的地址空间,需要一起重新映射在ext共享内存段后,再将原订阅数据先复制,后添加新的订阅在共享内存中。详细的可以阅读源码实现*/
if ((err_info = sr_shmmod_change_subscription_add(&conn->ext_shm, shm_mod, xpath, session->ds, priority, sub_opts,
(*subscription)->evpipe_num))) {
goto error_unlock_unsub;
}
/* add subscription into structure and create separate specific SHM segment */
/* sr_sub_change_add非常有意思,就是change的cb 挂到session结构的change module块,并为该订阅创建一个独立的SHM内存段*/
if ((err_info = sr_sub_change_add(session, module_name, xpath, callback, private_data, priority, sub_opts,
*subscription))) {
goto error_unlock_unsub_unmod;
}
/* add the subscription into session */
/* 将已处理好的子订阅添加到session中,监听线程可以按事件处理不同的订阅变化*/
if ((err_info = sr_ptr_add(&session->ptr_lock, (void ***)&session->subscriptions, &session->subscription_count,
*subscription))) {
goto error_unlock_unsub_unmod;
}
/* SHM UNLOCK */
sr_shmmain_unlock(conn, SR_LOCK_WRITE, 1, 0, __func__);
return sr_api_ret(session, NULL);
error_unlock_unsub_unmod:
sr_shmmod_change_subscription_del(conn->ext_shm.addr, shm_mod, xpath, session->ds, priority, sub_opts,
(*subscription)->evpipe_num, 0, NULL);
error_unlock_unsub:
if (opts & SR_SUBSCR_CTX_REUSE) {
sr_sub_change_del(module_name, xpath, session->ds, callback, private_data, priority, sub_opts, *subscription);
} else {
_sr_unsubscribe(*subscription);
*subscription = NULL;
}
error_unlock:
/* SHM UNLOCK */
sr_shmmain_unlock(conn, SR_LOCK_WRITE, 1, 0, __func__);
return sr_api_ret(session, err_info);
}
看完之后,整个代码的实现逻辑很简单,注释也挺丰富的,没必要再说详细说。
核心的两个实现是sr_subs_new(),sr_shmmod_change_subscription_add(),其它三类订阅也有相同与类似的实现。
5.3 RPC订阅
所有的功能都可以使用RPC机制。可以这么理解,netconf2协议的本质也是RPC机制。如果没有相应订阅匹配匹配,将不会发送相应的RPC请求。假如一个RPC事件提供了输入,不管操作成功或者失败,相应的RPC回调都将按预期执行,并向原始的发送者返回相应的输出结果。当然,也可以通过YANG文件中定义相应的xpath增加相应的过滤,可以获取指定路径的输出结果,该方式在实际中最常用的方式。但是,需要注意的时,因为订阅的本质是机制,所以,就有可能多个订阅匹配了同一个RPC,(不管在老版本还是新版本都不可避免)。对于多个订阅同时匹配时,就需要通过优先级来解决这问题,其它的原则是,优先级值越低,越先调用。这个原则,在change订阅的重要性就不言而唯喻。
syrepo中提供两个相关的RPC接口,一个是sr_rpc_subscribe(),另一个是sr_rpc_subscribe_tree(),这两个接口都共同调用_sr_rpc_subscribe()。差别在回调上的不同。
typedef int (*sr_rpc_cb)
(sr_session_ctx_t *session,
const char *op_path,
const sr_val_t *input,
const size_t input_cnt,
sr_event_t event,
uint32_t request_id,
sr_val_t **output,
size_t *output_cnt,
void *private_data);
typedef int (*sr_rpc_tree_cb)
(sr_session_ctx_t *session,
const char *op_path,
const struct lyd_node *input,
sr_event_t event,
uint32_t request_id,
struct lyd_node *output,
void *private_data);
从两个回调味的参数不同就能发现两个rpc处理的不同,sr_rpc_cb输入/输出是对应数组类型数据的处理sr_rpc_tree_cb输入/输出是对应libyang tree型数据的处理。处理的数据类型不一样。
static int
_sr_rpc_subscribe(sr_session_ctx_t *session,
const char *xpath,
sr_rpc_cb callback,
sr_rpc_tree_cb tree_callback,
void *private_data,
uint32_t priority,
sr_subscr_options_t opts,
sr_subscription_ctx_t **subscription)
{
....
/*_sr_rpc_subscribe函数的处理逻辑与sr_module_change_subscribe()的处理逻辑相似,只是订阅的CB挂载处不同,就不一一细说。*/
}
5.4 Notification 订阅
Notification订阅是支持订阅既发送请求又可以接收数据的一类特殊的订阅方式。为了后续可能的replay ,系统会存储已发送的notification请求。
当用户使用了Notification订阅,有几个可选项需要专门指定并且这些个可选项是遵循NETCONF Notification RFC的定义。一般情况下,全部的Notification 都是实时响应的。如果是replay 请求,这些Notification都是replay。如果replay请求完成并且该订阅切换成标准的Notification订阅,则会发送replay完成通知。最后,一旦停止时间到而导致Notification被终止后,在删除Notification将停止接收返回。
Notification提供了两类数据4个接口,一类是面向sr_val_s结构数据型的sr_event_notif_subscribe()和sr_event_notif_send(),另一类是面向libyang tree型数据sr_event_notif_subscribe_tree()和sr_event_notif_send_tree();这两可以按使用场景来做区分使用。
API int
sr_event_notif_subscribe(
sr_session_ctx_t *session,
const char *module_name,
const char *xpath,
time_t start_time,
time_t stop_time,
sr_event_notif_cb callback,
void *private_data,
sr_subscr_options_t opts,
sr_subscription_ctx_t **subscription) {
}
API int
sr_event_notif_subscribe_tree(
sr_session_ctx_t *session,
const char *module_name,
const char *xpath,
time_t start_time,
time_t stop_time,
sr_event_notif_tree_cb callback,
void *private_data,
sr_subscr_options_t opts,
sr_subscription_ctx_t **subscription) {
}
这两个接口都统一调用
static int
_sr_event_notif_subscribe(
sr_session_ctx_t *session,
const char *mod_name,
const char *xpath,
time_t start_time,
time_t stop_time,
sr_event_notif_cb callback,
sr_event_notif_tree_cb tree_callback,
void *private_data,
sr_subscr_options_t opts,
sr_subscription_ctx_t **subscription)
添加订阅的逻辑与change,rpc类似,只是订阅的挂载的数据结构点不同,没有过多的差异,感兴趣的话,可以阅读源码的实现.
/*接口功能:发送一个notification,数据类型为sr_val_t结构。如果一个session中有多个notifications,大于100个,全部的的notifications都将先保存等待回应,并且要求的库有写的权限.*/
int sr_event_notif_send(
sr_session_ctx_t *session,
const char *path,
const sr_val_t *values,
const size_t values_cnt) {
//临时变量定义
//参数安全检查
//函数的核心,将sr_val_t结构转成libyang支持的lyd_node后,并调用sr_event_notif_send_tree()函数发送notification。
/* transform values into a data tree */
for (i = 0; i < values_cnt; ++i) {
val_str = sr_val_sr2ly_str(session->conn->ly_ctx, &values[i], values[i].xpath, buf);
if ((err_info = sr_val_sr2ly(session->conn->ly_ctx, values[i].xpath, val_str, values[i].dflt, 0, ¬if_tree))) {
goto cleanup;
}
}
/* API function */
if ((ret = sr_event_notif_send_tree(session, notif_tree)) != SR_ERR_OK) {
lyd_free_withsiblings(notif_tree);
return ret;
}
}
所以,发送notification的核心实现,是sr_event_notif_send_tree()函数。函数源码如下:
/*接口功能:发送一个notification,数据类型为libyang treee结构。如果一个session中有多个notifications,大于100个,全部的的notifications都将先保存等待回应,并且要求的库有写的权限.*/
int sr_event_notif_send_tree(sr_session_ctx_t *session, struct lyd_node *notif)
{
sr_error_info_t *err_info = NULL, *cb_err_info = NULL, *tmp_err_info = NULL;
struct sr_mod_info_s mod_info;
struct lyd_node *notif_op;
sr_mod_data_dep_t *shm_deps;
sr_mod_t *shm_mod;
time_t notif_ts;
uint16_t shm_dep_count;
sr_mod_notif_sub_t *notif_subs;
uint32_t notif_sub_count;
char *xpath = NULL;
/*参数的安全检查与结构mod_info的初始化*/
SR_CHECK_ARG_APIRET(!session || !notif, session, err_info);
if (session->conn->ly_ctx != notif->schema->module->ctx) {
sr_errinfo_new(&err_info, SR_ERR_INVAL_ARG, NULL, "Data trees must be created using the session connection libyang context.");
return sr_api_ret(session, err_info);
}
memset(&mod_info, 0, sizeof mod_info);
/* remember when the notification was generated */
//记录notification的产生时间
notif_ts = time(NULL);
/* check notif data tree */
/*通过节点类型检查是调度节点是否是LYS_NOTIF型节点,不是LYS_NOTIF直接返回.*/
switch (notif->schema->nodetype) {
case LYS_NOTIF:
for (notif_op = notif; notif->parent; notif = notif->parent);
break;
case LYS_CONTAINER:
case LYS_LIST:
/* find the notification */
notif_op = notif;
if ((err_info = sr_ly_find_last_parent(¬if_op, LYS_NOTIF))) {
return sr_api_ret(session, err_info);
}
if (notif_op->schema->nodetype == LYS_NOTIF) {
break;
}
/* fallthrough */
default:
sr_errinfo_new(&err_info, SR_ERR_INVAL_ARG, NULL, "Provided tree is not a valid notification invocation.");
return sr_api_ret(session, err_info);
}
/* SHM LOCK (accessing subscriptions) */
if ((err_info = sr_shmmain_lock_remap(session->conn, SR_LOCK_READ, 0, 0, __func__))) {
return sr_api_ret(session, err_info);
}
/* check write/read perm */
/*确认相应的库是存在的并且对应库的startup是有读/写权限*/
shm_mod = sr_shmmain_find_module(&session->conn->main_shm, session->conn->ext_shm.addr, lyd_node_module(notif)->name, 0);
SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup_shm_unlock);
if ((err_info = sr_perm_check(lyd_node_module(notif)->name, (shm_mod->flags & SR_MOD_REPLAY_SUPPORT) ? 1 : 0))) {
goto cleanup_shm_unlock;
}
/* collect all required modules for validation
*(including checking that the nested notification
* can be invoked meaning its parent data node exists) */
xpath = lys_data_path(notif_op->schema);
SR_CHECK_MEM_GOTO(!xpath, err_info, cleanup_shm_unlock);
if ((err_info = sr_shmmod_collect_op(session->conn, xpath, notif_op, 0, &shm_deps, &shm_dep_count, &mod_info))) {
goto cleanup_shm_unlock;
}
/* MODULES READ LOCK */
if ((err_info = sr_shmmod_modinfo_rdlock(&mod_info, 0, session->sid))) {
goto cleanup_mods_unlock;
}
/* load all input dependency modules data */
if ((err_info = sr_modinfo_data_load(&mod_info, MOD_INFO_TYPE_MASK, 1, &session->sid, NULL, SR_OPER_CB_TIMEOUT, 0,
&cb_err_info)) || cb_err_info) {
goto cleanup_mods_unlock;
}
/* validate the operation */
if ((err_info = sr_modinfo_op_validate(&mod_info, notif_op, shm_deps, shm_dep_count, 0, &session->sid,
SR_OPER_CB_TIMEOUT, &cb_err_info))) {
goto cleanup_mods_unlock;
}
if (cb_err_info) {
goto cleanup_mods_unlock;
}
/* MODULES UNLOCK */
sr_shmmod_modinfo_unlock(&mod_info, 0);
/* store the notification for a replay, we continue on failure */
/*将notification为了replay做保存*/
err_info = sr_replay_store(session, notif, notif_ts);
/* check that there is a subscriber */
/* 检查要发送的notification是存在相应的子订阅,如果没有,直接返回*/
if ((tmp_err_info = sr_notif_find_subscriber(session->conn, lyd_node_module(notif)->name, ¬if_subs, ¬if_sub_count))) {
goto cleanup_shm_unlock;
}
if (notif_sub_count) {
/* publish notif in an event, do not wait for subscribers */
if ((tmp_err_info = sr_shmsub_notif_notify(notif, notif_ts, session->sid, (uint32_t *)notif_subs, notif_sub_count))) {
goto cleanup_shm_unlock;
}
} else {
SR_LOG_INF("There are no subscribers for \"%s\" notifications.", lyd_node_module(notif)->name);
}
/* success */
goto cleanup_shm_unlock;
cleanup_mods_unlock:
/* MODULES UNLOCK */
sr_shmmod_modinfo_unlock(&mod_info, 0);
cleanup_shm_unlock:
/* SHM UNLOCK */
sr_shmmain_unlock(session->conn, SR_LOCK_READ, 0, 0, __func__);
free(xpath);
sr_modinfo_free(&mod_info);
if (tmp_err_info) {
sr_errinfo_merge(&err_info, tmp_err_info);
}
if (cb_err_info) {
/* return callback error if some was generated */
sr_errinfo_merge(&err_info, cb_err_info);
err_info->err_code = SR_ERR_CALLBACK_FAILED;
}
return sr_api_ret(session, err_info);
}
通过源码可以得出,不需要replay,操作较简单,将需要的Notification通过订阅可以实现,需要replay的需要在订阅的基础上增加send的调用。
5.5 Operational 订阅
OP订阅是为了向客户端提供数据,通常使场景是,用于获取当前设备的状态,可以返回设备的状态节点数据 。也可以用于配置的数据节点,获取相应节点的实际配置信息。
Operational 回调不会被调用除非有相应的数据调用请求。在使用OP回调时需要注意下面几点:
第一:如果你提供父节点的数据不存在时,那么其子节点也将不存在。对于这点从相反的角度就很好理解,例如,我们的订阅路径为:/ietf-interfaces:interfaces/interface-list/state-leaf,那么,回调将对每一个存在的实例/ietf-interfaces:interfaces/interface-list/都起作用。
第二:如果有嵌套回调,则较深的的订阅称为最后一个订阅,这是因为父节点都是较子节点优先创建出来的。在实际使用中,这个可以这么理解,我们订阅了/ietf-interfaces:interfaces/interface-list/和/ietf-interfaces:interfaces/interface-list/state-leaf两个订阅,那么前一个订阅的回调会先于后一个回调。而不必管interface-list的订阅是什么时,state-leaf都是interface-list叶子节点。
第三:如果使用了filter过滤器,但是没有选择OP回调所提供的数据,该回调味通常情况下是不会调用的。这就执行上下文的检查,而大量减小了需要覆盖的全部cass的检查。对于这点,可以这样理解,如果对/ietf-interfaces:interfaces/interface-list[name = "eth0"]执行了订阅,但是发的请求是/ietf-interfaces:interfaces/interface-list/*,那么定义的订阅的回调就不会执行。同样也不会执行/ietf-interfaces:interfaces/interface-list[name = "eth1"]的请求。
以上是执行op 订阅时需要的内存实现逻辑,具体的一点,可以参阅sr_shmsub_oper_listen_process_module_events()函数的实现。
/*函数功能:在对给定的xpath提供operational数据回调的注册
*参数说明:
* sr_session_ctx_t *session:
* const char *module_name:订阅感兴趣的模块
* const char *xpath: 订阅模块下的更深的节点路径。
* sr_module_change_cb callback:数据发生变化时需要执行的回调函数
* void *private_data:传给回调函数的私有数据,对sysrepo不透明
* sr_subscr_options_t opts: 订阅的可选参数,用来改写默认的订阅行为
* sr_subscription_ctx_t **subscription: 订阅的上下文,在取消订阅时,由
* sr_unsubscribe释放
*
*/
API int
sr_oper_get_items_subscribe(
sr_session_ctx_t *session,
const char *module_name,
const char *path,
sr_oper_get_items_cb callback,
void *private_data,
sr_subscr_options_t opts,
sr_subscription_ctx_t **subscription)
oper订阅的注册整体流程与change注册相似,唯一的不同点在于回调挂载点不同,oper是调用sr_sub_oper_add将CB挂载于sr_subscription_ctx_s->oper_subs[i]处。
5.6 订阅的处理流程
通过阅读4类订阅的源码,整个sysrepo的处理核心入口在函数是sr_subs_new()函数,这个函数应该是各类订阅的开始,也在函数中创建sr_shmsub_listen_thread的listen监听线路。sr_subs_new()函数在 change,rpc notify operational 4订阅API中都会也直接调用。请看源码:
执行的逻辑如下
if (!(opts & SR_SUBSCR_CTX_REUSE)) {
/* create a new subscription */
if ((err_info = sr_subs_new(conn, opts, subscription))) {
goto cleanup_unlock;
}
}
sr_error_info_t * sr_subs_new(sr_conn_ctx_t *conn, sr_subscr_options_t opts,
sr_subscription_ctx_t **subs_p)
{
sr_error_info_t *err_info = NULL;
sr_main_shm_t *main_shm;
char *path = NULL;
int ret;
/* allocate new subscription */
/*分配一个新的订阅,并做相应的初始化*/
*subs_p = calloc(1, sizeof **subs_p);
SR_CHECK_MEM_RET(!*subs_p, err_info);
sr_mutex_init(&(*subs_p)->subs_lock, 0);
(*subs_p)->conn = conn;
(*subs_p)->evpipe = -1;
/* get new event pipe number and increment it */
main_shm = (sr_main_shm_t *)(*subs_p)->conn->main_shm.addr;
(*subs_p)->evpipe_num = ATOMIC_INC_RELAXED(main_shm->new_evpipe_num);
if ((*subs_p)->evpipe_num == (uint32_t)(ATOMIC_T_MAX - 1)) {
/* the value in the main SHM is actually ATOMIC_T_MAX and calling another INC would cause an overflow */
ATOMIC_STORE_RELAXED(main_shm->new_evpipe_num, 1);
}
/* get event pipe name */
/* 获取管道名,管道通过编译宏SR_REPO_PATH_ENV定义,这个路径在编译时指定*/
if ((err_info = sr_path_evpipe((*subs_p)->evpipe_num, &path))) {
goto error;
}
/* create the event pipe */
/ *通过path,指指定管道文件的权限创建管道*/
if (mkfifo(path, SR_EVPIPE_PERM) == -1) {
SR_ERRINFO_SYSERRNO(&err_info, "mkfifo");
goto error;
}
/* open it for reading AND writing (just so that there always is a "writer", otherwise it is always ready
* for reading by select() but returns just EOF on read) */
(*subs_p)->evpipe = open(path, O_RDWR | O_NONBLOCK);
if ((*subs_p)->evpipe == -1) {
SR_ERRINFO_SYSERRNO(&err_info, "open");
goto error;
}
/* add the new subscription into main SHM state */
/*将新订阅是添加到main shm 状态机机*/
if ((err_info = sr_shmmain_state_add_evpipe(conn, (*subs_p)->evpipe_num))) {
goto error;
}
/*创建监听线程,标志SR_SUBSCR_NO_THREAD:处理该订阅将不会创建监听线程。*/
if (!(opts & SR_SUBSCR_NO_THREAD)) {
/* set thread_running to non-zero so that thread does not immediately quit */
ATOMIC_STORE_RELAXED((*subs_p)->thread_running, 1);
/* start the listen thread */
ret = pthread_create(&(*subs_p)->tid, NULL, sr_shmsub_listen_thread, *subs_p);
if (ret) {
sr_errinfo_new(&err_info, SR_ERR_INTERNAL, NULL, "Creating a new thread failed (%s).", strerror(ret));
goto error;
}
}
free(path);
return NULL;
error:
free(path);
if ((*subs_p)->evpipe > -1) {
close((*subs_p)->evpipe);
}
free(*subs_p);
return err_info;
}
listen线程创建后,核心处理流程如下下图所示,完成netconf各类事件的响应,分析事件的变化,调用相应挂载的订阅函数。
Sysrepo是如何影响上层客户端各类事件(edit,get rpc,notify),sysrepo又是如何处理的这些事件的,主要就是集中于上面4个事件函数的响应有,留着有时间在后面说吧。
PS:个人觉得sysrepo这套实现机制很好,代码非常清晰,结构简单,注释又详尽,是不可多得的好代码。但是个人时间毕竟有限,最近又不没研究为这块,词不达意,纠结呀,所以不敢发出来。
希望后面能和大家一起探讨,把sysrepo机制能彻底吃透,需要更多的的鼓励。