xiaolincoder / xiaolincoding_comment

7 stars 0 forks source link

[Vssue]2.3 Linux 系统是如何收发网络包的? | 小林coding #16

Open xiaolincoder opened 2 years ago

xiaolincoder commented 2 years ago

http://121.43.173.240:8080/network/1_base/how_os_deal_network_package.html

zhuyongchn commented 2 years ago

给小林点个赞,以后就是常看的网站了

Jack-Lxx commented 2 years ago

LVS(Linux Virtual Server)即Linux虚拟服务器,是由章文嵩博士主导的开源负载均衡项目,目前LVS已经被集成到Linux内核模块中。

zelenezhang commented 2 years ago

有一个问题请教一下,Linux接收网络包流程中您举得例子 中断处理函数处理完需要「暂时屏蔽中断」,然后唤醒「软中断」来轮询处理数据,直到没有新数据时才恢复中断,这样一次中断处理多个网络包,这里的 屏蔽的中断 是指硬中断吗?如果网络包比较多,那CPU是不是有一段时间都在没有办法接收其他IO,我理解应该是触发软中断之后,就可以恢复硬中断了

c2ch commented 2 years ago
xiaolincoder commented 2 years ago

@c2ch

  • 错别字

    当网络包超过 MTU 的大小,就会在网络层分片,以确保分片后的 IP 包不会超过 MTU 大小,如果 MTU 越小,需要的分包就越多,那么网络吞吐能力就越差,相反的,如果 MTU 越大,需要的分包就越小,那么网络吞吐能力就越好。

    文中说如果MTU越小,需要的分包就越多,应当是MTU越大,需要的分包就越多。

这里没错哈,比如有一个1000字节的包,如果mtu是500字节,那需要分成2个500字节的包。如果mtu是100字节,那就要分成10个100字节的包。

c2ch commented 2 years ago

MTU不是可以最大1500字节嘛,如果是1000字节,一个MTU就够了吧。如果是2000字节,应该是要两个MTU?

发自我的iPhone

在 2022年3月28日,19:46,小林coding @.***> 写道:

 @c2ch

错别字 当网络包超过 MTU 的大小,就会在网络层分片,以确保分片后的 IP 包不会超过 MTU 大小,如果 MTU 越小,需要的分包就越多,那么网络吞吐能力就越差,相反的,如果 MTU 越大,需要的分包就越小,那么网络吞吐能力就越好。

文中说如果MTU越小,需要的分包就越多,应当是MTU越大,需要的分包就越多。

这里没错哈,比如有一个1000字节的包,如果mtu是500字节,那需要分成2个500字节的包。如果mtu是100字节,那就要分成10个100字节的包。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.

xiaolincoder commented 2 years ago

@c2ch

MTU不是可以最大1500字节嘛,如果是1000字节,一个MTU就够了吧。如果是2000字节,应该是要两个MTU?

MTU最大不是1500字节,只是在以太网默认是1500字节。文中的「如果 MTU 越小,需要的分包就越多,如果 MTU 越大,需要的分包就越小」这句话是没错的哈。我再看看我说的这个例子:比如有一个1000字节的包,如果mtu是500字节,那需要分成2个500字节的包。如果mtu是100字节,那就要分成10个100字节的包。可以看到,mtu越大,需要进行分包的次数就越小。

c2ch commented 2 years ago

所以这个MTU大小的分配是不固定的,它是我们可以指定的,还是自己随机分配大小的?

发自我的iPhone

在 2022年3月28日,20:00,小林coding @.***> 写道:

 @c2ch

MTU不是可以最大1500字节嘛,如果是1000字节,一个MTU就够了吧。如果是2000字节,应该是要两个MTU?

MTU最大不是1500字节,只是在以太网默认是1500字节。文中的「如果 MTU 越小,需要的分包就越多,如果 MTU 越大,需要的分包就越小」这句话是没错的哈。我再看看我说的这个例子:比如有一个1000字节的包,如果mtu是500字节,那需要分成2个500字节的包。如果mtu是100字节,那就要分成10个100字节的包。可以看到,mtu越大,需要进行分包的次数就越小。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.

xiaolincoder commented 2 years ago

@c2ch

所以这个MTU大小的分配是不固定的,它是我们可以指定的,还是自己随机分配大小的?

