设为首页收藏本站

网络分析论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 18737|回复: 10

ISOS节点树剖析

[复制链接]
jewsus 发表于 2008-9-11 16:00 | 显示全部楼层 |阅读模式
ISOS节点树剖析

在isos中,数据都是保存在内存中的一棵结点树上。所以,这棵结点树又称为信息模型im(information model),或称为vim(versiate information model)。这颗结点树就是一系列C++类通过面向对象高级语言的多态、继承、单例等特性组成的一格树型结构。
ISOS里面的各种配置手段,无论是Web界面还是CLI控制台或者是未来将会开发出来的其它配置手段。都是跟这节点树打交道,在把信息保存到树结点属性上的同时,通过消息机制触发底层调用通知底层的协议栈,防火墙,web服务器等做相应的配置。可以这么认为,这棵树除了承担Web开发中的数据库角色之外,同时还是各种配置界面与底层各模块的一个桥梁。

一、        定义节点树
ISOS在启动时怎么知道应该在内存中建立一个怎样的节点树,节点树上有哪些类的实例,各个节点(也就是类的实例)之间是个什么样的继承关系呢?这就是source/im目录下的C++代码的职责。一般来讲,一个节点对应了一个抽象C++类和一个具体的C++类,抽象类定义了一些虚拟的接口函数,具体类实现了这些虚拟接口函数。不要抽象类行吗?我猜想是不行的,为什么呢?在ISOS里面定位节点树上的节点时,使用了C++的模版库,比方<dynamic_cast>,<const_cast>,Attribute:set,Attribute:get等。这类操作使用的都是抽象类,所以不要这个抽象接口类是不行的,而不要具体类却是可以的(只要抽象类中没有定义虚函数)。
如果每个类都放在一个C++文件中的话,那么一个节点就相应的有两个C++文件,比方我们的登陆Web界面时使用的Web用户,对应两个类:抽象用户类ImUser.cc(抽象类一般是以Im开头),具体用户类User.cc(实现了ImUser中的虚拟接口函数)。
每个节点都有自己固有的几个重要特性:节点号、属性、路径。节点号就是一个ID号,用于在几个同类的节点中标识一个节点。每个节点都有自己的路径,路径用来在节点树上定位一个节点,我们在Web页面上backup出来的config file中就包含有每个节点的路径,属性是每个节点上的一些变量。我们来看看ImUser.admin这个节点:


N ImUsers ImUsers
N ImUser ImUsers.admin
                                                        A username admin
                                                        A password cIb=aDg?lGd?j?i@
                                                        A comment 'Admin user'
                                                        A accessLevel superuser
                                                        A mayConfigure true
                                                        A mayConfigureWeb true
                                                        A mayDialIn false
                                A pppLoginAuth none
以N开头的这行 N ImUser ImUsers.admin,N表明这是个节点,ImUser是这个节点对应的C++类名,而ImUsers.admin就是这个节点的路径,路径一般是这样命名的
                 父节点路径.本节点的ID号
其中ImUsers.admin就表示这个节点的父节点是ImUsers,而本节点的ID号就是本用户的名字admin。有了这个节点的路径,我们就可以在Web文件中使用系统提供的宏很轻易的定位到这个节点上。而随后就可以使用属性名定位到这个节点的任意一个属性上
emweb:/nodeFromValueString?ImUsers.admin;  //使用节点路径定位到节点
emweb:/attributeFromValueString? mayConfigureWeb;  //使用属性名定位到属性上
emweb:/currentAttributeValue; //当前属性的值,也就是true
上面的宏是在Web页面上定位节点,那么在C代码中怎样去定位节点呢?这好像使用了C++的动态模板库<dynamic_cast>和<const_cast>,具体细节我不是很清楚。没研究过C++的模板库,也就不清楚底层具体是怎么实现的了。有知道的可以完善一下本文档。
        这些C++类文件中有一些重要的预编译宏,就是通过这些宏来定义节点,节点属性以及这些节点之间的父子关系。编译器在编译时会根据这些预编译宏,自动生成很多C代码。这些C代码就位于编译生成的build目录下的parse_tree.cc文件中。Isos正是根据这个文件中的代码来在节点上上挂载各种用预编译宏指定的C++类实例。以及这些类实例之间的父子兄弟关系。具体的这些预编译宏载录如下:
ISOS_CLASS_DEF(User);    // 定义一个节点类
ISOS_ATTRIBUTE_DEF(User,const char *,username);  //在User节点上定义一个字符串类型的属性
ISOS_ENUM_INFO(User::AuthEnum,AuthEnumStrings); //指明AuthEnum属性是个枚举类型的变量

