当前位置: 首页 > news >正文

wordpress认证seo中文意思

wordpress认证,seo中文意思,东莞技术好的网站建设推广,工程分包网1、数据如何从网卡到网络协议栈 1.1内核收包的过程 1、数据帧从外部网络到达网卡 2、网卡把数据帧从自己的缓存DMA(拷贝到)和内核共有的RingBuffer上 3、网卡发出硬中断通知CPU 4、CPU响应硬中断#xff0c;简单处理后发出软中断 5、k’softirqd线程处理软中断#xff0c;调…1、数据如何从网卡到网络协议栈 1.1内核收包的过程 1、数据帧从外部网络到达网卡 2、网卡把数据帧从自己的缓存DMA(拷贝到)和内核共有的RingBuffer上 3、网卡发出硬中断通知CPU 4、CPU响应硬中断简单处理后发出软中断 5、k’softirqd线程处理软中断调用网卡驱动注册的poll函数开始收包 6、帧被从RingBuffer上摘下来被保存为一个skb 7、协议层开始处理网络帧处理玩后的数据data被放到socket的接收队列中 8、内核唤醒用户进程 1.2 ksoftirqd内核线程的创建 Linux的软中断都是在专门的内核线程(ksoftirqd)中进行的。 该线程的数量等于设备的核数。 系统初始化的时候在kernel/smpboot.c中调用smpboot_register_percpu_thread,该函数进一步会执行到spawn_ksoftirqd位于kernel/softirq.c来创建出softirqd线程。 相关代码如下 static struct smp_hotplug_thread softirq_threads {.store ksoftirqd,.thread_should_run ksoftirqd_should_run,.thread_fn run_ksoftirqd,.thread_comm ksoftirqd/%u, };static __init int spawn_ksoftirqd(void) {cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, softirq:dead, NULL,takeover_tasklets);BUG_ON(smpboot_register_percpu_thread(softirq_threads));return 0; } early_initcall(spawn_ksoftirqd); 当ksoftirqd被创建出来以后就会进入线程循环函数ksoftirqd_should_run和run_ksoftirqd了。接下来判断有没有软中断需要处理。软中断不只有网络软中断还有其他类型。Linux内核在interrupt.h定义了所有类型的软中断类型: enum {HI_SOFTIRQ 0, /* 高优先级软中断用于紧急任务 */TIMER_SOFTIRQ, /* 定时器软中断处理定时器事件 */NET_TX_SOFTIRQ, /* 网络传输软中断处理网络数据包发送 */NET_RX_SOFTIRQ, /* 网络接收软中断处理网络数据包接收 */BLOCK_SOFTIRQ, /* 块设备软中断处理块设备I/O操作 */IRQ_POLL_SOFTIRQ, /* IRQ轮询软中断用于轮询模式下的硬件中断处理 */TASKLET_SOFTIRQ, /* 任务队列软中断执行任务队列中的工作 */SCHED_SOFTIRQ, /* 调度软中断处理调度器相关的工作 */HRTIMER_SOFTIRQ, /* 高分辨率定时器软中断未使用 */RCU_SOFTIRQ, /* 读-复制更新(RCU)软中断处理RCU更新和同步 */NR_SOFTIRQS /* 软中断的数量 */ };1.3 网络子系统初始化 在网络子系统的初始化过程中会为每个CPU初始化softnet_data,也会为RX_SOFTIRQ和TX_SOFTIRQ注册处理函数流程如下: 1、调用subsys_initcall(net_dev_init)net/core/dev.c 2、为每个CPU初始化softnet_data //include.linux/netdevice.h 3、将NET_RX_SOFTIRQ的处理函数注册为net_rx_action,将NET_TX_SOFTIRQ的处理函数注册为net_tx_action 4、将软中断与处理函数的对应关系记录到soft_irq_vec数组 网络子系统初始化会执行net_dev_init函数在这个函数里会为每个CPU都申请一个softnet_data数据结构这个数据结构里的poll_list用于等待驱动程序将其poll函数注册进来另外open_softirq为每一种软中断都注册一个处理函数继续跟踪open_softirq后发现这个注册的方式是记录在softirq_vec变量里的后面ksoftirqd线程收到软中断的时候也会使用这个变量查找每一种软中断对应的处理函数。 static int __init net_dev_init(void) {int i, rc -ENOMEM; // 初始化返回码为 -ENOMEM表示内存不足错误BUG_ON(!dev_boot_phase); // 确保设备启动阶段标志已设置// 初始化 /proc/net 设备文件if (dev_proc_init())goto out;// 初始化网络设备相关的 kobjectif (netdev_kobject_init())goto out;INIT_LIST_HEAD(ptype_all); // 初始化协议类型列表头for (i 0; i PTYPE_HASH_SIZE; i) {INIT_LIST_HEAD(ptype_base[i]); // 初始化协议类型哈希表}INIT_LIST_HEAD(offload_base); // 初始化 offload 列表头// 注册网络设备相关的 per-net 子系统if (register_pernet_subsys(netdev_net_ops))goto out;/** 初始化数据包接收队列。*/for_each_possible_cpu(i) { // 遍历所有可能的 CPUstruct work_struct *flush per_cpu_ptr(flush_works, i);struct softnet_data *sd per_cpu(softnet_data, i);INIT_WORK(flush, flush_backlog); // 初始化 backlog 刷新工作skb_queue_head_init(sd-input_pkt_queue); // 初始化输入数据包队列skb_queue_head_init(sd-process_queue); // 初始化处理数据包队列INIT_LIST_HEAD(sd-poll_list); // 初始化轮询列表sd-output_queue_tailp sd-output_queue; // 设置输出队列尾指针#ifdef CONFIG_RPSsd-csd.func rps_trigger_softirq; // 设置 RPS 触发函数sd-csd.info sd; // 设置 RPS 信息指针sd-cpu i; // 设置 CPU 编号 #endifsd-backlog.poll process_backlog; // 设置 backlog 处理函数sd-backlog.weight weight_p; // 设置 backlog 权重}dev_boot_phase 0; // 清除设备启动阶段标志/** 如果网络命名空间中有其他网络设备* 则环回设备必须存在。* 由于我们现在动态分配和释放环回设备* 确保通过将环回设备作为网络设备列表中的第一个设备来维护此不变式。* 确保环回设备是第一个出现的设备并且是最后一个消失的网络设备。*/if (register_pernet_device(loopback_net_ops))goto out;if (register_pernet_device(default_device_ops))goto out;open_softirq(NET_TX_SOFTIRQ, net_tx_action); // 打开网络传输软中断open_softirq(NET_RX_SOFTIRQ, net_rx_action); // 打开网络接收软中断rc cpuhp_setup_state_nocalls(CPUHP_NET_DEV_DEAD, net/dev:dead, NULL, dev_cpu_dead);WARN_ON(rc 0); // 警告如果 CPUHP 状态设置失败dst_subsys_init(); // 初始化目的地子系统rc 0; // 设置返回码为 0表示成功 out:return rc; // 返回初始化结果 }void open_softirq(int nr, void (*action)(struct softirq_action *)) {softirq_vec[nr].action action; }1.4 协议栈注册 内核实现了网络层的IP协议也实现了传输层的TCP协议和UDP协议这些协议对应的实现函数分别是ip_rcv()、tcp_v4_rcv()和udp_rcv。fs_initcall调用inet_init后开始网络协议栈注册通过inet_init将这些函数注册到inet_protos和ptype_base数据结构中。 相关代码如下 IP static struct packet_type ip_packet_type __read_mostly {.type cpu_to_be16(ETH_P_IP),.func ip_rcv, };struct proto tcp_prot {.name TCP,.owner THIS_MODULE,.close tcp_close, // net/ipv4/tcp.c.connect tcp_v4_connect, // net/ipv4/tcp.c.disconnect tcp_disconnect, // net/ipv4/tcp.c.accept inet_csk_accept, // net/ipv4/inet_connection_sock.c.ioctl tcp_ioctl, // net/ipv4/tcp.c.init tcp_v4_init_sock, // net/ipv4/tcp.c.destroy tcp_v4_destroy_sock, // net/ipv4/tcp.c.shutdown tcp_shutdown, // net/ipv4/tcp.c.setsockopt tcp_setsockopt, // net/ipv4/tcp.c.getsockopt tcp_getsockopt, // net/ipv4/tcp.c.keepalive tcp_set_keepalive, // net/ipv4/tcp.c.recvmsg tcp_recvmsg, // net/ipv4/tcp.c.sendmsg tcp_sendmsg, // net/ipv4/tcp.c.sendpage tcp_sendpage, // net/ipv4/tcp.c.backlog_rcv tcp_v4_do_rcv, // net/ipv4/tcp.c.release_cb tcp_release_cb, // net/ipv4/tcp.c.hash inet_hash, // net/ipv4/inet_hashtables.c.unhash inet_unhash, // net/ipv4/inet_hashtables.c.get_port inet_csk_get_port, // net/ipv4/inet_hashtables.c.enter_memory_pressure tcp_enter_memory_pressure, // net/ipv4/tcp.c.stream_memory_free tcp_stream_memory_free, // net/ipv4/tcp.c.sockets_allocated tcp_sockets_allocated, // net/ipv4/tcp.c.orphan_count tcp_orphan_count, // net/ipv4/tcp.c.memory_allocated tcp_memory_allocated, // net/ipv4/tcp.c.memory_pressure tcp_memory_pressure, // net/ipv4/tcp.c.sysctl_mem sysctl_tcp_mem, // net/ipv4/tcp_sysctl.c.sysctl_wmem sysctl_tcp_wmem, // net/ipv4/tcp_sysctl.c.sysctl_rmem sysctl_tcp_rmem, // net/ipv4/tcp_sysctl.c.max_header MAX_TCP_HEADER,.obj_size sizeof(struct tcp_sock), // net/ipv4/tcp.h.slab_flags SLAB_TYPESAFE_BY_RCU,.twsk_prot tcp_timewait_sock_ops, // net/ipv4/tcp.c.rsk_prot tcp_request_sock_ops, // net/ipv4/tcp.c.h.hashinfo tcp_hashinfo, // net/ipv4/tcp_hash.c.no_autobind true, #ifdef CONFIG_COMPAT.compat_setsockopt compat_tcp_setsockopt, // net/ipv4/tcp_compat.c.compat_getsockopt compat_tcp_getsockopt, // net/ipv4/tcp_compat.c #endif.diag_destroy tcp_abort, // net/ipv4/tcp_diag.c }; struct proto udp_prot {.name UDP,.owner THIS_MODULE,.close udp_lib_close, // net/ipv4/udp.c.connect ip4_datagram_connect, // net/ipv4/ip_input.c.disconnect udp_disconnect, // net/ipv4/udp.c.ioctl udp_ioctl, // net/ipv4/udp.c.init udp_init_sock, // net/ipv4/udp.c.destroy udp_destroy_sock, // net/ipv4/udp.c.setsockopt udp_setsockopt, // net/ipv4/udp.c.getsockopt udp_getsockopt, // net/ipv4/udp.c.sendmsg udp_sendmsg, // net/ipv4/udp.c.recvmsg udp_recvmsg, // net/ipv4/udp.c.sendpage udp_sendpage, // net/ipv4/udp.c.release_cb ip4_datagram_release_cb, // net/ipv4/datagram.c.hash udp_lib_hash, // net/ipv4/udp.c.unhash udp_lib_unhash, // net/ipv4/udp.c.rehash udp_v4_rehash, // net/ipv4/udp.c.get_port udp_v4_get_port, // net/ipv4/udp.c.memory_allocated udp_memory_allocated, // net/ipv4/udp.c.sysctl_mem sysctl_udp_mem, // net/ipv4/udp_sysctl.c.sysctl_wmem sysctl_udp_wmem_min, // net/ipv4/udp_sysctl.c.sysctl_rmem sysctl_udp_rmem_min, // net/ipv4/udp_sysctl.c.obj_size sizeof(struct udp_sock), // net/ipv4/udp.h.h.udp_table udp_table, // net/ipv4/udp.c #ifdef CONFIG_COMPAT.compat_setsockopt compat_udp_setsockopt, // net/ipv4/udp_compat.c.compat_getsockopt compat_udp_getsockopt, // net/ipv4/udp_compat.c #endif.diag_destroy udp_abort, // net/ipv4/udp_diag.c };struct proto raw_prot {.name RAW,.owner THIS_MODULE,.close raw_close,.destroy raw_destroy,.connect ip4_datagram_connect,.disconnect __udp_disconnect,.ioctl raw_ioctl,.init raw_init,.setsockopt raw_setsockopt,.getsockopt raw_getsockopt,.sendmsg raw_sendmsg,.recvmsg raw_recvmsg,.bind raw_bind,.backlog_rcv raw_rcv_skb,.release_cb ip4_datagram_release_cb,.hash raw_hash_sk,.unhash raw_unhash_sk,.obj_size sizeof(struct raw_sock),.h.raw_hash raw_v4_hashinfo, #ifdef CONFIG_COMPAT.compat_setsockopt compat_raw_setsockopt,.compat_getsockopt compat_raw_getsockopt,.compat_ioctl compat_raw_ioctl, #endif.diag_destroy raw_abort, };struct proto ping_prot {.name PING,.owner THIS_MODULE,.init ping_init_sock, // net/ipv4/icmp.c.close ping_close, // net/ipv4/icmp.c.connect ip4_datagram_connect, // net/ipv4/ip_input.c.disconnect __udp_disconnect, // net/ipv4/udp.c.setsockopt ip_setsockopt, // net/ipv4/ip_options.c.getsockopt ip_getsockopt, // net/ipv4/ip_options.c.sendmsg ping_v4_sendmsg, // net/ipv4/icmp.c.recvmsg ping_recvmsg, // net/ipv4/icmp.c.bind ping_bind, // net/ipv4/icmp.c.backlog_rcv ping_queue_rcv_skb, // net/ipv4/icmp.c.release_cb ip4_datagram_release_cb, // net/ipv4/datagram.c.hash ping_hash, // net/ipv4/icmp.c.unhash ping_unhash, // net/ipv4/icmp.c.get_port ping_get_port, // net/ipv4/icmp.c.obj_size sizeof(struct inet_sock), // include/linux/inet.h };static const struct net_proto_family inet_family_ops {.family PF_INET,.create inet_create,//socket创建函数.owner THIS_MODULE, };static int __init inet_init(void) {struct inet_protosw *q; // 定义一个指针用于遍历所有的 inet_protosw 结构,用于为IP协议注册套接字接口struct list_head *r; // 定义一个指针用于遍历所有的 inetsw 数组int rc -EINVAL; // 初始化返回码为 -EINVAL表示无效参数错误// 检查 sock_skb_cb 结构体的大小是否合适sock_skb_cb_check_size(sizeof(struct inet_skb_parm));// 注册 TCP 协议rc proto_register(tcp_prot, 1);if (rc)goto out; // 如果注册失败跳转到 out 标签进行清理// 注册 UDP 协议rc proto_register(udp_prot, 1);if (rc)goto out_unregister_tcp_proto; // 如果注册失败跳转到 out_unregister_tcp_proto 标签进行清理// 注册 RAW 协议rc proto_register(raw_prot, 1);if (rc)goto out_unregister_udp_proto; // 如果注册失败跳转到 out_unregister_udp_proto 标签进行清理// 注册 ICMP 协议rc proto_register(ping_prot, 1);if (rc)goto out_unregister_raw_proto; // 如果注册失败跳转到 out_unregister_raw_proto 标签进行清理// 告诉 SOCKET 子系统我们的存在(void)sock_register(inet_family_ops);#ifdef CONFIG_SYSCTL// 初始化 IP 相关的 sysctl 接口ip_static_sysctl_init(); #endif// 添加所有基础协议if (inet_add_protocol(icmp_protocol, IPPROTO_ICMP) 0)pr_crit(%s: Cannot add ICMP protocol\n, __func__);if (inet_add_protocol(udp_protocol, IPPROTO_UDP) 0)pr_crit(%s: Cannot add UDP protocol\n, __func__);if (inet_add_protocol(tcp_protocol, IPPROTO_TCP) 0)pr_crit(%s: Cannot add TCP protocol\n, __func__); #ifdef CONFIG_IP_MULTICASTif (inet_add_protocol(igmp_protocol, IPPROTO_IGMP) 0)pr_crit(%s: Cannot add IGMP protocol\n, __func__); #endif// 注册 socket 层的协议交换结构for (r inetsw[0]; r inetsw[SOCK_MAX]; r)INIT_LIST_HEAD(r);for (q inetsw_array; q inetsw_array[INETSW_ARRAY_LEN]; q)inet_register_protosw(q);// 初始化 ARP 模块arp_init();// 初始化 IP 模块ip_init();// 初始化 TCP 模块tcp_init();// 初始化 UDP 内存阈值udp_init();// 注册 UDP-Lite 协议udplite4_register();// 初始化 ICMP 模块ping_init();// 初始化多播路由器 #if defined(CONFIG_IP_MROUTE)if (ip_mr_init())pr_crit(%s: Cannot init ipv4 mroute\n, __func__); #endif// 初始化 per-net 操作if (init_inet_pernet_ops())pr_crit(%s: Cannot init ipv4 inet pernet ops\n, __func__);// 初始化 IPv4 MIBif (init_ipv4_mibs())pr_crit(%s: Cannot init ipv4 mibs\n, __func__);// 初始化 IPv4 proc 文件系统ipv4_proc_init();// 初始化 IP 分片处理ipfrag_init();// 注册 IP 包协议类型dev_add_pack(ip_packet_type);// 初始化 IP 隧道核心模块ip_tunnel_core_init(); }inet_add_protocol函数将TCP和UDP对应的处理函数都注册到inet_protos数组中。ip_pack_type结构体中的Type是协议名func是ip_rcv函数它们在dev_add_pack中会注册到ptype_base哈希表中。软中断根据ptype_base找到ip_rcv函数地址进而将IP包正确地送到ip_rcv中执行。在ip_rcv中将会通过inet_protos找到TCP或者UDP的处理函数再把包转发给udp_rcv或tcp_rcv。 1.5 网卡驱动初始化 每一个驱动程序会使用module_init向内核注册一个初始化函数当驱动程序被加载时内核就会调用这个函数比如igb网卡驱动程序位于drivers/net/ethernet/intel/igb/igb_main.c中。 static struct pci_driver igb_driver {.name igb_driver_name,.id_table igb_pci_tbl,.probe igb_probe,.remove igb_remove, #ifdef CONFIG_PM.driver.pm igb_pm_ops, #endif.shutdown igb_shutdown,.sriov_configure igb_pci_sriov_configure,.err_handler igb_err_handler };static int __init igb_init_module(void) {int ret; // 用于存储函数返回值// 打印驱动程序的字符串和版本信息pr_info(%s - version %s\n, igb_driver_string, igb_driver_version);// 打印驱动程序的版权信息pr_info(%s\n, igb_copyright);// 如果配置了 DCADirect Cache Access支持则注册 DCA 通知#ifdef CONFIG_IGB_DCAdca_register_notify(dca_notifier);#endif// 注册 PCI 驱动程序ret pci_register_driver(igb_driver);// 返回注册操作的结果return ret; }驱动的pci_register_driver调用完成后Linux内核就知道了该驱动的相关信息。当网卡设备被识别之后内核会调用其驱动的probe方法让设备处于ready状态。对于igb网卡其igb_probe位于drivers/net/ethernet/inetl/igb_main.c下。该函数主要执行操作如下: 1、启动注册到内核 2、调用网卡驱动Probe 3、获取网卡MAC 4、DMA初始化 5、注册ethtool实现函数 6、注册net_device_ops、netdev等变量 7、NAPI初始化注册poll函数 drivers/net/ethernet/inetl/igb_main.c static const struct net_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, // 设置MAC地址.ndo_change_mtu igb_change_mtu, // 更改MTU大小.ndo_do_ioctl igb_ioctl, // 处理控制命令.ndo_tx_timeout igb_tx_timeout, // 处理发送超时.ndo_validate_addr eth_validate_addr, // 验证MAC地址.ndo_vlan_rx_add_vid igb_vlan_rx_add_vid, // 添加VLAN ID.ndo_vlan_rx_kill_vid igb_vlan_rx_kill_vid, // 移除VLAN ID.ndo_set_vf_mac igb_ndo_set_vf_mac, // 设置虚拟功能的MAC地址.ndo_set_vf_vlan igb_ndo_set_vf_vlan, // 设置虚拟功能的VLAN标签.ndo_set_vf_rate igb_ndo_set_vf_bw, // 设置虚拟功能的带宽限制.ndo_set_vf_spoofchk igb_ndo_set_vf_spoofchk, // 设置虚拟功能的MAC地址欺骗检查.ndo_get_vf_config igb_ndo_get_vf_config, // 获取虚拟功能的配置信息 #ifdef CONFIG_NET_POLL_CONTROLLER.ndo_poll_controller igb_netpoll, // 网络控制器轮询模式 #endif.ndo_fix_features igb_fix_features, // 修正网络设备功能特性.ndo_set_features igb_set_features, // 设置网络设备功能特性.ndo_fdb_add igb_ndo_fdb_add, // 向FDB添加MAC地址.ndo_features_check igb_features_check, // 检查网络设备功能特性 };第7步在igb_probe初始化过程中还调用到了igb_alloc_q_vector。它注册了一个NAPI机制必需的poll函数。 igb_probe-igb_sw_init-igb_init_interrupt_scheme-igb_alloc_q_vectors-igb_alloc_q_vector- netif_napi_add static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent) {.../* setup the private structure */err igb_sw_init(adapter);... } static int igb_sw_init(struct igb_adapter *adapter) {.../* set default ring sizes */adapter-tx_ring_count IGB_DEFAULT_TXD;//256adapter-rx_ring_count IGB_DEFAULT_RXD;//256adapter-max_frame_size netdev-mtu ETH_HLEN ETH_FCS_LEN VLAN_HLEN;//mtu1444adapter-min_frame_size ETH_ZLEN ETH_FCS_LEN;//604/* This call may decrease the number of queues */if (igb_init_interrupt_scheme(adapter, true)) {dev_err(pdev-dev, Unable to allocate memory for queues\n);return -ENOMEM;}...return 0; }static int igb_init_interrupt_scheme(struct igb_adapter *adapter, bool msix) {struct pci_dev *pdev adapter-pdev; // 获取PCI设备指针int err;// 根据 msix 参数设置中断类型MSI-X 或者 非 MSI-Xigb_set_interrupt_capability(adapter, msix);// 分配队列向量err igb_alloc_q_vectors(adapter);if (err) {// 如果分配失败打印错误信息并跳转到错误处理代码dev_err(pdev-dev, Unable to allocate memory for vectors\n);goto err_alloc_q_vectors;}// 缓存环形注册这可能涉及到将环形缓冲区的地址缓存到硬件寄存器igb_cache_ring_register(adapter);return 0; // 成功返回 0err_alloc_q_vectors:// 在错误处理代码中重置中断能力释放已分配的资源igb_reset_interrupt_capability(adapter);return err; // 返回错误代码 }static int igb_alloc_q_vectors(struct igb_adapter *adapter) {int q_vectors adapter-num_q_vectors;int rxr_remaining adapter-num_rx_queues;int txr_remaining adapter-num_tx_queues;int rxr_idx 0, txr_idx 0, v_idx 0;int err;if (q_vectors (rxr_remaining txr_remaining)) {for (; rxr_remaining; v_idx) {err igb_alloc_q_vector(adapter, q_vectors, v_idx,0, 0, 1, rxr_idx);if (err)goto err_out;/* update counts and index */rxr_remaining--;rxr_idx;}}for (; v_idx q_vectors; v_idx) {int rqpv DIV_ROUND_UP(rxr_remaining, q_vectors - v_idx);int tqpv DIV_ROUND_UP(txr_remaining, q_vectors - v_idx);err igb_alloc_q_vector(adapter, q_vectors, v_idx,tqpv, txr_idx, rqpv, rxr_idx);if (err)goto err_out;/* update counts and index */rxr_remaining - rqpv;txr_remaining - tqpv;rxr_idx;txr_idx;}... }static int igb_alloc_q_vector(struct igb_adapter *adapter,int v_count, int v_idx,int txr_count, int txr_idx,int rxr_count, int rxr_idx) {struct igb_q_vector *q_vector;struct igb_ring *ring;int ring_count, size;/* igb only supports 1 Tx and/or 1 Rx queue per vector */if (txr_count 1 || rxr_count 1)return -ENOMEM;ring_count txr_count rxr_count;size sizeof(struct igb_q_vector) (sizeof(struct igb_ring) * ring_count);/* allocate q_vector and rings */q_vector adapter-q_vector[v_idx];if (!q_vector) {q_vector kzalloc(size, GFP_KERNEL);} else if (size ksize(q_vector)) {kfree_rcu(q_vector, rcu);q_vector kzalloc(size, GFP_KERNEL);} else {memset(q_vector, 0, size);}if (!q_vector)return -ENOMEM;/* initialize NAPI */netif_napi_add(adapter-netdev, q_vector-napi,igb_poll, 64);/* tie q_vector and adapter together */adapter-q_vector[v_idx] q_vector;q_vector-adapter adapter;/* initialize work limits */q_vector-tx.work_limit adapter-tx_work_limit;/* initialize ITR configuration */q_vector-itr_register adapter-io_addr E1000_EITR(0);q_vector-itr_val IGB_START_ITR;/* initialize pointer to rings */ring q_vector-ring;/* intialize ITR */if (rxr_count) {/* rx or rx/tx vector */if (!adapter-rx_itr_setting || adapter-rx_itr_setting 3)q_vector-itr_val adapter-rx_itr_setting;} else {/* tx only vector */if (!adapter-tx_itr_setting || adapter-tx_itr_setting 3)q_vector-itr_val adapter-tx_itr_setting;}if (txr_count) {/* assign generic ring traits */ring-dev adapter-pdev-dev;ring-netdev adapter-netdev;/* configure backlink on ring */ring-q_vector q_vector;/* update q_vector Tx values */igb_add_ring(ring, q_vector-tx);/* For 82575, context index must be unique per ring. */if (adapter-hw.mac.type e1000_82575)set_bit(IGB_RING_FLAG_TX_CTX_IDX, ring-flags);/* apply Tx specific ring traits */ring-count adapter-tx_ring_count;ring-queue_index txr_idx;u64_stats_init(ring-tx_syncp);u64_stats_init(ring-tx_syncp2);/* assign ring to adapter */adapter-tx_ring[txr_idx] ring;/* push pointer to next ring */ring;}if (rxr_count) {/* assign generic ring traits */ring-dev adapter-pdev-dev;ring-netdev adapter-netdev;/* configure backlink on ring */ring-q_vector q_vector;/* update q_vector Rx values */igb_add_ring(ring, q_vector-rx);/* set flag indicating ring supports SCTP checksum offload */if (adapter-hw.mac.type e1000_82576)set_bit(IGB_RING_FLAG_RX_SCTP_CSUM, ring-flags);/* On i350, i354, i210, and i211, loopback VLAN packets* have the tag byte-swapped.*/if (adapter-hw.mac.type e1000_i350)set_bit(IGB_RING_FLAG_RX_LB_VLAN_BSWAP, ring-flags);/* apply Rx specific ring traits */ring-count adapter-rx_ring_count;ring-queue_index rxr_idx;u64_stats_init(ring-rx_syncp);/* assign ring to adapter */adapter-rx_ring[rxr_idx] ring;}return 0; }static int igb_alloc_q_vector(struct igb_adapter *adapter,int v_count, int v_idx,int txr_count, int txr_idx,int rxr_count, int rxr_idx) {...// 初始化 NAPInetif_napi_add(adapter-netdev, q_vector-napi,igb_poll, 64);... }1.5.1 启动网卡 当上面都初始化完成之后就可以启动网卡。 1、启动网卡 2、调用net_device_ops中注册的open函数如igb_open 3、分配Rx、Tx队列内存 4、注册中断处理函数 5、打开硬中断等待包进来 int igb_open(struct net_device *netdev) {return __igb_open(netdev, false); }static int __igb_open(struct net_device *netdev, bool resuming) {//分配传输描述符数组/* allocate transmit descriptors */err igb_setup_all_tx_resources(adapter);if (err)goto err_setup_tx;//分配接收描述符数组/* allocate receive descriptors */err igb_setup_all_rx_resources(adapter);//注册中断处理函数err igb_request_irq(adapter);if (err)goto err_req_irq;/* 通知栈实际的队列计数 *//* Notify the stack of the actual queue counts. */err netif_set_real_num_tx_queues(adapter-netdev,adapter-num_tx_queues);if (err)goto err_set_queues;err netif_set_real_num_rx_queues(adapter-netdev,adapter-num_rx_queues);//启用NAPIfor (i 0; i adapter-num_q_vectors; i)napi_enable((adapter-q_vector[i]-napi));//启动中断igb_irq_enable(adapter); } static int igb_setup_all_tx_resources(struct igb_adapter *adapter) {struct pci_dev *pdev adapter-pdev;int i, err 0;for (i 0; i adapter-num_tx_queues; i) {err igb_setup_tx_resources(adapter-tx_ring[i]);if (err) {dev_err(pdev-dev,Allocation for Tx Queue %u failed\n, i);for (i--; i 0; i--)igb_free_tx_resources(adapter-tx_ring[i]);break;}}return err; } _igb_open函数调用了igb_setup_all_tx_resources和igb_setup_all_rx_resources。在调用igb_setup_all_rx_resources这一步操作中分配了RingBuffer并建立了内存和Rx队列的映射关系。 static int igb_setup_all_rx_resources(struct igb_adapter *adapter) {for (i 0; i adapter-num_rx_queues; i) {err igb_setup_rx_resources(adapter-rx_ring[i]);...} }在上面的源码中通过循环创建了若干个接收队列。下面看每一个队列是怎么创建出来的。 int igb_setup_rx_resources(struct igb_ring *rx_ring) {//1.申请igb_rx_buffer数组内存size sizeof(struct igb_rx_buffer) * rx_ring-count;rx_ring-rx_buffer_info vmalloc(size);//2.申请e1000_adv_rx_desc DMA数组内存rx_ring-size rx_ring-count * sizeof(union e1000_adv_rx_desc);rx_ring-size ALIGN(rx_ring-size, 4096);rx_ring-desc dma_alloc_coherent(dev, rx_ring-size,rx_ring-dma, GFP_KERNEL);//3.初始化队列成员rx_ring-next_to_alloc 0;rx_ring-next_to_clean 0;rx_ring-next_to_use 0; }从上述源码可以看到实际上Ringbuffer的内部不是仅有一个环形队列数组而是有两个。 igb_rx_buffer数组这个数组是内核使用通过vzalloc申请 e1000_adv_rx_desc数组这个数组是网卡硬件使用的通过dma_alloc_coherent分配。 static int igb_request_irq(struct igb_adapter *adapter) {if (adapter-flags IGB_FLAG_HAS_MSIX) {err igb_request_msix(adapter);...} }static int igb_request_msix(struct igb_adapter *adapter) {...for (i 0; i adapter-num_q_vectors; i) {...err request_irq(adapter-msix_entries[vector].vector,igb_msix_ring, 0, q_vector-name,q_vector);} }对于多队列网卡为每一个队列都注册了中断其对应中断处理函数为igb_msix_ring。在msix方式下每个RX队列都有队列的MSI-X中断从网卡硬件中断的层面就可以设置让收到的包被不同的CPU处理。(可以通过irqalance,或者修改/proc/irq/IRQ_NUMBER/smp_affinity,从而修改和CPU的绑定行为)。 1.5 数据接收 当数据帧从网线到达网卡时第一站是网卡的接收队列网卡在自己的RingBuffer寻找可用的内存位置找到DMA引擎会把数据DMA到网卡之前关联的内存里到这个时候CPU都是无感的当DMA操作完成之后网卡向CPU发起一个硬中断通知CPU有数据到达。过程如下 1、数据帧从外部网络到达网卡 2、网卡把帧DMA到内存 3、发出IRQ硬中断 4、调用驱动注册的硬中断处理函数 5、启动NAPI发出软中断 网卡的硬中断处理函数是igb_msix_ring。 static irqreturn_t igb_msix_ring(int irq, void *data) {struct igb_q_vector *q_vector data; // 获取传递给中断处理函数的数据/* 写入 ITR 值该值根据上一个中断计算得出。 */igb_write_itr(q_vector);/* 调度 NAPI 来处理接收和发送的数据包。 */napi_schedule(q_vector-napi);return IRQ_HANDLED; // 指示中断已被处理 }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); }#define or_softirq_pending(x) (local_softirq_pending() | (x))可以看到这里只是把驱动napi_struct传过来的poll_list添加到CPU变量softnet_data中的poll_list。紧接着__raise_softirq_irqoff触发了一个软中断NET_RX_SOFTIRQ这个触发过程只是进行了一个或运算。 1.5.1 ksoftirqd内核线程处理软中断 软中断处理流程如下 ksoftirqd中两个线程函数ksoftirqd_should_run和run_ksoftirqd。其中ksoftirqd_should_run函数代码如下: kernel/softirq.c static int ksoftirqd_should_run(unsigned int cpu) {return local_softirq_pending(); } #define local_softirq_pending() \__IRQ_STAT(smp_processor_id(), __softirq_pending)从这里可以看到此函数和硬中断调用了同一个函数local_softirq_pending。 注意硬中断中的设置软中断标记和ksoftirq中判断是否有软中断到达都是基于smp_processor_id()。这意味着只要硬中断在哪个CPU上被响应那么软中断也是在这个CPU上处理的。如果发现Linux软中断的某个CPU消耗都集中在一个核上正确做法应该是调整硬中断的CPU亲和性。 使用方式的不同之处在于在硬中断处理中是为了写入标记这里是读取。如果读取到了NET_RX_SOFTIRQ,则进入内核线程处理函数run_ksoftirqd进行处理: static void run_ksoftirqd(unsigned int cpu) {local_irq_disable();if (local_softirq_pending()) {//处理待处理的软中断__do_softirq();...}local_irq_enable(); }在__do_softirq中判断根据当前CPU的软中断类型调用个其注册的action方法。 asmlinkage __visible void __softirq_entry __do_softirq(void) {pending local_softirq_pending(); // 获取待处理的软中断位掩码account_irq_enter_time(current); // 账户中断进入时间h softirq_vec; // 获取软中断处理程序数组while ((softirq_bit ffs(pending))) { // 找到第一个待处理的软中断位unsigned int vec_nr;int prev_count;h softirq_bit - 1; // 获取对应的软中断处理程序vec_nr h - softirq_vec; // 计算软中断向量号prev_count preempt_count(); // 获取当前的抢占计数kstat_incr_softirqs_this_cpu(vec_nr); // 增加该 CPU 上的软中断计数trace_softirq_entry(vec_nr); // 跟踪软中断进入h-action(h); // 调用软中断处理程序trace_softirq_exit(vec_nr); // 跟踪软中断退出if (unlikely(prev_count ! preempt_count())) { // 检查抢占计数是否改变pr_err(huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n,vec_nr, softirq_to_name[vec_nr], h-action,prev_count, preempt_count());preempt_count_set(prev_count); // 还原抢占计数}h;pending softirq_bit; // 更新待处理的位掩码} }net/core/dev.c 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);int budget netdev_budget;LIST_HEAD(list);LIST_HEAD(repoll);//关中断local_irq_disable();list_splice_init(sd-poll_list, list);//开中断local_irq_enable();for (;;) {struct napi_struct *n;if (list_empty(list)) {if (!sd_has_rps_ipi_waiting(sd) list_empty(repoll))goto out;break;}n list_first_entry(list, struct napi_struct, poll_list);budget - napi_poll(n, repoll);if (unlikely(budget 0 ||time_after_eq(jiffies, time_limit))) {sd-time_squeeze;break;}}... }在硬中断中将设备添加到poll_list会不会重复添加呢? 答案是不会的在软中断处理函数net_rx_action这里先调用local_irq_disable把所有的硬中断关闭了把CPU的netsoft_data的poll_list直接拷贝出来之后再开的中断不会给硬中断重复添加poll_list的机会。 函数开头的time_limit和budget是用来控制net_rx_action函数主动退出的目的是保证网络包的接收不霸占CPU不放。这个函数核心逻辑就是获取softnet_data的poll_list进行遍历然后执行到网卡驱动注册的poll函数。对于igb网卡来说就是igb_poll函数。 static int napi_poll(struct napi_struct *n, struct list_head *repoll) {if (test_bit(NAPI_STATE_SCHED, n-state)) {work n-poll(n, weight);trace_napi_poll(n, work, weight);} }static int igb_poll(struct napi_struct *napi, int budget) {struct igb_q_vector *q_vector container_of(napi,struct igb_q_vector,napi);/* 清理发送队列的中断 */if (q_vector-tx.ring)clean_complete igb_clean_tx_irq(q_vector, budget);/* 清理接收队列的中断 */if (q_vector-rx.ring) {int cleaned igb_clean_rx_irq(q_vector, budget);}... }在读取操作中igb_poll的重点工作是对igb_clean_rx_igb的调用。 drivers/net/ethernet/inetl/igb_main.cstatic int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget) {struct igb_ring *rx_ring q_vector-rx.ring; // 获取接收队列struct sk_buff *skb rx_ring-skb; // 获取当前正在处理的 sk_buffunsigned int total_bytes 0, total_packets 0; // 用于统计接收到的总字节和数据包数u16 cleaned_count igb_desc_unused(rx_ring); // 获取尚未处理的描述符数量while (likely(total_packets budget)) {union e1000_adv_rx_desc *rx_desc; // 指向当前接收描述符struct igb_rx_buffer *rx_buffer; // 指向当前接收缓冲区unsigned int size; // 数据包大小// 如果有足够的描述符被清理重新向硬件返回一些缓冲区if (cleaned_count IGB_RX_BUFFER_WRITE) {igb_alloc_rx_buffers(rx_ring, cleaned_count);cleaned_count 0;}rx_desc IGB_RX_DESC(rx_ring, rx_ring-next_to_clean); // 获取下一个待清理的描述符size le16_to_cpu(rx_desc-wb.upper.length); // 获取数据包大小if (!size)break; // 如果没有数据退出循环// 确保在读取描述符其他字段之前描述符已经被写回dma_rmb();rx_buffer igb_get_rx_buffer(rx_ring, size); // 获取对应的接收缓冲区// 从接收队列中检索一个缓冲区if (skb)igb_add_rx_frag(rx_ring, rx_buffer, skb, size); // 添加到当前数据包的片段else if (ring_uses_build_skb(rx_ring))skb igb_build_skb(rx_ring, rx_buffer, rx_desc, size); // 构建一个新的 sk_buffelseskb igb_construct_skb(rx_ring, rx_buffer, rx_desc, size); // 构造一个新的 sk_buff// 如果未能检索到缓冲区增加分配失败的统计并退出if (!skb) {rx_ring-rx_stats.alloc_failed;rx_buffer-pagecnt_bias;break;}igb_put_rx_buffer(rx_ring, rx_buffer); // 将缓冲区放回接收队列cleaned_count; // 增加已清理的描述符计数// 如果当前描述符不是帧的结束继续处理下一个缓冲区if (igb_is_non_eop(rx_ring, rx_desc))continue;// 验证数据包布局是否正确if (igb_cleanup_headers(rx_ring, rx_desc, skb)) {skb NULL; // 如果布局不正确丢弃当前 sk_buffcontinue;}// 更新接收到的总字节数total_bytes skb-len;// 处理数据包的校验和、时间戳、VLAN 标签和协议igb_process_skb_fields(rx_ring, rx_desc, skb);napi_gro_receive(q_vector-napi, skb); // 将数据包传递给上层网络堆栈// 重置 sk_buff 指针skb NULL;// 更新预算统计total_packets;}// 将未完成的帧重新放回接收队列以便后续完成rx_ring-skb skb;// 更新接收到的总字节和数据包数的统计u64_stats_update_begin(rx_ring-rx_syncp);rx_ring-rx_stats.packets total_packets;rx_ring-rx_stats.bytes total_bytes;u64_stats_update_end(rx_ring-rx_syncp);q_vector-rx.total_packets total_packets;q_vector-rx.total_bytes total_bytes;// 如果有清理的描述符重新为它们分配缓冲区if (cleaned_count)igb_alloc_rx_buffers(rx_ring, cleaned_count);return total_packets; // 返回处理的数据包数量 }igb_fetch_rx_buffer和igb_is_non_eop的作用就是把数据帧从RingBuffer取下来Skb被从RingBuffer取下来后会通过igb_alloc_rx_buffer申请新的skb再重新挂上去。接下来进入napi_gro_receive函数。 gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) {skb_mark_napi_id(skb, napi);trace_napi_gro_receive_entry(skb);skb_gro_reset_offset(skb);return napi_skb_finish(dev_gro_receive(napi, skb), skb); }dev_gro_receive这个函数代表的是网卡GRO特性可以简单理解成把相关小包组成一个大包目的是减少传送给网络栈的包数有助于降低对CPU的使用量。接下来看napi_skb_finish这个函数主要是调用了netif_receive_skb_internal,netif_receive_skb_internal又调用了__netif_receive_skb在__netif_receive_skb中数据包将被送到协议栈。 static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb) {switch (ret) {case GRO_NORMAL:if (netif_receive_skb_internal(skb))ret GRO_DROP;break;... }static int netif_receive_skb_internal(struct sk_buff *skb) {...ret __netif_receive_skb(skb);rcu_read_unlock();return ret; }1.6 网络协议栈处理 __netif_receive_skb函数会根据包的协议进行处理处理流程如下图所示 net/core/dev.cstatic int __netif_receive_skb(struct sk_buff *skb) {int ret;/* 检查是否有内存分配的套接字和数据包是否使用了页面外内存 */if (sk_memalloc_socks() skb_pfmemalloc(skb)) {unsigned int noreclaim_flag;noreclaim_flag memalloc_noreclaim_save(); // 保存当前的 memalloc_noreclaim 状态ret __netif_receive_skb_core(skb, true); // 调用核心函数处理数据包启用 PF_MEMALLOC 路径memalloc_noreclaim_restore(noreclaim_flag); // 恢复之前的 memalloc_noreclaim 状态} else {ret __netif_receive_skb_core(skb, false); // 调用核心函数处理数据包不启用 PF_MEMALLOC 路径}return ret; // 返回处理结果 }static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc) {....../* 遍历全局和设备的 packet_type 列表 ,pcap逻辑这里会将数据送入抓包点tcpdump就是从这个入口获取包的*/list_for_each_entry_rcu(ptype, ptype_all, list) {if (pt_prev)ret deliver_skb(skb, pt_prev, orig_dev);pt_prev ptype;}list_for_each_entry_rcu(ptype, skb-dev-ptype_all, list) {if (pt_prev)ret deliver_skb(skb, pt_prev, orig_dev);pt_prev ptype;}skip_taps:#ifdef CONFIG_NET_INGRESSif (static_key_false(ingress_needed)) {skb sch_handle_ingress(skb, pt_prev, ret, orig_dev);if (!skb)goto out;/* 处理入口过滤,这是netfilter第一个输入挂接点处理注册的回调函数 */if (nf_ingress(skb, pt_prev, ret, orig_dev) 0)goto out;} #endif...... }在__netif_receive_skb_core中可以看到tcpdump命令的抓包点和netfilter的输入的第一个挂接点tcpdump将抓包函数以协议形式挂到ptype_all上设备层遍历所有协议就能抓到数据包了tcpdump会执行packet_create。 static int packet_create(struct net *net, struct socket *sock, int protocol,int kern) {po-prot_hook.func packet_rcv;if (sock-type SOCK_PACKET)po-prot_hook.func packet_rcv_spkt;po-prot_hook.af_packet_priv sk;if (proto) {po-prot_hook.type proto;register_prot_hook(sk);} }register_prot_hook会把tcpdump用到的协议挂到ptype_all上。 接着__netif_receive_skb_core函数取出protocol它会从数据包中取出协议信息然后遍历注册在这个协议上的回调函数列表。ptype_base是一个哈希表。ip_rcv函数地址就存在这个哈希表中。 static inline int deliver_skb(struct sk_buff *skb,struct packet_type *pt_prev,struct net_device *orig_dev) {if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))return -ENOMEM;atomic_inc(skb-users);return pt_prev-func(skb, skb-dev, pt_prev, orig_dev); }pt_prev-func调用了协议层注册的处理函数对于IP包就会进入ip_rcv如果是arp包就会进入arp_rcv。 1.7 IP层处理 net/ipv4/ip_input.cint ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) {....../* 调用 netfilter 钩子继续处理数据包 */return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,net, NULL, skb, dev, NULL,ip_rcv_finish); }这是第二个iptables_netfilter的挂载点(NF_INET_PRE_ROUTING)。 net/ipv4/arp.c: NF_HOOK(NFPROTO_ARP, NF_ARP_OUT,dev_net(skb-dev), NULL, skb, NULL, skb-dev,arp_xmit_finish); net/ipv4/arp.c: return NF_HOOK(NFPROTO_ARP, NF_ARP_IN,dev_net(dev), NULL, skb, dev, NULL,arp_process); net/ipv4/ip_input.c: return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,net, NULL, skb, skb-dev, NULL,ip_local_deliver_finish); net/ipv4/ip_input.c: return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,net, NULL, skb, dev, NULL,ip_rcv_finish); net/ipv4/ip_forward.c: return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,net, NULL, skb, skb-dev, rt-dst.dev,ip_forward_finish); net/ipv4/xfrm4_output.c NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,net, sk, skb, NULL, skb_dst(skb)-dev,__xfrm4_output,!(IPCB(skb)-flags IPSKB_REROUTED)); net/ipv4/ip_output.c nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,net, sk, skb, NULL, skb_dst(skb)-dev,dst_output); net/ipv4/ip_output.c NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING,net, sk, newskb, NULL, newskb-dev,ip_mc_finish_output); net/ipv4/ip_output.c return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,net, sk, skb, NULL, skb_dst(skb)-dev,dst_output);当执行完注册的钩子后就会执行到最后一个参数指向的函数ip_rcv_finish。 net/ipv4/ip_input.c static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb) {/* 初始化数据包在 Linux 网络中的虚拟路径缓存 */if (!skb_valid_dst(skb)) {int err ip_route_input_noref(skb, iph-daddr, iph-saddr,iph-tos, dev);if (unlikely(err)) {if (err -EXDEV)__NET_INC_STATS(net, LINUX_MIB_IPRPFILTER);goto drop;}}/* 将数据包传递给目的地 */return dst_input(skb); }int ip_route_input_noref(struct sk_buff *skb, __be32 daddr, __be32 saddr,u8 tos, struct net_device *dev) {if (ipv4_is_multicast(daddr)) {...if (our #ifdef CONFIG_IP_MROUTE||(!ipv4_is_local_multicast(daddr) IN_DEV_MFORWARD(in_dev)) #endif) {res ip_route_input_mc(skb, daddr, saddr,tos, dev, our);}...} }static int ip_route_input_mc(struct sk_buff *skb, __be32 daddr, __be32 saddr,u8 tos, struct net_device *dev, int our) {rth rt_dst_alloc(dev_net(dev)-loopback_dev, flags, RTN_MULTICAST,IN_DEV_CONF_GET(in_dev, NOPOLICY), false, false);if (!rth)goto e_nobufs;rth-dst.output ip_rt_bug;rth-rt_is_input 1;#ifdef CONFIG_IP_MROUTE//如果是多播赋予多播处理函数if (!ipv4_is_local_multicast(daddr) IN_DEV_MFORWARD(in_dev))rth-dst.input ip_mr_input; #endifRT_CACHE_STAT_INC(in_slow_mc);skb_dst_set(skb, rth-dst);... } struct rtable *rt_dst_alloc(struct net_device *dev,unsigned int flags, u16 type,bool nopolicy, bool noxfrm, bool will_cache) {...if (rt) {...rt-dst.output ip_output;if (flags RTCF_LOCAL)rt-dst.input ip_local_deliver;}return rt; }所以回到ip_rcv_finish中的return dst_input(skb)调用的input方法就是路由子系统赋的ip_local_deliver。 net/ipv4/ip_input.c int ip_local_deliver(struct sk_buff *skb) {struct net *net dev_net(skb-dev);if (ip_is_fragment(ip_hdr(skb))) {if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))return 0;}return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,net, NULL, skb, skb-dev, NULL,ip_local_deliver_finish); }static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb) {...{int protocol ip_hdr(skb)-protocol;...ipprot rcu_dereference(inet_protos[protocol]);if (ipprot) {ret ipprot-handler(skb);} ...} } inet_protos中保存着tcp_v4_rcv和udp_rcv的函数地址。这里会根据包的协议类型选择分发在这里skb包派送到更上层的协议中。 1.8 收包小结 准备工作 创建ksoftirqd线程为它设置好自己的线程函数协议栈注册网卡驱动初始化每个驱动都有一个初始化函数内核让驱动也初始化在初始化过程中把自己的DMA准备好把NAPI的poll函数地址告诉内核启动网卡分配RX、TX队列注册中断对应的处理函数 收包过程 网卡将数据帧DMA到内存的RingBuffer中然后向CPU发起中断通知CPU响应中断请求调用网卡启动时注册的中断处理函数中断函数发起软中断请求内核线程ksoftirqd发现有软中断请求到来关闭硬中断ksoftirq线程开始调用驱动的poll函数收包poll函数将收到的包送到协议栈注册的ip_rcv函数中ip_rcv函数将包送到udp_rcv或tcp_rcv函数中
http://www.dnsts.com.cn/news/13491.html