一般操作系统mtu默认是1500字节,我们自己也是可以设置大小的,比如 ifconfig eth0 mtu 1472 这样就把eth0网卡的mtu设置为1472字节。

c2ch commented 2 years ago

理解了,感谢。 我一开始把这个MTU误当成是发送的网络包大小了。

发自我的iPhone

在 2022年3月28日,20:07,小林coding @.***> 写道:

 @c2ch

所以这个MTU大小的分配是不固定的,它是我们可以指定的,还是自己随机分配大小的?

一般操作系统mtu默认是1500字节,我们自己也是可以设置大小的,比如 ifconfig eth0 mtu 1472 这样就把eth0网卡的mtu设置为1472字节。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.

xiaolincoder commented 2 years ago

@zelenezhang

有一个问题请教一下,Linux接收网络包流程中您举得例子 中断处理函数处理完需要「暂时屏蔽中断」,然后唤醒「软中断」来轮询处理数据,直到没有新数据时才恢复中断,这样一次中断处理多个网络包,这里的 屏蔽的中断 是指硬中断吗?如果网络包比较多,那CPU是不是有一段时间都在没有办法接收其他IO,我理解应该是触发软中断之后,就可以恢复硬中断了

嗯,屏蔽的硬件中断。

void __napi_schedule(struct napi_struct *n)
{
    unsigned long flags;

   //关闭硬件中断
    local_irq_save(flags);
    ____napi_schedule(this_cpu_ptr(&softnet_data), n);
   //恢复硬件中断
    local_irq_restore(flags);
}

static inline void ____napi_schedule(struct softnet_data *sd,
                     struct napi_struct *napi)
{
    list_add_tail(&napi->poll_list, &sd->poll_list);
    //唤醒软中断
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

你可以去看这篇源码分析:设备收发包之NAPI/非NAPI方式收包

xiaolincoder commented 2 years ago

@xiaolincoder

@zelenezhang

有一个问题请教一下,Linux接收网络包流程中您举得例子 中断处理函数处理完需要「暂时屏蔽中断」,然后唤醒「软中断」来轮询处理数据,直到没有新数据时才恢复中断,这样一次中断处理多个网络包,这里的 屏蔽的中断 是指硬中断吗?如果网络包比较多,那CPU是不是有一段时间都在没有办法接收其他IO,我理解应该是触发软中断之后,就可以恢复硬中断了

嗯,屏蔽的硬件中断。

void __napi_schedule(struct napi_struct *n)
{
    unsigned long flags;

   //关闭硬件中断
    local_irq_save(flags);
    ____napi_schedule(this_cpu_ptr(&softnet_data), n);
   //恢复硬件中断
    local_irq_restore(flags);
}

static inline void ____napi_schedule(struct softnet_data *sd,
                     struct napi_struct *napi)
{
    list_add_tail(&napi->poll_list, &sd->poll_list);
    //唤醒软中断
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

你可以去看这篇源码分析:设备收发包之NAPI/非NAPI方式收包

另外,软中断处理函数会设置一个cpu执行时间(函数开头的time_limit和budget),如果超过了这个执行时间,就会放弃cpu,目的是保证网络包的接收不霸占CPU不放,等下次网卡再有硬中断过来的时候再处理剩下的接收数据包。

/* 收包软中断处理程序 */
static __latent_entropy void net_rx_action(struct softirq_action *h)
{
    struct softnet_data *sd = this_cpu_ptr(&softnet_data);
    unsigned long time_limit = jiffies +
        usecs_to_jiffies(netdev_budget_usecs);

    /* 遍历列表 */
    for (;;) {
         ....

        /* If softirq window is exhausted then punt.
         * Allow this to run for 2 jiffies since which will allow
         * an average latency of 1.5/HZ.
         */
        /* 总配额用尽,或者中断时间窗口用尽,跳出 */
        if (unlikely(budget <= 0 ||
                 time_after_eq(jiffies, time_limit))) {
            sd->time_squeeze++;
            break;
        }
    }

}
yezhening commented 2 years ago

2022.4.13,看完

yezhening commented 2 years ago

@c2ch

  • 错别字

    当网络包超过 MTU 的大小,就会在网络层分片,以确保分片后的 IP 包不会超过 MTU 大小,如果 MTU 越小,需要的分包就越多,那么网络吞吐能力就越差,相反的,如果 MTU 越大,需要的分包就越小,那么网络吞吐能力就越好。

