kernel_thread会产生一个task_struct实例出来,所以应该算是内核进程了,x86上的实现代码是:
<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; /* Ok, create the new process.. */ return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); } 期间的关键节点有: 1. do_fork在调用的copy_process函数里生成一个新的task_struct实例p,代表一个新进程 2. 通过alloc_pages为新进程生成一个独立的内核栈(栈大小8KB):alloc_thread_info_node,之后将父进程内核栈的内容copy到新进程内核栈中。 3. p加入运行队列等待调度 4. copy_thread: struct pt_regs *childregs; *childregs = *regs; p->thread.sp = (unsigned long) childregs; p->thread.sp0 = (unsigned long) (childregs+1); p->thread.ip = (unsigned long) ret_from_fork; 因此,新进程拥有自己的堆栈且会根据regs中的内容进行修改。 5. p被调度运行,因为p->thread.ip = (unsigned long) ret_from_fork,所以从ret_from_fork开始执行(在新进程上下文中) 6. ret_from_fork最后调用syscall_exit,后者最后以INTERRUPT_RETURN(也就是ia32中iret指令或者是x86_64中的iretq指令)返回,因此将从当前堆栈中找到返回地址,该地址在上面的第4步中被赋值为regs.ip = (unsigned long) kernel_thread_helper,所以kernel_thread_helper将在新进程环境中被调用。 简单的总结就是:kernel_thread产生一个新进程p,p->thread.ip=ret_from_fork,所以当新进程被调度执行时将从ret_from_fork函数开始执行(原因在进程切换用的switch_to代码中),ret_from_fork执行的最后执行iret,因为堆栈中的ip被指向了regs.ip = (unsigned long) kernel_thread_helper,所以将执行kernel_thread_helper函数。 接下来的过程可参考解密Linux kernel中的内核线程 ,本帖实际上是该贴的一个补充。 |
|