相关文章:

  • 成都网站排名优化公司seo网站内部优化方案
  • 做物流网站电话常州云之家网站建设网络公司怎么样
  • vs2013网站开发wordpress做招聘网站
  • 先锋设计网站想开个小说网站怎么做
  • 商城网站建设公司电话wordpress内页模板
  • 湖北手机网站制作浙江建设厅网站安全员证书查询
  • shopify做旅游网站为什么网站要用外链
  • 自己创建公司网站建设网站总经理讲话范本
  • 找人做网站需要问哪些问题网站建设一流公司
  • 网站代理建设顺的品牌网站设计信息
  • 拥有域名后怎么建设网站做网站调用无广告视频
  • wordpress主题模板视频网站模板wordpress 自定义内容类型 pan
  • 网站访问统计 曲线图网站开发者模式怎么打开
  • 郑州一建集团工程建设有限公司网站寿宁建设局网站
  • 个人域名备案 网站名称注册公司网站模板下载
  • 上海金融网站建设公司做视频网站视频用什么插件
  • 怎样建设论坛网站外贸建站优化
  • 优良网站徐州做网站建设
  • 玩具网站建设策划书宝塔如何搭建网站
  • 长沙网站建站模板软件开发图片
  • 什么网站会更有浏览量wordpress 慢途网
  • 荣成市信用建设官方网站电商平台正在建设中网站页面
  • 北京网站设计制作关键词wordpress分类标题自定义
  • 专业微信网站开发公司178网站建设
  • 苏州网站备案查询东圃那里有做网站设计的
  • 网站开发前台代码和后台代码推广计划与推广单元设置
  • 小女孩做网站网站建设与管理 孙伟
  • 学做网站要多少钱橄榄树网站建设
  • 网站中高端建设网站建设东北
  • 做告状网站index百度指数