fork函数详解

在一个进程中可以使用fork函数创建一个子进程,当该子进程创建时,它从fork函数的下一条语句(或者说从fork的返回处)开始只执行与父进程相同的代码。fork函数产生一个和当前进程完全一样的新进程,并和当前进程一样从fork函数调用中返回。

#include <stdio.h>
#include <stdlib.h> //malloc,exit
#include <unistd.h> //fork
#include <signal.h>
#include <sys/wait.h>

//信号处理函数
void sig_usr(int signo)
{
	printf("收到了SIGUSR1信号,进程ID=%d!\n", getpid());

	int status;
	switch (signo) {
	case SIGUSR1:
		printf("收到了SIGUSR1信号,进程ID=%d \n", getpid());
		break;

	case SIGCHLD:
		printf("收到了SIGCHLD信号,进程ID=%d\n", getpid());
		//-1表示等待任何子进程,status保存子进程的状态信息,WNOHANG表示不要阻塞立即返回
		pid_t pid = waitpid(-1, &status, WNOHANG); 

		//子进程没有结束
		if (pid == 0)
			return;

		//表示waitpid调用出现错误
		if (pid == -1)
			return;

		return;
		break;

	}
}


int main(int argc,char* const *argv)
{
  
	pid_t pid;

	printf("进程开始执行! \n");

	if (signal(SIGUSR1, sig_usr) == SIG_ERR){
		printf("无法捕捉SIGUSR1信号!\n");
		exit(1);
	}

	if (signal(SIGCHLD, sig_usr) == SIG_ERR) {
		printf("无法捕捉SIGCHLD信号!\n");
		exit(1);
	}

	//-----------------------------
	pid = fork();	///创建1个子进程

	
	if (pid < 0) {
		printf("子进程创建失败!\n");
		exit(1);
	}

	//现在,父进程和子进程同时开始运行了
	for(;;) {
		sleep(1);
		printf("休息1s,进程ID = %d !\n", getpid());
	}

	printf("再见了\n");
  
    return 0;
}

注意

1、调用fork函数创建出一个子进程后,后续的代码是父进程先执行还是子进程先执行并不确定,不代表父进程一定快,因为存在进程的时间片调度问题。

2、虽然子进程是后面用fork函数创建出来的,但在子进程创建出来之前父进程执行的所有代码都相当于在子进程中执行过了。(fork之前执行的代码,对子进程来说也同样执行 了)。

僵尸进程的产生、解决,SIGCHLD

如果一个子进程终止了,但父进程还活着,但该父进程没有调用wait,waitpid 函数来进程一些额外的处理,那么这个子进程将变成一个僵尸进程。僵尸进程是占用资源的,至少会占用ID(PID)。整个操作系统的进程ID号是有限的,所以不能允许有僵尸进程的存在。

解决方案:对于源码中有fork行为的进程,应该拦截并处理SIGCHLD信号。

fork执行效率高的原因

fork产生新进程的速度非常快,产生的新进程并不复制原进程的内存空间,而是和原进程(父进程)一起共享一个内存空间。这个内存空间的特性是“写时复制”,也就是说,原来的进程和复制出来的子进程可以同时自由读取内存,但如果子进程(或者父进程)对内存进程修改,这个内存就会复制一份给该进程单独使用,以免影响共享该内存空间的其他进程使用。

fork函数在父进程中返回的值和在子线程中返回的值是不同的,根据返回值可以编写代码识别出当前是父进程还是子进程,从而让父子进程执行不同的分支代码。父进程fork返回值>0,子进程fork返回值==0。


版权声明:本文为pzywinner原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。