    文中说如果MTU越小,需要的分包就越多,应当是MTU越大,需要的分包就越多。

没错。MTU越大,能存的数据越多,也就不需要再细分那么多包了。类比一个袋子能装完十个苹果,就不会用两个袋子了。

guangyu890 commented 2 years ago

没玩没了打错了,应该是没完没了。

banyefengchen commented 2 years ago

文章中最后提到的

这一些准备好后,会触发软中断告诉网卡驱动程序,这里有新的网络包需要发送,最后驱动程序通过 DMA,从发包队列中读取网络包,将其放入到硬件网卡的队列中,随后物理网卡再将它发送出去。

这里的软中断是啥意思,软中断不是linux内核的中断下半部吗?这里是否应该是个ipc通知。

banyefengchen commented 2 years ago

其次关于文章中提到的: 中断处理函数处理完需要「暂时屏蔽中断」,然后唤醒「软中断」来轮询处理数据,直到没有新数据时才恢复中断,这样一次中断处理多个网络包,于是就可以降低网卡中断带来的性能开销。 似乎也不是很准确,恢复中断应该是唤醒操作一结束就恢复了,而不是直到没有新数据才恢复中断。详见linux的中断上半部和下半部。

imjianjian commented 2 years ago

总结的第一句有误:【电脑与电脑之间通常都是通话网卡、交换机、路由器等......】,应该是【通过】吧

jemmy512 commented 2 years ago

这两处用拷贝描述不是很准确,会让人误以为发生了数据拷贝;实际上是传递 sk_buff 指针:

  1. 那软中断是怎么处理网络包的呢?它会从 Ring Buffer 中拷贝数据到内核 struct sk_buff 缓冲区中
  2. 根据四元组「源 IP、源端口、目的 IP、目的端口」 作为标识,找出对应的 Socket,并把数据拷贝到 Socket 的接收缓冲区
xiaolincoder commented 2 years ago

@Jemmy512

这两处用拷贝描述不是很准确,会让人误以为发生了数据拷贝;实际上是传递 sk_buff 指针:

  1. 那软中断是怎么处理网络包的呢?它会从 Ring Buffer 中拷贝数据到内核 struct sk_buff 缓冲区中
  2. 根据四元组「源 IP、源端口、目的 IP、目的端口」 作为标识,找出对应的 Socket,并把数据拷贝到 Socket 的接收缓冲区

已修正

xiaolincoder commented 2 years ago

@imjianjian

总结的第一句有误:【电脑与电脑之间通常都是通话网卡、交换机、路由器等......】,应该是【通过】吧

已修正

JackMariano commented 2 years ago

有一点不太懂,触发软中断的时候硬中断的屏蔽恢复了吗?

xiaolincoder commented 2 years ago

@huboy-zhao

有一点不太懂,触发软中断的时候硬中断的屏蔽恢复了吗?

文中写了,会恢复硬中断

fallinggit commented 2 years ago

@xiaolincoder

@imjianjian

总结的第一句有误:【电脑与电脑之间通常都是通话网卡、交换机、路由器等......】,应该是【通过】吧

已修正

通过话网卡 多了个话

dablelv commented 2 years ago

“如果每一层都用一个结构体” 应该是 "如果每一层都用不同结构体"。

dablelv commented 2 years ago

大佬,想问下 sk_buff 挂到 RingBuffer,这一步不用发生拷贝吗?

dablelv commented 1 year ago

Linux 网络协议栈一图中,LVS 指的是什么?

xiaolincoder commented 1 year ago

@dablelv

Linux 网络协议栈一图中,LVS 指的是什么?

内核的负载均衡模块,常听说的四层负载均衡就是LVS模块实现的。

xiaolincoder commented 1 year ago

@dablelv

大佬,想问下 sk_buff 挂到 RingBuffer,这一步不用发生拷贝吗?

不用。

Ring Buffer 队列内存放的是一个个 Packet Descriptor ,其有两种状态: ready 和 used 。初始时 Descriptor 是空的,指向一个空的 sk_buff,处在 ready 状态。当有数据时,DMA 负责从网卡取数据,并在 Ring Buffer 上按顺序找到下一个 ready 的 Descriptor,将数据写入该 Descriptor 指向的 sk_buff 中,并标记槽为 used。

zllion commented 1 year ago

对IP层切片不太懂,是把比较大的数据切成几片然后每个片自己加IP头吗? 举个例子,一个传输层的segement要切成两个packet,那第一个就是IP+TCP头?第二个只有IP头?接收方怎么把切好的两个packet拼起来变成一个segment呢? 不是很懂。

xiaolincoder commented 1 year ago

@zllion

对IP层切片不太懂,是把比较大的数据切成几片然后每个片自己加IP头吗? 举个例子,一个传输层的segement要切成两个packet,那第一个就是IP+TCP头?第二个只有IP头?接收方怎么把切好的两个packet拼起来变成一个segment呢? 不是很懂。

是的。

第一个就是IP+TCP头,第二个只有IP头。接收方会在ip层组装完好完整的ip报文后,然后把ip报文的数据部分交给传输层。这个数据部分就是完整的tcp报文(包含tcp头+tcp数据)

ok8722971 commented 1 year ago

前面提到 "為了在層級之間傳遞資料時,不發生複製,只用 sk_buff 一個結構體來描述所有的網路包。"

那為何後面又說"在使用 TCP 傳輸協議的情況下,從傳輸層進入網路層的時候,每一個 sk_buff 都會被克隆一個新的副本出來。副本 sk_buff 會被送往網路層"

不太明白意義是什麼

feixintianxia commented 1 year ago

@xiaolincoder

@zllion

对IP层切片不太懂,是把比较大的数据切成几片然后每个片自己加IP头吗? 举个例子,一个传输层的segement要切成两个packet,那第一个就是IP+TCP头?第二个只有IP头?接收方怎么把切好的两个packet拼起来变成一个segment呢? 不是很懂。

