进程模型5-0号进程
内核版本 | 架构 | 作者 | GitHub | CSDN |
---|---|---|---|---|
Linux-3.0.1 | armv7-A | Lux1206 |
0号进程的作用
在 Linux 中除了 init_task 0号进程,所有的线/进程都是通过 do_fork 函数复制父线/进程创建得到,因为 0号进程产生时没有任何进程可以参照,只能通过静态方式构造进程描述符 task_struct 和进程控制块 thread_info,然后在内核编译完成后相当于创建完成。
0号进程在完成必要的内核早期初始化和创建出 1号进程(所有用户进程的父进程)和 2号进程(所有内核线程的父进程)之后会退化为当前 cpu上运行队列的 idle 空闲进程,永久运行在内核态,继续默默守护整个内核。需要特殊说明的是 0号进程执行 start_kernel 不是通过调度执行的,而是内核直接跳转到 start_kernel,最后在 rest_init 中开启调度。
0号进程的创建
在 linux 3.0.1 中 0号进程描述符 init_task 还是与具体的架构相关的,对于arm 架构位于 ./arch/arm/kernel/init_task.c
使用宏 INIT_THREAD_INFO 和 INIT_TASK 定义了全局的 init_task 进程描述符和 init_thread_union 进程控制块。
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
struct task_struct init_task = INIT_TASK(init_task);
0号进程的描述符
#define INIT_TASK(tsk) \
{ \
.state = 0, \
.stack = &init_thread_info,/*内核栈*/ \
.usage = ATOMIC_INIT(2), \
.flags = PF_KTHREAD, \
.prio = MAX_PRIO-20, \
.static_prio = MAX_PRIO-20, \
.normal_prio = MAX_PRIO-20, \
.policy = SCHED_NORMAL, \
.cpus_allowed = CPU_MASK_ALL, \
.mm = NULL, \
.active_mm = &init_mm, \
...\
.tasks = LIST_HEAD_INIT(tsk.tasks), \
...\
.real_parent = &tsk, \
.parent = &tsk, \
...\
.comm = "swapper", \
.thread = INIT_THREAD, \
.fs = &init_fs, \
.files = &init_files, \
.signal = &init_signals, \
.sighand = &init_sighand, \
.nsproxy = &init_nsproxy, \
...\
.pids = { \
[PIDTYPE_PID] = INIT_PID_LINK(PIDTYPE_PID), \
[PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID), \
[PIDTYPE_SID] = INIT_PID_LINK(PIDTYPE_SID), \
}, \
.thread_group = LIST_HEAD_INIT(tsk.thread_group), \
...\
}
INIT_TASK 宏初始了 init_task 的进程描述符,当内核编译完成生成镜像,这个 init_task 就已经在数据段中占有自己的一席之地,init_task也决定了系统之后所有进程、线程的基因, 由它完成内核早期初始化后, 最终演变为0号进程idle, 永久运行在内核态。
0号进程的内核栈
#define init_thread_info (init_thread_union.thread_info)
union thread_union {
struct thread_info thread_info; /* 进程控制块信息 */
unsigned long stack[THREAD_SIZE/sizeof(long)]; /* 8K大小的内核栈空间 */
};
在 INIT_TASK 中将内核堆栈指向了 init_thread_info,这也通过宏定义初始化的全局变量,类型为 union thread_union。这种结构在 arm 满栈递减的栈模式下,stack[0] 相当于是栈底,stack[THREAD_SIZE/sizeof(long)]
相当于是栈顶,而 thread_info 控制块则是从栈底位置开始放置的进程控制块。0号进程的 init_task ->stack 内核栈指针指向 init_thread_union.thread_info 就相当于指向了内核栈的栈底位置。
除了0号进程其余线/进程的内核堆栈都是在 fork 时申请得到,只有0号进程是通过全局变量直接初始化形成,那么这个0号进程的内核栈是放置在什么位置呢?在 定义全局 init_thread_union 是发现有一个 __init_task_data
修饰符,具体的宏定义如下:
#define __init_task_data __attribute__((__section__(".data..init_task")))
这表明整个内核堆栈时在编译时被放置在 .data..init_task
段,即 init_task 的堆栈指向 ".data..init_task" 这个位置,通过搜索在 ./include/asm-generic/vmlinux.lds.h
中发现如下宏定义:
#define INIT_TASK_DATA(align) \
. = ALIGN(align); \
*(.data..init_task)
继续搜索 INIT_TASK_DATA 发现在./arch/arm/kernel/vmlinux.lds.S
中的 .data 段最开始的位置占用了 THREAD_SIZE 大小的空间,在 arm 32bits下 THREAD_SIZE = 8K
,刚好与 union thread_union 下的栈大小相同,至此0号进程的内核栈被创建完成。
.data : AT(__data_loc) {
_data = .; /* address in memory */
_sdata = .;
/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
INIT_TASK_DATA(THREAD_SIZE)
...
_edata = .;
}
0号进程的sp设置
通过宏定义已经将 0号进程 inti_task 的描述符和所需要的内核堆栈创建出来了,但是要想运行起来还必须将当前模式下的 sp 指针(sp_svc)设置到内核堆栈和合适位置。在芯片上电后,一般需要先经过 uboot 或其他固件的执行,在执行完成后跳转到内核入口开始内核早期且重要的初始化,最后跳转到 start_kernel 入口开始在 C语言环境下初始化内核,而堆栈指针 sp 的初始化就是在跳转到 start_kernel 之前设置的。这一切都发生 head.S 和 head-common.S 中。
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags/dtb pointer
* r9 = processor ID
*/
__INIT
__mmap_switched:
adr r3, __mmap_switched_data @将 __mmap_switched_data是开辟的一片地址,存放了一些变量,将地址赋值给r3
ldmia r3!, {r4, r5, r6, r7} @ 从__mmap_switched_data 将下的4 words 载入到r4~r7寄存器,r3地址会自动递增
cmp r4, r5 @ Copy data segment if needed 判断 __data_loc 与 _sdata是否相同,不相同时需要重定位拷贝
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp) 设置 fp 为0,并初始化.bss段为0
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp}) @从ARM模式下继续将 5个words 载入到r4~r7和sp寄存器, 这里设置了0号进程的内核堆栈位置
THUMB( ldmia r3, {r4, r5, r6, r7} ) @THUMB模式下只能最多一次出栈到4个寄存器
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID 将寄存器r9的值(processor ID)存储到 processor_id
str r1, [r5] @ Save machine type 将寄存器r1的值(machine ID)存储到__machine_arch_type
str r2, [r6] @ Save atags pointer 将寄存器r2的值(atags/dtb pointer)存储到__atags_pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values 将寄存器r0,r4的值(cp#15 control register)存储到cr_alignment
b start_kernel @ 跳转到start_kernel开始进行内核初始化
ENDPROC(__mmap_switched)
.align 2
.type __mmap_switched_data, %object
__mmap_switched_data: @ 定义 __mmap_switched_data 数据结构
.long __data_loc @ r4. 其中__data_loc,_sdata,__bss_start,_end是在编译时赋值的
.long _sdata @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4 其中processor_id,__machine_arch_type,__atags_pointer,cr_alignment是等待被被赋值的
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp 设置到 (&init_thread_union + THREAD_SIZE - 8)的栈顶位置,设置好SP,后面就可以运行了
.size __mmap_switched_data, . - __mmap_switched_data
最终在 __mmap_switched
汇编函数中执行可能需要的数据段重定位和 .bss 段清除,并将在汇编环境下获取到的信息赋值给C环境中的 processor_id
, __machine_arch_type
, __atags_pointer
, cr_alignment
,最后将 sp 指针设置到 0号进程内核栈的 (&init_thread_union + THREAD_SIZE - 8) 位置,最后进入 start_kernel 中执行初始化和创建1, 2号进程。
注意:在内核在 start_kernel 完成初始化之前,内核会一直使用这个栈来进行工作。
0号进程的退化
在 start_kernel 执行到 rest_init 函数中时所有的初始化已经全部完成,并创建了内核态和用户态各自的进程始祖,此时 0号进程作为初始模版的使命已经结束。此时,init_task 进程便会退化成主处理器的 idle 进程,继续发挥重要作用。而多喝处理器中其他核的 idle 进程则是在 kernel_init(1号进程)中,为每个从处理器的运行队列上通过 fork 创建出来的 pid 同样为 0。
start_kernel
└── rest_init
└── init_idle_bootup_task
└── init_idle_bootup_task
static noinline void __init_refok rest_init(void)
{
...
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);/* 让init_task进程隶属到idle调度类中,即选择idle的调度相关函数 */
preempt_enable_no_resched(); /* 打开抢占,但是不进行调度 */
schedule(); /* 执行进程切换,因为0号进程已经切换为idle进程了,所以主动调度,使1号和2号线程可以运行 */
preempt_disable(); /* 重新关闭抢占 */
/* Call into cpu_idle with preempt disabled,空闲进程进入while中 */
cpu_idle();
}
void __cpuinit init_idle_bootup_task(struct task_struct *idle)
{
idle->sched_class = &idle_sched_class;
}
🌀路西法 的CSDN博客拥有更多美文等你来读。