Linux收包(L2层)

发布时间 2023-12-06 22:40:06作者: 划水的猫

一、环境说明

内核版本:Linux 3.10

内核源码地址:https://elixir.bootlin.com/linux/v3.10/source (包含各个版本内核源码,且王页可全局搜索函数)

网卡:Intel的igb网卡

网卡驱动源码目录:drivers/net/ethernet/intel/igb/

二、Linux启动

Linux驱动,内核协议栈等等模块在具备接收网卡数据包之前,要做很多的准备工作才行。
比如要提前创建好ksoftirqd内核线程,要注册好各个协议对应的处理函数,网络设备子系统要提前初始化好,网卡要启动好等等。

1.创建软中断ksoftirqd内核进程

每个CPU负责执行一个ksoftirq内核进程,比如ksoftirqd/0运行在CPU 0上,这些内核进程执行不同软中断注册的中断处理函数。
执行硬中断的处理函数的 CPU 核心,也会执行该硬中断后续的软中断处理函数。
ksoftirqd 内核进程通过 spawn_ksoftirqd 函数初始化:

// file: kernel/softirq.c
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)
{
    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了。不停地判断有没有软中断需要被处理。

2.网络子系统初始化

网络子系统通过net_dev_init函数进行初始化:

// file: net/core/dev.c
static int __init net_dev_init(void)
{
    ......
    // 为每个CPU都申请一个softnet_data数据结构
    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);
        sd->output_queue = NULL;
        sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPS
        sd->csd.func = rps_trigger_softirq;
        sd->csd.info = sd;
        sd->csd.flags = 0;
        sd->cpu = i;
#endif

        sd->backlog.poll = process_backlog;
        sd->backlog.weight = weight_p;
        sd->backlog.gro_list = NULL;
        sd->backlog.gro_count = 0;
    }

    ......
    // 注册软中断处理函数,用于处理接收和发送的数据包
    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);
    ......
}

subsys_initcall(net_dev_init);

内核通过open_softirq函数来注册软中断处理函数:

// file: kernel/softirq.c
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    softirq_vec[nr].action = action;
}

3.协议栈注册

linux通过inet_init将TCP、UDP和ICMP的接收函数注册到了inet_protos数组中,将IP的接收函数注册到了ptype_base哈希表中。

// file: net/ipv4/af_inet.c
static int __init inet_init(void)
{
    ......
    // 添加基础网络协议到inet_protos数组中
    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__);

    ......
    // 将IP的接收函数ip_rcv注册到ptype_base列表里
    dev_add_pack(&ip_packet_type);
    ......
}

fs_initcall(inet_init);

TCP、UDP、ICMP和IP协议的处理函数:

//file: net/ipv4/af_inet.c
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 const struct net_protocol udp_protocol = {
    .handler =    udp_rcv,
    .err_handler =    udp_err,
    .no_policy =    1,
    .netns_ok =    1,
};
static const struct net_protocol icmp_protocol = {
    .handler =    icmp_rcv,
    .err_handler =    icmp_err,
    .no_policy =    1,
    .netns_ok =    1,
};
static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,
};

4.网卡驱动程序初始化以及网卡启动

详细说明见:网卡驱动程序初始化文档

三、迎接数据的到来

 

1.硬中断处理

2.软中断处理