本章节主要是通过分析frr sysrepo纳管实现来讲讲frr-7.5是如何实现对sysrepo的支持。以isis协议纳管实现为例。了解本章节的内容需要先了解前面章节的内容。本章节的内容不会过多重复前面的内容。通过前一章节的内容,首先是要确保frr-isisd.yang能在sysrepo中成功加载,这是前提,需要先确保;其次,启动isisd进程。

1 isisd进程的启动分析

Isisd集成sysrepo的启动方法如下:

/usr/lib/frr/isisd -d -M sysrepo:/usr/lib64/frr/modules/ --log-level debugging
【说明1】:调试时,可以增加--log-level选项,便于调试
【说明2】:sysrepo属于frr的一个模块,要加载模块时一定要带-M [module],否则模块不会启动,如果不带模块的路径,使用的模块路径是编译时指定的默认路径。

isisd进程启动的入口位于{$frr}\isisd\isis_main.c的main函数中,main()函数的frr主线程初始化函数frr_init()。但是在了解frr_init()之前,需要将frr中涉及到di的来源先搞清楚,static struct frr_daemon_info *di = NULL; di是一个指向frr_daemon_info结构的static指 针,在frr_preinit(struct frr_daemon_info ,*daemon,...)函数有这样一行代码   di = daemon; daemon实际是指向所在isisd_di地址空间,所以,di最终指向的是isisd_di所指向的空间,所以看看isisd_di是如实实现完成初始化的:


static struct frr_daemon_info isisd_di;  //全局isisd_di结构定义FRR_DAEMON_INFO(isisd, ISIS, .vty_port = ISISD_VTY_PORT,.proghelp = "Implementation of the IS-IS routing protocol.",.copyright ="Copyright (c) 2001-2002 Sampo Saaristo,"" Ofer Wald and Hannes Gredler",.signals = isisd_signals,.n_signals = array_size(isisd_signals),.privs = &isisd_privs, .yang_modules = isisd_yang_modules,.n_yang_modules = array_size(isisd_yang_modules), )#define FRR_DAEMON_INFO(execname, constname, ...)                              \static struct frr_daemon_info execname##_di = {.name = #execname,      \.logname = #constname,  \.module = THIS_MODULE,  \__VA_ARGS__};           \FRR_COREMOD_SETUP(.name = #execname,                                   \.description = #execname " daemon",                  \.version = FRR_VERSION, )  再看看frr_daemon_info数据结构的定义,
struct frr_daemon_info {unsigned flags;const char *progname;const char *name;const char *logname;unsigned short instance;struct frrmod_runtime *module;char *vty_addr;int vty_port;char *vty_sock_path;bool dryrun;bool daemon_mode;bool terminal;enum frr_cli_mode cli_mode;struct thread *read_in;const char *config_file;const char *backup_config_file;const char *pid_file;
#ifdef HAVE_SQLITE3const char *db_file;
#endifconst char *vty_path;const char *module_path;const char *pathspace;bool zpathspace;const char *early_logging;const char *early_loglevel;const char *proghelp;void (*printhelp)(FILE *target);const char *copyright;char startinfo[128];struct quagga_signal_t *signals;size_t n_signals;struct zebra_privs_t *privs;const struct frr_yang_module_info *const *yang_modules;size_t n_yang_modules;bool log_always;
};
所以,isisd_di结构的初始是通过宏FRR_DAEMON_INFO完成的,
FRR_DAEMON_INFO()中的每项的定义,各项都封装成可变__VA_ARGS__,
保存到frr_daemon_info的对应的各项参数中,
例如.yang_modules = isisd_yang_modules,
就是保存到frr_daemon_info->yang_modules。

通过GDB isisd进程初始化时,打印isisd_di结构就明显可以看出,简单贴一个调试的方法:

 gdb isisd(gdb) set args -d -M sysrepo:/usr/lib64/frr/modules/ --log-level debugging(gdb) b frr_preinit(gdb) b frr_init(gdb) run

执行完frr_preinit后,我们再来看来isisd_di的变化,基本没有变化,这里因为都是使用初始配置,才基本没有变化,如果指定到相应的pid,配置保存文件,也就会有相应的变化,这个不多说。

