linux 进程控制 fork

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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值