在linux系统中,要创建新进程,最常用的方法是调用fork来创建子进程。
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char *argv[]){
pid_t pid=fork();
------------------------后面的代码,父子进程都会执行---------------------------------------
if ( pid < 0 ) {
fprintf(stderr,"错误!");
} else if( pid == 0 ) {
printf("子进程空间");
exit(0);
} else {
printf("父进程空间,子进程pid为%d",pid);
}
// 可以使用wait或waitpid函数等待子进程的结束并获取结束状态
exit(0);
}
上面的代码就是fork的典型用法:创建了一个子进程
在父进程调用完fork函数返回的时候,判断pid的值是一个大于0的值,这个值就是子进程的pid。
子进程开始执行的地方也是fork函数返回的时候,区别就在于子进程返回的pid是0
这里就有一些疑问了,为什么fork函数会返回2次?为什么返回值不一样?为什么子进程可以直接从fork函数之后执行?子进程到底从什么地方开始执行的?
要解开这些疑问,就一定要去看fork在linux内核里面的实现了。
首先第一点,fork函数为什么会返回2次?这描述其实不准确,函数调用返回只会一次。这里是一个进程变成 了两个进程,每个进程都会从fork函数返回,所以是两个进程(父子进程)都从fork返回了。
所以子进程开始执行的地方就是fork返回 的时候。子进程执行的第一句到底 是什么呢?
我们先 来看看子进程到底 是怎么创建出来的。
fork在内核里面的实现是在kernel/fork.c
用户空间的fork函数没有任何参数的时候,通过系统调用进入内核后:
_do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
参数非常简单,这里也不进行介绍了,查询相关手册就能知道 了。
_dor_fork函数来完成子进程的创建,
_do_fork -> copy_process -> copy_thread_tls -> copy_thread
copy_thread函数做了子进程的寄存器准备工作。
进程的寄存器信息是存放在task_struct->thread_info.reg
copy_thread中先获取到子进程的task_struct->thread_info.reg,然后进程填充:
int copy_thread(unsigned long clone_flags, unsigned long stack_start,
unsigned long stk_sz, struct task_struct *p)
{
struct pt_regs *childregs = task_pt_regs(p);
....
childregs->regs[0] = 0;
....
p->thread.cpu_context.pc = (unsigned long)ret_from_fork;
p->thread.cpu_context.sp = (unsigned long)childregs;
}
这里对寄存器做了几个操作,首先会吧reg0写为0.这个寄存器是函数返回值存放的寄存器。所以子进程返回的时候,fork函数返回值就是0
pc指针就是函数即将执行 的代码,这里指向ret_from_fork,也就是子进程开始执行的第一个函数。这个函数就是fork的返回函数。
这样我们就明确了fork子进程是从哪开始执行了,以及为什么子进程fork的返回值是0