qemu网络虚拟化之数据流向分析三
2016-09-27
前篇文章通过分析源代码,大致描述了各个数据结构之间的关系是如何建立的,那么今天就从数据包的角度,分析下数据包是如何在这些数据结构中间流转的!
这部分内容需要结合前面两篇文章来看,我们还是按照从Tap设备->Hub->NIC的流程。
首先看Tap设备,在Tap.c文件中:
先看下Tap设备注册的处理函数
static NetClientInfo net_tap_info = {
.type = NET_CLIENT_OPTIONS_KIND_TAP,
.size = sizeof(TAPState),
.receive = tap_receive,
.receive_raw = tap_receive_raw,
.receive_iov = tap_receive_iov,
.poll = tap_poll,
.cleanup = tap_cleanup,
};
其中重点就是tap_receive,该函数中会根据情况调用tap_receive_raw,而tap_receive_iov是在使用IO向量的情况下使用的,逻辑上和tap_receive是一个级别,关于IO向量下面首先会简要分析下:
IO向量:
IO向量的主要目的是让调用在一次原子操作中连续读写多个缓冲区,从而提高效率。IO向量结构如下:
struct iovec {
void *iov_base;
size_t iov_len;
};
结构很简单,前者iov_base指向一个缓冲区,iov_len记录缓冲区的长度。一般会有一个iovec数组来描述IO向量,数组的个数就是缓冲区的个数。
先看下tap_receive函数
static ssize_t tap_receive(NetClientState *nc, const uint8_t *buf, size_t size)
{
TAPState *s = DO_UPCAST(TAPState, nc, nc);
struct iovec iov[]; if (s->host_vnet_hdr_len && !s->using_vnet_hdr) {
return tap_receive_raw(nc, buf, size);
} iov[].iov_base = (char *)buf;
iov[].iov_len = size; return tap_write_packet(s, iov, );
}
该函数接收从用户应用程序传递过来的数据,然后写入到设备文件中。在没有使用virtIO 的情况下是直接交给tap_receive_raw处理,否则设置IO向量并调用tap_write_packet函数
static ssize_t tap_write_packet(TAPState *s, const struct iovec *iov, int iovcnt)
{
ssize_t len; do {
len = writev(s->fd, iov, iovcnt);
} while (len == - && errno == EINTR); if (len == - && errno == EAGAIN) {
tap_write_poll(s, true);
return ;
} return len;
}
在该函数中调用了writev函数进向TAPState->fd 进行数据的写入。这里数据被组织成IO向量,写入完成需要调用tap_write_poll更新下fd的处理函数(原因)。
而tap_receive_raw函数和tap_receive_iov函数本质上和tap_receive实现类似的功能,只有一些细枝末节的变化,这里就不在分析。
下面还是转换方向,从tap部分的发送函数说起,这里看tap_send函数
static void tap_send(void *opaque)
{
TAPState *s = opaque;
int size; do {
uint8_t *buf = s->buf;
size = tap_read_packet(s->fd, s->buf, sizeof(s->buf));
if (size <= ) {
break;
}
//如果设置了vnet头部长度,但是using_vnet_hdr为0,就在移动buffer指针且修正size。因为这里buffer里面包含了头部,但是设备并没有使用
if (s->host_vnet_hdr_len && !s->using_vnet_hdr) {
buf += s->host_vnet_hdr_len;
size -= s->host_vnet_hdr_len;
} size = qemu_send_packet_async(&s->nc, buf, size, tap_send_completed);
if (size == ) {
tap_read_poll(s, false);
}
} while (size > && qemu_can_send_packet(&s->nc));
}
这里通过一个循环反复调用tap_read_packet函数从打开的设备文件中读取数据,并每一次读取完毕调用qemu_send_packet_async函数进行数据的发送。前者比较简单,就是普通的读文件操作,后者我们来看下
size_t qemu_send_packet_async(NetClientState *sender,
const uint8_t *buf, int size,
NetPacketSent *sent_cb)
{
return qemu_send_packet_async_with_flags(sender, QEMU_NET_PACKET_FLAG_NONE,
buf, size, sent_cb);
}
可以看到这里调用了qemu_send_packet_async_with_flags函数,那么继续深入
static ssize_t qemu_send_packet_async_with_flags(NetClientState *sender,
unsigned flags,
const uint8_t *buf, int size,
NetPacketSent *sent_cb)
{
NetQueue *queue; #ifdef DEBUG_NET
printf("qemu_send_packet_async:\n");
hex_dump(stdout, buf, size);
#endif
//如果发送端口的peer指针为空会发送失败,即不存在目标网卡
if (sender->link_down || !sender->peer) {
return size;
}
//获取对方的接收缓冲队列
queue = sender->peer->incoming_queue; return qemu_net_queue_send(queue, sender, flags, buf, size, sent_cb);
这里就做了一下实质性的判断,查看下net client的连接是否打开并且对方net client是否存在,不满足条件直接返回,通过的话就获取对方的接收队列sender->peer->incoming_queue;注意这里是对方的接收队列,下面可以看到实际上只是把数据从buffer中复制到队列维护的链表中了。
然后调用qemu_net_queue_send函数进行发送
ssize_t qemu_net_queue_send(NetQueue *queue,
NetClientState *sender,
unsigned flags,
const uint8_t *data,
size_t size,
NetPacketSent *sent_cb)
{
ssize_t ret;
//这里表示如果queue正在发送就直接把buffer附加到队列的package链表,当然是在条件允许的情况下
if (queue->delivering || !qemu_can_send_packet(sender)) {
qemu_net_queue_append(queue, sender, flags, data, size, sent_cb);
return ;
}
//否则需要启动队列进行数据的发送
ret = qemu_net_queue_deliver(queue, sender, flags, data, size);
if (ret == ) {
qemu_net_queue_append(queue, sender, flags, data, size, sent_cb);
return ;
}
//否则只能刷新下queue
qemu_net_queue_flush(queue); return ret;
}
该函数就要做具体的工作了,首先判断队列是否正在进行发送,是的话直接调用qemu_net_queue_append函数把buffer附加到queue的发送链表中,否则还需要重新启动队列发送,然后在附加到发送链表。
假如都不成功就只能调用qemu_net_queue_flush函数重置下队列。
static void qemu_net_queue_append(NetQueue *queue,
NetClientState *sender,
unsigned flags,
const uint8_t *buf,
size_t size,
NetPacketSent *sent_cb)
{
NetPacket *packet; if (queue->nq_count >= queue->nq_maxlen && !sent_cb) {
return; /* drop if queue full and no callback */
}
packet = g_malloc(sizeof(NetPacket) + size);
packet->sender = sender;
packet->flags = flags;
packet->size = size;
packet->sent_cb = sent_cb;
memcpy(packet->data, buf, size); queue->nq_count++;
QTAILQ_INSERT_TAIL(&queue->packets, packet, entry);
}
可以看到这里做的工作很简单,就是分配一个package把数据复制到里面,然后插入发送链表。
Hub 端
前面结合源代码大致分析了下Tap端数据的发送接收流程,本节介绍下Hub接收并转发数据包的流程,代码大部分都在hub.c中
相比前面的Tap。这里Hub完成的工作就要简单的多,代码量也要少很多,因为它其实并不分哪一端,只负责转发数据包,看下net_hub_receive函数
static ssize_t net_hub_receive(NetHub *hub, NetHubPort *source_port,
const uint8_t *buf, size_t len)
{
NetHubPort *port;
//收到数据包就从其他端口转发
QLIST_FOREACH(port, &hub->ports, next) {
if (port == source_port) {
continue;
} qemu_send_packet(&port->nc, buf, len);
}
return len;
}
这里可以看到遍历Hub上的所有端口,然后调用qemu_send_packet函数对单个端口进行发送数据,其中忽略source_port。还有一个函数和这个函数相对就是net_hub_receive_iov,该函数以IO向量的方式对数据包做处理
static ssize_t net_hub_receive_iov(NetHub *hub, NetHubPort *source_port,
const struct iovec *iov, int iovcnt)
{
NetHubPort *port;
ssize_t len = iov_size(iov, iovcnt); QLIST_FOREACH(port, &hub->ports, next) {
if (port == source_port) {
continue;
} qemu_sendv_packet(&port->nc, iov, iovcnt);
}
return len;
}
前面依然是遍历端口,不同的最后调用qemu_sendv_packet函数
ssize_t
qemu_sendv_packet(NetClientState *nc, const struct iovec *iov, int iovcnt)
{
return qemu_sendv_packet_async(nc, iov, iovcnt, NULL);
}
ssize_t qemu_sendv_packet_async(NetClientState *sender,
const struct iovec *iov, int iovcnt,
NetPacketSent *sent_cb)
{
NetQueue *queue; if (sender->link_down || !sender->peer) {
return iov_size(iov, iovcnt);
} queue = sender->peer->incoming_queue; return qemu_net_queue_send_iov(queue, sender,
QEMU_NET_PACKET_FLAG_NONE,
iov, iovcnt, sent_cb);
}
该函数是核心函数,这里的内容和前面Tap发送函数有些类似,sender是Hub上的转发端口的NetClientState结构,这里发送的 方式也是向对方的incoming_queue copy数据,唯一的区别在于这里采用的IO向量的方式,前面IO向量我们忽略了,这里就分析下,直接看向队列的链表中添加package的函数qemu_net_queue_append_iov
static void qemu_net_queue_append_iov(NetQueue *queue,
NetClientState *sender,
unsigned flags,
const struct iovec *iov,
int iovcnt,
NetPacketSent *sent_cb)
{
NetPacket *packet;
size_t max_len = ;
int i; if (queue->nq_count >= queue->nq_maxlen && !sent_cb) {
return; /* drop if queue full and no callback */
}
for (i = ; i < iovcnt; i++) {
max_len += iov[i].iov_len;
} packet = g_malloc(sizeof(NetPacket) + max_len);
packet->sender = sender;
packet->sent_cb = sent_cb;
packet->flags = flags;
packet->size = ; for (i = ; i < iovcnt; i++) {
size_t len = iov[i].iov_len; memcpy(packet->data + packet->size, iov[i].iov_base, len);
packet->size += len;
} queue->nq_count++;
QTAILQ_INSERT_TAIL(&queue->packets, packet, entry);
}
这里首先判断queue的发送链表是否已满,然后获取数据的长度,需要结合所有IO向量包含的数据长度和,最后申请一段内存做package,需要包含所有的数据以及NetPacket结构,并对package做一些参数的设置。然后逐项从向量代表的buffer中复制数据。最后在一次性的把整个package插入链表。
NIC端
终于到了关键的时刻,这里其实函数不多,但是函数体很庞大,我们看e1000网卡的接收数据流程,先看e1000_receive函数
static ssize_t
e1000_receive(NetClientState *nc, const uint8_t *buf, size_t size)
{
const struct iovec iov = {
.iov_base = (uint8_t *)buf,
.iov_len = size
}; return e1000_receive_iov(nc, &iov, );
}
这里不管有没有使用IO向量都把数据封装到了一个向量里面,然后调用e1000_receive_iov函数,该函数的函数体比较庞大,按模块分析的话也并不难。
static size_t e1000_receive_iov(NetClientState *nc, const struct iovec *iov, int iovcnt)
{
E1000State *s = qemu_get_nic_opaque(nc);
PCIDevice *d = PCI_DEVICE(s);
struct e1000_rx_desc desc;
dma_addr_t base;
unsigned int n, rdt;
uint32_t rdh_start;
uint16_t vlan_special = ;
uint8_t vlan_status = ;
uint8_t min_buf[MIN_BUF_SIZE];
struct iovec min_iov;
uint8_t *filter_buf = iov->iov_base;
size_t size = iov_size(iov, iovcnt);
size_t iov_ofs = ;
size_t desc_offset;
size_t desc_size;
size_t total_size; if (!(s->mac_reg[STATUS] & E1000_STATUS_LU)) {
return -;
} if (!(s->mac_reg[RCTL] & E1000_RCTL_EN)) {
return -;
} /* Pad to minimum Ethernet frame length */
if (size < sizeof(min_buf)) {
iov_to_buf(iov, iovcnt, , min_buf, size);
memset(&min_buf[size], , sizeof(min_buf) - size);
min_iov.iov_base = filter_buf = min_buf;
min_iov.iov_len = size = sizeof(min_buf);
iovcnt = ;
iov = &min_iov;
} else if (iov->iov_len < MAXIMUM_ETHERNET_HDR_LEN) {
/* This is very unlikely, but may happen. */
iov_to_buf(iov, iovcnt, , min_buf, MAXIMUM_ETHERNET_HDR_LEN);
filter_buf = min_buf;
} /* Discard oversized packets if !LPE and !SBP. */
if ((size > MAXIMUM_ETHERNET_LPE_SIZE ||
(size > MAXIMUM_ETHERNET_VLAN_SIZE
&& !(s->mac_reg[RCTL] & E1000_RCTL_LPE)))
&& !(s->mac_reg[RCTL] & E1000_RCTL_SBP)) {
return size;
}
//先对数据包进行过滤
if (!receive_filter(s, filter_buf, size)) {
return size;
}
//如果网卡支持vlan并且数据包是vlan数据包
if (vlan_enabled(s) && is_vlan_packet(s, filter_buf)) {
vlan_special = cpu_to_le16(be16_to_cpup((uint16_t *)(filter_buf
+ )));
iov_ofs = ;
if (filter_buf == iov->iov_base) {
memmove(filter_buf + , filter_buf, );//destination,src,count
} else {
iov_from_buf(iov, iovcnt, , filter_buf, );
while (iov->iov_len <= iov_ofs) {
iov_ofs -= iov->iov_len;
iov++;
}
}
vlan_status = E1000_RXD_STAT_VP;
size -= ;
} rdh_start = s->mac_reg[RDH];
desc_offset = ;
total_size = size + fcs_len(s);//加上crc校验
if (!e1000_has_rxbufs(s, total_size)) {
set_ics(s, , E1000_ICS_RXO);
return -;
}
do {
desc_size = total_size - desc_offset;
if (desc_size > s->rxbuf_size) {
desc_size = s->rxbuf_size;
}
base = rx_desc_base(s) + sizeof(desc) * s->mac_reg[RDH];
pci_dma_read(d, base, &desc, sizeof(desc));
desc.special = vlan_special;
desc.status |= (vlan_status | E1000_RXD_STAT_DD);
if (desc.buffer_addr) {
if (desc_offset < size) {
size_t iov_copy;
hwaddr ba = le64_to_cpu(desc.buffer_addr);
size_t copy_size = size - desc_offset;
if (copy_size > s->rxbuf_size) {
copy_size = s->rxbuf_size;
}
do {
iov_copy = MIN(copy_size, iov->iov_len - iov_ofs);
pci_dma_write(d, ba, iov->iov_base + iov_ofs, iov_copy);
copy_size -= iov_copy;
ba += iov_copy;
iov_ofs += iov_copy;
if (iov_ofs == iov->iov_len) {
iov++;
iov_ofs = ;
}
} while (copy_size);
}
desc_offset += desc_size;
desc.length = cpu_to_le16(desc_size);
if (desc_offset >= total_size) {
desc.status |= E1000_RXD_STAT_EOP | E1000_RXD_STAT_IXSM;
} else {
/* Guest zeroing out status is not a hardware requirement.
Clear EOP in case guest didn't do it. */
desc.status &= ~E1000_RXD_STAT_EOP;
}
} else { // as per intel docs; skip descriptors with null buf addr
DBGOUT(RX, "Null RX descriptor!!\n");
}
pci_dma_write(d, base, &desc, sizeof(desc)); if (++s->mac_reg[RDH] * sizeof(desc) >= s->mac_reg[RDLEN])
s->mac_reg[RDH] = ;
/* see comment in start_xmit; same here */
if (s->mac_reg[RDH] == rdh_start) {
DBGOUT(RXERR, "RDH wraparound @%x, RDT %x, RDLEN %x\n",
rdh_start, s->mac_reg[RDT], s->mac_reg[RDLEN]);
set_ics(s, , E1000_ICS_RXO);
return -;
}
} while (desc_offset < total_size); s->mac_reg[GPRC]++;
s->mac_reg[TPR]++;
/* TOR - Total Octets Received:
* This register includes bytes received in a packet from the <Destination
* Address> field through the <CRC> field, inclusively.
*/
n = s->mac_reg[TORL] + size + /* Always include FCS length. */ ;
if (n < s->mac_reg[TORL])
s->mac_reg[TORH]++;
s->mac_reg[TORL] = n; n = E1000_ICS_RXT0;
if ((rdt = s->mac_reg[RDT]) < s->mac_reg[RDH])
rdt += s->mac_reg[RDLEN] / sizeof(desc);
if (((rdt - s->mac_reg[RDH]) * sizeof(desc)) <= s->mac_reg[RDLEN] >>
s->rxbuf_min_shift)
n |= E1000_ICS_RXDMT0; set_ics(s, , n); return size;
}
结合上面的代码,首先进行的是判断数据的长度是否满足一个最小以太网帧的长度,如果不满足就必须按照以太网帧的最小长度对齐,即后面填充0即可。
然后丢弃超过最大标准的数据包;
接着就调用receive_filter函数对数据包进行过滤,这是数据链路层的过滤,需要判断数据包的类型(广播、组播或者网卡是混杂模式都直接接收),如果是单播需要分析链路层头部,比对MAC地址。
然后下面的do循环中就开始数据的写入,这是直接采用DMA的方式吧数据直接写入到客户机内存,然后向客户机注入软中断通知客户机。
写入的方式比较复杂,但是主要是逻辑混乱,也不难理解,这里就不重点描述。
最后写入完成调用set_ics注入软中断。剩下的就是客户机的操作了。
而E1000的发送函数就是start_xmit函数,位于E1000.c中。
static void
start_xmit(E1000State *s)
{
PCIDevice *d = PCI_DEVICE(s);
dma_addr_t base;
struct e1000_tx_desc desc;
uint32_t tdh_start = s->mac_reg[TDH], cause = E1000_ICS_TXQE; if (!(s->mac_reg[TCTL] & E1000_TCTL_EN)) {
DBGOUT(TX, "tx disabled\n");
return;
} while (s->mac_reg[TDH] != s->mac_reg[TDT]) {
base = tx_desc_base(s) +
sizeof(struct e1000_tx_desc) * s->mac_reg[TDH];
pci_dma_read(d, base, &desc, sizeof(desc)); DBGOUT(TX, "index %d: %p : %x %x\n", s->mac_reg[TDH],
(void *)(intptr_t)desc.buffer_addr, desc.lower.data,
desc.upper.data); process_tx_desc(s, &desc);
cause |= txdesc_writeback(s, base, &desc); if (++s->mac_reg[TDH] * sizeof(desc) >= s->mac_reg[TDLEN])
s->mac_reg[TDH] = ;
/*
* the following could happen only if guest sw assigns
* bogus values to TDT/TDLEN.
* there's nothing too intelligent we could do about this.
*/
if (s->mac_reg[TDH] == tdh_start) {
DBGOUT(TXERR, "TDH wraparound @%x, TDT %x, TDLEN %x\n",
tdh_start, s->mac_reg[TDT], s->mac_reg[TDLEN]);
break;
}
}
set_ics(s, , cause);
}
具体的步骤和接收数据的模式类似,网卡的发送寄存器会包含数据包的head和tail,如果两者不一致就说明有新数据包。然后获取发送缓冲区的地址,注意这里需要先获取对应本次传输数据的e1000_tx_desc结构,这也是首次调用pci_dma_read函数的作用,该结构中记录了数据buffer的实际地址,这个地址是需要再次通过DMA读取。获取到desc描述符后,就调用process_tx_desc(s, &desc)函数进行具体的传输数据DMA操作。
static void
process_tx_desc(E1000State *s, struct e1000_tx_desc *dp)
{
PCIDevice *d = PCI_DEVICE(s);
uint32_t txd_lower = le32_to_cpu(dp->lower.data);
uint32_t dtype = txd_lower & (E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D);
unsigned int split_size = txd_lower & 0xffff, bytes, sz, op;
unsigned int msh = 0xfffff;
uint64_t addr;
struct e1000_context_desc *xp = (struct e1000_context_desc *)dp;
struct e1000_tx *tp = &s->tx; s->mit_ide |= (txd_lower & E1000_TXD_CMD_IDE);
if (dtype == E1000_TXD_CMD_DEXT) { // context descriptor
op = le32_to_cpu(xp->cmd_and_length);
tp->ipcss = xp->lower_setup.ip_fields.ipcss;
tp->ipcso = xp->lower_setup.ip_fields.ipcso;
tp->ipcse = le16_to_cpu(xp->lower_setup.ip_fields.ipcse);
tp->tucss = xp->upper_setup.tcp_fields.tucss;
tp->tucso = xp->upper_setup.tcp_fields.tucso;
tp->tucse = le16_to_cpu(xp->upper_setup.tcp_fields.tucse);
tp->paylen = op & 0xfffff;
tp->hdr_len = xp->tcp_seg_setup.fields.hdr_len;
tp->mss = le16_to_cpu(xp->tcp_seg_setup.fields.mss);
tp->ip = (op & E1000_TXD_CMD_IP) ? : ;
tp->tcp = (op & E1000_TXD_CMD_TCP) ? : ;
tp->tse = (op & E1000_TXD_CMD_TSE) ? : ;
tp->tso_frames = ;
if (tp->tucso == ) { // this is probably wrong
DBGOUT(TXSUM, "TCP/UDP: cso 0!\n");
tp->tucso = tp->tucss + (tp->tcp ? : );
}
return;
} else if (dtype == (E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D)) {
// data descriptor
if (tp->size == ) {
tp->sum_needed = le32_to_cpu(dp->upper.data) >> ;
}
tp->cptse = ( txd_lower & E1000_TXD_CMD_TSE ) ? : ;
} else {
// legacy descriptor
tp->cptse = ;
} if (vlan_enabled(s) && is_vlan_txd(txd_lower) &&
(tp->cptse || txd_lower & E1000_TXD_CMD_EOP)) {
tp->vlan_needed = ;
stw_be_p(tp->vlan_header,
le16_to_cpup((uint16_t *)(s->mac_reg + VET)));
stw_be_p(tp->vlan_header + ,
le16_to_cpu(dp->upper.fields.special));
}
/*这里就是获取客户机中数据buffer的地址*/
addr = le64_to_cpu(dp->buffer_addr);
if (tp->tse && tp->cptse) {
msh = tp->hdr_len + tp->mss;
do {
bytes = split_size;
if (tp->size + bytes > msh)
bytes = msh - tp->size; bytes = MIN(sizeof(tp->data) - tp->size, bytes);
pci_dma_read(d, addr, tp->data + tp->size, bytes);
sz = tp->size + bytes;
if (sz >= tp->hdr_len && tp->size < tp->hdr_len) {
memmove(tp->header, tp->data, tp->hdr_len);
}
tp->size = sz;
addr += bytes;
if (sz == msh) {
xmit_seg(s);
memmove(tp->data, tp->header, tp->hdr_len);
tp->size = tp->hdr_len;
}
} while (split_size -= bytes);
} else if (!tp->tse && tp->cptse) {
// context descriptor TSE is not set, while data descriptor TSE is set
DBGOUT(TXERR, "TCP segmentation error\n");
} else {
split_size = MIN(sizeof(tp->data) - tp->size, split_size);
pci_dma_read(d, addr, tp->data + tp->size, split_size);
tp->size += split_size;
} if (!(txd_lower & E1000_TXD_CMD_EOP))
return;
if (!(tp->tse && tp->cptse && tp->size < tp->hdr_len)) {
xmit_seg(s);
}
tp->tso_frames = ;
tp->sum_needed = ;
tp->vlan_needed = ;
tp->size = ;
tp->cptse = ;
函数中需要首先判断描述符是什么类型。比较关键的是E1000_TXD_CMD_DEXT和E1000_TXD_DTYP_D,
然后调用pci_dma_read函数把内存(客户机内存)中的数据读到设备缓冲区中,这点和实际的DMA道理是一样的。
然后调用xmit_seg
static void
xmit_seg(E1000State *s)
{
uint16_t len, *sp;
unsigned int frames = s->tx.tso_frames, css, sofar, n;
struct e1000_tx *tp = &s->tx; if (tp->tse && tp->cptse) {
css = tp->ipcss;
DBGOUT(TXSUM, "frames %d size %d ipcss %d\n",
frames, tp->size, css);
if (tp->ip) { // IPv4
stw_be_p(tp->data+css+, tp->size - css);
stw_be_p(tp->data+css+,
be16_to_cpup((uint16_t *)(tp->data+css+))+frames);
} else // IPv6
stw_be_p(tp->data+css+, tp->size - css);
css = tp->tucss;
len = tp->size - css;
DBGOUT(TXSUM, "tcp %d tucss %d len %d\n", tp->tcp, css, len);
if (tp->tcp) {
sofar = frames * tp->mss;
stl_be_p(tp->data+css+, ldl_be_p(tp->data+css+)+sofar); /* seq */
if (tp->paylen - sofar > tp->mss)
tp->data[css + ] &= ~; // PSH, FIN
} else // UDP
stw_be_p(tp->data+css+, len);
if (tp->sum_needed & E1000_TXD_POPTS_TXSM) {
unsigned int phsum;
// add pseudo-header length before checksum calculation
sp = (uint16_t *)(tp->data + tp->tucso);
phsum = be16_to_cpup(sp) + len;
phsum = (phsum >> ) + (phsum & 0xffff);
stw_be_p(sp, phsum);
}
tp->tso_frames++;
} if (tp->sum_needed & E1000_TXD_POPTS_TXSM)
putsum(tp->data, tp->size, tp->tucso, tp->tucss, tp->tucse);
if (tp->sum_needed & E1000_TXD_POPTS_IXSM)
putsum(tp->data, tp->size, tp->ipcso, tp->ipcss, tp->ipcse);
if (tp->vlan_needed) {
memmove(tp->vlan, tp->data, );
memmove(tp->data, tp->data + , );
memcpy(tp->data + , tp->vlan_header, );
e1000_send_packet(s, tp->vlan, tp->size + );
} else
e1000_send_packet(s, tp->data, tp->size);
s->mac_reg[TPT]++;
s->mac_reg[GPTC]++;
n = s->mac_reg[TOTL];
if ((s->mac_reg[TOTL] += s->tx.size) < n)
s->mac_reg[TOTH]++;
}
然后调用e1000_send_packet
static void
e1000_send_packet(E1000State *s, const uint8_t *buf, int size)
{
NetClientState *nc = qemu_get_queue(s->nic);
if (s->phy_reg[PHY_CTRL] & MII_CR_LOOPBACK) {
nc->info->receive(nc, buf, size);
} else {
qemu_send_packet(nc, buf, size);
}
}
最后就是qemu_send_packet,这就是之前我们分析过的函数了!!
最后放一张逻辑图示,旨在说明 数据包的流向,注意是逻辑图:
参考:qemu源码
linux内核源码
qemu网络虚拟化之数据流向分析三的更多相关文章
- qemu网络虚拟化之数据流向分析二
2016-09-27 上篇文章大致介绍了qemu网络虚拟化相关的数据结构,本篇就结合qemu-kvm源代码分析下各个数据结构是如何初始化以及建立联系的. 这里还是分为三个部分: 1.Tap设备区 2. ...
- qemu网络虚拟化之数据流向分析一
插曲: 今天下午欣喜的想写点关于qemu网络部分的功能,但是中途出现了点小插曲,电脑被某人搞得死机了,并且文章也没有保存.结果,,,就只能重新写了!!所以这里强烈建议开发团队提供自动保存的功能! ...
- MapReduce数据流向分析
MR数据流向示意图 步骤 1 输入文件从HDFS流向Mapper节点.在一般情况下,map所需要的数据就存在本节点,这就是数据本地化计算的优势,但是往往集群中数据分布不均衡(1000台节点,数据冗余度 ...
- KDD Cup 99网络入侵检测数据的分析
看论文 该数据集是从一个模拟的美国空军局域网上采集来的 9 个星期的网络连接数据, 分成具有标识的训练数据和未加标识的测试数据.测试数据和训练数据有着不同的概率分布, 测试数据包含了一些未出现在训练数 ...
- 深入浅出Hyper-V网络虚拟化(序)
服务器虚拟化已经越来越普及,很多企业也都在尝试着将现有业务转换成虚拟化的方式来运行,即在一个物理服务器上虚拟出多个实例,而每个实例彼此隔离,就好像在使用一台真实主机一样:网络虚拟化也同样如此,在同一条 ...
- 网络数据包分析 网卡Offload
http://blog.nsfocus.net/network-packets-analysis-nic-offload/ 对于网络安全来说,网络传输数据包的捕获和分析是个基础工作,绿盟科技研 ...
- 数据中心网络技术新贵:VXLAN与园区网络虚拟化
摘要:为了应对传统数据中心网络对服务器虚拟化技术的限制,VXLAN技术应运而生. 1 概述 传统数据中心网络面临的问题 虚拟机规模受设备表项规格限制 在传统二层网络中,交换机通过查询MAC地址表来转发 ...
- 一:Neutron实现网络虚拟化
一 云计算时代数据中心物理网络的问题 数据中心虚拟化成为了趋势,最典型的场景莫过于:对数据中心的服务器进行虚拟化,来提高资源利用率,同时降低单位能耗. 但是,随着数据中心虚拟化程度的不断提高.虚拟化服 ...
- Neutron 理解 (1): Neutron 所实现的网络虚拟化 [How Neutron Virtualizes Network]
学习 Neutron 系列文章: (1)Neutron 所实现的网络虚拟化 (2)Neutron OpenvSwitch + VLAN 虚拟网络 (3)Neutron OpenvSwitch + GR ...
随机推荐
- sql 记录
INSERT INTO B([name],[info]) SELECT [name,'10'] FROM A 级联更新1:update tb1, tb2 set tb1.a=tb2.a,tb1.b=t ...
- ffmpeg 从内存中读取数据 .
http://blog.csdn.net/leixiaohua1020/article/details/12980423 ——————————————————————————————————————— ...
- Hadoop源码分析之数据节点的握手,注册,上报数据块和心跳
转自:http://www.it165.net/admin/html/201402/2382.html 在上一篇文章Hadoop源码分析之DataNode的启动与停止中分析了DataNode节点的启动 ...
- Classification / Recognition
转载 https://handong1587.github.io/deep_learning/2015/10/09/recognition.html#facenet Classification / ...
- 【BZOJ】1689: [Usaco2005 Open] Muddy roads 泥泞的路(贪心)
http://www.lydsy.com/JudgeOnline/problem.php?id=1689 一开始我也想到了贪心,,,策略是如果两个连续的水池的距离小于l的话,那么就将他们链接起来,,, ...
- 【剑指offer】翻转单词顺序
转载请注明出处:http://blog.csdn.net/ns_code/article/details/27372033 题目描写叙述: JOBDU近期来了一个新员工Fish,每天早晨总是会拿着一本 ...
- ThinkPHP项目笔记之数据库配置篇
对于配置文件,有几点说明 common:公共配置,也就是前台,后台,都可以调用的文件,具有普遍性 前台/后台:就是针对前后台的配置文件,具有针对性. 如:(公共文件基本配置) <?php ret ...
- 《Linux实验要求》
实验 1:登录和使用基本的 Linux 命令 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统. 有另外一个无特权用户 student,密码 ...
- asp.net后台cs中的JSON格式变量在前台Js中调用方法(前后台示例代码)
//后台cs代码: using System; using System.Collections.Generic; using System.Linq; using System.Web; using ...
- es6 javascript的class的静态方法、属性和实例属性
http://blog.csdn.net/qq_30100043/article/details/53542966