1 网卡中断逻辑
网卡接到数据后,触发中断,内核回调中断处理程序 ISR
. 一般中断都会分成上半部和下半部 (bh), 上半部执行时间短,不允许程序休眠,并且此时中断处于禁止状态。下半部有多种实现,网卡使用软中断,由 ksoftirqd
处理,耗时较长。
在石器时代,网卡中断只由一个 cpu 处理,但是在大数据高吐吞时,就会把某个核(一般是 cpu0) 拖跨,一直频繁的响应中断,严重影响网卡吞吐。所以就有了硬件层面的 RSS
概念,receive side scaling
,网卡实现多个队列,每个队列一个中断并且绑定到不同 cpu, 将中断分摊到多个 cpu. 如果硬件不支持硬件队列呢?就有了 RPS
概念,在软件层面模拟一个队列,如果有 RSS
,就没必要用 RPS
读取网卡数据有两种方式:中断和轮循。只有中断会使 cpu 负载变得很高,一直忙于响应中断。只用轮循时延得不到保证,会使其它进程恶心。为了高效的使用 cpu,现代操作系统都是采用 中断 + 轮循 的方式,这就是下面会提到的 NAPI
2 操作系统网络初始化
在网卡正式工作前,先由内核为网络做准备工作。内核调用 net/dev/core.c net_dev_init
初始化网络,主要做如下工作:
dev_proc_init
在操作系统/proc/net
目录下生成相关文件,/proc/net/dev
,/proc/net/ptype
,/proc/net/dev_mcast
存放一些统计及状态信息netdev_kobject_init
创建操作系统目录/sys/class/net
, 当网卡启动后都会在这里进行注册,方便查看- 初始化全局
ptype_all
结构,这里存放不同协义族如何处理接收数据包的回调。打印/proc/net/ptype
文件可以看到全部,比如ip_rcv
, 这一块很重要 for_each_possible_cpu
初始化每个 cpu 的局部变量,per_cpu
是一个宏,表示他所引用的变量,每个 cpu 都有一个私有的。
for_each_possible_cpu(i) {
struct work_struct *flush = per_cpu_ptr(&flush_works, i);
struct softnet_data *sd = &per_cpu(softnet_data, i);
INIT_WORK(flush, flush_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_RPS
sd->csd.func = rps_trigger_softirq;
sd->csd.info = sd;
sd->cpu = i;
#endif
sd->backlog.poll = process_backlog;
sd->backlog.weight = weight_p;
}
work_struct
每个 cpu 都会有一个,如果网卡消失了,那么回调