应用程序通过将对Sysrepo的调用通过Sysrepo提供的相应的API接口访问方法,称为Syrepo的间接访问方法。该方法是应用程序通过创建Deamon进程,通过IPC Shm机制与Sysrepo通信。可以做到对Sysrepo的即插即用,最后由Sysrepo纳管,这就是Plugind,命名为sysrepo-plugind。要快速的使用Sysrepo,并快速开发出适配于Sysrepo的插件,就要先了解sysrepo-plugind的实现原理与机制,就需要先从实现sysrepo-plugind的源码处着手。Sysrepo源码路径:git clone https://github.com/sysrepo/sysrepo.git Sysrepo-plugind实现的路径为sysrepo/src/executables/sysrepo-plugind.c。下面也就从该文件开始说。
2.1 数据结构
1 struct srpd_plugin_s { 2 void *handle; 3 srp_init_cb_t init_cb; 4 srp_cleanup_cb_t cleanup_cb; 5 void *private_data; 6 }; 7 /*结构参数说明*/ 8 handle:动态库句柄,在load_plugin中细说 9 10 srp_init_cb_t: 11 /*Sysrepo plugin initialization callback.*/ 12 typedef int (*srp_init_cb_t)(sr_session_ctx_t *session, void **private_data); 13 14 srp_cleanup_cb_t : 15 /*brief Sysrepo plugin cleanup callback.*/ 16 typedef void (*srp_cleanup_cb_t)(sr_session_ctx_t *session, void *private_data); 17 18 private_data: Private context opaque to sysrepo
2.2 main函数实现
1 int 2 main(int argc, char** argv) 3 { 4 struct srpd_plugin_s *plugins = NULL; /*plugin结构*/ 5 sr_conn_ctx_t *conn = NULL; /*sysrepo连接的上下文,该结构定义于common.h.in*/ 6 sr_session_ctx_t *sess = NULL; /*sysrepo会话的上下文,该结构定义于common.h.in中*/ 7 sr_log_level_t log_level = SR_LL_ERR; /*输出log等级,默认是ERR*/ 8 int plugin_count = 0, i, r, rc = EXIT_FAILURE, opt, debug = 0; 9 struct option options[] = { 10 {"help", no_argument, NULL, 'h'}, 11 {"version", no_argument, NULL, 'V'}, 12 {"verbosity", required_argument, NULL, 'v'}, 13 {"debug", no_argument, NULL, 'd'}, 14 {NULL, 0, NULL, 0}, 15 }; /*命令行支持的参数*/ 16 17 /* process options */ 18 opterr = 0; 19 20 /*整个while循环是解析命令的参数,例如,在调试时,输入“sysrepo-plugind -d -v 4” 是debug模式 21 *下log级别DBG级,将会打印全部的调试信息 22 */ 23 while ((opt = getopt_long(argc, argv, "hVv:d", options, NULL)) != -1) { 24 switch (opt) { 25 case 'h': 26 version_print(); 27 help_print(); 28 rc = EXIT_SUCCESS; 29 goto cleanup; 30 case 'V': 31 version_print(); 32 rc = EXIT_SUCCESS; 33 goto cleanup; 34 case 'v': 35 if (!strcmp(optarg, "none")) { 36 log_level = SR_LL_NONE; 37 } else if (!strcmp(optarg, "error")) { 38 log_level = SR_LL_ERR; 39 } else if (!strcmp(optarg, "warning")) { 40 log_level = SR_LL_WRN; 41 } else if (!strcmp(optarg, "info")) { 42 log_level = SR_LL_INF; 43 } else if (!strcmp(optarg, "debug")) { 44 log_level = SR_LL_DBG; 45 } else if ((strlen(optarg) == 1) && (optarg[0] >= '0') && (optarg[0] <= '4')) { 46 log_level = atoi(optarg); 47 } else { 48 error_print(0, "Invalid verbosity \"%s\"", optarg); 49 goto cleanup; 50 } 51 break; 52 case 'd': 53 debug = 1; 54 break; 55 default: 56 error_print(0, "Invalid option or missing argument: -%c", optopt); 57 goto cleanup; 58 } 59 } 60 61 /* check for additional argument */ 62 if (optind < argc) { 63 error_print(0, "Redundant parameters"); 64 goto cleanup; 65 } 66 67 /* load plugins:将所有的pluginl加载,这是整个main第一处核心点,这关系用户开发的plugin能否正确加载*/ 68 if (load_plugins(&plugins, &plugin_count)) { 69 goto cleanup; 70 } 71 72 /* daemonize, sysrepo-plugind no longer directly logs to stderr */ 73 daemon_init(debug, log_level); 74 75 /* create connection (after we have forked so that our PID is correct) */ 76 /*调用sysrepo API(sr_connect)创建与sysrepo的连接,并将返回创建连接的上下发*/ 77 if ((r = sr_connect(0, &conn)) != SR_ERR_OK) { 78 error_print(r, "Failed to connect"); 79 goto cleanup; 80 } 81 82 /* create session */ 83 /*调用sysrepo API(sr_session_start)创建与sysrepo running库的会话,并启动该会话*/ 84 if ((r = sr_session_start(conn, SR_DS_RUNNING, &sess)) != SR_ERR_OK) { 85 error_print(r, "Failed to start new session"); 86 goto cleanup; 87 } 88 /*sr_connect(), sr_session_start(),是连接sysrepo基础,这两点基础实现,在后面sysrepo源码 89 *分析做详细说明,不在sysrepo-plugin中说明 90 */ 91 92 /* init plugins */ 93 /*对所有已加载的plugin通过调用init_cb注册的回调初始化,这是整个main第二处核心点,与用户是强 94 *相关用户开发的插件,注册,订阅,初始化都通过init_cb,否则不能将sysrepol通信连接*/ 95 for (i = 0; i < plugin_count; ++i) { 96 r = plugins[i].init_cb(sess, &plugins[i].private_data); 97 if (r != SR_ERR_OK) { 98 SRP_LOG_ERR("Plugin initialization failed (%s).", sr_strerror(r)); 99 goto cleanup; 100 } 101 } 102 103 /* wait for a terminating signal */ 104 pthread_mutex_lock(&lock); 105 while (!loop_finish) { 106 pthread_cond_wait(&cond, &lock); 107 } 108 pthread_mutex_unlock(&lock); 109 110 /* cleanup plugins */ 111 /* sysrepo-plugindf正常结束后,回收plugin初始化时分配的资源*/ 112 for (i = 0; i < plugin_count; ++i) { 113 plugins[i].cleanup_cb(sess, plugins[i].private_data); 114 } 115 116 /* success */ 117 rc = EXIT_SUCCESS; 118 119 /*结束后,回收已分配的全部资源*/ 120 cleanup: 121 for (i = 0; i < plugin_count; ++i) { 122 dlclose(plugins[i].handle); 123 } 124 free(plugins); 125 126 sr_disconnect(conn); 127 return rc; 128 }
2.3 load_plugins
1 static int 2 load_plugins(struct srpd_plugin_s **plugins, int *plugin_count) 3 { 4 void *mem, *handle; 5 DIR *dir; 6 struct dirent *ent; 7 const char *plugins_dir; 8 char *path; 9 int rc = 0; 10 11 *plugins = NULL; 12 *plugin_count = 0; 13 14 /* get plugins dir from environment variable, or use default one */ 15 /* bin_common.h.in #define SRPD_PLUGINS_PATH "@PLUGINS_PATH@" 16 * @PLUGINS_PATH@在CMakeList.txt中定义,在编译时也可以自定义 17 * CMakeList.txt对其定义如下 18 * if(NOT PLUGINS_PATH) 19 * set(PLUGINS_PATH 20 * "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/sysrepo/plugins/" CACHE PATH 21 * "Sysrepo plugin daemon plugins path.") 22 * endif() 23 * 用户不指定plugin的路径时,debian系统默认将plugin的动态库文件*.so安装 24 * 于/usr/lib/x86_64-linux-gnu/sysrepo/plugins/目录下, 25 * 而Centos系统的默认安装路径为/usr/lib/sysrepo/plugins,在开发plugind时,安装路径也需要 26 * 指定到该路径下,否则,*.so找不到,则load不成功。 27 */ 28 plugins_dir = getenv("SRPD_PLUGINS_PATH"); 29 if (!plugins_dir) { 30 plugins_dir = SRPD_PLUGINS_PATH; 31 } 32 33 /* create the directory if it does not exist */ 34 /* access函数,:检查调用进程是否可以对指定的文件执行某种操作, F_OK文件是否存在 35 * 本段代码是检测SRPD_PLUGINS_PATH目录是否存在,如果不存在,调用sr_mkpath创建目录,并设置* 36 * 目录的访问权限000777。本段代码是安全性代码,确保指定的路径存在。对于实际开发中,是通过编 37 * 译是指定,不存在路径的动态库无法安装。 38 */ 39 if (access(plugins_dir, F_OK) == -1) { 40 if (errno != ENOENT) { 41 error_print(0, "Checking plugins dir existence failed (%s).", strerror(errno)); 42 return -1; 43 } 44 if (sr_mkpath(plugins_dir, 00777) == -1) { 45 error_print(0, "Creating plugins dir \"%s\" failed (%s).", plugins_dir, strerror(errno)); 46 return -1; 47 } 48 } 49 50 /* opendir函数,找开指定的目录文件,并返回DIR*形态的目录流, 51 * 目录的读取与搜查也都需要此目录流 52 */ 53 dir = opendir(plugins_dir); 54 if (!dir) { 55 error_print(0, "Opening \"%s\" directory failed (%s).", plugins_dir, strerror(errno)); 56 return -1; 57 } 58 59 /*readdir函数,读取目录,返回参数dir目录流的下个目录进入点 60 * 返回的结果是struct dirent的内容*/ 61 while ((ent = readdir(dir))) { 62 /*Linux系统中存在"." ".."两类目录,这两类目录名结构,在实际是不需要使用,需要跳过。*/ 63 if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { 64 continue; 65 } 66 67 /* open the plugin */ 68 /*将SRPD_PLUGINS_PATH与也读取的目录文件名,组成一个完成的动态库路径,供后面操作。*/ 69 if (asprintf(&path, "%s/%s", plugins_dir, ent->d_name) == -1) { 70 error_print(0, "asprintf() failed (%s).", strerror(errno)); 71 rc = -1; 72 break; 73 } 74 75 /*RTLD_LAZY:暂缓决定,等有需要时再解出符号 76 *以RTLD_LAZY模式打开动态库,返回一个句柄给调用进程,如果失败,则返回。 77 */ 78 handle = dlopen(path, RTLD_LAZY); 79 if (!handle) { 80 error_print(0, "Opening plugin \"%s\" failed (%s).", path, dlerror()); 81 free(path); 82 rc = -1; 83 break; 84 } 85 free(path); 86 87 /* allocate new plugin */ 88 /* 分配一个新的plugin空间,并将新分配的men挂载plugins结构下*/ 89 mem = realloc(*plugins, (*plugin_count + 1) * sizeof **plugins); 90 if (!mem) { 91 error_print(0, "realloc() failed (%s).", strerror(errno)); 92 dlclose(handle); 93 rc = -1; 94 break; 95 } 96 *plugins = mem; 97 98 /* find required functions */ 99 /* plugins结构中有两个必须的回调函数,一个是init_cb,另一个是cleanup_cb 100 * 通过 void *dlsym(void *handle, const char* symbol);, 101 * handle是使用dlopen函数之后返回的句柄, 102 * symbol是要求获取的函数的名称。 103 * SRP_INIT_CB定义如下:#define SRP_INIT_CB "sr_plugin_init_cb" 104 * SRP_CLEANUP_CB定义下:#define SRP_CLEANUP_CB "sr_plugin_cleanup_cb" 105 * 此两个CB函数,也就是在开发插件中必须实现的两个入口函数,如果不存在,则加载失败。 106 */ 107 *(void **)&(*plugins)[*plugin_count].init_cb = dlsym(handle, SRP_INIT_CB); 108 if (!(*plugins)[*plugin_count].init_cb) { 109 error_print(0, "Failed to find function \"%s\" in plugin \"%s\".", SRP_INIT_CB, ent->d_name); 110 dlclose(handle); 111 rc = -1; 112 break; 113 } 114 115 *(void **)&(*plugins)[*plugin_count].cleanup_cb = dlsym(handle, SRP_CLEANUP_CB); 116 if (!(*plugins)[*plugin_count].cleanup_cb) { 117 error_print(0, "Failed to find function \"%s\" in plugin \"%s\".", SRP_CLEANUP_CB, ent->d_name); 118 dlclose(handle); 119 rc = -1; 120 break; 121 } 122 123 /* finally store the plugin */ 124 /*最后,本次so解析成功,保存本次so的解析结果,执行一下次目录文件的读取*/ 125 (*plugins)[*plugin_count].handle = handle; 126 (*plugins)[*plugin_count].private_data = NULL; 127 ++(*plugin_count); 128 } 129 /*目录文件读取结束,关闭目录读取流,返回的参考中有插件结构plugins。*/ 130 closedir(dir); 131 return rc; 132 }
2.4 init_cb
srpd_plugin_s结构中定义了init的回调函数
如下:
typedef int (*srp_init_cb_t)(sr_session_ctx_t *session, void **private_data);
在load plugin时,
#define SRP_INIT_CB "sr_plugin_init_cb"
init_cb = dlsym(handle, SRP_INIT_CB);
在sysrepo-plugind的main实现时,需要对plugin的初始化,实际就是需要用户对sr_plugin_init_cb()实现,是完成该plugin的资源分配,用户感兴趣的事情做订阅,Module change RPC/Action,Notify, get——items等操作,均在此处完成。有如下例子,请参考。
1 int 2 sr_plugin_init_cb(sr_session_ctx_t *session, void **private_ctx) 3 { 4 int rc; 5 struct plugind_ctx *ctx; 6 ctx = calloc(1, sizeof *ctx); 7 if (!ctx) 8 { 9 rc = SR_ERR_NOMEM; 10 goto error; 11 } 12 13 /*在下面初始与之有关的操作,例如,本地数据结构的初始化,sysrepo的订阅初始化*/ 14 ... 15 16 SRP_LOG_DBGMSG("plugin initialized successfully."); 17 ctx->session = session; 18 *private_ctx = ctx; 19 return SR_ERR_OK; 20 21 error: 22 SRP_LOG_ERR("plugin initialization failed (%s).", sr_strerror(rc)); 23 sr_unsubscribe(ctx->subscription); 24 free(ctx); 25 return rc; 26 }
2.5 cleanup_cb
srpd_plugin_s结构中定义了cleanup的回调函数
如下:
typedef void (*srp_cleanup_cb_t)(sr_session_ctx_t *session, void *private_data);
在load plugin时,
#define SRP_CLEANUP_CB "sr_plugin_cleanup_cb"
cleanup_cb = dlsym(handle, SRP_CLEANUP_CB);
所以,对于用户来就,是需要对sr_plugin_cleanup_cb()实现,回收plugin在初始化时分配的资源。例如下面的cleanup实现,可以参考
1 void 2 sr_plugin_cleanup_cb(sr_session_ctx_t *session, void *private_ctx) 3 { 4 (void)session; 5 6 struct plugind_ctx *ctx = (struct plugind_ctx *)private_ctx; 7 sr_unsubscribe(ctx->subscription); 8 free(ctx); 9 10 nb_terminate(); 11 yang_terminate(); 12 13 SRP_LOG_DBGMSG("plugin cleanup finished."); 14 }
整个sysrepo-plugind.c代码结构简单,注释丰富,没有使用复杂的语法,还是非常容易理解的。