第五章 sysrepo订阅

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, &notif_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(&notif_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, &notif_subs, &notif_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机制能彻底吃透,需要更多的的鼓励。

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值