通过上面的过程的di是怎么来的,在frr_init()中还有一个很重要的概念,module_path,是指sysrepo.so模块路径所在路径,请看调试信息

这个路径是由编译器在编译时指定MODULE_PATH。

而我们在命令行中提供的路径是指定yang文件所在的路径,这个路径是在命令行参数解析时,保存在全局变量中

static struct option_chain *modules = NULL, **modnext = &modules;

通过上面的分析,将模块加载的需要的资源已经详细指出,我们再来看看frr_init()它的实现如下,已经将不相关的删除,只留与module相关的代码,有兴趣的可以对照源码一起看:

struct thread_master *frr_init(void)
{struct option_chain *oc;struct frrmod_runtime *module;  //正在启动运行的模块char moderr[256];//char p_instance[16] = "", p_pathspace[256] = "";const char *dir;dir = di->module_path ? di->module_path : frr_moduledir;/*这段不相关,不在这细说*///di->module是指要加载运行的sysrep.so模块//frrmod_init(),是使用modinfo信息对全局就是的初始化frrmod_init(di->module);  while (modules) { //modules是命令行解析所记录的:sysrepo:/usr/lib64/frr/modules/modules = (oc = modules)->next;/* oc->arg,sysrepo:/usr/lib64/frr/modules* /usr/lib64/frr/modules* 通过dlopen完成syrepo.so的加载*/module = frrmod_load(oc->arg, dir, moderr, sizeof(moderr)); if (!module) {fprintf(stderr, "%s\n", moderr);exit(1);}XFREE(MTYPE_TMP, oc);}/*这段不相关,不在这细说*/yang_init();  //为作用于全部容器的yang上下文的初始化,影响全部的yangy文件。//debug_init_cli();/* di->yang_modules是指向isisd_yang_modules[]* di->n_yang_modules yang的数量,isis就与3个yang相关。* nb_init(),北向初始化,sysrepo是在frr的上层,与sysrepo间的通道也是北向接口*/nb_init(master, di->yang_modules, di->n_yang_modules);if (nb_db_init() != NB_OK)flog_warn(EC_LIB_NB_DATABASE,"%s: failed to initialize northbound database",__func__);return master;
}

2 北向接口初始化(nb_init)分析

在分析nb_init()之前,需要讲讲引入的一个非常重要的数据结构红黑树(rb_tree)

红黑树是节点颜色作为额外属性的二叉搜索树,它满足于如下一系列条件:

1)、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

2)、根节点是黑色

3)、所有叶子都是黑色

4)、每个红色节点(除了根节点)都有一个黑色的父节点

红黑树每个操作的时间复杂度不超过O(log n),最大深度2log(n+1)

#define RB_BLACK 0
#define RB_RED      1struct rbt_tree {struct rb_entry *rbt_root;
};struct rb_entry {struct rb_entry *rbt_parent;struct rb_entry *rbt_left;struct rb_entry *rbt_right;unsigned int rbt_color;
};//rbtree type定义
struct rb_type {int (*t_compare)(const void *, const void *);void (*t_augment)(void *);unsigned int t_offset; /* offset of rb_entry in type */
};
其它的定义可以参考openbsd-tree.h而frr中又是如何使用rbtree结构的
首先:
/*构建一颗rb_tree的头结点,root节点*/
RB_HEAD(_name, _type)
/*定义一颗指定类型的RB树的全部操作,该RB树的操作包括rb_init,rb_insert,rb_remove等等*/
#define RB_PROTOTYPE(_name, _type, _field, _cmp)
/*构建指定type的rb_type的比较函数*/
#define RB_GENERATE(_name, _type, _field, _cmp)                                \RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL)
/*初始化*/
#define RB_INITIALIZER(_head)   { { NULL } }     所以,FRR的北侧用到了很多RB操作了,配置的增删改查,都是通过rb-tree实现的。
其次,实际看一下yang模块是如何使用rb-tree的。
/*yang_module的数据结构定义*/
struct yang_module {RB_ENTRY(yang_module) entry;const char *name;const struct lys_module *info;
#ifdef HAVE_CONFDint confd_hash;
#endif
#ifdef HAVE_SYSREPOsr_subscription_ctx_t *sr_subscription;
#endif
};
1、
/*构建头节点*/
RB_HEAD(yang_modules, yang_module);
扩展出来的样式为:
struct yang_modules {struct rbt_tree rbh_root;
}2、yang_module rb-trees基本操作库函数的构建
RB_PROTOTYPE(yang_modules, yang_module, entry, yang_module_compare);3、yang_modules的内部entry的比较操作
RB_GENERATE(yang_modules, yang_module, entry, yang_module_compare)4、定义一个静态的yang_modules结构,用于保存记录
struct yang_modules yang_modules = RB_INITIALIZER(&yang_modules);5、按实际需求将数据插入到yang_modules的rb_tree中
RB_INSERT(yang_modules, &yang_modules, module);

