文章目录
本文以 Linux v2.6.39 为例,说明 Linux中这三个进程的关系及启动过程。
三个进程的关系
Linux 中有pid 0, pid 1 和 pid 2 三个特殊的进程。
- pid 0,即 “swapper” 进程,是 pid 1 和 pid 2 的父进程。
- pid 1,即 “init” 进程,所有用户空间的进程均派生自该进程。
- pid 2,即 “kthreadd” 进程,是内核空间所有进程的父进程。
除了pid 0为静态生成外,其他进程实际都是调用 do_fork
生成。
$ ps -ef | head -n 3
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 09:20 ? 00:00:13 /sbin/init auto noprompt
root 2 0 0 09:20 ? 00:00:00 [kthreadd]
要注意,编号1,2 不是特地为这两个进程保留,而是按照生成进程的顺序分配得来。
调用链
INIT_TASK /* pid 0 */
start_kernel
rest_init
kernel_thread
kernel_init /* pid 1 */
init_post
run_init_process
kernel_thread
kthreadd /* pid 2 */
具体启动过程
进程 pid 0 通过代码静态生成
/arch/x86/kernel/init_task.c
struct task_struct init_task = INIT_TASK(init_task);
EXPORT_SYMBOL(init_task);
INIT_TASK 宏定义如下:
/include/linux/init_task.h
#define INIT_TASK(tsk) \
{ \
.state = 0, \
.stack = &init_thread_info, \
/* 设置默认优先级 */
.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), \
/* 设置进程间的关系,tsk为当前进程,也就是这里父进程为自身 */
.real_parent = &tsk, \
.parent = &tsk, \
.children = LIST_HEAD_INIT(tsk.children), \
.sibling = LIST_HEAD_INIT(tsk.sibling), \
.group_leader = &tsk, \
.comm = "swapper", \
.thread = INIT_THREAD, \
...
}
进程 pid 1 和 pid 2 由 pid 0 生成
生成 pid 1 和 pid 2
要注意,编号1,2 不是特地为这两个进程保留,而是按照生成进程的顺序分配得来。
/init/main.c
asmlinkage void __init start_kernel(void)
{
...
mm_init_owner(&init_mm, &init_task);
...
rest_init();
}
static noinline void __init_refok rest_init(void)
{
/* 生成 pid 1 */
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
/* 生成 pid 2 */
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
/* 进程 pid 0 变成 idle 进程,不做任何实际工作,只在没有进程运行的时候运行 */
cpu_idle();
}
kernel_thread 函数定义
注意,这里设置段寄存器指向内核。
/arch/x86/kernel/process.c
int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct pt_regs regs;
memset(®s, 0, sizeof(regs));
regs.si = (unsigned long) fn;
regs.di = (unsigned long) arg;
/* 设置段描述符寄存器 */
#ifdef CONFIG_X86_32
regs.ds = __USER_DS;
regs.es = __USER_DS;
regs.fs = __KERNEL_PERCPU;
regs.gs = __KERNEL_STACK_CANARY;
#else
regs.ss = __KERNEL_DS;
#endif
regs.orig_ax = -1;
regs.ip = (unsigned long) kernel_thread_helper;
regs.cs = __KERNEL_CS | get_kernel_rpl();
regs.flags = X86_EFLAGS_IF | 0x2;
return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
进程 pid 1 的生成函数
static int __init kernel_init(void * unused)
{
init_post();
return 0;
}
static noinline int init_post(void)
{
/* 逐个尝试执行不同的 "init" 指令,
* 如果执行成功,则当前进程的text字段就被替换,后续命令也会被覆盖
*/
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
static void run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
kernel_execve(init_filename, argv_init, envp_init);
}
进程 pid 2 的生成函数
pid 2 的 comm 字段为 “kthreadd”.
/kernel/kthread.c
int kthreadd(void *unused)
{
struct task_struct *tsk = current;
/* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
set_cpus_allowed_ptr(tsk, cpu_all_mask);
set_mems_allowed(node_states[N_HIGH_MEMORY]);
current->flags |= PF_NOFREEZE | PF_FREEZER_NOSIG;
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
/* 只要 kthread_create_list 不为空
* 就通过 create_thread 创建新的进程
* 因此 通过 kthread_create_list 创建的进程均为其子进程
*/
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
list_del_init(&create->list);
spin_unlock(&kthread_create_lock);
create_kthread(create);
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}