  • IP层切片是把比较大的数据切成几片然后每个片自己加IP头吗?

是的。

  • 举个例子,一个传输层的segement要切成两个packet,那第一个就是IP+TCP头?第二个只有IP头?接收方怎么把切好的两个packet拼起来变成一个segment呢?

第一个就是IP+TCP头,第二个只有IP头。接收方会在ip层组装完好完整的ip报文后,然后把ip报文的数据部分交给传输层。这个数据部分就是完整的tcp报文(包含tcp头+tcp数据)

不是的。当一个传输层的segment需要被切割成两个或更多的IP包(packet)时,每个packet都会包含IP头和TCP头。这是因为每个packet都需要独立在网络中传输,所以每个packet都需要包含足够的信息来找到它的目的地和在那里被正确地处理。

kkkkkkkkkka commented 1 year ago

关于nabi,是这样理解的吗?

phpgao commented 1 year ago

传输层取出 TCP 头或 UDP 头,根据四元组「源 IP、源端口、目的 IP、目的端口」 作为标识,找出对应的 Socket,并把数据放到 Socket 的接收缓冲区。

根据前面的描述,此时已经丢弃了IP包头,理论上说不是应该与IP无关了,但是四元组里面又有IP这个值,怎么取到的呢?

xinyi127 commented 1 year ago

@kkkkkkkkkka

关于nabi,是这样理解的吗?

  • 当第一个数据包到来之后,网卡通过DMA技术写入内存的指定位置,然后发起一个硬件中断通知CPU,CPU调用硬件中断处理函数。
  • 这个函数首先暂时屏蔽中断,就是说后续的数据包直接写入内存,不需要再发送中断请求,避免 CPU 不停的被中断。
  • 然后发起软中断。不过这里有几个问题就是经过多久发起软中断?软中断就是去读取数据吗?轮询又是在哪里体现的?读取数据是一直持续到这段时间积攒的数据全部读取完结束,还是固定的一段时间就结束?
  • 文章里写发起软中断,然后恢复屏蔽的中断,这是指发起软中断之后就恢复,还是等软中断处理函数执行完成后恢复中断? 我的基础不太好,感谢小林或者其他小伙伴解答我的疑问
  1. 是的。当第一个数据包到达时,网卡使用DMA技术将数据写入内存的指定位置,然后触发一个硬件中断通知CPU。CPU会调用硬件中断处理函数来处理这个中断。

  2. 硬件中断处理函数的第一步是暂时屏蔽中断。这意味着之后的数据包可以直接写入内存,而无需再次触发中断请求,从而避免不必要地中断CPU的执行。

  3. 接下来,硬件中断处理函数会发起软中断。软中断的触发时机可能因系统实现而异,一般是在合适的时机触发,以处理已经到达的数据。软中断并不是直接读取数据,而是通过软中断处理函数来完成具体的操作,例如从内存中读取数据进行处理或者唤醒相应的线程进行处理。

