版权声明:本文为博主原创文章,未经博主允许不得转载。 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代码