跟踪一次网络发送

一、报文的分层转发

当我们上层通过write来发送消息的时候,会走到socket文件系统的发送接口。

首先,socket是整个系统中所有网络设备在用户态的一个抽象,也就是一个socket可以为appletalk,bluetooth一样,代表不同的协议类型。所以此时首先经过一次协议类型的转发,整个最为上层的就是我们通常最为常见的IPV6协议,调用的接口为

const struct proto_ops inet_stream_ops = {
 .family     = PF_INET,
 .owner     = THIS_MODULE,
 .release    = inet_release,
 .bind     = inet_bind,
 .connect    = inet_stream_connect,
 .socketpair    = sock_no_socketpair,
 .accept     = inet_accept,
 .getname    = inet_getname,
 .poll     = tcp_poll,
 .ioctl     = inet_ioctl,
 .listen     = inet_listen,
 .shutdown    = inet_shutdown,
 .setsockopt    = sock_common_setsockopt,
 .getsockopt    = sock_common_getsockopt,
 .sendmsg    = inet_sendmsg,
 .recvmsg    = inet_recvmsg,
 .mmap     = sock_no_mmap,
 .sendpage    = inet_sendpage,
 .splice_read    = tcp_splice_read,
#ifdef CONFIG_COMPAT
 .compat_setsockopt = compat_sock_common_setsockopt,
 .compat_getsockopt = compat_sock_common_getsockopt,
#endif
};

当消息确定为IPV4协议之后,就可以继续确定是以太网上的哪个协议,此时就可以有TCP/UDP/Raw之类的再次转发,此时经过的就是TCP的send

struct proto tcp_prot = {
 .name   = "TCP",
 .owner   = THIS_MODULE,
 .close   = tcp_close,
 .connect  = tcp_v4_connect,
 .disconnect  = tcp_disconnect,
 .accept   = inet_csk_accept,
 .ioctl   = tcp_ioctl,
 .init   = tcp_v4_init_sock,
 .destroy  = tcp_v4_destroy_sock,
 .shutdown  = tcp_shutdown,
 .setsockopt  = tcp_setsockopt,
 .getsockopt  = tcp_getsockopt,
 .recvmsg  = tcp_recvmsg,
 .sendmsg  = tcp_sendmsg,
 .sendpage  = tcp_sendpage,
 .backlog_rcv  = tcp_v4_do_rcv,
 .hash   = inet_hash,
 .unhash   = inet_unhash,
 .get_port  = inet_csk_get_port,
 .enter_memory_pressure = tcp_enter_memory_pressure,

注意:这个函数是IPV4和IPV6公用的一个函数。

在该函数中,完成的操作为

tcp_push--->>__tcp_push_pending_frames--->>tcp_write_xmit---->>tcp_transmit_skb--->>ip_queue_xmit(注意,这个函数使用了我们另一个比较关系的概念,就是路由表查询的概念)-->>ip_local_out-->>ip_output--->>ip_finish_output-->>dev_queue_xmit-->>dev_queue_xmit--->>>dev_hard_start_xmit,然后以我们最为熟悉的realtek为例说明:

static const struct net_device_ops ne2k_netdev_ops = {
 .ndo_open  = ne2k_pci_open,
 .ndo_stop  = ne2k_pci_close,
 .ndo_start_xmit  = ei_start_xmit,
 .ndo_tx_timeout  = ei_tx_timeout,

二、网络设备的中断申请

根据Linux当前的设备分类方法,网络设备已经和block char 设备一样,成为系统中一个单独的、和前两者主流设备并列的设备。

通过内核的调试,可以知道系统中刚启动的时候是通过ioctrl的IOUP来设置一个设备开始进入工作状态,然后对应的可以通过down或者close来关闭一个设备,这些也就是我们经常来进行一个设备启动和关闭时进行的操作。

同样对于realtek设备的初始化路径

devinet_ioctl-->>>dev_change_flags---->>>dev_change_flags--->>>__dev_open-->>>ne2k_pci_open

其中对于设备的打开操作,其中执行了一个比较华丽的操作:

if ((old_flags ^ flags) & IFF_UP) { /* Bit is different  ? */
  ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev);这是对函数指针直接的操作,看来没有做不到,只有想不到

在这个设备的打开过程中,执行了中断向量的申请操作。

 int ret = request_irq(dev->irq, ei_interrupt, IRQF_SHARED, dev->name, dev);
三、中断函数的处理__ei_interrupt

 if (interrupts & ENISR_OVER)
   ei_rx_overrun(dev);
  else if (interrupts & (ENISR_RX+ENISR_RX_ERR))
  {
   /* Got a good (?) packet. */
   ei_receive(dev);
  }
  /* Push the next to-transmit packet through. */
  if (interrupts & ENISR_TX)
   ei_tx_intr(dev);
  else if (interrupts & ENISR_TX_ERR)
   ei_tx_err(dev);

者三个处理函数和我们用户进程看到的东西类似,包含了标准输出、标准输入和标准错误三个接口,分别处理网卡的接受、输入和错误

现在看一下接收到的数据时如何进行的

   skb = dev_alloc_skb(pkt_len+2);
    skb_reserve(skb,2); /* IP headers on 16 byte boundaries */
    skb_put(skb, pkt_len); /* Make room */
    ei_block_input(dev, pkt_len, skb, current_offset + sizeof(rx_frame));
    skb->protocol=eth_type_trans(skb,dev);
    netif_rx(skb);
    dev->stats.rx_packets++;
    dev->stats.rx_bytes += pkt_len; 一些统计信息的增加
    if (pkt_stat & ENRSR_PHY)
     dev->stats.multicast++;

接下来的操作为:
enqueue_to_backlog--->>>____napi_schedule

 __raise_softirq_irqoff(NET_RX_SOFTIRQ);
可以看到,对于网卡数据的接受是通过软中断来实现的,而且也可以看到,对于网卡接收到的数据是最早挂在一个设备对应的全局结构中的

通过代码可以知道,其中的全局队列就是

   __skb_queue_tail(&sd->input_pkt_queue, skb);
中使用的input_pkt_queue,所有的网卡接受的信息都应该放在这个设备上,从而等待进一步的路由。

四、网卡软中断的处理

linux-2.6.37.1\net\core\dev.c

static int __init net_dev_init(void)

 open_softirq(NET_TX_SOFTIRQ, net_tx_action);
 open_softirq(NET_RX_SOFTIRQ, net_rx_action);

 

接下来是软中断中的处理:

  sd->backlog.poll = process_backlog;
其中在软中断中的处理

static void net_rx_action(struct softirq_action *h)
奇怪的是其中使用的是这个polllist

 while (!list_empty(&sd->poll_list)) {
这个结构的设置位于enqueue_to_backlog函数

  /* Schedule NAPI for backlog device
   * We can use non atomic operation since we own the queue lock
   */
  if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
   if (!rps_ipi_queued(sd))
    ____napi_schedule(sd, &sd->backlog);

向上继续操作

process_backlog-->>>__netif_receive_skb--->>deliver_skb

以ARP报文为例说明一下

static struct packet_type arp_packet_type __read_mostly = {
 .type = cpu_to_be16(ETH_P_ARP),
 .func = arp_rcv,
};

arp_rcv--->>>arp_process--->>>arp_send-->>arp_xmit--->>>dev_queue_xmit

这样我们就完成了一个闭环的操作

posted on 2019-03-06 20:13  tsecer  阅读(131)  评论(0编辑  收藏  举报

导航