通过以上,至少对rb的使用有一个简单的理解,再来看看rb_init是操作的

1、yang模块的加载frr_init()为nb_init()函数传入了yang_module_info结构的实际数据如下:static const struct frr_yang_module_info *const isisd_yang_modules[] = {&frr_interface_info,&frr_isisd_info,&frr_route_map_info,};/*调用libyang提供的解析函数ly_ctx_load_module(),
*将yang文件的内容解析到数据结构struct lys_module,
*并将解析出的module_info插入本地的yang_modules rbtree中。
*/
struct yang_module *yang_module_load(const char *module_name)
{struct yang_module *module;const struct lys_module *module_info;module_info = ly_ctx_load_module(ly_native_ctx, module_name, NULL);/*错误检查不看,省点空间*/module = XCALLOC(MTYPE_YANG_MODULE, sizeof(*module));module->name = module_name;module->info = module_info;//模块插入if (RB_INSERT(yang_modules, &yang_modules, module) != NULL) {/*错误检查不看,省点空间*/}return module;
}2、frr nb_nodes的创建,YANG文件通过ly_ctx_load_module()的解析
已经将数据保存到于在yang_modules rb tress中,
nb_nodes_create()完成将libyang所支持snode与本地的nb_node建立连接void nb_nodes_create(void)
{/*iterate是遍历,是要完成已经解析出的全部YANG模块的全部节点*/yang_snodes_iterate_all(nb_node_new_cb, 0, NULL);
}int yang_snodes_iterate_all(yang_iterate_cb cb, uint16_t flags, void *arg)
{struct yang_module *module;int ret = YANG_ITER_CONTINUE;//yang_modules,也就是在解析时,已经完成本地创建的,//并且所保存的module_info就是YANG的全部数据RB_FOREACH (module, yang_modules, &yang_modules) {ret = yang_snodes_iterate_module(module->info, cb, flags, arg);if (ret == YANG_ITER_STOP)return ret;}return ret;
}int yang_snodes_iterate_module(const struct lys_module *module,yang_iterate_cb cb, uint16_t flags, void *arg)
{struct lys_node *snode;int ret = YANG_ITER_CONTINUE;/从libyang tree型数据开始遍历,snode,对应info中所保存的data,LY_TREE_FOR (module->data, snode) {/*snodes的遍历是根据不同的节点类型做不同处理,就不一一说,可以看源码*/ret = yang_snodes_iterate_subtree(snode, cb, flags, arg);if (ret == YANG_ITER_STOP)return ret;}....return ret;
}在snode遍历时,按节点类型解析后,将执行回调实现nb_node的创建;
static int nb_node_new_cb(const struct lys_node *snode, void *arg)
{struct nb_node *nb_node;struct lys_node *sparent, *sparent_list;/*分配nb_noded的内存和相应的初始化*/nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node));yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath,sizeof(nb_node->xpath));  nb_node->priority = NB_DFLT_PRIORITY;sparent = yang_snode_real_parent(snode);if (sparent)nb_node->parent = sparent->priv;sparent_list = yang_snode_parent_list(snode);if (sparent_list)nb_node->parent_list = sparent_list->priv;/* Set flags. *//*下面非常重要,如果就CONTAINER节点和LIST型节点,需要递归遍历该节点下的全部子节点*/if (CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST)) {bool config_only = true;yang_snodes_iterate_subtree(snode, nb_node_check_config_only,YANG_ITER_ALLOW_AUGMENTATIONS,&config_only);if (config_only)SET_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY);}if (CHECK_FLAG(snode->nodetype, LYS_LIST)) {struct lys_node_list *slist;slist = (struct lys_node_list *)snode;if (slist->keys_size == 0)SET_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST);}/** Link the northbound node and the libyang schema node with one* another.*//*在snode与nb_node建立连接*/nb_node->snode = snode;lys_set_private(snode, nb_node);return YANG_ITER_CONTINUE;
}3、加载在yang_module_info中定义的全部回调,先看看frr_yang_module_info就如何定义的,其定义如下:frr_struct frr_yang_module_info {const char *name; /* YANG module name. *//* Northbound callbacks. */const struct {const char *xpath; /* Data path of this YANG node. */struct nb_callbacks cbs; /* Callbacks implemented for this node. */uint32_t priority; /* Priority - lower priorities are processed first. */} nodes[];
};const struct frr_yang_module_info frr_isisd_info = {.name = "frr-isisd",  //模块名.nodes = {{//就是YANG文件实际对应的路径,可以对照frr_isis.yang文件理解.xpath = "/frr-isisd:isis/instance",  .cbs = {   //创建的回调函数.cli_show = cli_show_router_isis,.create = isis_instance_create,.destroy = isis_instance_destroy,},.priority = NB_DFLT_PRIORITY - 1,},.....
}/*将预定义的CB挂到指定的nb_node中*/
static void nb_load_callbacks(const struct frr_yang_module_info *module)
{for (size_t i = 0; module->nodes[i].xpath; i++) {struct nb_node *nb_node;uint32_t priority;/*nb_node_new_cb()的最后完将新建的nb_node挂到snode->priv结构中,* nb_node_find()是要通过module->nodes[i].xpath定义的path,找到对应的nb_node*/nb_node = nb_node_find(module->nodes[i].xpath);if (!nb_node) {continue;}/*找到nb_node后,将预定义的cbs, priority挂载到nb_node,*需要注意的是,nb_node是保存在 snode->priv的*/nb_node->cbs = module->nodes[i].cbs;priority = module->nodes[i].priority;if (priority != 0)nb_node->priority = priority;}
}4、用户定义CB的有效性检验,yang_snodes_iterate_all就不在看吧,主要看它的回调实现
static int nb_node_validate(const struct lys_node *snode, void *arg)
{/*nb_load_callbacks()函数已经实现将用户定义的回调都保存snode->priv中*/struct nb_node *nb_node = snode->priv;  unsigned int *errors = arg;/* Validate callbacks and priority. */*errors += nb_node_validate_cbs(nb_node);*errors += nb_node_validate_priority(nb_node);return YANG_ITER_CONTINUE;
}在看用户定义回调的检验之前,这里就是先大致了解YANG文件,和用户根据yang文件定义的CB间的关系。
module frr-isisd {yang-version 1.1;namespace "http://frrouting.org/yang/isisd";prefix frr-isisd;<......>container isis {description"Configuration of the IS-IS routing daemon.";list instance {key "area-tag";description"IS-IS routing instance.";leaf area-tag {type string;description"Area-tag associated to this routing instance.";}<.......>}}
}
通过上面的分析,已经它知道,通过libyang提供的的库函数,YANG文件的数据解析并保存与snode中const struct frr_yang_module_info frr_isisd_info = {.name = "frr-isisd",.nodes = {{.xpath = "/frr-isisd:isis/instance",.cbs = {.cli_show = cli_show_router_isis,.create = isis_instance_create,.destroy = isis_instance_destroy,},.priority = NB_DFLT_PRIORITY - 1,},<..........>
}通过上面的分析,已经将用户自定义的yang_module_info,
YANG文件映射snode中,用户定义模块CB保存到snode->priv中,也是nb_node中。bool nb_operation_is_valid(enum nb_operation operation,const struct lys_node *snode)
{//看到这,就能感觉到在nb_node_new_cb中将nb_node与snode建立连接的妙处,这是一种双向通道struct nb_node *nb_node = snode->priv;  struct lys_node_container *scontainer;struct lys_node_leaf *sleaf;switch (operation) {case NB_OP_CREATE:if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))return false;switch (snode->nodetype) {case LYS_LEAF:sleaf = (struct lys_node_leaf *)snode;if (sleaf->type.base != LY_TYPE_EMPTY)return false;break;case LYS_CONTAINER:scontainer = (struct lys_node_container *)snode;if (!scontainer->presence)return false;break;case LYS_LIST:case LYS_LEAFLIST:break;default:return false;}return true;<.......>}5、最后在内存中创建一份空的配置,用于后续直接在内存中增,删,改,查配置。running_config = nb_config_new(NULL);running_config_entries = hash_create(running_config_entry_key_make,running_config_entry_cmp,"Running Configuration Entries");

通过nb_init(),完成了将用户YANG文件的解析并保存到yang_modules.info的rb_tree中,也完成了用户对yang文件回调的解析保存到snode->priv中,也完成用户自定义的回调的校验,完成上面工作,前期的相关初始化算完成了。

3 北向syrepo初始化分析

1、先看北向sysrepo的初始化入口定义FRR_MODULE_SETUP, 这个处理在前面聊过,可以回头看。
2、再看看hook_register的扩展/*向hook添加回调*/
extern void _hook_register(struct hook *hook, void *funcptr, void *arg,bool has_arg, struct frrmod_runtime *module,const char *funcname, int priority);#define hook_register(hookname, func)                                          \_hook_register(&_hook_##hookname, _hook_typecheck_##hookname(func),    \NULL, false, THIS_MODULE, #func, HOOK_DEFAULT_PRIORITY)static int frr_sr_module_init(void)
{/*向_hook_frr_late_init,添加frr_sr_module_late_init回调*/hook_register(frr_late_init, frr_sr_module_late_init);/*向_hook_frr_very_late_init,添加frr_sr_module_very_late_init回调*/hook_register(frr_very_late_init, frr_sr_module_very_late_init);return 0;
}FRR_MODULE_SETUP(.name = "frr_sysrepo", .version = FRR_VERSION,.description = "FRR sysrepo integration module",.init = frr_sr_module_init, )综上,frr北向syrepo的初始化入口函数,实质是注册了两个hook函数。

frr_sysrepo的hook回调已经注册上,那么,是如何启动相应的模块的初始化,多说一句的,FRR的全部模块都是使用这一模式初始化的。

再回过头看看isis的main()函数,main()函数在快要结束是调用frr_config_fork()函数,

void frr_config_fork(void)
{hook_call(frr_late_init, master); //这就对应执行frr_sr_module_late_init初始化if (!(di->flags & FRR_NO_CFG_PID_DRY)) {/* Don't start execution if we are in dry-run mode */if (di->dryrun) {frr_config_read_in(NULL);exit(0);}thread_add_event(master, frr_config_read_in, NULL, 0,&di->read_in);}if (di->daemon_mode || di->terminal)frr_daemonize();if (!di->pid_file)di->pid_file = pidfile_default;pid_output(di->pid_file);zlog_tls_buffer_init();
}
再看看hook_call的执行
#define hook_call(hookname, ...) hook_call_##hookname(__VA_ARGS__)
hook_call实际就是hook_call_frr_late_init(master),也就是执行的frr_sr_module_late_init()而frr_config_read_in()又注册了hook_call(frr_very_late_init, master);
这个hook与上面的原理一到致,最后调用到frr_sr_module_very_late_init().
因此在main()的config_fork中完成模块的最后初始化。

frr_sr_module_very_late_init()的处理流程

static int frr_sr_module_very_late_init(struct thread_master *tm)
{master = tm;if (frr_sr_init() < 0) {flog_err(EC_LIB_SYSREPO_INIT,"failed to initialize the Sysrepo module");return -1;}//注册frr_sr_finish的hook,在进程结束退出时用于回收分配的sysrepo资源hook_register(frr_fini, frr_sr_finish);return 0;
}/* FRR's Sysrepo 初始化*/
static int frr_sr_init(const char *program_name)
{struct yang_module *module;int  ret;</*frr-sysrepo相当于在sysrepo新创建一个插件,插件如何初始化,可以再回头看看前面的章节./>/* 与sysrepo完成建立链接并启用一个会话后,需要对相应的回调做相应的订阅.* yang_modules还是前面nb_init()所解析出并保存在yang_modules rb_tree中的yang_modules.* 还是一个一个yang module的订阅,完成module_change,state, rpc,action的订阅/* Perform subscriptions. */RB_FOREACH (module, yang_modules, &yang_modules) {int event_pipe;frr_sr_subscribe_config(module);yang_snodes_iterate(module->info, frr_sr_subscribe_state, 0,module);yang_snodes_iterate(module->info, frr_sr_subscribe_rpc, 0,module);/* Watch subscriptions. */ret = sr_get_event_pipe(module->sr_subscription, &event_pipe);if (ret != SR_ERR_OK) {flog_err(EC_LIB_SYSREPO_INIT,"%s: sr_get_event_pipe(): %s", __func__,sr_strerror(ret));goto cleanup;}thread_add_read(master, frr_sr_read_cb, module->sr_subscription,event_pipe, &module->sr_thread);}/*注册nb_notification_send的hook函数*/hook_register(nb_notification_send, frr_sr_notification_send);return 0;
}static void frr_sr_subscribe_config(struct yang_module *module)
{int ret;/* frr使用对整个模块的订阅,不具体区分特定的子订阅,这样的操作是,将订阅实现简单我我化.* 回调frr_sr_config_change_cb是对整个模块变化的响应,* 基于module的订阅,需要相应的处理的请求就大幅度降低,从而提高了整个sysrepo的处理性能.*/ret = sr_module_change_subscribe(session, module->name, NULL, frr_sr_config_change_cb, NULL, 0,SR_SUBSCR_DEFAULT | SR_SUBSCR_ENABLED | SR_SUBSCR_NO_THREAD,&module->sr_subscription);if (ret != SR_ERR_OK)flog_err(EC_LIB_LIBSYSREPO, "sr_module_change_subscribe(): %s",sr_strerror(ret));
}/*  state、rpc、action的订阅类似,都与节点类型相关,非不相关的节点,不需要执行相应的订阅*  以state的订阅为例,*  const struct lys_node *snode  //yang文件解析出的数据,*                                //包括用户遍历时创建的私有数据,这个就厉 *                                //害,snode->priv与nb_node相互转化, *                                //都是指向同一块内存空间
*/
static int frr_sr_subscribe_state(const struct lys_node *snode, void *arg)
{struct yang_module *module = arg;struct nb_node *nb_node;int ret;/*节点类型的检查,非config false节点,不需要订阅*/if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))return YANG_ITER_CONTINUE;/* We only need to subscribe to the root of the state subtrees. */if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R))return YANG_ITER_CONTINUE;/*通过snode->priv得到nb_node,nb_node结构中有xpath,就可以相应的路径做state的订阅.*/nb_node = snode->priv;DEBUGD(&nb_dbg_client_sysrepo, "%s: providing data to '%s'", __func__,nb_node->xpath);ret = sr_dp_get_items_subscribe(session, nb_node->xpath, frr_sr_state_cb, NULL,SR_SUBSCR_CTX_REUSE, &module->sr_subscription);if (ret != SR_ERR_OK)flog_err(EC_LIB_LIBSYSREPO, "sr_dp_get_items_subscribe(): %s",sr_strerror(ret));return YANG_ITER_CONTINUE;
}