二、        节点树的初始化
内核启动,节点树建立起来了。但是要对各个节点的值进行初始化,也就是要把整个系统的配置状态恢复到上次使用时的状态,或者是第一次使用时,把整个系统状态初始化到工厂配置状态。这是im/proc/proc.cc中定义的初始化进程的工作。
        我们知道,Linux系统启动时,当bootloader二阶段引导完毕,把内核镜像copy到SDRAM并跳转到kernel开始执行的第一个内核函数是start_kernel,在这个函数中初始化中断向量表,建立内核页表,挂载根文件系统之后初始化各种硬件之后所做的重要工作就是启动第一个init进程,然后通过init进程生成很多用户空间的子进程(通过解析/etc/inittab文件和/etc/rd.d/init.d目录下的脚本)。总之一句话,启动各种网络服务,生成各种进程。ISOS的启动过程也是这样的,引导加载后执行的第一个内核函数时xs_Startup,这个函数的功能就是启动了isos中需要的各种进程。这些进程包括
         Im-----------节点树配置进程
         fm-----------文件系统管理进程
         ipmain  iplocal----------------------ip协议栈进程
         security firewall----------------------安全体系, 防火墙进程
          bun--------------------------bun设备驱动框架进程
          rip----------------------------rip路由守护进程
还有很多进程,为什么连ip栈、防火墙、bun驱动都做为一个进程来实现了呢?在linux里面这些都是以内核模块或者直接编译进内核的方式实现的。听别人讲这就是微内核的系统架构。能移出内核作为用户空间进程的都移出内核作为进程实现,各个进程使用消息来通信。这就是微内核架构。而linux不是一个微内核的架构。
话归正传,linux是通过/etc/inittab或者/etc/rc.d/init.d目录下的脚本知道应该启动那些进程。那么,isos怎么知道启动时要启动那些进程呢?大家就要看看编译之后生成的build目录下的init.c文件了。
BEGIN_PROCESS_DEFINITIONS

DEFINE_PROCESS(NULL, timer_timer_main, NULL,                                         NULL, timer, HS_PRI_DEFAULT,                                          1024, )
DEFINE_PROCESS(uart_prologue, uart_process, NULL,                                         uart_qhandle, uart, HS_PRI_DEFAULT,                                          1024, )
        ………
END_PROCESS_DEFINITIONS
DECLARE_PROCESS_TABLESIZE(45)
        上面这一系列宏展开之后就是一个结构体数组,数组的每一项规定了启动时要启动的一个进程以及这个进程的入口函数。xs_startUp就是通过这个结构体数组知道要启动那些进程的。那么这个数组是根据什么规则在编译时自动生成的呢?这就要我们仔细留意一下各个模块下面的module配置文件了。比方说webserver模块下的webserver.module文件。就是这些module文件里面的Object指令告诉了编译时的解析工具生成了这个结构体数组。                        
        Object httpd.o
        {
          Executable httpd stack 2560
        }
        process webserver is httpd/httpd
其中,stack规定了这个进程使用的栈大小。process webserver is httpd/httpd指明这个进程的入口主函数所在的目标文件。

三、        节点树的持久化
节点树只是内存中的一个树型结构,内存中的东西在重启后就丢失了。为了在下次启动时把DUT配置恢复到最后一次使用时的状态或者是第一次使用时恢复到出厂时的默认配置状态,我们必须要把内存中节点树的信息以某种方式保存到flash等永久存储介质,ISOS是通过把节点树信息以一定格式保存到flash中的im.conf文件来实现的。这个文件的结构跟工厂配置文件im.conf.factory的结构是一样的。上面已经提到了,这里就不再介绍了。我们要知道的就是在source/im/proc/proc.cc文件中的im进程的主函数初始化节点树时使用的是im.conf还是im.conf.factory配置文件。ISOS首先查找im.conf文件,没有找到这个文件的情况下,再使用im.conf.factory来初始化节点树。第一次使用DUT时,flash中没有im.conf这个文件,所以使用im.conf.factory把DUT初始化到了出厂状态。我们使用过DUT之后,DUT的配置保存进了im.conf中。下次再重启DUT时就使用im.conf恢复到了最后一次使用时的状态。当进程恢复工厂设置这个动作时,会把im.conf.factory文件copy到im.conf文件,这时两个文件内容一样,重启后DUT就到了出厂设置状态。当我们在Web上上传一个结构跟im.conf和im.conf.factory文件格式一样的配置文件(比方backup出来的Sicmens.icf),就会用这个传上来的配置文件来重建内存中的节点树,并用上传上来的Sicmens.icf这个文件内容覆盖im.conf。当我们在Web上进行backup操作时,是把节点树的信息保存到一个中间文件Sicmens.icf后,把这个中间文件返回给客户端浏览器。有人会问,不要这个中间文件,直接把im.conf返回给客户端行吗?我的回答是:完全可以。TR069进行backup配置文件时就使这么做的。
四、        用户自定义节点
系统在source/im/目录下已经建立了很多节点,这些节点是否编译进镜像文件在gateway和dsl-gateway文件中也是有开发来控制的。当相应的一个模块开关打开后,跟这个模块相对应的节点也就编译进去了,否则不会编译。
除了系统自定义的节点,我们也可以建立自己的节点来保存我们的信息。大致流程上面已经介绍过了。定义一个抽象类,一个具体类。然后为每个节点定义一个对应的get方法和set方法,最后值得注意的就是要在C++类文件后面定义好各种预处理指令。并且要搞清楚我们的C++类必须继承相应的节点管理类。这里给出一个最愚蠢也是最有效的方法——照葫芦画瓢。
这里值得介绍的是,节点树上的所有节点是有自己的类型的。我大致把这些节点归类成三大类:集合类节点、普通类节点、单例类节点。集合类节点一般是其他节点的父节点(这种节点一般也是单例类节点),比方ImUsers。普通节点是节点上的大多数节点,一般是叶子节点,当然也可以作其他普通节点的父节点(对于这种节点的子节点,在节点定位时就比较麻烦了,所以大家定位这种节点时要仔细了)。
上面两类节点都好理解,我这里要详细介绍的是单例类型的节点。大家先看看一些节点定位的代码。
  emweb://mapi_nodeFromValueString? ImLanReconfig;
