目录
- 子进程与父进程
- fork()函数
- 多进程服务器
子进程与父进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。子进程指的是由另一进程(对应称之为父进程)所创建的进程
子进程通常通过父进程调用fork()函数产生,调用fork()后,操作系统会复制一个与父进程几乎完全相同的子进程,这两个进程共享代码空间(即文本段),但是数据空间是互相独立的
fork()函数
函数原型及所需的头文件如下
#include <unistd.h>
pid_t fork(void);
fork意为分叉,fork()函数用来创建一个子进程,并返回pid_t类型的进程ID
fork()函数调用时不需要参数,但是会返回两个返回值分别返回给父进程和子进程,父进程返回子进程的进程ID,子进程返回0,fork()函数执行后可以通过判断返回值判断父子进程,每个进程也可以通过调用getpid()函数获得自己的进程号,也可以通过getppid()获得自己父进程的进程号
值得注意的是,子进程拥有父进程当前运行到的位置(两进程的程序计数器PC值相同,即子进程是从fork返回处开始执行的),不过fork()创建进程之后子进程与父进程谁先开始运行是完全随机的,可以通过进程间通信(IPC)解决父子进程的同步问题
创建子进程后,父进程需要等待子进程的退出并为其善后:
如果父进程在子进程退出之前退出了,这时候子进程就变成了孤儿进程。当然每一个进程都应该有一个独一无_的父进程,init进程就是这样的-个“慈父”,Linux内核中所有的子进程在变成孤儿进程之后都会被init进程"领养”,这也意味着孤儿进程的父进程最终会变成init进程;如果一个已经终止但其父进程尚未对其进行善后处理(获取终止子进程的有关信息如CPU时间片、释放它锁占用的资源如文件描述符等)的进程被称僵死进程(zombie),ps命令将僵死进程的状态打印为Z
。那么父进程如何为子进程善后呢?
父进程通常通过调用wait()函数为子进程善后
wait()与waitpid()
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
当一个进程正常或异常退出时,内核就会向其父进程发送SIGCHLD信号。因为子进程退出是一个异步事件,所以这种信号也是内核向父进程发送的一个异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即将被执行的函数,父进程可以调用wait()或waitpid()可以用来查看子进程退出的状态
在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项可使调用者不用阻塞。
waitpid并不等待在其调用的之后的第一个终止进程,他有若干个选项,可以控制他所等待的进程
多进程服务器
多进程并发访问服务器的原理图如下,相较于多线程与多路复用,多进程是比较简单的一种实现服务器并发访问的方式,不过缺点也很明显:独立的地址空间使用进程共享状态信息变得困难,为了共享信息,必须使用IPC(进程间通信机制)。多进程的另一个缺点是,它们往往比较慢,因为进程控制和IPC的开销都较高
多进程服务器的模型非常清晰,一个进程不可能覆盖另一个进程的用户地址空间。这就消除了许多令人迷惑的错误。这是多进程实现并发服务器的优点。下面给出多进程服务器的实现代码(省略了socket部分,不过socket部分需要调用setsockopt()函数避免端口占用)
while(1)
{
printf("\n%d start waiting and accepting new client connect... \n",sockfd);
client_fd=accept(sockfd,(struct sockaddr *)&cli_addr,&cliaddr_len);
if(client_fd<0)
{
printf("connect failure: %s\n",strerror(errno));
return -3;
}
printf("accept new client with fd \n");
pid=fork();
if(pid<0)
{
printf("create new process failure: %s\n",strerror(errno));
close(client_fd);
continue;
}
if(pid>0)
{
close(client_fd);
continue;
}
if(pid==0)
{
close(sockfd);
memset(buf,0,sizeof(buf));
rv=read(client_fd,buf,sizeof(buf));
if(rv<0)
{
printf("connect to client error: %s\n",strerror(errno));
close(client_fd);
exit(0);//如果出错子进程直接退出
}
else if(rv==0)
{
printf("client connect to server get disconnected \n");
close(client_fd);
exit(0);
}
printf("get %d Bytes from client \n",rv);
rv=write(client_fd,MSG_STR,strlen(MSG_STR));
if(rv<0)
{
printf("write %d Bytes to client error: %s\n",rv,strerror(errno));
close(client_fd);
exit(0);
}
printf("write %d Bytes to client successfully! \n",rv);
sleep(1);
close(client_fd);
exit(0);
}
}
如果不等待子进程退出则需安装信号
// 避免出现僵尸进程
signal(SIGCHLD, SIG_IGN);
signal()函数会在捕捉到SIGCHLD信号后对其进行忽略操作(SIG_IGN,IGN即为ignore的缩写)