通过以上过程,FRR完成对sysrepo的纳管,整个过程中,最重要的是nb_init()、frr_sysrepo()的初始化,nb_init()完成将yang文件映射到内存中,核心的两个结构是yang_modules,snode, nb_node三个数据结构,以及相互的转换,这点处理的非常巧妙,frr_sysrepo()是完成对sysrepo的订阅,以上就是frr对整个sysrepo纳管的初始化流程分析。

而用户在frr_yang_module_info中依照模块YANG定义的回调,又是如何响应与sysrepo处理的实际请求。通俗些,ODL控制器下发一份配置,是怎样通过sysrepo-->frr-sysrepo->isis完成整个过程的处理,就放在下章节再细说。

第七章 frr sysrepo纳管初始化流程分析相关推荐

  1. Linux与云计算——第二阶段Linux服务器架设 第七章:网站WEB服务器架设—日志分析平台...

    Linux与云计算--第二阶段Linux服务器架设 第七章:网站WEB服务器架设-日志分析平台 日志分析:AWstats 安装AWstats分析http日志信息. [1] Install AWstat ...

  2. 【Android Camera1】Camera1初始化销毁流程(一) —— 官方Demo初始化流程分析

    Camera1初始化流程 一.摘要 二.Camera1 Demo分析 2.1 变量解析 2.2 构造函数 setUpPreview() adjustCameraParameters() 2.3 Sta ...

  3. OSAL初始化流程分析

    我使用的协议栈版本及例子信息: ZigBee2006\Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SampleApp    ...

  4. android6.0源码分析之Camera API2.0下的初始化流程分析

    1.Camera2初始化的应用层流程分析 Camera2的初始化流程与Camera1.0有所区别,本文将就Camera2的内置应用来分析Camera2.0的初始化过程.Camera2.0首先启动的是C ...

  5. Linphone_android jni下音视频流、初始化流程分析

    本来流程顺序上面做了排版(函数内部的流程,加了tab缩进的)方便理解, 但是发表之后,还是乱了. 流程顺序基本理清了,可以复制到notepad++,手动加下缩进吧. 主流程是序号 1.2.... 函数 ...

  6. Google原生输入法LatinIME引擎初始化流程分析(二)

    引擎初始化首先是在Java层调用native的初始化方法,Java层调用如下: private void initPinyinEngine() {byte usr_dict[];usr_dict = ...

  7. Spring Beans 初始化流程分析

    测试用例 依然使用这个官网上的用例,来进行调试: Person.java package org.shangyang.spring.container;/**- - @author shangyang ...

  8. Linphone录音器的初始化流程分析

    初始化入口: linphone_core_init() --linphonecore.c 1793 static void linphone_core_init(LinphoneCore * lc, ...

  9. 展锐UDX710:u-Boot 初始化流程分析

    一.mk文件说明 UDX710平台的uBoot文件夹位于u-boot15_orca中,device/sprd/orca/udx710_4h10下AndroidBoard.mk 定义了生成uboot:u ...

最新文章

  1. freemarker中运算符_如何在Web应用系统表示层开发中应用Velocity模板技术
  2. 解决“Internet Explorer 无法打开 Internet站点已终止操作”问题(转)
  3. C# 循环语句 for循环
  4. C++Primer:函数(参数传递-非引用形参)
  5. java string转number_Java 序列化
  6. Python return逻辑判断表达式 - 零基础入门教程
  7. Golang Web入门(2):如何实现一个RESTful风格的路由
  8. java 获取数据源_J2EE java 获取数据源
  9. oracle没有正常启动,Oracledbstart无法正常启动处理办法
  10. php安装调式redis扩展,下载安装thinkphp5.0,调试Redis是否可以正常使用
  11. 前端事件练习之轮播图代码
  12. Python数据处理041:数据分析之时间序列
  13. 代码审计“小迪安全课堂笔记” java
  14. jmeter学习指南之生成html性能结果报告(篇幅较长谨慎阅读)
  15. MySQL插入语句insert into,insert ignore into,insert into ... on duplicate key update,replace into-解决唯一键约束
  16. iconfont字体图标以及css字体图标在线制作和使用(推荐)
  17. 六安职院美和易思互联网+特色专业举行第一届HTML网页设计大赛
  18. 类似搜狐新闻的栏目定制
  19. HTML5 table表格合并单元格和合并边框
  20. TCP协议客户端与服务器端一般的通信过程

热门文章

  1. storm 实战及实例讲解(一)
  2. CorelDRAW 缩略图简单解决办法
  3. Arduino实验二十 数码管实验
  4. hive 中的正则表达式(筛选车牌号)
  5. swif4基础学习(4)- 闭包、枚举
  6. 【Spring Security】解答Spring Boot 中密码加密的正确方式?
  7. DC-DC升压恒压芯片-芯鼎盛TX4201IC
  8. qsv视频如何转换成mp4格式?
  9. 日程管理APP测试计划及测试矩阵
  10. 凌科芯安浅谈数字电视系统条件接收技术