  4. 关于轮询,它是在软中断处理函数内部实现的一种机制。在软中断处理函数执行期间,可以利用轮询方式持续读取数据,直到积攒的数据全部读取完为止。定期触发软中断可以使得数据及时处理,而不会等待太久。

  5. 文章里所说的"发起软中断,然后恢复屏蔽的中断"意味着在发起软中断后会恢复之前屏蔽的中断,具体是在软中断处理函数执行完成后恢复中断。

szluyu99 commented 1 year ago

@phpgao

传输层取出 TCP 头或 UDP 头,根据四元组「源 IP、源端口、目的 IP、目的端口」 作为标识,找出对应的 Socket,并把数据放到 Socket 的接收缓冲区。

根据前面的描述,此时已经丢弃了IP包头,理论上说不是应该与IP无关了,但是四元组里面又有IP这个值,怎么取到的呢?

我也很好奇这个问题,想要确认一个 Socket 需要 IP + 端口,但是那时候已经丢弃 IP 包头,TCP 头部里只有目标端口+源端口的信息,那是怎么确认一个 Socket 的呢?

ouyangbetter commented 11 months ago

前面不是说在传输层(TCP)分割数据包嘛,咋后面又是在网络层(IP)了呀

dablelv commented 10 months ago

TCP/IP 协议层图中的 MAC 一层是表示网络接口层吗?如果是的话,为什么不写成网络接口层呢?

Aldrice commented 9 months ago

有个问题,关于原文的

网络接口层会通过 ARP 协议获得下一跳的 MAC 地址,然后对 sk_buff 填充帧头和帧尾,接着将 sk_buff 放到网卡的发送队列中。

这里是怎么做到填充帧尾的呢,因为数据填充是通过将sk_buff的指针下移来添加各层header,要填充帧尾岂不是要将全部数据整体向下偏移,这似乎不是单单靠移动指针就能做到的,还涉及到数据拷贝

更新1: 没事了,搞明白了,具体看https://www.jianshu.com/p/3738da62f5f6。

通过调用函数 skb_put() 来使 tail 指针向下移动空出空间来添加数据

meifannao commented 9 months ago

@qugeminghaonan1

前面不是说在传输层(TCP)分割数据包嘛,咋后面又是在网络层(IP)了呀

应该是在传输层(TCP)分段,在网络层(IP)分片,传输层数据包最大表示为MSS,网络层最大表示为MTU。 如果应用层要传输1GB的数据,那么传输层先分段为若干个MSS大小的TCP数据包,每个TCP数据段又分为若干个MTU大小的IP数据包。如果某个IP数据包丢失了,那么TCP会重传这个IP数据包

dablelv commented 8 months ago

data 指针的移动过程的图种,只有 Ethernet header(帧头),是不是少了帧尾?

yangshuo-1 commented 6 months ago

@qugeminghaonan1

前面不是说在传输层(TCP)分割数据包嘛,咋后面又是在网络层(IP)了呀

传输层和网络层都有分割,一个是针对TCP的最大长度,一个是针对IP报文最大长度。

woderrrrr commented 5 months ago

这个跟2.1题感觉差不多

1669184962 commented 3 months ago

@szluyu99 我感觉应该是在网络层取出IP包头,获取源IP地址和目的IP地址时,会将该信息写入到一块区域(比如说是内存)存储起来,然后去除包头,将去除后的数据接着往传输层传。理解应该是每一层在去除对应的包头的时候都可能会保存数据,以便下一层读取。因此会有一块公共区域用以保存数据?

@phpgao

传输层取出 TCP 头或 UDP 头,根据四元组「源 IP、源端口、目的 IP、目的端口」 作为标识,找出对应的 Socket,并把数据放到 Socket 的接收缓冲区。

根据前面的描述,此时已经丢弃了IP包头,理论上说不是应该与IP无关了,但是四元组里面又有IP这个值,怎么取到的呢?

我也很好奇这个问题,想要确认一个 Socket 需要 IP + 端口,但是那时候已经丢弃 IP 包头,TCP 头部里只有目标端口+源端口的信息,那是怎么确认一个 Socket 的呢?

cold-bin commented 1 month ago

TCP层的分片能触发sk_buff拷贝吗?