zoukankan      html  css  js  c++  java
  • 跟踪一次网络发送

    一、报文的分层转发

    当我们上层通过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 etcoredev.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

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

  • 相关阅读:
    2.1 maven配置多镜像地址
    6.4 SpringData JPA的使用
    4.3 thymeleaf模板引擎的使用
    java面试题整理
    eclipse配置运行时变量
    postman上传文件
    Python定义字符串、循环
    Charles抓包
    jmeter压测
    JMeter,postman
  • 原文地址:https://www.cnblogs.com/tsecer/p/10485676.html
Copyright © 2011-2022 走看看