Linux C++ 通信 - fork
明镜止水,长话短说。
fork函数
fork是一个用来创建进程的函数,对于进程,一个可执行程序执行一次就是一个进程,在执行一次就又是一个进程,进程间是可以共享同一个可执行文件的。
在一个进程中创建一个子进程时,这个子进程会从fork函数的下一条语句开始执行与父进程相同代码,且之前父进程所执行过语句也相当于子进程执行过,就好比父进程把自己的所有的代码拷贝给了子进程一份,相当于两个进程在执行同一可执行文件。
C++代码示例:
#include<iostream>
#include<bits/stdc++.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
void sig_usr(int signo){
int status;
//防僵尸进程
switch(signo){
case SIGUSR1:
cout << "收到SIGUSER1信号,进程ID=" << getpid() << endl;
break;
case SIGCHLD:
cout << "收到SIGCHLD信号, 进程ID=" << getpid() << endl;
//waitpid获取子进程的终止状态,子进程就不会成为僵尸进程
pid_t pid = waitpid(-1, &status, WNOHANG);
/**
* 第一个参数,表示等待任何子进程
* 第二个参数,保存子进程的状态信息
* 第三个参数:提供额外选项,WHOHANG表示不要阻塞,让这个waitpid()立即返回
*/
if ( pid == 0 ){
return ;
}
if ( pid == -1 ){ //表示waitpid()调用错误
return ;
}
return ;
break;
}
}
int main(int argc, char * const * argv){
pid_t pid;
cout << "程序开始执行!" << endl;
if ( signal(SIGUSR1, sig_usr) == SIG_ERR ){
cout << "无法捕捉SIGUSER1信号!" << endl;
exit(1);
}
//手动终止子进程,父进程会收到SIGCHLD信号,如果父进程没有waitpid和wait函数来回收子进程,那个被终止的子进程就会成为僵尸进程,僵尸进程会占用资源,如PID进程号。
if ( signal(SIGCHLD, sig_usr) == SIG_ERR ){
cout << "无法捕捉SIGCHLD信号! " << endl;
exit(1);
}
pid = fork(); //创建一个子进程
if ( pid < 0 ){
cout << "创建子进程失败!" << endl;
exit(1);
}
if
while(1){
sleep(1);
cout << "休眠一秒, 进程ID=" << getpid() << endl;
}
cout << "Is Over!" << endl;
return 0;
}
编译执行:
root@ubuntu:/home/qiye# g++ -o t142 /mnt/hgfs/c/t142.cpp
root@ubuntu:/home/qiye# ./t142
程序开始执行!
休眠一秒, 进程ID=76640
休眠一秒, 进程ID=76641
休眠一秒, 进程ID=76640
休眠一秒, 进程ID=76641
休眠一秒, 进程ID=76641
休眠一秒, 进程ID=76640
休眠一秒, 进程ID=76640
休眠一秒, 进程ID=76641
^C
root@ubuntu:/home/qiye#
调用fork函数创建子进程后,后续代码执行是父进程先执行还子进程先执行并不确定,父进程并不一定快,因为进程的时间片调度问题(与内核调度算法有关)。
进程查看:
root@ubuntu:/home/qiye# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 't142'
76640 2983 2795 pts/0 76640 t142 S+
76641 76640 2795 pts/0 76640 t142 S+
76641的父进程为76640.
发送信号干涉:
root@ubuntu:/home/qiye# kill -usr1 76640 && kill -usr1 76641
结果:
休眠一秒, 进程ID=76640
休眠一秒, 进程ID=76641
休眠一秒, 进程ID=76640
收到SIGUSER1信号,进程ID=76640
休眠一秒, 进程ID=76640
收到SIGUSER1信号,进程ID=76641
休眠一秒, 进程ID=76641
休眠一秒, 进程ID=76640
休眠一秒, 进程ID=76641
两个进程皆能收到SIGUSR1信号,说明fork前的代码对于子进程来说也是执行过的。
防止僵尸进程
如果在STAT列中看到Z+状态,那是僵尸进程无疑。
在UNIX操作系统中,如果一个子进程被终止,而父进程还活着,如果父进程里没有调用wait函数和waitpid函数来回收子进程,那么这个子进程就会成为僵尸进程。
僵尸进程会占用资源,至少占用pid号,而pid号是有限的,所以防止僵尸进程是必要的。
从根本解决,一是重启计算机,而是终止父进程,那是这两种都不实际,尤其还是在不允许随意停止的服务器上。
终止子进程时,父进程会收到了SIGCHLD信号,所以会保留被无效的子进程,此时就需要wait函数和waitpid函数来拦截SIGCHLD信号。
pid_t pid = waitpid(-1, &status, WNOHANG);
/**
* 第一个参数,表示等待任何子进程
* 第二个参数,保存子进程的状态信息
* 第三个参数:提供额外选项,WHOHANG表示不要阻塞,让这个waitpid()立即返回
*/
完善fork代码
fork产生新进程的速度非常快,产生的新进程和父进程共享一个内存空间。这个内存空间的特性是"写时复制",也就是说,原来的进程和fork出来的进程可以同时自由读取内存,但如果子进程对内存进行修改,这个内存就会复制一份给进程单独使用,以免影响共享该内存空间的其他进程的使用。就是因为这种特性,fork的执行效率才这么高。
完善代码,直观子进程和父进程之间同个变量的关系:
#include<iostream>
#include<bits/stdc++.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
void sig_usr(int signo){
int status;
switch(signo){
case SIGUSR1:
cout << "收到SIGUSER1信号,进程ID=" << getpid() << endl;
break;
case SIGCHLD:
cout << "收到SIGCHLD信号, 进程ID=" << getpid() << endl;
//waitpid获取子进程的终止状态,子进程就不会成为僵尸进程
pid_t pid = waitpid(-1, &status, WNOHANG);
/**
* 第一个参数,表示等待任何子进程
* 第二个参数,保存子进程的状态信息
* 第三个参数:提供额外选项,WHOHANG表示不要阻塞,让这个waitpid()立即返回
*/
if ( pid == 0 ){
return ;
}
if ( pid == -1 ){ //表示waitpid()调用错误
return ;
}
return ;
break;
}
}
int main(int argc, char * const * argv){
pid_t pid;
cout << "程序开始执行!" << endl;
if ( signal(SIGUSR1, sig_usr) == SIG_ERR ){
cout << "无法捕捉SIGUSER1信号!" << endl;
exit(1);
}
if ( signal(SIGCHLD, sig_usr) == SIG_ERR ){
cout << "无法捕捉SIGCHLD信号! " << endl;
exit(1);
}
pid = fork(); //创建一个子进程
if ( pid < 0 ){
cout << "创建子进程失败!" << endl;
exit(1);
}
int a = 0;
if ( pid == 0 ){
while(1){
++a;
sleep(1);
cout << "I'm 子进程, ID=" << getpid() << " ,a=" << a << endl;
}
}else{
while(1){
++a;
sleep(4);
cout << "I'm 父进程, 进程ID=" << getpid() << " ,a=" << a << endl;
}
}
cout << "Is Over!" << endl;
return 0;
}
编译运行:
root@ubuntu:/home/qiye# g++ -o t142 /mnt/hgfs/c/t142.cpp
root@ubuntu:/home/qiye# ./t142
程序开始执行!
I'm 子进程, ID=126663 ,a=1
I'm 子进程, ID=126663 ,a=2
I'm 子进程, ID=126663 ,a=3
I'm 父进程, 进程ID=126662 ,a=1
I'm 子进程, ID=126663 ,a=4
I'm 子进程, ID=126663 ,a=5
I'm 子进程, ID=126663 ,a=6
I'm 子进程, ID=126663 ,a=7
I'm 父进程, 进程ID=126662 ,a=2
I'm 子进程, ID=126663 ,a=8
I'm 子进程, ID=126663 ,a=9
I'm 子进程, ID=126663 ,a=10
可以发现虽是同一个变量,但在不同的进程里的变化,也是不同的。
当有n个fork依次执行时,进程数量可满足2**n-1次。