一、进程的描述
操作系统内核实现操作系统的三大管理功能:进程管理,内核管理和文件系统,其中最核心的功能是进程管理。
在操作系统原理中,我们通过进程控制块PCB描述进程,通常采用一个数据结构struct task_struct来描述进程,该结构体大致分为以下几个部分:
进程状态(State)
进程调度信息(Scheduling Information)
各种标识符(Identifiers)
进程通信有关信息(IPC:Inter_Process Communication)
时间和定时器信息(Times and Timers)
进程链接信息(Links)
文件系统信息(File System)
虚拟内存信息(Virtual Memory)
页面管理信息(page)
对称多处理器(SMP)信息
和处理器相关的环境(上下文)信息(Processor Specific Context)
下面分析一下其中的进程状态:
Linux内核管理的进程状态转换如下图所示:
二、实验内容:分析Linux内核创建一个新进程的过程
在MenuOS中增加fork命令,具体命令和实现效果截图如下图所示:
打开另一个终端,进行gdb跟踪, 对sys_clone,do_fork,dup_task_struct,copy_process,copy_thread和ret_from_fork设置断点 ,实现效果如图所示:
- 运行后首先停在sys_clone处 :
然后是do_fork,之后是copy_process:
进入copy_thread:
在copy_thread中,我们可以查看p的值 :
但是回到copy_process后再查看,将得到一个value optimized out的提示,这是因为Linux内核打开gcc的-O2选项优化导致.如果想要关掉,可以参考:这里
ret_from_fork按照之前的分析被调用,跟踪到syscall_exit后无法继续.如果想在本机调试system call,那么当你进入system call时,系统已经在挂起状态了。如果想要跟踪调试system_call,可以使用kgdb等
新进程是从哪里开始执行的
之前谈到的copy_process中的copy_thread()函数,正是这个函数决定了子进程从系统调用中返回后的执行.
int copy_thread(unsigned long clone_flags, unsigned long sp,
unsigned long arg, struct task_struct *p)
{
...
*childregs = *current_pt_regs();
childregs->ax = 0;
if (sp)
childregs->sp = sp;
p->thread.ip = (unsigned long) ret_from_fork;
...
}
子进程执行ret_from_fork
ENTRY(ret_from_fork)
CFI_STARTPROC
pushl_cfi %eax
call schedule_tail
GET_THREAD_INFO(%ebp)
popl_cfi %eax
pushl_cfi $0x0202 # Reset kernel eflags
popfl_cfi
jmp syscall_exit
CFI_ENDPROC
END(ret_from_fork)
执行起点与内核堆栈如何保证一致?
在ret_from_fork之前,也就是在copy_thread()函数中childregs = current_pt_regs();该句将父进程的regs参数赋值到子进程的内核堆栈,
*childregs的类型为pt_regs,里面存放了SAVE ALL中压入栈的参数
故在之后的RESTORE ALL中能顺利执行下去.
三、总结
Linux通过复制父进程来创建一个新进程,通过调用do_fork来实现
Linux为每个新创建的进程动态地分配一个task_struct结构.
为了把内核中的所有进程组织起来,Linux提供了几种组织方式,其中哈希表和双向循环链表方式是针对系统中的所有进程(包括内核线程),而运行队列和等待队列是把处于同一状态的进程组织起来
fork()函数被调用一次,但返回两次.