您的位置:首页 > 运维架构 > Linux

Linux进程fork,exec,vfork详解

2015-08-13 16:03 639 查看
在Unix/Linux系统下进程创建时需要进行如下系统调用:fork/exec

fork()把一个进程复制成二个进程:parent (old PID), child (new PID)

exec()用新程序来重写当前进程:PID没有改变

接下来就重点学习这两个系统调用:

当我们fork() 创建一个继承的子进程将会发生如下事情:复制父进程的所有变量和内存,复制父进程的所有CPU寄存器(有一个寄存器例外)(这个寄存器是用来区分父进程和子进程的PID)

fork()的返回值:调用fork()函数成功时,将会有两个返回值。子进程的fork()返回0,父进程的fork()返回子进程标识符。fork() 返回值可方便后续使用,子进程可使用getpid()获取PID。

fork()执行过程对于子进程而言,是对父进程地址空间的一次复制。下面我们通过图示来看一下这个复制过程:



注意了,图示中的两个childPID的值是不同的,对于父进程中的childPID当然是子进程的PID,而子进程中的childPID的值为0。

fork()使用示例:

int  main()
{
pid_t  pid;
int  i;

for(i=0;  i<LOOP;  i++)
{
/* 创建新进程*/
pid = fork();
if  (pid < 0) { /*创建失败  */
fprintf(stderr, “Fork Failed”);
exit(-1);
}
else if (pid == 0) { /* 子进程 */
fprintf(stdout,  “i=%d,  pid=%d,  parent  pid=%d\n”,I,
getpid() ,getppid());
}
}
wait(NULL);
exit(0);
}


了解了fork()之后我们再来看看exec()系统调用:系统调用exec( )加载新程序取代当前运行进程,也就是说exec调用成功时,它是相同的进程,但是运行了不同的程序,代码段、堆栈和堆(heap)等也都完全重写了。它允许进程“加载”一个完全不同的程序,并从main开始执行,而我们的fork()创建出来的子进程是从fork()之后的代码段处开始执行的。

我们接下来讨论一下fork()的实现开销:当我们使用fork()系统调用时,首先要对子进程分配内存,然后复制父进程的内存和CPU寄存器到子进程里。开销昂贵!!

然而在99%的情况里,我们在调用fork()之后调用exec(),因为在大多数情况下我们都是不希望和父进程执行同样的代码,不然的话我们创建一个新的进程也就没有多大的意义了。这时候我们就该考虑一下这个问题了:既然不想使用父进程中的代码,在fork()操作中内存复制就是没有作用的。那么为什么不能结合它们在一个调用中?

此后,就产生了vfork():创建进程时,不再创建一个同样的内存映像。一些时候称之为轻量级fork(),子进程几乎立即调用exec()。而在子进程调用exec()之前,暂时与父进程共享地址空间。

我们注意到了,前面fork()的示例代码中使用了wait()函数,那么这个函数是干什么用的呢?这就要引出一个新的概念了:父进程等待子进程

wait()系统调用用于父进程等待子进程的结束:子进程结束时通过exit()向父进程返回一个值,父进程通过wait()接受并处理返回值

wait()系统调用的功能:有子进程存活时,父进程进入等待状态,等待子进程的返回结果。当某子进程调用exit()时,唤醒父进程,将exit()返回值作为父进程中wait的返回值

既然子进程结束时是通过exit()向父进程返回一个值,那么我们又要学习一下exit()系统调用了:

进程结束执行时调用exit(),完成进程资源回收

exit()系统调用的功能:

将调用参数作为进程的“结果”
关闭所有打开的文件等占用资源
释放内存
释放大部分进程相关的内核数据结构
检查是否父进程是存活着的,如存活,保留结果的值直到父进程需要它,进入僵尸(zombie/defunct)状态。如果没有,它释放所有的数据结构,进程结果
清理所有等待的僵尸进程

说到这里,大家可能对僵尸进程有点疑惑:所谓的僵尸进程,就是父进程没有调用wait收集子进程的退出状态,子进程就已经退出了,此时这个退出的子进程就成为了僵尸进程,它的很多进程资源都还没有释放(例如PID在进程表中仍然存在)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: