首页 > 代码库 > NRE代码阅读记录

NRE代码阅读记录

本来是为了论证自己的观点,把安全标签打在RunningConfig里,就写了个代码分析,结果写着写着发现的确不应该是在RunningVM里。意外的发现看代码的时候这么写写还是挺不错的,也避免了看了后面的忘记前面的。这种底层的代码实在是很难理解,对我来说就像是小学生去算高数一样,也只能硬着头皮去看了。

vmmng.cc对应的就是如下界面(回头放图上来,ubuntu下没有什么截图工具,总不能把整个屏幕放上来)
然后“3”键可以新建tiny-core虚拟机,对应到代码里,也就是input_thread方法里的:

case Keyboard::VK_1 ... Keyboard::VK_9: {
                if(pk->flags & Keyboard::RELEASE) {
                    size_t idx = pk->keycode - Keyboard::VK_1;
                    auto it = configs.begin();
                    for(; it != configs.end() && idx-- > 0; ++it)
                        ;
                    if(it != configs.end()) {
                        try {
                            vml.add(cm, &*it, cpucyc.next()->log_id());
                        }
                        catch(const Exception &e) {
                            Serial::get() << "Start of ‘" << it->name() << "‘ failed: " << e.msg() << "\n";
                        }
                    }
                }
            }

 

关键代码也就这一句:

vml.add(cm, &*it, cpucyc.next()->log_id());

其中vml是RunningVMList &vml = RunningVMList::get(),即RunningVMList的static变量 _inst,是当前系统中所有的RunningVM的列表;

cm是static变量ChildManager,it是指向VMConfig的迭代器).

然后看一下RunningVMList.add究竟做了什么:

void add(nre::ChildManager &cm, VMConfig *cfg, cpu_t cpu) {
        nre::ScopedLock<nre::UserSm> guard(&_sm);
        size_t console = alloc_console();
        try {
            nre::Child::id_type id = cfg->start(cm, console, cpu);
            nre::Reference<const nre::Child> child = cm.get(id); //上一句创建的child task的引用
            if(child.valid()) {
                _list.insert(new RunningVM(cfg, console, id, child->pd()));
                _max = nre::Math::max(_max, console);
            }
        }
        catch(...) {
            free_console(console);
            throw;
        }
    }

关键代码是cfg->start(cm,console,cpu)方法。

在start方法里,childmanager cm加载了一个child task,在这个加载过程中创建了一个新的protection domain,添加相关的segment到该pd中,创建并最终start一个新线程。方法返回这个child task的id。所以新建的这个vm最底层应该就是这个child task了。 如果该child task是有效的,那么就在RunningVM类的SortedSList<RunningVM>变量中创建并添加一个新的RunningVM, child->pd()是该RunningVM对应的child task的protection domain的Capability Selector (SEL) (a user-visible unsigned number, which serves as index for the memory space,port I/O space, or object space of a protection domain to select a Capability).

PD和SEL都是nova里的概念,因为有点遗忘所以再根据specification复习一下:
PD可以看成是一个隔离和保护的单元,可以由PD Capability Reference到。PD由一些spaces组成,这些spaces hold了这个pd里的execution contexts可以获取的资源的capability以及kernel object的capability。spaces包括memory space(hold和物理内存的页框相关的capability), port I/O space(hold I/O ports的capability)和object space(hold一些kernel object的capability.这些kernel object包括pd, Execution Context, Scheduling Context, Portal, Semaphore)。
SEL是capability Selector,它是用户可见的一个unsigned int,通过这个number相当于是capability在memory space的序号,所以通过这个序号可以找到对应的capability。

所以如果是打标签的话,应该是在RunningVM中加一个private变量,并且同时加入到child里。

因为打安全标签事实上是为后面的工作服务,包括根据安全等级划分域,为不同域分别提供驱动;以及根据安全等级确定是否允许诸如网络等I/O资源的访问。

由于ChildManager和Child里面的代码很长并且目测以我现在的理解程度应该是看不懂,所以还是打算从网络入手,先搞明白和网络相关的通信机制,于是还是从vmmng脚本出发,发现了和上面的vmmng.cc类似的network.cc,这个文件应该就是为“虚拟机开启网络服务”的可执行程序了。

从network.cc入手:

int main() {
    NICList nics;
    NetworkService srv(nics, "network");
    NE2K::detect(srv, nics);
    srv.start();
    return 0;
}

 

先看NetworkService srv(nics, "network");

其构造函数如下:

NetworkService::NetworkService(NICList &nics, const char *name)
    : Service(name, CPUSet(CPUSet::ALL), reinterpret_cast<portal_func>(portal)),
      _nics(nics) {
    // we want to accept two dataspaces and two sms
    for(auto it = CPU::begin(); it != CPU::end(); ++it) { //遍历了所有当前的online cpu
        Reference<LocalThread> ec = get_thread(it->log_id()); //log_id是该cpu的id
        UtcbFrameRef uf(ec->utcb());
        uf.accept_delegates(2);
    }
}

 

NetworkService继承了Service类(ipc/service.h)。

Service类 provide a service for clients. If you create an instance of it, it is registered at your parent with a specified name and services portals, that the client can call to open and close sessions. Sessions are used to bind data to them. Note that sessions are always used (also if no additional data is needed) to prevent a special case. As soon as a client has a session, it can use the service that is provided. That is, it can call the portals.

先看一下Service类的构造函数,这个构造函数里为指定的cpu创建了用来接收指定的client session的portal,并且创建了用来handle这个portal的线程 (也就是初始化了Service对象的ServiceCPUHandler **_insts 属性,这个ServiceCPUHandler的作用就是为指定的CPU提供用来打开或关闭session的portal, 并host相关线程)。

对于portal还是有点困惑,所以下面先再复习一下:

portal是进入一个pd的entry point,这个pd也就是创建这个portal的pd。因为portal也是kernel object的一种,所以也有种portal capability可以reference这个portal。它永久的与一个execution context绑定。portal里包含以下信息:execution context的引用;信息传递描述符Message Transfer Descriptor(用来控制异常与中断信息的内容);entry instruction的指针;portal的标识符。

现在看一下for循环里做了什么:

get_thread()方法得到了the local thread for the given CPU to handle the provided portal。local thread是thread that is only used to serve portal-calls. Therefore, it doesn‘t have an entry-point, but you can bind portals to it。也就是说localthread仅与portal相关,它和global thread都继承于thread类。thread可以看作是有一个stack和一个utcb的execution context

回到specification复习一下execution context和utcb的概念:

execution context(ec)是kernel object的一种,可以看作pd中的activity的抽象。和其他Kernel object一样有相应的capability来reference。ec永久的与创建它的pd绑定。可能会有一个scheduling context绑定在ec上。有两种可能的ec形式:kernel thread和virtual cpu。ec包含以下信息:pd的reference, Event Selector Base, reply capability register, cpu register, floating point unit和UTCB。

所以ec中是包含UTCB的。UTCB可以被用于ec间的通信,包括untyped items的转移,以及typed items(也就是capability的委托等)

再回到上面NetworkService的构造代码:
UtcbFrameRef uf(ec->utcb())获得了当前的utcbFrame
uf.accept_delegates(2);在utcb frame里创建order为2的delegation window,为capability delegation做准备。