在simbrick中,要实现对nic的模拟,要完成哪些步骤?实现流程是怎样的?
def connect(self, c: base.Channel) -> None:
# Note AK: a bit ugly, but I think we can't get around a rt check here
utils_base.has_expected_type(c, EthChannel)
super().connect(c)
注释当中,AK和rt检查是什么?
网卡的工作原理是什么?接口列表是什么意思?我理解的是一个网卡IP就对应了一个接口,网卡只有一个IP,那么应该只有一个接口?为什么会有接口列表的概念
super().add_if(0等super().意味着什么?
在simbricks的system当中,pcie.py与eth.py等都是对模拟接口的封装,那么在实现时,实现的代码是在哪里?实现的逻辑是怎样的?
在add(nic)方法中,断言self._components<1是什么意思?这是在判定什么?为什么要进行这个断言?
# helper method for simulators that do not support
# multiple sync periods etc. Should become eventually
# at some point in the future...
@staticmethod
def get_unique_latency_period_sync(
channels: list[sim_chan.Channel],
) -> tuple[int, int, bool]:
latency = None
sync_period = None
run_sync = False
for channel in channels:
sync_period = (
min(sync_period, channel.sync_period) if sync_period else channel.sync_period
)
run_sync = run_sync or channel._synchronized
latency = (
max(latency, channel.sys_channel.latency)
if latency
else channel.sys_channel.latency
)
if latency is None or sync_period is None:
raise Exception("could not determine eth_latency and sync_period")
return latency, sync_period, run_sync
详细解释一下这段代码
if eth_run_sync != pci_run_sync:
raise Exception(
"currently using different synchronization values for pci and eth is not supported"
)
run_sync = eth_run_sync
sync_period = min(pci_sync_period, eth_sync_period)
这里run_sync = eth_run_sync是什么意思?
def repo_base(self, relative_path: str | None = None, must_exist: bool = True) -> str:
if relative_path is None:
return self._simbricks_dir
return utils_file.join_paths(
base=self._simbricks_dir, relative_path=relative_path, must_exist=must_exist
)
详细解释一下repo_base方法,cmd = f"{inst.env.repo_base(relative_path=self._executable)} "中,inst.env是什么?
def get_parameters_url(
self,
inst: inst_base.Instantiation,
socket: inst_socket.Socket,
channel: sim_chan.Channel | None = None,
sync: bool | None = None,
latency: int | None = None,
sync_period: int | None = None,
) -> str:
if not channel and (sync == None or latency == None or sync_period == None):
raise ValueError(
"Cannot generate parameters url if channel and at least one of sync, "
"latency, sync_period are None"
)
if channel:
if not sync:
sync = channel._synchronized
if not latency:
latency = channel.sys_channel.latency
if not sync_period:
sync_period = channel.sync_period
sync_str = "true" if sync else "false"
if socket._type == inst_socket.SockType.CONNECT:
return (
f"connect:{socket._path}:sync={sync_str}:latency={latency}"
f":sync_interval={sync_period}"
)
else:
return (
f"listen:{socket._path}:{inst.env.get_simulator_shm_pool_path(self)}:sync={sync_str}"
f":latency={latency}:sync_interval={sync_period}"
)
解释一下这个得到url的代码
nic_devices = self.filter_components_by_type(ty=sys_nic.SimplePCIeNIC)
assert len(nic_devices) == 1
nic_device = nic_devices[0]
socket = inst.get_socket(interface=nic_device._pci_if)
assert socket is not None and socket._type == inst_socket.SockType.LISTEN
params_url = self.get_parameters_url(
inst, socket, sync=run_sync, latency=pci_latency, sync_period=sync_period
)
cmd += f"{params_url} "
socket = inst.get_socket(interface=nic_device._eth_if)
assert socket is not None and socket._type == inst_socket.SockType.LISTEN
params_url = self.get_parameters_url(
inst, socket, sync=run_sync, latency=eth_latency, sync_period=sync_period
)
cmd += f"{params_url} "
这里拼接的参数都有什么用,参数是怎么得到的?为什么要这些参数,而不是其他的?

这是sims目录下的一些模拟器代码,py胶水代码用的是哪个文件?并解析一下这些文件,与作用?如果我要自己实现一个模拟器,该怎么做?

def get_socket(self, interface: sys_base.Interface) -> inst_socket.Socket | None:
if self._opposing_interface_within_same_sim(interface=interface):
return None
if interface in self._socket_per_interface:
return self._socket_per_interface[interface]
# create socket
sock_path = self._interface_to_sock_path(interface=interface)
new_socket = inst_socket.Socket(path=sock_path, ty=self.get_interface_socktype(interface))
self._socket_per_interface[interface] = new_socket
return new_socket
这个get_socket代码是什么逻辑?socket是什么?模拟nic,在nic中模拟了ipv4地址,和eth接口,怎么有了socket的模拟?
def _interface_to_sock_path(self, interface: sys_base.Interface) -> str:
channel = interface.get_chan_raise()
queue_ident = f"{channel.a._id}.{channel._id}.{channel.b._id}"
queue_type = None
match interface:
case sys_pcie.PCIeHostInterface() | sys_pcie.PCIeDeviceInterface():
queue_type = "pci"
case sys_mem.MemDeviceInterface() | sys_mem.MemHostInterface():
queue_type = "mem"
case sys_eth.EthInterface():
queue_type = "eth"
case _:
raise Exception("cannot create socket path for given interface type")
assert queue_type is not None
return self.env.shm_base(f"{queue_type}-{queue_ident}")
详细解释一下这段代码?
simulation = sim_helpers.simple_simulation(
sys,
compmap={
system.FullSystemHost: sim.QemuSim,
system.IntelI40eNIC: sim.I40eNicSim,
system.EthSwitch: sim.SwitchNet,
},
)
这是模拟器胶水py对象nic构建时的代码,在胶水语言中,先是进行了self.get_channels(),这个get_channels()得到的是什么?为什么会得到pci和eth两个通道?以及通道连接的不就应该是接口if吗,为什么后续生成socket时,还是通过self的nic_device来得到的,直接用channels行吗?
nic_devices = self.filter_components_by_type(ty=sys_nic.SimplePCIeNIC)
assert len(nic_devices) == 1
nic_device = nic_devices[0]
在NICSim中,要得到自身模拟的对象nic_device,要通过filter_components方法,但是模拟器构建时
def simple_simulation(
system: system.System,
sync=False,
compmap=dict[type[system.Component], type[sim_base.Simulator]]
):
"""Create simple simulation from system. Uses a map from component type to
simulator type and then creates one simulator per component."""
simulation = sim_base.Simulation(
name=f"simulation-{system.name}", system=system
)
for comp in system._all_components.values():
if comp in simulation._sys_sim_map:
continue
for (ct,st) in compmap.items():
if isinstance(comp, ct):
simulator = st(simulation)
simulator.add(comp)
if comp.name:
simulator.name = comp.name
if not sync:
disalbe_sync_simulation(simulation=simulation)
return simulation
再重新解释一遍整个模拟构建的过程,尤其是模拟对象如何转化到实际的模拟器的,各模拟器的component什么时候开始有值的?
在模拟过程中,channels和socket是什么关系?有什么区别?
#include <iostream>
#include <stdarg.h>
#include <simbricks/nicbm/nicbm.h>
#include "sims/nic/e1000_gem5/i8254xGBe.h"
static nicbm::Runner *runner;
static bool debug_enable = false;
class Gem5DMAOp : public nicbm::DMAOp, public nicbm::TimedEvent {
public:
EventFunctionWrapper &ev_;
Gem5DMAOp(EventFunctionWrapper &ev) : ev_(ev) {}
virtual ~Gem5DMAOp() = default;
};
/******************************************************************************/
/* nicbm callbacks */
void IGbE::SetupIntro(struct SimbricksProtoPcieDevIntro &di)
{
di.bars[0].len = 128 * 1024;
di.bars[0].flags = SIMBRICKS_PROTO_PCIE_BAR_64;
di.pci_vendor_id = 0x8086;
di.pci_device_id = 0x1075;
di.pci_class = 0x02;
di.pci_subclass = 0x00;
di.pci_revision = 0x00;
}
void IGbE::RegRead(uint8_t bar, uint64_t addr, void *dest,
size_t len)
{
read(addr, len, dest);
// TODO delay!
}
void IGbE::RegWrite(uint8_t bar, uint64_t addr, const void *src,
size_t len)
{
write(addr, len, src);
// TODO delay!
}
void IGbE::DmaComplete(nicbm::DMAOp &op)
{
Gem5DMAOp *dma = dynamic_cast <Gem5DMAOp *>(&op);
if (dma->write_) {
delete[] ((uint8_t *) dma->data_);
} else {
// schedule callback event. THis is at the current time, but can't call
// directly to ensure event priorities are respected.
dma->ev_.sched = true;
dma->ev_.time_ = runner_->TimePs();
runner_->EventSchedule(dma->ev_);
}
delete dma;
}
void IGbE::EthRx(uint8_t port, const void *data, size_t len)
{
EthPacketPtr pp = std::make_shared<EthPacketData>(len);
pp->length = len;
memcpy(pp->data, data, len);
ethRxPkt(pp);
}
void IGbE::Timed(nicbm::TimedEvent &te)
{
if (Gem5DMAOp *dma = dynamic_cast <Gem5DMAOp *>(&te)) {
runner_->IssueDma(*dma);
} else if (EventFunctionWrapper *evw =
dynamic_cast <EventFunctionWrapper *>(&te)) {
evw->sched = false;
evw->callback();
} else {
abort();
}
}
/******************************************************************************/
/* gem5-ish APIs */
Tick IGbE::clockEdge(Tick t)
{
if (t % 1000 != 0)
t += 1000 - (t % 1000);
t += 1000;
return t;
}
void IGbE::schedule(EventFunctionWrapper &ev, Tick t)
{
if (ev.sched) {
fprintf(stderr, "schedule: already scheduled\n");
abort();
}
ev.time_ = t;
ev.sched = true;
runner_->EventSchedule(ev);
}
void IGbE::reschedule(EventFunctionWrapper &ev, Tick t, bool always)
{
if (ev.sched) {
runner_->EventCancel(ev);
ev.sched = false;
} else if (!always) {
fprintf(stderr, "reschedule: not yet scheduled\n");
abort();
}
schedule(ev, t);
}
void IGbE::deschedule(EventFunctionWrapper &ev)
{
if (!ev.sched) {
fprintf(stderr, "deschedule: not scheduledd\n");
abort();
}
runner_->EventCancel(ev);
ev.sched = false;
}
void IGbE::intrPost()
{
runner_->IntXIssue(true);
}
void IGbE::intrClear()
{
runner_->IntXIssue(false);
}
void IGbE::dmaWrite(Addr daddr, size_t len, EventFunctionWrapper &ev,
const void *buf, Tick delay)
{
Gem5DMAOp *op = new Gem5DMAOp(ev);
op->data_ = new uint8_t[len];
memcpy(op->data_, buf, len);
op->len_ = len;
op->write_ = true;
op->dma_addr_ = daddr;
op->priority_ = 1;
op->time_ = runner_->TimePs() + delay;
runner_->EventSchedule(*op);
ev.time_ = runner_->TimePs() + delay;
runner_->EventSchedule(ev);
}
void IGbE::dmaRead(Addr saddr, size_t len, EventFunctionWrapper &ev,
void *buf, Tick delay)
{
ev.sched = true;
Gem5DMAOp *op = new Gem5DMAOp(ev);
op->data_ = buf;
op->len_ = len;
op->write_ = false;
op->dma_addr_ = saddr;
op->time_ = runner_->TimePs() + delay;
op->priority_ = 2;
runner_->EventSchedule(*op);
}
bool IGbE::sendPacket(EthPacketPtr p)
{
runner_->EthSend(p->data, p->length);
ethTxDone();
return true;
}
void warn(const char *fmt, ...)
{
fprintf(stderr, "warn: ");
va_list va;
va_start(va, fmt);
vfprintf(stderr, fmt, va);
va_end(va);
}
void panic(const char *fmt, ...)
{
fprintf(stderr, "panic: ");
va_list va;
va_start(va, fmt);
vfprintf(stderr, fmt, va);
va_end(va);
abort();
}
static void debug_init()
{
#ifdef DEBUG_E1000
char *debug_env = getenv("E1000_DEBUG");
if (debug_env &&
(!strcmp(debug_env, "1") ||
!strcmp(debug_env, "y") ||
!strcmp(debug_env, "Y")))
{
warn("enabling debug messages because E1000_DEBUG envar set\n");
debug_enable = true;
}
#endif
}
void debug_printf(const char *fmt, ...)
{
if (debug_enable) {
va_list val;
va_start(val, fmt);
fprintf(stderr, "%lu: ", runner->TimePs());
vfprintf(stderr, fmt, val);
va_end(val);
}
}
/******************************************************************************/
int main(int argc, char *argv[])
{
debug_init();
IGbEParams params;
params.rx_fifo_size = 384 * 1024;
params.tx_fifo_size = 384 * 1024;
params.fetch_delay = 10 * 1000;
params.wb_delay = 10 * 1000;
params.fetch_comp_delay = 10 * 1000;
params.wb_comp_delay = 10 * 1000;
params.rx_write_delay = 0;
params.tx_read_delay = 0;
params.pio_delay = 0; // TODO
params.rx_desc_cache_size = 64;
params.tx_desc_cache_size = 64;
params.phy_pid = 0x02A8;
params.phy_epid = 0x0380;
IGbE *dev = new IGbE(¶ms);
runner = new nicbm::Runner(*dev);
if (runner->ParseArgs(argc, argv))
return EXIT_FAILURE;
dev->init();
return runner->RunMain();
}