emweb:/mapi_nodeFromValueString?ImDustbin;
上面宏只跟了一个类名,就能定位到相应的节点上。为什么呢?就是因为这些类都是单例类。也就是说在内存中只有这个类的唯一的一个实例,而没有第二个。所以通过类名即能定位节点。这是利用了面向对象等高级语言中的单例模式(singleton),这是一个最基本的设计模式,研究过设计模式的应该很容易理解,没有研究的大家可以去查查资料。
class LanReconfig : public ManagedNode
{
private:

    //
    // The pending IP addresses and masks to configure when the connection
    // closes.
    //
    // Whenever the values here are updated, they remain *pending* until
    // such time as they get applied. This is to avoid certain kinds of
    // browser error on IP connection failing.

    ipV4Addr pendingAddr_;
    ipV4Addr pendingMask_;

    ipV4Addr pendingMgmtAddr_;
    ipV4Addr pendingMgmtMask_;

public:
    static LanReconfig *instance();

    LanReconfig() :
        pendingAddr_(0) { }

LanReconfig *LanReconfig::instance()
{
    LanReconfig *inst;
    return Instances::instance(&inst);
}
      ……..
}
// Describe this class - in particular how to create it, and where to
// look for its attributes
ISOS_SINGLETON_CLASS_DEF(LanReconfig);
SOS_SINGLETON_CLASS_DEF宏就定义了这个类是个单例类,其实这个类的构造函数应该是private私有的,建立这个类的实例时只能使用静态的instance方法,而每次调用这个方法返回的都是这个类在内存中的同一个实例。把构造函数定义成私有的就能强制用户使用instance建立实例,而不是通过构造函数new出来。ISOS_SINGLETON_CLASS_DEF的作用就就是告诉im建立LanReconfig类的实例时使用它的instance方法而不是构造函数。
鉴于自己水平有限,错误在所难免,讲的不正确的地方还希望大家指出,共同探讨。欢迎大家对本文档作进一步整理、完善。

                                                         作者: 苏  飞
                                                         E-mail:  sufei759@163.com
                                                         2008-8-11
aone 发表于 2008-9-24 17:16 | 显示全部楼层
帮顶一下!写得很好,可惜我现在还看不太懂
彽調華郦 发表于 2009-11-5 14:56 | 显示全部楼层
这没便宜的手机,早知道就买了,帮你顶
毒刺骨 发表于 2009-11-5 15:04 | 显示全部楼层
看我没说错吧,有他就有你,你俩一伙的
避暑天皇 发表于 2011-1-6 16:57 | 显示全部楼层
路过看看!!!!!
梦想天堂 发表于 2011-1-13 14:30 | 显示全部楼层
学习了!!:victory:
陈无影 发表于 2011-4-18 22:16 | 显示全部楼层
路过看看了!!!
避暑天皇 发表于 2011-4-27 18:21 | 显示全部楼层
路过看看了!~
小猫吃鱼 发表于 2011-5-24 17:31 | 显示全部楼层
路过看看了!~!
初冬的风 发表于 2012-5-16 21:51 | 显示全部楼层
俺也是路过滴~!!
jianghanquan 发表于 2013-3-24 15:55 | 显示全部楼层
路过看看了!~

Archiver|手机版|小黑屋|网络分析论坛 ( 京ICP备05002225  

GMT+8, 2020-1-28 12:05 , Processed in 1.124993 second(s), 27 queries .

Powered by Discuz! X3.3

© 2001-2014 协议分析论坛

快速回复 返回顶部 返回列表