Linux内核创建一个新进程的过程
来源:互联网 发布:淘宝一直等待揽件陷阱 编辑:程序博客网 时间:2024/06/05 04:02
罗冲 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
1. 准备
在menu代码中增加fork函数
int pid; /* fork another process */ asm volatile( "mov $0x78, %%eax\n\t" "int $0x80\n\t" "mov %%eax, %0\n\t" :"=m"(pid) : ); if (pid < 0) { /* error occurred */ fprintf(stderr,"Fork Failed!"); exit(-1); } else if (pid == 0) { /* child process */ printf("This is Child Process!\n"); } else { /* parent process */ printf("This is Parent Process!\n"); /* parent will wait for the child to complete*/ wait(NULL); printf("Child Complete!\n"); } return 0;
按照课程中给定的方法进行编译。
2 实验开始
按照之前课程的gdb的方法,进行启动跟踪
启动完成后,在弹出窗口输入:
程序会在断点处停下:
3. 代码分析
3.1 系统调用分析
在本例中,通过int 0x80来进行系统调用时,其进入内核空间与返回用户空间的堆栈情况
从这里可以看到在中断进入时,系统会保存一系列寄存器的值。
3.2 fork主进程的返回
当执行mfork的时候,会调用中断:
int pid; /* fork another process */ asm volatile( "mov $0x78, %%eax\n\t" "int $0x80\n\t" "mov %%eax, %0\n\t" :"=m"(pid) : );
对于主进程,即当前正在执行上述代码的进程来说,其过程与普通的系统调用没有差别:
1)用户程序利用int 80,进入到内核态。此时CPU会自动保存用户的SS、ESP、EFLAGS、CS、EIP
2 ) 接着执行entry_32.S,即总控程序(调用entry_32.S中的ENTRY(system_call)),调用SAVE_ALL保存数据
3) entry_32.S保存完数据后,会调用
call *sys_call_table(,%eax,4)
其中sys_call_table为函数入口地址表。
4)经过计算后,找到函数,
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, int __user *, parent_tidptr, int __user *, child_tidptr, int, tls_val){ return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);}
其中返回值为do_fork()的返回值,即子进,程的pid的值
5)经过总控程序后,返回用户空间,继续执行后面的代码,即汇编之后的代码。
3.3 fork子进程的创建与返回
相对于主进程来说,子进程是由主进程复制而来。接下来分析子进程的创建过程。当系统进入到sys_clone之后,会调用do_fork函数创建子进程。而do_fork()函数中,有一条语句:
p = copy_process(clone_flags, stack_start, stack_size,child_tidptr, NULL, trace);
子进程就是在copy_process中进行创建,并运行的。
3.3.1 copy_process函数的分析
copy_process()整个函数可以分为三个部分:创建、修改、复制。
static struct task_struct *copy_process(...){ ... //1.复制。在这个函数中,它创建了两个对象task_struct与thread_info,并且将部分基本信息复制过去了。 p = dup_task_struct(current); //2. 修改。 当task_struct复制过来之后,修改时间之类的。 //3. 复制。诸如下面的代码 ... retval = copy_thread(clone_flags, stack_start, stack_size, p); ... }
3.3.2 copy_thread()分析
copy_thread()是子进程创建的一个关键部分,其函数是arch\x86\kernel\process_32.c中。下面结合代码,继续分析
int copy_thread(unsigned long clone_flags, unsigned long sp, unsigned long arg, struct task_struct *p){ ... //1. 获取当前主进程的堆栈数据的地址。拷贝当前主进程的已有数据。 //pt_regs的内容就是int 80与SAVE_ALL的值 *childregs = *current_pt_regs(); //设置子进程堆栈的eax的值为0,保证子进程的返回值为0 childregs->ax = 0; if (sp) childregs->sp = sp; //子进程的ip返回地址,即子进程返回时的执行位置 p->thread.ip = (unsigned long) ret_from_fork; ... }
3.3.3 子进程的返回
当子进程创建成功后,系统会将它加入到运行列表中,等待CPU调度,当CPU执行时,此时子进程的eip指向是entry_32.S中的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_ENDPROCEND(ret_from_fork)
查看ret_from_fork函数,可以看到相对于主进程的system_call而言,最终的处理都是调用jmp syscall_exit, 而子进程相对于主进程少了int 80与SAVE_ALL的压栈。但是子进程在 copy_thread()中的
*childregs = *current_pt_regs();这里写代码片
已经保存了相应的值,因此,子进程执行到此处时又与主进程相同了,但是其中eax的值已经修改为0, 以保证子进程可以执行它自己的程序。
4. 总结
1) 进程的创建也是一种系统调用。 主进程的返回与普通的系统调用的逻辑相同,但是子进程通过ret_from_fork与pt_reg的配合,巧妙的实现了子进程的返回
2)子进程的创建在内核空间,首次调用也是在内核空间,再从内核空间调度到用户空间
- 分析Linux内核创建一个新进程的过程(Linux)
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 6.分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 一个Strus2的简单示例(核心控制器 StrusPrepareAndExecuteFilter)
- redis持久化AOF与RDB
- redis持久化AOF与RDB配置
- leetcode_148 Sort List
- c++第3次作业
- Linux内核创建一个新进程的过程
- 分布式配置 tachyon 并运行Hadoop例子 MapReduce
- Incorrect string value: '\xC2\x8B\xC3\xA8\xC2\xAF...' for column 'name' at row 1
- JavaWeb分页技术总结
- The method getSupportFragmentManager() is undefined for the type
- 1015 milk produce
- poj-1833-排列 stl next_permutation(a,a+n)
- Android Studio R文件丢失
- 克服恐惧,先着眼手头的工作