1.fork函数创建一个新进程
#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork error");
}
else if(pid == 0)
{
printf("this is child process\n");
printf("child process gets parent processID by getpid() %d\n",getpid());
}
else
{
printf("this is parent process\n");
printf("parent process gets parent processID by pid %d\n",pid);
}
pause();
return 0;
}
结论:父进程的pid和子进程getpid相同
2.fork与僵尸进程
僵尸进程:简单来记忆就是父进程没有给子进程收尸
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
转载部分理解:
僵尸进程的产生:
当一个进程创建了一个子进程时,他们的运行时异步的。即父进程无法预知子进程会在什么时候结束,那么如果父进程很繁忙来不及wait 子进程时,那么当子进程结束时,会不会丢失子进程的结束时的状态信息呢?处于这种考虑unix提供了一种机制可以保证只要父进程想知道子进程结束时的信息,它就可以得到。(不管父进程先于还是后于子进程调用wait,系统都会告诉你子进程的信息)
这种机制是:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存。但是仍然保留了一些信息(如进程号pid 退出状态 运行时间等)。这些保留的信息直到进程通过调用wait/waitpid时才会释放。这样就导致了一个问题,如果没有调用wait/waitpid的话,那么保留的信息就不会释放。比如进程号就会被一直占用了。但系统所能使用的进程号的有限的,如果产生大量的僵尸进程,将导致系统没有可用的进程号而导致系统不能创建进程。所以我们应该避免僵尸进程
这里有一个需要注意的地方。如果子进程先结束而父进程后结束,即子进程结束后,父进程还在继续运行但是并未调用wait/waitpid那子进程就会成为僵尸进程。
但如果子进程后结束,即父进程先结束了,但没有调用wait/waitpid来等待子进程的结束,此时子进程还在运行,父进程已经结束。那么并不会产生僵尸进程。因为每个进程结束时,系统都会扫描当前系统中运行的所有进程,看看有没有哪个进程是刚刚结束的这个进程的子进程,如果有,就有init来接管它,成为它的父进程。
同样的在产生僵尸进程的那种情况下,即子进程结束了但父进程还在继续运行(并未调用wait/waitpid)这段期间,假如父进程异常终止了,那么该子进程就会自动被init接管。那么它就不再是僵尸进程了。应为intit会发现并释放它所占有的资源。(当然如果进程表越大,init发现它接管僵尸进程这个过程就会变得越慢,所以在init为发现他们之前,僵尸进程依旧消耗着系统的资源)
一个僵尸进程的例子:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <signal.h>
int main(void )
{
pid_t pid;
printf("befor fork pid:%d \n", getpid());
int abc = 10;
pid = fork(); //errno
if (pid == -1)
{
perror("fork error");
return -1;
}
if (pid > 0)
{
abc ++;
printf("parent: pid:%d \n", getpid());
printf("abc: %d \n", abc);
sleep(20);//父进程没有调用wait方法去收尸,故这20s内,子进程是僵尸进程
}
else if (pid == 0)
{
abc ++;
printf("child: %d, parent: %d \n", getpid(), getppid());
printf("abc: %d \n", abc);
}
printf("fork after....\n");
return 0;
}
在fork调用之后,子进程和父进程继续执行fork调用之后的指令。子进程获得父进程数据空间、堆和栈的副本,以及进程控制块PCB,但是并不共享这些存储空间部分。
fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。父、子进程的每个相同的打开描述符共享一个文件表项。
这种共享文件的方式使父、子进程对同一文件使用了一个文件偏移量这里需要注意一下,父、子进程是共享正文段的,但是fork之后,子进程获取到的PC(程序计数器)已经指向了fork之后的内容,所以,子进程只执行fork之后的代码。
fork有两种用法:
(1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。例如,父进程等待客户端的服务请求,然后fork一个子进程处理这个请求,自己则继续等待下一个服务请求。当请求达到时,父进程调用fork,子进程处理此请求。父进程继续等待下一个服务请求。
(2)一个进程要执行一个不同的程序,这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。
fork的特殊应用:fork两次可以避免僵死进程,父进程先fork一个子进程,子进程继续fork一个孙子进程,然后就直接退出。这样,父进程就可以很快的wait到子进程,释放其资源,不需要阻塞,继续自己的操作;子进程先于孙进程退出也不会产生僵尸进程,孙子进程交由了init进程托管,执行自己的操作而不用担心了。
子进程成全了父进程,因为子进程的退出父进程不会阻塞在那儿,父进程成功收尸并退出;
子进程成全了孙进程,因为子进程先于孙进程退出,故孙进程托孤给init进程。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork error");
}
else if(pid == 0)
{
printf("this is child process\n");
printf("child process gets parent processID by getpid() %d\n",getpid());
pid = fork();
if(pid == -1)
{
perror("fork error");
}
else if(pid == 0)
{
while(1)
{
sleep(1);
printf("this is child's child process\n");
}
}
else
{
//子进程先有孙进程退出,孙进程托孤给init
exit(110);
}
}
else
{
printf("this is parent process\n");
printf("parent process gets parent processID by pid %d\n",pid);
}
//父进程等待子进程退出
wait(NULL);
return 0;
}
#include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
pid_t wait(int *status)
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:
pid = wait(NULL);
如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
int status = 0;
if (pid < 0)
{
printf("error occurred!\n");
}
else if (pid == 0)
{
printf("This is child \n");
exit(10);
}
else
{
sleep(5);
pid_t pc = wait(&status);//就算子进程在父进程前面退出了,wait依然可以收集到子进程的状态,让子进程不在是僵尸状态
//5秒前子进程是僵尸进程,但是5秒后不是
//返回的值为子进程的ID
printf("status is %d,pro = %d", WEXITSTATUS(status),pc);
sleep(20);
}
}
/*编写一个孤儿进程,这个孤儿进程可以同时创建100个僵死进程。
* fork.c
*
* Created on: 2015年12月6日
* Author: Administrator
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(int argc, char* argv[]) {
pid_t pid = fork();
if (pid == -1) {
printf("fork failed %s\n", strerror(errno));
return -1;
}
int i = 0;
if (pid == 0) {
for (i = 0; i < 100; i++) {
pid_t tmp_pid = fork();
if (tmp_pid == 0) {
exit(0);
}
}
sleep(10);//10秒后父进程退出,又父进程创建的已经僵尸的进程也会被系统回收
//exit(0);
} else if (pid > 0) {
exit(0);
}
return 0;
}