Linux C++ 通信 - fork

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次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值