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.cstatic 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){ register_cpu_notifier(&cpu_nfb); BUG_ON(smpboot_register_percpu_thread(&softirq_threads)); return 0;}early_initcall(spawn_ksoftirqd);当ksoftirqd被创建出来以后,它就会进入自己的线程循环函数ksoftirqd_should_run和run_ksoftirqd了 。不停地判断有没有软中断需要被处理 。这里需要注意的一点是,软中断不仅仅只有网络软中断,还有其它类型 。//file: include/linux/interrupt.h【图解Linux网络包接收过程】enum{ 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来初始化各个子系统,在源代码目录里你可以grep出许多对这个函数的调用 。这里我们要说的是网络子系统的初始化,会执行到net_dev_init函数 。
//file: net/core/dev.cstatic int __init net_dev_init(void){ ...... for_each_possible_cpu(i) { struct softnet_data *sd = &per_cpu(softnet_data, i); memset(sd, 0, sizeof(*sd)); skb_queue_head_init(&sd->input_pkt_queue); skb_queue_head_init(&sd->process_queue); sd->completion_queue = NULL; INIT_LIST_HEAD(&sd->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.cvoid open_softirq(int nr, void (*action)(struct softirq_action *)){ softirq_vec[nr].action = action;}2.3 协议栈注册内核实现了网络层的ip协议,也实现了传输层的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.cstatic struct packet_type ip_packet_type __read_mostly = { .type = cpu_to_be16(ETH_P_IP), .func = ip_rcv,};static const struct net_protocol udp_protocol = { .handler = udp_rcv, .err_handler = udp_err, .no_policy = 1, .netns_ok = 1,};static const struct net_protocol tcp_protocol = { .early_demux = tcp_v4_early_demux, .handler = tcp_v4_rcv, .err_handler = tcp_v4_err, .no_policy = 1, .netns_ok = 1,};static int __init inet_init(void){ ...... if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0) pr_crit("%s: Cannot add ICMP protocoln", __func__); if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0) pr_crit("%s: Cannot add UDP protocoln", __func__); if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) pr_crit("%s: Cannot add TCP protocoln", __func__); ...... dev_add_pack(&ip_packet_type);}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 三种使用AI攻击网络安全的方法
- 理解了Linux I/O机制,才能真的明白“什么是多线程”
- Linux网络配置
- 带你重新认识Linux系统的inode
- 古剑奇谭网络版野外pvp 古剑奇谭ol剧情
- 从Linux源码看Socket的listen及连接队列
- linux网络编程常见API详解
- 了解神经网络和模型泛化
- TCP/IP 基础知识总结
- 如何在电脑上安装网络打印机?详细教程全部教给你
