版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、内核协议栈数据包转发目录1 NAPI流程与非NAPINAPI驱动流程1.2非NAPI流程NAPI和非NAPI的区别2内核接受数据2.1数据接收过程2.2采取DMA技术实现3 e100采用NAPI接收数据过程e100_open 启动 e100 网卡e100_rx_alloc_list建立环形缓冲区e100_rx_alloc_skb 分配 skb 缓存e100_poll轮询函数e100_rx_clean数据包的接收和传输e100_rx_indicate4队列层4.1、软中断与下半部4.2、队列层5采用非NAPI接收数据过程5.1netif_rx5.2轮询与中断调用netif_rx_schedule
2、不同点netif_rx_schedulenet_rx_actionprocess_backlog6数据包进入网络层netif_receive_skb():ip_rcv():ip_rcv_finish():dst_input():本地流程 ip_local_deliver:转发流程 ip_forward():1 NAPI流程与非NAPI1.1NAPI驱动流程:中断发生-确定中断原因是数据接收完毕(中断原因也可能是发送完毕,DMA完毕,甚至是中 断通道上的其他设备中断)-通过 netif_rx_schedule 将驱动自己的 napi 结构加入 softnet_data 的 poll_list 链
3、表,禁用网卡中断,并发出软中断NET_RX_SOFTIRQ-中断返回时触发软中断调用相应的函数net_rx_action,从softnet_data的poll_list 上取下刚挂入的napi结构,并且调用其poll函数,这个poll函数也是驱动自己提供的, 比如e100网卡驱动中的e100_poll等。-在poll函数中进行轮询,直到接受完所有的数据或者预算(budget)耗尽。每接收一 个报文要分配skb,用eth_type_trans处理并交给netif_receive_skb。-如果数据全部接收完(预算没有用完),则重新使能中断并将napi从链表中取下。如 果数据没接收完,则什么也不作
4、,等待下一次poll函数被调度。NAPI相关数据结构CPU私有变量TSECPC1网卡每个网络设备(MAC层)都有自己的net_device数据结构,这个结构上有napi_structo 每当收到数据包时,网络设备驱动会把自甘的napLstruct挂到CPU私有变量上。这样在软中断时,net rx action会遍历cpu私有变量的poll list,执行上面所挂的napi_struct结构的poll钩子函数.将数据包瓜驱动传到网络协议栈。1.2非NAPI流程:中断发生-确定中断发生的原因是接收完毕。分配skb,读入数据,用eth_type_trans处理并 且将skb交给netif_rx在 n
5、etif_rx 中,将 packet 加入到 softnet_data 的 input_pkt_queue 末尾(NAPI 驱动不使用这个 input_pkt_queue),再通过 napi_schedule 将 softnet_data 中的 backlog(这也是个napi结构)加入softnet_data的poll_list,最后发出软中断软中断 net_rx_action 从 poll_list 上取下 softnet_data 的 backlog,调用其 poll 函数,这个poll函数是内核提供的process_backlog-函数 process_backlog 从 softne
6、t_data 的 input_pkt_queue 末尾取下 skb,并且直 接交给 netif_receive_skb 处理。-如果input_pkt_queue中所有skb都处理完则将backlog从队列中除去(注意 input_pkt_queue中可能有多个网卡加入的报文,因为它是每cpu公用的)并退出循环;如果 预算用完后也跳出循环。最后返回接受到的包数no NAPICPU私有变量softnetdatapollnapi_structpolliiit pull Iisl不配置NAPI的时候,网络设备不使用自己的napLstrucB ,所有网络设备驱动都使用同n a p i_s tru c
7、L即c p u私有变星_g et_cpu_var(softnet_data).backlog每当收到数据包时,网*SiS-3EtiB_get_cpu_var(softnet_clata).backloggJ_get_cpu_var(softnet_dataJ.p0ll_list 上面。所以软中断里nEt_rx_acticin遍历cpu私有变星_get_cpu_vmsDftnet_data).pci|list时,上面挂的 napi_structRdev指向接收设备将skb-mac_header指向data(此时data就是指向mac起始地址)调用 skb_pull(skb, ETH_HLEN)将
8、 skb-data 后移 14 字节指向 ip 首部通过比较目的mac地址判断包的类型,并将skb-pkt_type赋值 PACKET_BROADCAST 或 PACKET_MULTICAST 或者 PACKET_OTHERHOST,因为 PACKET_HOST 为 0, 所以是默认值最后判断协议类型,并返回(大部分情况下直接返回eth首部的protocol字段 的值),这个返回值被存在skb-protocol字段中总结,结束后,skb-data 指向 ip 首部,skb-mac_header 指向 mac 首部,skb-protocol 储存L3的协议代码,skb-pkt_type已被设置,
9、skb-len等于接收到的报文长度减去eth 首部长度,也就是整个ip报文的总长。其余字段基本上还是默认值。no NAPI2内核接受数据2.1数据接收过程内核从网卡接受数据,传统的经典过程:1、数据到达网卡;2、网卡产生一个中断给内核;3、内核使用I/O指令,从网卡I/O区域中去读取数据;就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的 资源来处理中断本身。这就是no NAPI方式。no NAPI: mac每收到一个以太网包,都会产生一个接收中断给cpu,即完全靠中断方 式来收包,收包缺点是当网络流量很大时,cpu大部分时间都耗在了处理mac的中断。NAPI:采用中
10、断+轮询的方式:mac收到一个包来后会产生接收中断,但是马上 关闭。直到收够了 netdev_max_backlog个包(默认300),或者收完mac上所有包后,才再 打开接收中断。通过 sysctl 来修改 dev_max_backlog 或者通过 proc 修改 /proc/sys/net/core/netdev_max_backlog2.2 DMA技术实现从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要 占用CPU资源,“CPU从I/O区域 读,然后把它放到内存(这个内存指的是系统本身的物 理内存,跟外设的内存不相干,也叫主内存)中。Linux使用DM
11、A技术一一让网卡直接从 主内存之间读写它们的I/O数据,就不关CPU的事。1、首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓 冲区)。2、内核将这个缓冲区通过DMA映射,把这个队列交给网卡;3、网卡收到数据,就直接放进这个环形缓冲区了一一也就是直接放进主内存了;然后, 向系统产生一个中断;4、内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要 CPU的干预,效率提高了.对应以上4步,来看它的具体实现:1、分配环形DMA缓冲区Linux内核中,用skb来描述一个缓存,所谓分配
12、,就是建立一定数量的skb,然后把 它们组织成一个双向链表;2、建立DMA映射内核 通过调 用 dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction)建 立映射关系。struct device *dev,描述一个设备;要映射全部,当然是做一个双buffer:把哪个地址映射给设备;也就是某一个skb向链表的循环即可;size :缓存大小;direction:映射方向一一谁传给谁:一般来说,是“双向”映射,数据在设备和内存之 间双向流动;对于PCI设备而言(网卡一般是
13、PCI的),通过另一个包裹函数pci_map_single,这样,就 把buffer交给设备了!设备可以直接从里边读/取数据。3、这一步由硬件完成;4、取消映射dma_unmap_single,对PCI而言,大多调用它的包裹函数pci_unmap_single,不取消的 话,缓存控制权还在设备手里,要调用它,把主动权掌握在CPU手里一一因为我们已经接 收到数据了,应该由CPU把数据交给上层网络栈;当然,不取消之前,通常要读一些状态位信息,诸如此类,一般是调用dma_sync_single_for_cpu()让CPU在取消映射前,就可以访问DMA缓冲区中的内容。每个网卡(MAC)都有自己的专用
14、DMA Engine,如上图的TSEC和e1000网卡 intel82546。上图中的红色线就是以太网数据流,DMA与DDR打交道需要其他模块的协助,如TSEC, PCI controller。以太网数据在 TSECDDR PCI_ControllerDDR 之间的流动,CPU 的 core是不需要介入的,只有在数据流动结束时(接收完、发送完),DMA Engine才会以外 部中断的方式告诉CPU的core3 e100接收数据过程e100_open 启动 e100 网卡e100_open(struct net_device *dev),调用e100_up,就是环形缓冲区的建立,这一步,是 通过
15、e100_rx_alloc_list函数调用完成的。static int e1 OO_Up(struct nic nic)int err;if(err =lit(门ic)return err;if(err = elOO_alloc_cbs(nic)goto Ierr_rx_clean_list;if( err = e 100_hw_init(nic)goto i-err_clean_cbs;e 1 OO_set_multicast_list(nic- netdev);e 1 OO_start_receiver(nic? NULL);mod_timer(8inic- watchdog? jiff
16、ies);mod_timer(8inic- traffic_timer? jiffies);if(err = request_irq(nic- pdev- irq? elOO_intr? SA_SHIRQ?nic- netdev- name? nic- netdev)goto i-err_no_irq;netif_wake_queue(nic- netdev);netif_poll_enable(nic- netdev); ,| | . fI-! | | | |! .| !e100_rx_alloc_list建立环形缓冲区static int e100_rx_alloc_list(struct
17、 nic *nic)struct rx *rx;unsigned int i, count = nic-params.rfds.count;nic-rx_to_use = nic-rx_to_clean = NULL;nic-ru_running = RU_UNINITIALIZED;/*结构struct rx用来描述一个缓冲区节点,这里分配了 count个*/if(!(nic-rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)return -ENOMEM;memset(nic-rxs, 0, sizeof(struct rx) * cou
18、nt);/*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间skb用来描述内核中的一个数据包,呵呵,说到重点了*/for(rx = nic-rxs, i = 0; i next = (i + 1 rxs;rx-prev = (i = 0) ? nic-rxs + count - 1 : rx - 1;if(e100_rx_alloc_skb(nic, rx) /* 分配缓存*/e100_rx_clean_list(nic);return -ENOMEM;nic-rx_to_use = nic-rx_to_clean = nic-rxs;nic-ru_ru
19、nning = RU_SUSPENDED;return 0;e100_rx_alloc_skb 分配 skb 缓存static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)/*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用 alloc_skb的区别在于,它是原子的,所以,通常在中断上下文中使用*/if(!(rx-skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN) return -ENOMEM;/*初始化必要的成员*/rx-skb-
20、dev = nic-netdev;skb_reserve(rx-skb, NET_IP_ALIGN);/*这里在数据区之前,留了一块sizeof(struct rfd)这么大的空间,该结构的一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过它,来判断是否真有数据到达等,诸如此类*/memcpy(rx-skb-data, &nic-blank_rfd, sizeof(struct rfd);/*这是最关键的一步,建立DMA映射,把每一个缓冲区rx-skb-data都映射给了设备, 缓存区节点rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/rx-dma_a
21、ddr = pci_map_single(nic-pdev, rx-skb-data, RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);if(pci_dma_mapping_error(rx-dma_addr) dev_kfree_skb_any(rx-skb);rx-skb = 0;rx-dma_addr = 0;return -ENOMEM;/* Link the RFD to end of RFA by linking previous RFD to* this one, and clearing EL bit of previous. */if(rx-prev-
22、skb) struct rfd *prev_rfd = (struct rfd *)rx-prev-skb-data;/*put_unaligned(val,ptr);用到把var放到ptr指针的地方,它能处理处理内存对齐的问题 prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/ put_unaligned(cpu_to_le32(rx-dma_addr),(u32 *)&prev_rfd-link);wmb();prev_rfd-command &= cpu_to_le16(cb_el);pci_dma_sync_single_for_device(n
23、ic-pdev, rx-prev-dma_addr, sizeof(struct rfd), PCI_DMA_TODEVICE);return 0;e100_rx_alloc_list函数在一个循环中,建立了环形缓冲区,并调用e100_rx_alloc_skb为每个 缓冲区分配了空间,并做了 DMA映射。这样,我们就可以来看接收数据的过程了。e100_poll轮询函数中断函数中,调用netif_rx_schedule,表明使用轮询技术,net_rx_action系统会在未来 某一时刻,调用设备的poll函数:static int e100_poll(struct net_device *net
24、dev, int *budget)struct nic *nic = netdev_priv(netdev);/* netdev-quota是当前CPU能够从所有接口中接收数据包的最大数目,budget 是在初始化阶段分配给接口的weight值,轮询函数必须接受二者之间的最小值。表示 轮 询函数本次要处理的数据包个数。*/unsigned int work_to_do = min(netdev-quota, *budget);unsigned int work_done = 0;int tx_cleaned;/*进行数据包的接收和传输*/e100_rx_clean(nic, &work_don
25、e, work_to_do);tx_cleaned = e100_tx_clean(nic);/*接收和传输完成后,就退出poll模块,重启中断*/* If no Rx and Tx cleanup work was done, exit polling mode. */if(!tx_cleaned & (work_done = 0) | !netif_running(netdev) netif_rx_complete(netdev);e100_enable_irq(nic);return 0;*budget -= work_done;netdev-quota -= work_done;ret
26、urn 1;目前,我们只关心rx,所以,e100_rx_clean函数就成了我们关注的对象,它用来从缓冲队 列中接收全部数据(这或许是取名为clean的原因吧!)e100_rx_clean数据包的接收和传输static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,unsigned int work_to_do) struct rx *rx;int restart_required = 0;struct rx *rx_to_start = NULL;/* are we already rnr? then p
27、ay attention! this ensures thatthe state machine progression never allows a start with apartially cleaned list, avoiding a race between hardwareand rx_to_clean when in NAPI mode */if(RU_SUSPENDED = nic-ru_running) restart_required = 1;/*函数最重要的工作,就是遍历环形缓冲区,接收数据*/for(rx = nic-rx_to_clean; rx-skb; rx =
28、 nic-rx_to_clean = rx-next) int err = e100_rx_indicate(nic, rx, work_done, work_to_do);if(-EAGAIN = err) /* hit quota so have more work to do, restart once* cleanup is complete */ restart_required = 0;break; else if(-ENODATA = err)break; /* No more to clean */* save our starting point as the place w
29、ell restart the receiver */ if(restart_required)rx_to_start = nic-rx_to_clean;/* Alloc new skbs to refill list */for(rx = nic-rx_to_use; !rx-skb; rx = nic-rx_to_use = rx-next) if(unlikely(e100_rx_alloc_skb(nic, rx)break; /* Better luck next time (see watchdog) */if(restart_required) / ack the rnr?wr
30、iteb(stat_ack_rnr, &nic-csr-scb.stat_ack);e100_start_receiver(nic, rx_to_start);if(work_done)(*work_done)+;e100_rx_indicatestatic inline int e100_rx_indicate(struct nic *nic, struct rx *rx,unsigned int *work_done, unsigned int work_to_do) struct sk_buff *skb = rx-skb;struct rfd *rfd = (struct rfd *)
31、skb-data;u16 rfd_status, actual_size;if(unlikely(work_done & *work_done = work_to_do)return -EAGAIN;/*读取数据之前,也就是取消DMA映射之前,需要先读取cb_complete状态位,以确定数据是否真的准备好了,并且,rfd的actual_size中,也包含了真实的数据 大小pci_dma_sync_single_for_cpu函数前面已经介绍过,它让CPU在取消DMA映射之 前,具备访问DMA缓存的能力*/pci_dma_sync_single_for_cpu(nic-pdev, rx-dma
32、_addr, sizeof(struct rfd), PCI_DMA_FROMDEVICE);rfd_status = le16_to_cpu(rfd-status);DPRINTK(RX_STATUS, DEBUG status=0 x%04Xn”, rfd_status);/* If data isnt ready, nothing to indicate */if(unlikely(!(rfd_status & cb_complete)return -ENODATA;/* Get actual data size */actual_size = le16_to_cpu(rfd-actua
33、l_size) & 0 x3FFF;if(unlikely(actual_size RFD_BUF_LEN - sizeof(struct rfd) actual_size = RFD_BUF_LEN - sizeof(struct rfd);/*取消映射,因为通过DMA,网卡已经把数据放在了主内存中,这里一取消,也 就意味着,CPU可以处理主内存中的数据了 */pci_unmap_single(nic-pdev, rx-dma_addr,RFD_BUF_LEN, PCI_DMA_FROMDEVICE);/* this allows for a fast restart without re-
34、enabling interrupts */ if(le16_to_cpu(rfd-command) & cb_el)nic-ru_running = RU_SUSPENDED;/*正确地设置data指针,因为最前面有一个sizeof(struct rfd)大小区域,跳过它*/ skb_reserve(skb, sizeof(struct rfd);/*更新skb的tail和len指针,也是就更新接收到这么多数据的长度*/skb_put(skb, actual_size);/*设置协议位*/skb-protocol = eth_type_trans(skb, nic-netdev);if(un
35、likely(!(rfd_status & cb_ok) (/* Dont indicate if hardware indicates errors */nic-net_stats.rx_dropped+;dev_kfree_skb_any(skb); else if(actual_size nic-netdev-mtu + VLAN_ETH_HLEN) (/* Dont indicate oversized frames */nic-rx_over_length_errors+;nic-net_stats.rx_dropped+;dev_kfree_skb_any(skb); else (
36、/*网卡驱动要做的最后一步,就是统计接收计数器,设置接收时间戳,然 后调用netif_ receive_skb,把数据包交给上层协议栈,自己的光荣始命也就完成了*/nic-net_stats.rx_packets+;nic-net_stats.rx_bytes += actual_size;nic-netdev-last_rx = jiffies;netif_receive_skb(skb);if(work_done)(*work_done)+;rx-skb = NULL;return 0;网卡驱动执行到这里,数据接收的工作,也就处理完成了。但是,使用这一种方法的驱 动,省去了网络栈中一个重要
37、的内容,就是“队列层”,让我们来看看,传统中断接收数据 包模式下,使用netif_rx函数调用,又会发生什么。4队列层4.1、软中断与下半部当用中断处理的时候,为了减少中断处理的工作量,比如,一般中断处理时,需要屏蔽 其它中断,如果中断处理时间过长,那么其它中断有可能得不到及时处理,也以,有一种机 制,就是把“不必马上处理”的工作,推迟一点,让它在中断处理后的某一个时刻得到处理。 这就是下半部。下半部只是一个机制,它在Linux中,有多种实现方式,其中一种对时间要求最严格的 实现方式,叫“软中断”,可以使用:open_softirq()来向内核注册一个软中断,然后,在合适 的时候,调用 rai
38、se_softirq_irqoff()触 发它。如果采用中断方式接收数据(这一节就是在说中断方式接收,后面,就不用这种假设了),同样也需要软中断,可以调用 open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL)向 内核 注册一个名为NET_RX_SOFTIR的软中断,net_rx_action是软中断的处理函数。然后,在驱动中断处理完后的某一个时刻,调用raise_softirq_irqoff(NET_RX_SOFTIRQ);触发它,这样net_rx_action将得到执行。4.2、队列层队列层通常指的是在网卡收发数据的时候,需要维护一个缓冲区队列,
39、来缓存可能存在 的突发数据,类似于前面的DMA环形缓冲区。队列层中,包含了一个叫做struct softnet_data:struct softnet_data/*throttle用于拥塞控制,当拥塞发生时,throttle将被设置,后续进入的数据包将 被丢弃*/intthrottle;/*netif_rx函数返回的拥塞级别*/intcng_level;intavg_blog;/*softnet_data结构包含一个指向接收和传输队列的指针,input_pkt_queue成员指 向准备传送给网络层的sk_buffs包链表的首部的指针,这个队列中的包是由netif_rx函数递交的*/struct
40、 sk_buff_headinput_pkt_queue;struct list_headstruct net_devicestruct sk buffpoll_list;*output_queue;*completion_queue;struct net devicebacklog_dev;/* Sorry. 8) */;内核使用了一个同名的变量softnet_data,它是一个Per-CPU变量,每个CPU都有一个。 net/core/dev.cDECLARE_PER_CPU(struct softnet_data,softnet_data);Copy to clipboardCODE:/
41、*网络模块的核心处理模块.*/static int _init net_dev_init(void)int i, rc = -ENOMEM;BUG_ON(!dev_boot_phase);net_random_init();if (dev_proc_init()/*初始化 proc 文件系统*/goto out;if (netdev_sysfs_init() /*初始化 sysfs 文件系统*/ goto out;/*ptype_all和ptype_base是重点,后面会详细分析,它们都是struct list_head类型变量,这里初始化链表成员*/INIT_LIST_HEAD(&ptype
42、_all);for (i = 0; i 16; i+)INIT_LIST_HEAD(&ptype_basei);for (i = 0; i ARRAY_SIZE(dev_name_head); i+) INIT_HLIST_HEAD(&dev_name_headi);for (i = 0; i ARRAY_SIZE(dev_index_head); i+) INIT_HLIST_HEAD(&dev_index_headi);/*初始化包接收队列,这里我们的重点了.*/*遍历每一个CPU,取得它的softnet_data,我们说过,它是一个struct softnet_data 的Per-CPU
43、变量*/for (i = 0; i input_pkt_queue);queue-throttle = 0;queue-cng_level = 0;queue-avg_blog = 10; /* arbitrary non-zero */ queue-completion_queue = NULL;INIT_LIST_HEAD(&queue-poll_list);set_bit(_LINK_STATE_START, &queue-backlog_dev.state);queue-backlog_dev.weight = weight_p;/*这里,队列中backlog_dev设备,它是一个伪网
44、络设备,不对应任何物 理设备,它的poll函数,指向了process_backlog,后面我们会详细分析*/ queue-backlog_dev.poll = process_backlog;atomic_set(&queue-backlog_dev.refcnt, 1);#ifdef OFFLINE_SAMPLEsamp_timer.expires = jiffies + (10 * HZ);add_timer(&samp_timer);#endifdev_boot_phase = 0;/*注册收/发软中断*/open_softirq(NET_TX_SOFTIRQ, net_tx_actio
45、n, NULL);open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);hotcpu_notifier(dev_cpu_callback, 0);dst_init();dev_mcast_init();rc = 0;out:return rc;这样,初始化完成后,在驱动程序中,在中断处理函数中,会调用netif_rx将数据交上 来,这与采用轮询技术,有本质的不同:5采用非NAPI接收数据过程netif_rxint netif_rx(struct sk_buff *skb)int this_cpu;struct softnet_data *queue
46、;unsigned long flags;/* if netpoll wants it, pretend we never saw it */if (netpoll_rx(skb)return NET_RX_DROP;/*接收时间戳未设置,设置之*/if (!skb-stamp.tv_sec)net_timestamp(&skb-stamp);/*这里准备将数据包放入接收队列,需要禁止本地中断,在入队操作完成后,再 打开中断.*/local_irq_save(flags);/*获取当前CPU对应的softnet_data变量*/this_cpu = smp_processor_id();que
47、ue = &_get_cpu_var(softnet_data);/*接收计数器累加*/_get_cpu_var(netdev_rx_stat).total+;/*接收队列是否已满*/if (queue-input_pkt_queue.qlen input_pkt_queue.qlen) (if (queue-throttle)/* 拥塞发生了,丢弃数据包*/goto drop;/*数据包入队操作*/enqueue:dev_hold(skb-dev);/* 累加设备引入计数器*/_skb_queue_tail(&queue-input_pkt_queue,skb);/*将数据包加入接收队列*/
48、#ifndef OFFLINE_SAMPLEget_sample_stats(this_cpu);#endiflocal_irq_restore(flags);return queue-cng_level;/*驱动程序不断地调用net_rx函数,实现接收数据包的入队操作,当qlen =0时,则进入这段代码,这里,如果已经被设置拥塞标志的话,则清除它,因为这里将 要调用软中断,开始将数据包交给上层了,即上层协议的接收函数将执行出队操作,拥塞 自然而然也就不存在了。*/if (queue-throttle)queue-throttle = 0;/*netif_rx_schedule函数完成两件重要
49、的工作:*1、将bakclog_dev设备加入“处理数据包的设备”的链表当中;2、触发软中断函数,进行数据包接收处理;*/netif_rx_schedule(&queue-backlog_dev);goto enqueue;/*前面判断了队列是否已满,如果已满而标志未设置,设置之,并累加拥塞计数器*/if (!queue-throttle) (queue-throttle = 1;_get_cpu_var(netdev_rx_stat).throttled+;/*拥塞发生,累加丢包计数器,释放数据包*/drop:_get_cpu_var(netdev_rx_stat).dropped+;loc
50、al_irq_restore(flags);kfree_skb(skb);return NET_RX_DROP;从这段代码的分析中,我们可以看到,当第一个数据包被接收后,因为qlen=0,所以 首先会调用netif_rx_schedule触发软中断,然后利用goto跳转至入队。因为软中断被触发 后,将执行出队操作,把数据交往上层处理。而当这个时候,又有数据包进入,即网卡中断 产生,因为它的优先级高过软中断,这样,出队操作即被中断,网卡中断程序再将被调用, netif_rx函数又再次被执行,如果队列未满,就入队返回。中断完成后,软中断的执行过程 被恢复而继续执行出队一一如此生产者/消费者循环不止
51、,生生不息5.2轮询与中断netif_rx_schedule不同点netif_rx调用netif_rx_schedule进一步处理数据包,我们注意到:1、前面讨论过,采用轮询技术时,同样地,也是调用ietif_rx_schedule,把设备自己传递了 过去;2、这里,采用中断方式,传递的是队列中的一个“伪设备”,并且,这个伪设备的poll函 数指针,指向了一个叫做process_backlog的函数;netif_rx_schedule函数完成两件重要的工作:1、将bakclog_dev设备加入“处理数据包的设备”的链表当中;2、触发软中断函数,进行数据包接收处理;这样,我们可以猜想,在软中断函
52、数中,不论是伪设备bakclog_dev,还是真实的设备(如 前面讨论过的e100),都会被软中断函数以:dev-poll()的形式调用,对于e100来说,poll 函数的接收过程已经分析了,而对于其它所有没有采用轮询技术的网络设备来说,它们将统 统调用process_backlog函数OK,我想分析到这里,关于中断处理与轮询技术的差异,已经基本分析开了netif_rx_schedule 进一步调用_netif_rx_schedule:5.3_netifLrx_schedulestatic inline void netif_rx_schedule(struct net_device *dev
53、)if (netif_rx_schedule_prep(dev)_netif_rx_schedule(dev);/* Add interface to tail of rx poll list. This assumes that _prep has* already been called and returned 1.*/static inline void _netif_rx_schedule(struct net_device *dev)unsigned long flags;local_irq_save(flags);dev_hold(dev);/*伪设备也好,真实的设备也罢,都被加
54、入了队列层的设备列表*/list_add_tail(&dev-poll_list, &_get_cpu_var(softnet_data).poll_list);if (dev-quota quota += dev-weight;elsedev-quota = dev-weight;/*触发软中断*/_raise_softirq_irqoff(NET_RX_SOFTIRQ);local_irq_restore(flags);net_rx_action软中断被触发,注册的net_rx_action函数将被调用:/*接收的软中断处理函数*/static void net_rx_action(str
55、uct softirq_action *h)struct softnet_data *queue = &_get_cpu_var(softnet_data);unsigned long start_time = jiffies;int budget = netdev_max_backlog;local_irq_disable();/*遍历队列的设备链表,如前所述,netif_rx_schedule已经执行了* list_add_tail(&dev-poll_list, &_get_cpu_var(softnet_data).poll_list);*设备bakclog_dev已经被添加进来了*/
56、while (!list_empty(&queue-poll_list) struct net_device *dev;if (budget 1) goto softnet_break;local_irq_enable();/*取得链表中的设备*/dev = list_entry(queue-poll_list.next,struct net_device, poll_list);netpoll_poll_lock(dev);/*调用设备的poll函数,处理接收数据包,这样,采用轮询技术的网卡, 它的真实的poll函数将被调用,这就回到我们上一节讨论的e100_poll函数去了,而对于采用传统
57、中断处 理的设备,它们调用的,都将是bakclog_dev 的 process_backlog 函数*/if (dev-quota poll(dev, &budget) ( netpoll_poll_unlock(dev);/*处理完成后,把设备从设备链表中删除,又重置于末尾*/local_irq_disable();list_del(&dev-poll_list);list_add_tail(&dev-poll_list, &queue-poll_list);if (dev-quota quota += dev-weight;elsedev-quota = dev-weight; else
58、(netpoll_poll_unlock(dev);dev_put(dev);local_irq_disable();out:local_irq_enable();return;softnet_break:_get_cpu_var(netdev_rx_stat).time_squeeze+;_raise_softirq_irqoff(NET_RX_SOFTIRQ);goto out;对于dev-poll(dev, &budget)的调用,一个真实的poll函数的例子,我们已经分析过了, 现在来看 process_backlogprocess_backlogstatic int process_backlog(struct net_device *backlog_dev, int *budget)int work = 0;int quota = min(backlog_dev-quota, *budget);struct softnet_data *queue = &_get_cpu_var(softnet_data);unsigned long start_time = jiffies;backlog_dev-weight = weight_p;/*在这个循环中,执行出队操作,把数据
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 高安市九年级上学期语文期中考试卷
- 二年级数学计算题专项练习集锦
- 脱硫废水零排放技术协议书(2篇)
- 高中技术学业水平测试试卷
- 南京工业大学浦江学院《食品标准与法规》2022-2023学年第一学期期末试卷
- 翰林国际(原曹妃甸科教城共享居住及配套)土地固化施工组织设计
- 多种多样的生态系统说课稿
- gkh说课稿第课时
- 《小数的性质》说课稿
- 租地合同范本(2篇)
- 【参考】华为腾讯职位管理0506
- 五年级英语上册Unit1Getupontime!教案陕旅版
- 风机安装工程质量通病及预防措施
- 三角形钢管悬挑斜撑脚手架计算书
- 文件和文件夹的基本操作教案
- 剪纸教学课件53489.ppt
- 旅游业与公共关系PPT课件
- 劳动法讲解PPT-定稿..完整版
- 彩色的翅膀_《彩色的翅膀》课堂实录
- 假如你爱我的正谱
- 铜芯聚氯乙烯绝缘聚氯乙烯护套控制电缆检测报告可修改
评论
0/150
提交评论