An introduction to shadow simulator

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010643777/article/details/81514985

 这里简单介绍下shadow[1]这款离散事件仿真器,目前官方文档也很少。我只是大致看了一下,简单了解其工作原理。这个仿真器使用了linux中的LD_PRELOAD技术,就是屏蔽原有so库中的函数,使其调用走用户定义的函数。
 因为它是一款网络仿真器,那么他就需要preload一些与网络库相关的调用函数。在/shadow-master/src/preload/shd-preload-defs可以看到其preload的函数。例如:

PRELOADDEF(return, int, epoll_create, (int a), a);
PRELOADDEF(return, int, epoll_create1, (int a), a);
PRELOADDEF(return, int, epoll_ctl, (int a, int b, int c, struct epoll_event* d), a, b, c, d);
PRELOADDEF(return, int, epoll_wait, (int a, struct epoll_event* b, int c, int d), a, b, c, d);
PRELOADDEF(return, int, epoll_pwait, (int a, struct epoll_event* b, int c, int d, const sigset_t* e), a, b, c, d, e);

/* socket/io family */

PRELOADDEF(return, int, socket, (int a, int b, int c), a, b, c);
PRELOADDEF(return, int, socketpair, (int a, int b, int c, int d[2]), a, b, c, d);
PRELOADDEF(return, int, bind, (int a, const struct sockaddr* b, socklen_t c), a, b, c);
PRELOADDEF(return, int, getsockname, (int a, struct sockaddr* b, socklen_t* c), a, b, c);
PRELOADDEF(return, int, connect, (int a, const struct sockaddr* b, socklen_t c), a, b, c);

 如果你的应用使用了shadow库,程序中调用connect函数的时候,就会调用shadow中定义的connect函数。
 PRELOADDEF这个宏定义:

#if defined(PRELOADDEF)
#undef PRELOADDEF
#endif
#define PRELOADDEF(returnstatement, returntype, functionname, argumentlist, ...) \
returntype functionname argumentlist { \
    Process* proc = NULL; \
    if((proc = _doEmulate()) != NULL) { \
        returnstatement process_emu_##functionname(proc, ##__VA_ARGS__); \
    } else { \
        ENSURE(functionname); \
        returnstatement director.next.functionname(__VA_ARGS__); \
    } \
}

 这里就以connect函数为例分析。它对用的函数就是process_emu_connect函数。

int process_emu_connect(Process* proc, int fd, const struct sockaddr* addr, socklen_t len)  {
    if(prevCTX == PCTX_PLUGIN) {
        _process_changeContext(proc, PCTX_SHADOW, PCTX_PTH);
        utility_assert(proc->tstate == pth_gctx_get());
        ret = pth_connect(fd, addr, len);
        _process_changeContext(proc, PCTX_PTH, PCTX_SHADOW);
        if(ret == -1) {
            _process_setErrno(proc, errno);
        }
    } else {
        _process_changeContext(proc, PCTX_SHADOW, prevCTX);
        ret = _process_emu_addressHelper(proc, fd, addr, &len, SCT_CONNECT);
        _process_changeContext(proc, prevCTX, PCTX_SHADOW);
    }
}

 我并不太清楚它究竟调用了哪一个分支,只追踪一个。

static gint _process_emu_addressHelper(Process* proc, gint fd, const struct sockaddr* addr, socklen_t* len,
        enum _SystemCallType type) {
            case SCT_CONNECT: {
                result = host_connectToPeer(proc->host, fd, addr);
                break;
            }        
        }
gint host_connectToPeer(Host* host, gint handle, const struct sockaddr* address) {
    return socket_connectToPeer(socket, peerIP, peerPort, family);
}  
//这里的vtable在tcp_new和udp_new中被初始化。在tcp_new中传入的就是tcp_functions
gint tcp_connectToPeer(TCP* tcp, in_addr_t ip, in_port_t port, sa_family_t family) {
    /* send 1st part of 3-way handshake, state->syn_sent */
    Packet* packet = _tcp_createPacket(tcp, PTCP_SYN, NULL, 0);
    _tcp_bufferPacketOut(tcp, packet);
    _tcp_flush(tcp);//把tcp->throttledOutput中的数据包刷出去

    /* the output buffer holds the packet ref now */
    packet_unref(packet);
}
static void _tcp_bufferPacketOut(TCP* tcp, Packet* packet) {
    if(!priorityqueue_find(tcp->throttledOutput, packet)) {
        /* TCP wants to avoid congestion */
        priorityqueue_push(tcp->throttledOutput, packet);
        packet_ref(packet);
        }
}
static void _tcp_flush(TCP* tcp) {
        gboolean success = socket_addToOutputBuffer(&(tcp->super), packet);
}
gboolean socket_addToOutputBuffer(Socket* socket, Packet* packet) {
    g_queue_push_tail(socket->outputBuffer, packet);
     /* tell the interface to include us when sending out to the network */
    in_addr_t ip = packet_getSourceIP(packet);
    NetworkInterface* interface = host_lookupInterface(worker_getActiveHost(), ip);
    networkinterface_wantsSend(interface, socket);
}

 剩下的就是似乎经过一定的时延模拟,仿真调度器直接将数据包投递给对端了。这一点是否理解地正确,需要后续验证。
[1]The Shadow Simulator
[2]利用LD_PRELOAD hook代码

猜你喜欢

转载自blog.csdn.net/u010643777/article/details/81514985