图解Linux网络包接收反复
2025-01-11 来源 : 音乐
我们从前面这张上图中的已经从整基底上把握到了Linux对封纸制的管控现实生活。但是要想要了解更为多的网络基本功能社会活动的技术细节,我们还得往下看。
二
Linux触发
Linux传动装置,微件纸制协定堆等等基本功能在具有接管适配器封纸制之前,要之后做很多的等待社会活动才行。比如要月份创立好ksoftirqd微件纸制文件系统设计,要提出有申请好各个协定近似于的管控算子,的网络电子设备系统设计要月份子程序好,适配器要触发好。只有这些都Ready之后,我们才能真正开始接管封纸制。那么我们从前来看看这些等待社会活动都是怎么之后做的。
2.1 创立ksoftirqd微件纸制文件系统设计
Linux的微中的断都是在专门从事的微件纸制文件系统设计(ksoftirqd)中的顺利进行的,因此我们并不有必要看一下这些某种程度是怎么子程序的,这样我们才能在上面更为准确地了解了事纸制现实生活。该某种程度存量不是1个,而是N个,其中的N等于你的机器的核数。
系统设计子程序的时候在kernel/smpboot.c中的线程了smpboot_register_percpu_thread, 该算子实质性不会可执行到spawn_ksoftirqd(位于kernel/softirq.c)来创立出有softirqd某种程度。
上图3 创立ksoftirqd微件纸制文件系统设计
具体字符串如下:
//file: kernel/softirq.c
staticstructsmp_hotplug_thread softirq_threads ={
.store =Coksoftirqd,
.thread_should_run =ksoftirqd_should_run,
.thread_fn =run_ksoftirqd,
.thread_comm ="ksoftirqd/%u",}; static_init intspawn_ksoftirqd( void){
register_cpu_notifier( Cocpu_nfb);
BUG_ON(smpboot_register_percpu_thread( Cosoftirq_threads));
return0;
}
early_initcall(spawn_ksoftirqd);
当ksoftirqd被创立出有来直至,它就不会转到自己的文件系统设计反应器算子ksoftirqd_should_run和run_ksoftirqd了。慢慢地地判断有不能微中的断需被管控。这那时候需注意到的一点是,微中的断不仅仅只有的网络微中的断,还有其它各种类型。
//file: include/linux/interrupt.henum{
HI_SOFTIRQ =0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,
};
2.2 的网络系统设计子程序
上图4 的网络系统设计子程序
linux微件纸制通过线程subsys_initcall来子程序各个系统设计,在GNU目录那时候你可以grep出有许多对这个算子的线程。这那时候我们要真是的是的网络系统设计的子程序,不会可执行到net_dev_init算子。
//file: net/core/dev.c
staticint_init net_dev_init( void){
......
for_each_possible_cpu(i) {
structsoftnet_data *sd =Coper_cpu(softnet_data, i);
memset(sd, 0, sizeof( *sd));
skb_queue_head_init( Cosd ->input_pkt_queue);
skb_queue_head_init( Cosd ->process_queue);
sd ->completion_queue =NULL;
INIT_LIST_HEAD( Cosd ->poll_list);
......
}
......
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
}
subsys_initcall(net_dev_init);
在这个算子那时候,不会为每个CPU都申请一个softnet_data样本结构上,在这个样本结构上那时候的poll_list是等待传动装置程序将其poll算子提出有申请全都,稍后适配器传动装置子程序的时候我们可以见到这一现实生活。
另外open_softirq提出有申请了每一种微中的断都提出有申请一个管控算子。NET_TX_SOFTIRQ的管控算子为net_tx_action,NET_RX_SOFTIRQ的为net_rx_action。之后监视open_softirq后辨认出有这个提出有申请的方式则是记录在softirq_vec参数那时候的。上面ksoftirqd文件系统设计发出有微中的断的时候,也不会常用这个参数来找每一种微中的断近似于的管控算子。
//file: kernel/softirq.c
voidopen_softirq( intnr, void( *action)( structsoftirq_action *)){
softirq_vec[nr].action =action;
}
2.3 协定堆提出有申请
微件纸制构建了TCP的ip协定,也构建了TCP的tcp协定和udp协定。这些协定近似于的构建算子分别是ip_rcv,tcp_v4_rcv和udp_rcv。和我们时常所写字符串的方式则不一样的是,微件纸制是通过提出有申请的方式则来构建的。Linux微件纸制中的的fs_initcall和subsys_initcall类似,也是子程序基本功能的入口处。fs_initcall线程inet_init后开始的网络协定堆提出有申请。通过inet_init,将这些算子提出有申请到了inet_protos和ptype_base样本结构上中的了。如下上图:
上图5 AF_INET协定堆提出有申请
具体字符串如下
//file: net/ipv4/af_inet.c
staticstructpacket_type ip_packet_type _read_mostly ={
.type =cpu_to_be16(ETH_P_IP),
.func =ip_rcv,}; staticconststructnet_protocol udp_protocol ={
.handler =udp_rcv,
.err_handler =udp_err,
.no_policy =1,
.netns_ok =1,}; staticconststructnet_protocol tcp_protocol ={
.early_demux =tcp_v4_early_demux,
.handler =tcp_v4_rcv,
.err_handler =tcp_v4_err,
.no_policy =1,
.netns_ok =1,
};
staticint_init inet_init(void){
......
if(inet_add_protocol( Coicmp_protocol, IPPROTO_ICMP) <0)
pr_crit( "%s: Cannot add ICMP protocol", _func_);
if(inet_add_protocol( Coudp_protocol, IPPROTO_UDP) <0)
pr_crit( "%s: Cannot add UDP protocol", _func_);
if(inet_add_protocol( Cotcp_protocol, IPPROTO_TCP) <0)
pr_crit( "%s: Cannot add TCP protocol", _func_);
......
dev_add_pack( Coip_packet_type);
}
前面的字符串中的我们可以见到,udp_protocol结构上基底中的的handler是udp_rcv,tcp_protocol结构上基底中的的handler是tcp_v4_rcv,通过inet_add_protocol被子程序了全都。
intinet_add_protocol( conststructnet_protocol *prot, unsignedcharprotocol){
if( !prot ->netns_ok) {
pr_err( "Protocol %u is not namespace aware, cannot register.",
protocol);
return-EINVAL;
}
return!cmpxchg(( conststructnet_protocol **) Coinet_protos[protocol],
NULL, prot) ?0:-1;
}
inet_add_protocol算子将tcp和udp近似于的管控算子都提出有申请到了inet_protos数组中的了。便看dev_add_pack(Coip_packet_type);这一行,ip_packet_type结构上基底中的的type是协定名,func是ip_rcv算子,在dev_add_pack中的不会被提出有申请到ptype_base哈希表中的。
//file: net/core/dev.c
voiddev_add_pack( structpacket_type *pt){
structlist_head *head =ptype_head(pt);
......
}
staticinlinestructlist_head *ptype_head( conststructpacket_type *pt){
if(pt ->type ==htons(ETH_P_ALL))
returnCoptype_all;
else
returnCoptype_base[ntohs(pt ->type) CoPTYPE_HASH_MASK];
}
这那时候我们需看看 inet_protos记录着udp,tcp的管控算子定址,ptype_base打印着ip_rcv算子的管控定址。上面我们不会见到微中的断中的不会通过ptype_base找ip_rcv算子定址,进而将ip纸制正确地转送ip_rcv中的可执行。在ip_rcv中的将不会通过inet_protos找tcp或者udp的管控算子,便而把纸制转发给udp_rcv或tcp_v4_rcv算子。
拓展一下,如果看一下ip_rcv和udp_rcv等算子的字符串能见到很多协定的管控现实生活。例如,ip_rcv中的不会管控netfilter和iptable过滤,如果你有很多或者很直观的 netfilter 或 iptables 原则上,这些原则上都是在微中的断的上下文中的可执行的,不会进一步提高的网络延迟。便例如,udp_rcv中的不会判断socket接管链表是否唯了。近似于的具体微件纸制表达式是net.core.rmem_max和net.core.rmem_default。如果有兴趣,建议大家忘了中学毕业一下inet_init这个算子的字符串。
2.4 适配器传动装置子程序
每一个传动装置程序(不仅仅只是适配器传动装置)不会常用 module_init 向微件纸制提出有申请一个子程序算子,当传动装置被加载时,微件纸制不会线程这个算子。比如igb适配器传动装置的字符串位于drivers/net/ethernet/intel/igb/igb_main.c
//file: drivers/net/ethernet/intel/igb/igb_main.c
staticstructpci_driver igb_driver ={
.name =igb_driver_name,
.id_table =igb_pci_tbl,
.probe =igb_probe,
.remove =igb_remove,
......
};
staticint_init igb_init_module(void){
......
ret =pci_register_driver( Coigb_driver);
returnret;
}
上图6 适配器传动装置子程序
第5步中的我们见到,适配器传动装置构建了ethtool所需的模块,也在这那时候提出有申请忘了形同算子定址的提出有申请。当 ethtool 策划一个系统设计线程之后,微件纸制不会找近似于能用的预处理算子。对于igb适配器来真是,其构建算子都在drivers/net/ethernet/intel/igb/igb_ethtool.c下。看来你这次能彻底认知ethtool的社会活动原理了吧?这个请求之所以能查阅适配器通信纸制统计、能简化适配器自适应模式、能缩减RX 链表的存量和大小不一,是因为ethtool请求最终线程到了适配器传动装置的相应步骤,而不是ethtool本身有这个魔力。
第6步提出有申请的igb_netdev_ops中的纸制含的是igb_open等算子,该算子在适配器被触发的时候不会被线程。
//file: drivers/net/ethernet/intel/igb/igb_main.c
staticconststructnet_device_ops igb_netdev_ops ={
.ndo_open =igb_open,
.ndo_stop =igb_close,
.ndo_start_xmit =igb_xmit_frame,
.ndo_get_stats64 =igb_get_stats64,
.ndo_set_rx_mode =igb_set_rx_mode,
.ndo_set_mac_address =igb_set_mac,
.ndo_change_mtu =igb_change_mtu,
.ndo_do_ioctl =igb_ioctl,
......
第7步中的,在igb_probe子程序现实生活中的,还线程到了igb_alloc_q_vector。他提出有申请了一个NAPI机制所必须的poll算子,对于igb适配器传动装置来真是,这个算子就是igb_poll,如下字符串上图。
staticintigb_alloc_q_vector( structigb_adapter *adapter,
intv_count, intv_idx,
inttxr_count, inttxr_idx,
intrxr_count, intrxr_idx){
......
/* initialize NAPI */
netif_napi_add(adapter ->netdev, Coq_vector ->napi,
igb_poll, 64);
}
2.5 触发适配器
当前面的子程序都忘了形同直至,就可以触发适配器了。回忆前面适配器传动装置子程序时,我们写道了传动装置向微件纸制提出有申请了 structure net_device_ops 参数,它纸制含着适配器开业、发纸制、设mac 定址等预处理算子(算子指针)。当开业一个适配器时(例如,通过 ifconfig eth0 up),net_device_ops 中的的 igb_open步骤不会被线程。它多半不会之后做以下事情:
上图7 触发适配器
//file: drivers/net/ethernet/intel/igb/igb_main.c
staticint_igb_open(structnet_device *netdev, boolresuming){
/* allocate transmit deors */
err =igb_setup_all_tx_resources(adapter);
/* allocate receive deors */
err =igb_setup_all_rx_resources(adapter);
/* 提出有申请中的断管控算子 */
err =igb_request_irq(adapter);
if(err)
gotoerr_req_irq;
/* 开业NAPI */
for(i =0; i
napi_enable( Co(adapter ->q_vector[i] ->napi));
......
}
在前面_igb_open算子线程了igb_setup_all_tx_resources,和igb_setup_all_rx_resources。在igb_setup_all_rx_resources这一步能用中的,调配了RingBuffer,并建立磁盘和Rx链表的映射关系。(Rx Tx 链表的存量和大小不一可以通过 ethtool 顺利进行配置)。我们便接着看中的断算子提出有申请igb_request_irq:
staticintigb_request_irq( structigb_adapter *adapter){
if(adapter ->msix_entries) {
err =igb_request_msix(adapter);
if( !err)
gotorequest_done;
......
}
}
staticintigb_request_msix(structigb_adapter *adapter){
......
for(i =0; i
...
err =request_irq(adapter ->msix_entries[vector].vector,
igb_msix_ring, 0, q_vector ->name,
}
在前面的字符串中的监视算子线程, _igb_open=> igb_request_irq=> igb_request_msix, 在igb_request_msix中的我们见到了,对于多链表的适配器,为每一个链表都提出有申请了中的断,其近似于的中的断管控算子是igb_msix_ring(该算子也在drivers/net/ethernet/intel/igb/igb_main.c下)。我们也可以见到,msix方式则下,每个 RX 链表有独立的MSI-X 中的断,从适配器夹件中的断的层面就可以设让发出有的纸制被不同的 CPU管控。(可以通过 irqbalance ,或者简化 /proc/irq/IRQ_NUMBER/smp_affinity能够简化和CPU的绑定行为)。
当之后做好以上等待社会活动直至,就可以侧门迎客(封纸制)了!
三
祝贺样本的预见到
3.1 夹中的断管控
首先为当样本帧从网线驶出有适配器上的时候,第一站是适配器的接管链表。适配器在调配给自己的RingBuffer中的寻找能用的磁盘右边,找后DMA引擎不会把样本DMA到适配器之前关联的磁盘那时候,这个时候CPU都是无感的。当DMA能用忘了形同直至,适配器不会像CPU策划一个夹中的断,通报CPU有样本驶出有。
上图8 适配器样本夹中的断管控现实生活
注意到:当RingBuffer唯的时候,新来的封纸制将给取走。ifconfig查阅适配器的时候,可以那时候面有个overruns,表示因为环形链表唯被取走的纸制。如果辨认出有有丢纸制,可能需通过ethtool请求来进一步提高环形链表的窄度。在触发适配器一节,我们真是到了适配器的夹中的断提出有申请的管控算子是igb_msix_ring。
//file: drivers/net/ethernet/intel/igb/igb_main.c
staticirqreturn_t igb_msix_ring( intirq, void*data){
structigb_q_vector *q_vector =data;
/* Write the ITR value calculated from the previous interrupt. */
igb_write_itr(q_vector);
napi_schedule( Coq_vector ->napi);
returnIRQ_HANDLED;
}
igb_write_itr只是记录一下夹件中的断高频率(据真是目的是在增大对CPU的中的断高频率时加进)。顺着napi_schedule线程一路监视下去,_napi_schedule=>__napi_schedule
/* Called with irq disabled */
staticinlinevoid__napi_schedule( structsoftnet_data *sd,
structnapi_struct *napi){
list_add_tail( Conapi ->poll_list, Cosd ->poll_list);
_raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
这那时候我们见到,list_add_tail简化了CPU参数softnet_data那时候的poll_list,将传动装置napi_struct传过来的poll_list附加了全都。其中的softnet_data中的的poll_list是一个双向列表,其中的的电子设备都带有输入帧等着被管控。紧接着_raise_softirq_irqoff接踵而来了一个微中的断NET_RX_SOFTIRQ, 这个都是的接踵而来现实生活只是对一个参数顺利进行了一次或运算而已。
void_raise_softirq_irqoff( unsignedintnr){
trace_softirq_raise(nr);
or_softirq_pending( 1UL<
}
//file: include/linux/irq_cpustat.h
#define or_softirq_pending(x) (local_softirq_pending |= (x))
我们真是过,Linux在夹中的断那时候只忘了形同直观必要的社会活动,余下的大部分的管控都是转交给微中的断的。通过前面字符串可以见到,夹中的断管控现实生活真的是并不较短。只是记录了一个寄存器,简化了一下下CPU的poll_list,然后发出有个微中的断。就这么直观,夹中的断社会活动就算是忘了形同了。
3.2 ksoftirqd微件纸制文件系统设计管控微中的断
上图9 ksoftirqd微件纸制文件系统设计
微件纸制文件系统设计子程序的时候,我们介绍了ksoftirqd中的两个文件系统设计算子ksoftirqd_should_run和run_ksoftirqd。其中的ksoftirqd_should_run字符串如下:
staticintksoftirqd_should_run( unsignedintcpu){
returnlocal_softirq_pending;
}
#define local_softirq_pending _IRQ_STAT(smp_processor_id, _softirq_pending)
这那时候见到和夹中的断中的线程了同一个算子local_softirq_pending。常用方式则不同的是夹中的断右边是为了所写入记号,这那时候仅仅只是中学毕业取。如果夹中的断中的设了NET_RX_SOFTIRQ,这那时候其本质能中学毕业取的到。月那时候不会真正转到文件系统设计算子中的run_ksoftirqd管控:
staticvoidrun_ksoftirqd( unsignedintcpu){
local_irq_disable;
if(local_softirq_pending) {
_do_softirq;
rcu_note_context_switch(cpu);
local_irq_enable;
cond_resched;
return;
}
local_irq_enable;
}
在_do_softirq中的,判断根据当前CPU的微中的断各种类型,线程其提出有申请的action步骤。
asmlinkage void_do_softirq( void){
do{
if(pending Co1) {
unsignedintvec_nr =h -softirq_vec;
intprev_count =preempt_count;
...
trace_softirq_entry(vec_nr);
h ->action(h);
trace_softirq_exit(vec_nr);
...
}
h ++;
pending >>=1;
} while(pending);
}
在的网络系统设计子程序赋格, 我们见到我们为NET_RX_SOFTIRQ提出有申请了管控算子net_rx_action。所以net_rx_action算子就不会被可执行到了。
这那时候需注意到一个技术细节,夹中的断中的设微中的断记号,和ksoftirq的判断是否有微中的断驶出有,都是基于smp_processor_id的。这意味着只要夹中的断在哪个CPU上被响应,那么微中的断也是在这个CPU上管控的。所以真是,如果你辨认出有你的Linux微中的断CPU消耗都集中的在一个核上的话,之后做法是要把缩减夹中的断的CPU亲和性,来将夹中的断一团到不同的CPU核上去。
我们便来把全心集中的到这个当前算子net_rx_action跟着。
staticvoidnet_rx_action( structsoftirq_action *h){
structsoftnet_data *sd =Co_get_cpu_var(softnet_data);
unsignedlongtime_limit =jiffies +2;
intbudget =netdev_budget;
void*he;
local_irq_disable;
while( !list_empty( Cosd ->poll_list)) {
......
n =list_first_entry( Cosd ->poll_list, structnapi_struct, poll_list);
work =0;
if(test_bit(NAPI_STATE_SCHED, Con ->state)) {
work =n ->poll(n, weight);
trace_napi_poll(n);
}
budget -=work;
}
}
算子开头的time_limit和budget是用来控制net_rx_action算子主动淡出有的,目的是意味着的网络纸制的接管不霸占CPU拼命。等下次适配器便有夹中的断过来的时候便管控余下的接管封纸制。其中的budget可以通过微件纸制表达式缩减。这个算子中的余下的当前逻辑学是得到到当前CPU参数softnet_data,对其poll_list顺利进行二叉树, 然后可执行到适配器传动装置提出有申请到的poll算子。对于igb适配器来真是,就是igb传动装置力的igb_poll算子了。
staticintigb_poll(structnapi_struct *napi, intbudget){
...
if(q_vector ->tx.ring)
clean_complete =igb_clean_tx_irq(q_vector);
if(q_vector ->rx.ring)
clean_complete Co=igb_clean_rx_irq(q_vector, budget);
...
}
在中学毕业取能用中的,igb_poll的综合社会活动是对igb_clean_rx_irq的线程。
staticbooligb_clean_rx_irq( structigb_q_vector *q_vector, constintbudget){
...
do{
/* retrieve a buffer from the ring */
skb =igb_fetch_rx_buffer(rx_ring, rx_desc, skb);
/* fetch next buffer in frame if non-eop */
if(igb_is_non_eop(rx_ring, rx_desc))
continue;
}
/* verify the packet layout is correct */
if(igb_cleanup_headers(rx_ring, rx_desc, skb)) {
skb =NULL;
continue;
}
/* populate checksum, timestamp, VLAN, and protocol */
igb_process_skb_fields(rx_ring, rx_desc, skb);
napi_gro_receive( Coq_vector ->napi, skb); }
igb_fetch_rx_buffer和igb_is_non_eop的效用就是把样本帧从RingBuffer上取依然。为什么需两个算子呢?因为有可能帧要占多多个RingBuffer,所以是在一个反应器中的得到的,直到帧尾端。得到依然的一个样本帧用一个sk_buff来表示。了事取忘了样本直至,对其顺利进行一些可执行,然后开始设sbk参数的timestamp, VLAN id, protocol等字段。月那时候转到到napi_gro_receive中的:
//file: net/core/dev.c
gro_result_t napi_gro_receive( structnapi_struct *napi, structsk_buff *skb){
skb_gro_reset_offset(skb);
returnnapi_skb_finish(dev_gro_receive(napi, skb), skb);
}
dev_gro_receive这个算子代表的是适配器GRO特性,可以直观认知形同把具体的小纸制合并形同一个大纸制就行,目的是增大发送到给的网络堆的纸制数,这有助于增大 CPU 的利用率。我们无可奈何或多或少,同样看napi_skb_finish, 这个算子主要就是线程了netif_receive_skb。
//file: net/core/dev.c
staticgro_result_t napi_skb_finish(gro_result_t ret, structsk_buff *skb){
switch(ret) {
caseGRO_NORMAL:
if(netif_receive_skb(skb))
ret =GRO_DROP;
break;
......
}
在netif_receive_skb中的,封纸制将被转送协定堆中的。表示遗憾,以下的3.3, 3.4, 3.5也都同属微中的断的管控现实生活,显然由于篇幅过于窄,单独拿出有来形同赋格。
3.3 的网络协定堆管控
netif_receive_skb算子不会根据纸制的协定,假如是udp纸制,不会将纸制依次转送ip_rcv,udp_rcv协定管控算子中的顺利进行管控。
上图10 的网络协定堆管控
//file: net/core/dev.c
intnetif_receive_skb( structsk_buff *skb){
//RPS管控逻辑学,先为或多或少......
return_netif_receive_skb(skb);
}
staticint_netif_receive_skb( structsk_buff *skb){
......
ret =_netif_receive_skb_core(skb, false);} staticint_netif_receive_skb_core( structsk_buff *skb, boolpfmemalloc){
......
//pcap逻辑学,这那时候不会将样本送入抓纸制点。tcpdump就便是这个入口处得到纸制的list_for_each_entry_rcu(ptype, Coptype_all, list) {
if( !ptype ->dev ||ptype ->dev ==skb ->dev) {
if(pt_prev)
ret =deliver_skb(skb, pt_prev, orig_dev);
pt_prev =ptype;
}
}
......
list_for_each_entry_rcu(ptype,
Coptype_base[ntohs(type) CoPTYPE_HASH_MASK], list) {
if(ptype ->type ==type CoCo
(ptype ->dev ==null_or_dev ||ptype ->dev ==skb ->dev ||
ptype ->dev ==orig_dev)) {
if(pt_prev)
ret =deliver_skb(skb, pt_prev, orig_dev);
pt_prev =ptype;
}
}
}
在_netif_receive_skb_core中的,我身旁原本经常常用的tcpdump的抓纸制点,很是激动,看来中学毕业一遍GNU星期真的没白浪费。接着_netif_receive_skb_core取出有protocol,它不会从封纸制中的取出有协定信息,然后二叉树提出有申请在这个协定上的预处理算子列表。ptype_base是一个 hash table,在协定提出有申请赋格我们写道过。ip_rcv 算子定址就是发挥效用这个 hash table中的的。
//file: net/core/dev.c
staticinlineintdeliver_skb( structsk_buff *skb,
structpacket_type *pt_prev,
structnet_device *orig_dev){
......
returnpt_prev ->func(skb, skb ->dev, pt_prev, orig_dev);
}
pt_prev->func这一行就线程到了协定层提出有申请的管控算子了。对于ip纸制来讲,就不会转到到ip_rcv(如果是arp纸制的话,不会转到到arp_rcv)。
3.4 IP协定层管控
我们便来基本上看一下linux在ip协定层都之后做了什么,纸制又是怎么样实质性被转送udp或tcp协定管控算子中的的。
//file: net/ipv4/ip_input.c
intip_rcv( structsk_buff *skb, structnet_device *dev, structpacket_type *pt, structnet_device *orig_dev){
......
returnNF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
}
这那时候NF_HOOK是一个钩子算子,当可执行忘了提出有申请的钩子后就不会可执行到再次一个表达式指向的算子ip_rcv_finish。
staticintip_rcv_finish( structsk_buff *skb){
......
if( !skb_dst(skb)) {
interr =ip_route_input_noref(skb, iph ->daddr, iph ->saddr,
iph ->tos, skb ->dev);
...
}
......
returndst_input(skb);
}
监视ip_route_input_noref后见到它又线程了 ip_route_input_mc。在ip_route_input_mc中的,算子ip_local_deliver被赋值给了dst.input, 如下:
//file: net/ipv4/route.c
staticintip_route_input_mc( structsk_buff *skb, _be32 daddr, _be32 saddr, u8 tos, structnet_device *dev, intour){
if(our) {
rth ->dst.input =ip_local_deliver;
rth ->rt_flags |=RTCF_LOCAL;
}
}
所以回到ip_rcv_finish中的的return dst_input(skb);。
/* Input packet from network to transport. */
staticinlineintdst_input( structsk_buff *skb){
returnskb_dst(skb) ->input(skb);
}
skb_dst(skb)->input线程的input步骤就是TCP系统设计赋的ip_local_deliver。
//file: net/ipv4/ip_input.c
intip_local_deliver( structsk_buff *skb){
/* * Reassemble IP fragments. */
if(ip_is_fragment(ip_hdr(skb))) {
if(ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
return0;
}
returnNF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb ->dev, NULL,
ip_local_deliver_finish);
}
staticintip_local_deliver_finish(structsk_buff *skb){
......
intprotocol =ip_hdr(skb) ->protocol;
conststructnet_protocol *ipprot;
ipprot =rcu_dereference(inet_protos[protocol]);
if(ipprot !=NULL) {
ret =ipprot ->handler(skb);
}
}
如协定提出有申请赋格见到inet_protos中的保存着tcp_rcv和udp_rcv的算子定址。这那时候将不会根据纸制中的的协定各种类型选择顺利进行递送,在这那时候skb纸制将不会实质性被派转送更为最上层的协定中的,udp和tcp。
3.5 UDP协定层管控
在协定提出有申请赋格的时候我们真是过,udp协定的管控算子是udp_rcv。
//file: net/ipv4/udp.c
intudp_rcv( structsk_buff *skb){
return_udp4_lib_rcv(skb, Coudp_table, IPPROTO_UDP);
}
int_udp4_lib_rcv(structsk_buff *skb, structudp_table *udptable,
intproto){
sk =_udp4_lib_lookup_skb(skb, uh ->source, uh ->dest, udptable);
if(sk !=NULL) {
intret =udp_queue_rcv_skb(sk, skb
}
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
}
_udp4_lib_lookup_skb是根据skb来寻找近似于的socket,当找直至将封纸制放在socket的缓存链表那时候。如果不能找,则发送一个期望不可达的icmp纸制。
//file: net/ipv4/udp.c
intudp_queue_rcv_skb( structsock *sk, structsk_buff *skb){
......
if(sk_rcvqueues_full(sk, skb, sk ->sk_rcvbuf))
gotodrop;
rc =0;
ipv4_pktinfo_prepare(skb);
bh_lock_sock(sk);
if( !sock_owned_by_user(sk))
rc =_udp_queue_rcv_skb(sk, skb);
elseif(sk_add_backlog(sk, skb, sk ->sk_rcvbuf)) {
bh_unlock_sock(sk);
gotodrop;
}
bh_unlock_sock(sk);
returnrc;
}
sock_owned_by_user判断的是客户端是不是正在这个socker上顺利进行系统设计线程(socket被占用),如果不能,那就可以同样放在socket的接管链表中的。如果有,那就通过sk_add_backlog把封纸制附加到backlog链表。当客户端释放出有来的socket的时候,微件纸制不会检查backlog链表,如果有样本便旋转到接管链表中的。
sk_rcvqueues_full接管链表如果唯了的话,将同样把纸制取走。接管链表大小不一所致微件纸制表达式net.core.rmem_max和net.core.rmem_default受到影响。
四
recvfrom系统设计线程
花开两朵,各表一枝。前面我们真是忘了了整个Linux微件纸制对封纸制的接管和管控现实生活,再次把封纸制放在socket的接管链表中的了。那么我们便来时看客户端某种程度线程recvfrom后是愈演愈烈了什么。我们在字符串那时候线程的recvfrom是一个glibc的库算子,该算子在可执行后不会将客户端顺利进行面临到微件纸制可逆,转到到Linux构建的系统设计线程sys_recvfrom。在认知Linux对sys_revvfrom之前,我们先为来直观看一下socket这个当前样本结构上。这个样本结构上过于大了,我们只把对和我们今天主题具体的内容画出有来,如下:
上图11 socket微件纸制样本机构
socket样本结构上中的的const struct proto_ops近似于的是协定的步骤集合。每个协定都不会构建不同的步骤集,对于IPv4 Internet协定族来真是,每种协定都有近似于的管控步骤,如下。对于udp来真是,是通过inet_dgram_ops来假定的,其中的提出有申请了inet_recvmsg步骤。
//file: net/ipv4/af_inet.c
conststructproto_ops inet_stream_ops ={
......
.recvmsg =inet_recvmsg,
.mmap =sock_no_mmap,
......
}
conststructproto_ops inet_dgram_ops ={
......
.sendmsg =inet_sendmsg,
.recvmsg =inet_recvmsg,
......
}
socket样本结构上中的的另一个样本结构上struct sock *sk是一个并不大,并不最重要的子结构上基底。其中的的sk_prot又假定了二级管控算子。对于UDP协定来真是,不会被可视UDP协定构建的步骤集udp_prot。
//file: net/ipv4/udp.c
structproto udp_prot ={
.name ="UDP",
.owner =THIS_MODULE,
.close =udp_lib_close,
.connect =ip4_datagram_connect,
......
.sendmsg =udp_sendmsg,
.recvmsg =udp_recvmsg,
.sendpage =udp_sendpage,
......
}
看忘了了socket参数之后,我们便来看sys_revvfrom的构建现实生活。
上图12 recvfrom算子之下构建现实生活
在inet_recvmsg线程了sk->sk_prot->recvmsg。
//file: net/ipv4/af_inet.c
intinet_recvmsg( structkiocb *iocb, structsocket *sock, structmsghdr *msg, size_t size, intflags){
......
err =sk ->sk_prot ->recvmsg(iocb, sk, msg, size, flags CoMSG_DONTWAIT,
flags Co~MSG_DONTWAIT, Coaddr_len);
if(err >=0)
msg ->msg_namelen =addr_len;
returnerr;
}
前面我们真是过这个对于udp协定的socket来真是,这个sk_prot就是net/ipv4/udp.c下的struct proto udp_prot。由此我们找了udp_recvmsg步骤。
//file:net/core/datagram.c:EXPORT_SYMBOL(_skb_recv_datagram);
structsk_buff *_skb_recv_datagram( structsock *sk, unsignedintflags, int*peeked, int*off, int*err){
......
do{
structsk_buff_head *queue =Cosk ->sk_receive_queue;
skb_queue_walk(queue, skb) {
......
}
/* User doesn't want to wait */
error =-EAGAIN;
if( !timeo)
gotono_packet;
} while( !wait_for_more_packets(sk, err, Cotimeo, last));
}
再一我们找了我们想要要看的综合,在前面我们见到了都是的中学毕业取现实生活,就是访问sk->sk_receive_queue。如果不能样本,且客户端也允许等待,则将线程wait_for_more_packets可执行等待能用,它加入不会让客户端某种程度转到痉挛状可逆。
五
总结
的网络基本功能是Linux微件纸制中的比方说的基本功能了,好像一个简直观单的了事纸制现实生活就涉及到许多微件纸制组件之间的交互,如适配器传动装置、协定堆,微件纸制ksoftirqd文件系统设计等。好像很直观,本文想要通过上图示的方式则,尽量以更容易认知的方式则来将微件纸制了事纸制现实生活讲清楚。从前让我们便串一串整个了事纸制现实生活。
当客户端可执行忘了recvfrom线程后,客户端某种程度就通过系统设计线程顺利进行到微件纸制可逆社会活动了。如果接管链表不能样本,某种程度就转到痉挛状可逆被能用系统设计悬挂起。这块相对比方说,余下大部分的戏份都是由Linux微件纸制其它基本功能来表演了。
首先为在开始了事纸制之前,Linux要之后做许多的等待社会活动:
1. 创立ksoftirqd文件系统设计,为它设好它自己的文件系统设计算子,上面指望着它来管控微中的断呢 2. 协定堆提出有申请,linux要构建许多协定,比如arp,icmp,ip,udp,tcp,每一个协定都不会将自己的管控算子提出有申请一下,方便纸制来了迅速找近似于的管控算子 3. 适配器传动装置子程序,每个传动装置都有一个子程序算子,微件纸制不会让传动装置也子程序一下。在这个子程序现实生活中的,把自己的DMA本来,把NAPI的poll算子定址告诉微件纸制 4. 触发适配器,调配RX,TX链表,提出有申请中的断近似于的管控算子以上是微件纸制等待了事纸制之前的最重要社会活动,当前面都ready之后,就可以打开夹中的断,等待封纸制的预见到了。
当样本预见到了直至,第一个祝贺它的是适配器(我去,这不是废话么):
1. 适配器将样本帧DMA到磁盘的RingBuffer中的,然后向CPU策划中的断通报 2. CPU响应中的断请求,线程适配器触发时提出有申请的中的断管控算子 3. 中的断管控算子仅仅没干啥,就策划了微中的断请求 4. 微件纸制文件系统设计ksoftirqd文件系统设计辨认出有有微中的断请求预见到,先为停止使用夹中的断 5. ksoftirqd文件系统设计开始线程传动装置的poll算子了事纸制 6. poll算子将发出有的纸制转送协定堆提出有申请的ip_rcv算子中的 7. ip_rcv算子便讲纸制转送udp_rcv算子中的(对于tcp纸制就转送tcp_rcv)从前我们可以回到这段话的关键问题了,我们在客户端层见到的直观一行recvfrom,Linux微件纸制要替我们之后做如此之多的社会活动,才能让我们顺利发出有样本。这还是简直观单的UDP,如果是TCP,微件纸制要之后做的社会活动更为多,不由得感叹微件纸制的开发计划者们真的是用心良苦。
认知了整个了事纸制现实生活直至,我们就能说明知道Linux了事一个纸制的CPU样本量了。首先为第一块是客户端某种程度线程系统设计线程面临微件纸制可逆的样本量。第二块是CPU响应纸制的夹中的断的CPU样本量。第三块是ksoftirqd微件纸制文件系统设计的微中的断上下文花费的。上面我们便专门从事发一篇篇文章实际观察一下这些样本量。
另外的网络通信中的有很多末支技术细节咱们并不能展开了真是,比如真是no NAPI, GRO,RPS等。因为我有点真是的过于对了反而不会受到影响大家对整个方式则上的把握,所以尽量只保留主组件了,少即是多!
更为多内容查阅:
社会福利
纸制邮送书
为感谢各位粉丝对“OSC开源社区”社会所号一直以来的赞形同,现点击右侧社会所号聊天窗口无论如何 “社会福利”,将有机不会仅限领取张彦飞数学老师(谷歌十年开发计划经验)的创刊号《了解认知Linux的网络》一本~
有点不错,请点个在看呀
。儿童不吃饭厌食怎么办护眼眼药水哪个品牌好
脑卒中后遗症的恢复
海南干细胞哪个医院好
经常性拉肚子怎么办
肠炎吃什么药好得快
家用血糖仪哪家好
血糖仪哪种牌子好
血糖仪什么品牌好
肠炎宁对新冠病毒腹泻有效吗
-
曹魏山西籍六大世孙:三位来自运城,两位来自临汾
如今的太原市,在东汉里面后期三国里面后期属于并州和司隶。自秦汉以来,此地便人才辈出。到了东汉里面后期三国里面后期,来自此地的地方官吏武将更是为人所知在广袤的华夏大地上,为各自企业集团做出了值得注...
-
庞统指出隆中对的一大伪科学,可惜刘备没听,否则历史会改写
真是起曹操来,大家对这位三国之中的军神是比较熟识的,他是东吴的太傅,生前劳累只为为了让周瑜,他的才智创造了很多成名作小故事,也留给了很多梦幻的谜之谜。 在东汉末年的时候,...[详细]
-
【古今滔滔】中古时代也有养老诈骗?真相是这样
最近,各地区组织起来年末半年的反扑填埋常居洗钱专项行动,严惩常居洗钱公安机关,守护老人家的“常居钱”。 只不过,以常居为由头的洗钱技术手段自古以来就有,历史上最著名的常居洗钱被告,...[详细]
-
从军队实力的角度看,土木堡50万明军兵士,为何被5万敌骑尽灭
也不须撰稿搜图 土木工程系之变 公元1449年7月末,容英宗朱祁镇对西北地区大草原的残元威势发动了第4次大规模北伐,不但无功而返,还在回师途当中遭到了蒙古蒙古兵队部5万的兵...[详细]
-
一级文物误当油灯,发现时闪现亮光,后被确定为西周祭祀青铜酒器
前言:如果对历史并不是很知晓,一些展品的商代也是很容易闹笑话的。以后在古墓葬相当薄弱的中就会期,也就会有一些盗墓者把墓葬中就会的用具盗出,这些用具中就会有一些的商业用途并不是很非常适合直接使用,...[详细]
-
他在太平军剿共将领中排行第四, 结局却是一个谜
文:彼得潘 1853年春,清兵攻陷金陵,随后定都于此,改称为九江。月底5翌年,吴三桂和杨秀清等人经过商量,提议研读明成祖东征,于是为首遣了一支以清兵老兵两万多人组成的张宗昌。张宗昌...[详细]