Linux网络开始收发包之前需要做的事情
1、创建ksoftirqd内核进程
2、网络子系统初始化
3、内核网络协议栈注册
4、网卡驱动初始化
5、启动网卡
以上5步就是我们在收发包之前内核要做的事情。
1、创建ksoftirqd内核进程
ksoftirqd内核进程也称为 per_cpu进程(每一个cpu核都有一个)。其由smpboot_register_percpu_thread函数创建。
在kernel/smpboot.c中调用smpboot_register_percpu_thread函数
int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread)
{
unsigned int cpu;
int ret = 0;
get_online_cpus();
mutex_lock(&smpboot_threads_lock);
for_each_online_cpu(cpu) {
ret = __smpboot_create_thread(plug_thread, cpu);
if (ret) {
smpboot_destroy_threads(plug_thread);
goto out;
}
smpboot_unpark_thread(plug_thread, cpu);
}
list_add(&plug_thread->list, &hotplug_threads);
out:
mutex_unlock(&smpboot_threads_lock);
put_online_cpus();
return ret;
}
EXPORT_SYMBOL_GPL(smpboot_register_percpu_thread);
在这里我们要搞懂俩个事情,这个函数是被谁调用的,它最终做了什么,至于里面的细节暂时先不谈。
一、这个函数是谁调用的:
下面一张截图是其调用流程
early_initcall(spawn_ksoftirqd) 中调用smpboot_register_percpu_thread函数。内核启动过程中会有start_kernel --> rest_init这个流程,在reset_init会创建一个内核线程,执行kernel_init,early_initcall需要在SMP初始化之前被调用,其在do_pre_smp_initcalls函数中被调用。early_initcall宏的调用调用时刻有点复杂,暂时先不深入了解。
ksoftirq进程其实是由kthreadd进程创建的,其是所有Linux内核线程的鼻祖,swapper进程是init进程和kthreadd进程的父进程,是Linux启动的第一个进程;
kernel_init --> kernel_init_freeable --> do_pre_smp_initcalls
|---> do_basic_setup --> do_initcalls
二、这个函数执行完后发生了什么:
1、为每一个cpu核都创建了一个ksoftirqd进程*(kthread_create_on_cpu),回调函数是smpboot_thread_fn。
追踪代码发现实际上是调用到了__kthread_create_on_node函数创建ksoftirqd进程*,创建的过程如图所示。
分配一个kthread_create_info结构体,并初始化它,然后加入到kthread_create_list全局链表中,此链表是内核线程链表,所有的内核线程都在里面。并使用wake_up_process唤醒ksoftirqd进程。
2、线程被唤醒后会执行回调函数,在回调函数smpboot_thread_fn会阻塞休眠,一旦有软中断需要处理(进程run),就会调用softirq_threads->thread_fn。
3、在softirq_threads->thread_fn中开始执行软中断处理函数。
下面是完整的代码流程,涉及到/kernel/softirq.c和/kernel/smpboot.c。
在/kernel/softirq.c中初始化softirq_threads结构体,并调用smpboot_register_percpu_thread
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run = ksoftirqd_should_run,
.thread_fn = run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};
BUG_ON(smpboot_register_percpu_thread(&softirq_threads));
/kernel/smpboot.c
int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread)
{
unsigned int cpu;
int ret = 0;
get_online_cpus();
mutex_lock(&smpboot_threads_lock);
for_each_online_cpu(cpu) {
ret = __smpboot_create_thread(plug_thread, cpu);
if (ret) {
smpboot_destroy_threads(plug_thread);
goto out;
}
smpboot_unpark_thread(plug_thread, cpu);
}
list_add(&plug_thread->list, &hotplug_threads);
out:
mutex_unlock(&smpboot_threads_lock);
put_online_cpus();
return ret;
}
EXPORT_SYMBOL_GPL(smpboot_register_percpu_thread);
__smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
{
struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);
struct smpboot_thread_data *td;
if (tsk)
return 0;
td = kzalloc_node(sizeof(*td), GFP_KERNEL, cpu_to_node(cpu));
if (!td)
return -ENOMEM;
td->cpu = cpu;
td->ht = ht;
在这里创建kthread进程,实际最终调用的是上面讲的__kthread_create_on_node函数,回调函数为smpboot_thread_fn。
tsk = kthread_create_on_cpu(smpboot_thread_fn, td, cpu,
ht->thread_comm);
if (IS_ERR(tsk)) {
kfree(td);
return PTR_ERR(tsk);
}
kthread_set_per_cpu(tsk, cpu);
/*
* Park the thread so that it could start right on the CPU
* when it is available.
*/
kthread_park(tsk);
get_task_struct(tsk);
*per_cpu_ptr(ht->store, cpu) = tsk;
if (ht->create) {
/*
* Make sure that the task has actually scheduled out
* into park position, before calling the create
* callback. At least the migration thread callback
* requires that the task is off the runqueue.
*/
if (!wait_task_inactive(tsk, TASK_PARKED))
WARN_ON(1);
else
ht->create(cpu);
}
return 0;
}
在回调函数smpboot_thread_fn会一直循环去检测是否需要执行软中断处理,一旦有软中断需要处理,就会调用smp_hotplug_thread->thread_fn。
static int smpboot_thread_fn(void *data)
{
struct smpboot_thread_data *td = data;
struct smp_hotplug_thread *ht = td->ht;
while (1) {
set_current_state(TASK_INTERRUPTIBLE);
preempt_disable();
if (kthread_should_stop()) {
__set_current_state(TASK_RUNNING);
preempt_enable();
/* cleanup must mirror setup */
if (ht->cleanup && td->status != HP_THREAD_NONE)
ht->cleanup(td->cpu, cpu_online(td->cpu));
kfree(td);
return 0;
}
if (kthread_should_park()) {
__set_current_state(TASK_RUNNING);
preempt_enable();
if (ht->park && td->status == HP_THREAD_ACTIVE) {
BUG_ON(td->cpu != smp_processor_id());
ht->park(td->cpu);
td->status = HP_THREAD_PARKED;
}
kthread_parkme();
/* We might have been woken for stop */
continue;
}
BUG_ON(td->cpu != smp_processor_id());
/* Check for state change setup */
switch (td->status) {
case HP_THREAD_NONE:
__set_current_state(TASK_RUNNING);
preempt_enable();
if (ht->setup)
ht->setup(td->cpu);
td->status = HP_THREAD_ACTIVE;
continue;
case HP_THREAD_PARKED:
__set_current_state(TASK_RUNNING);
preempt_enable();
if (ht->unpark)
ht->unpark(td->cpu);
td->status = HP_THREAD_ACTIVE;
continue;
}
if (!ht->thread_should_run(td->cpu)) {
preempt_enable_no_resched();
schedule();
} else {
__set_current_state(TASK_RUNNING);
preempt_enable();
ht->thread_fn(td->cpu);
}
}
}
static void run_ksoftirqd(unsigned int cpu)
{
local_irq_disable();
if (local_softirq_pending()) {
/*
* We can safely run softirq on inline stack, as we are not deep
* in the task stack here.
*/
__do_softirq();
local_irq_enable();
cond_resched();
return;
}
local_irq_enable();
}
从数据结构的角度来看,总共涉及到三个结构体和一个链表,他们的关系如截图所示。