10.wait族函数和exec族函数


10.1.进程的诞生和消亡
(1)进程的诞生;进程0(由操作系统内核在启动过程中构建而来);进程1(在内核态由进程0通过内核中的fork相关函数复制而来);fork和vfork用于在用户态下创建进程;程序和进程的区别;程序即静态的在硬盘中存储的可执行程序;进程即动态的正在内存中运行的程序。
(2)进程的消亡分为正常终止和异常终止,进程在运行时需要消耗系统资源(内存–进程向操作系统请求的内存资源、IO–进程跟别的外部设备进行IO通信),进程终止时理应完全释放该类资源,如果进程消亡后仍然没有释放相应资源则这些资源就丢失了。
(3)linux系统设计时规定每个进程退出时操作系统会自动回收该进程工作时所消耗的资源(譬如malloc申请内存没有free则当前进程结束时该内存会被释放;譬如open打开的文件没有close则在当前进程结束时会被关闭),操作系统仅回收了该进程工作时消耗的内存和IO,但并没有回收该进程本身占用的内存(8KB=task_struct进程结构体+栈内存);因为进程本身的8KB内存操作系统不能回收需要别人来辅助回收,则每个进程都需要该进程的父进程帮助它收尸。
(4)僵尸进程;子进程先于父进程结束,子进程除task_struct和栈外其余内存空间皆已被操作系统清理,子进程结束后父进程此时并没有立即帮子进程回收资源,在子进程已经结束且父进程尚未帮其收尸的时期内子进程就成为了僵尸进程。
(5)父进程可以使用wait或waitpid以显式回收子进程的剩余待回收内存资源并且获取子进程退出状态;父进程也可以不使用wait或者waitpid回收子进程,此时父进程结束时会自动回收子进程的剩余待回收内存资源(为防止父进程忘记显式调用wait/waitpid来回收子进程从而造成内存泄漏)。
(6)孤儿进程;父进程先于子进程结束,子进程成为1个孤儿进程;linux系统规定所有的孤儿进程都自动成为某个特殊进程(进程1即init进程)的子进程。


10.2.父进程wait回收子进程
(1)wait的工作原理;子进程结束时,操作系统会自动向其父进程发送SIGCHILD信号;父进程调用wait函数后阻塞等待接收SIGCHILD信号;父进程被SIGCHILD信号唤醒然后去回收僵尸子进程;父子进程之间是异步的,SIGCHILD信号机制就是为了解决父子进程之间的异步通信问题,让父进程可以及时的去回收僵尸子进程;若父进程没有任何子进程则父进程调用wait会返回错误。
(2)wait实战编程;wait的参数status用来返回子进程结束时的状态,父进程通过wait得到status后即可知道子进程的某些结束状态信息;wait的返回值pid_t即本次wait回收的子进程的PID,当前进程可能有多个子进程,wait函数阻塞直到其中某个子进程结束wait就会返回,wait的返回值可判断具体哪个子进程本次被回收了;wait主要是用来回收子进程资源,回收同时还可以得知被回收子进程的pid和退出状态。
(3)WIFEXITED/WIFSIGNALED/WEXITSTATUS这几个宏用来获取子进程的退出状态;WIFEXITED宏用来判断子进程是否正常终止(return/exit/_exit退出);WIFSIGNALED宏用来判断子进程是否非正常终止(被信号终止);WEXITSTATUS宏用来得到正常终止情况下的进程返回值的。


10.3.父进程waitpid回收子进程
(1)waitpid和wait的基本功能一样,都是父进程用来回收子进程资源的;waitpid可以回收指定PID的子进程;waitpid可分为阻塞式或非阻塞式两种工作模式。
(2)使用waitpid实现wait的效果waitpid(-1,&status,0);-1表示回收任意1个子进程;0表示用默认方式(阻塞式)来进行等待;返回值是本次回收成功的子进程的PID。
(3)父进程等待回收PID为pid的该子进程waitpid(pid,&status,0);若当前进程并没有ID号为pid的子进程,则返回值为-1;若成功回收了该pid子进程则返回值为回收成功的子进程的PID。
(4)父进程非阻塞式的回收子进程waitpid(pid,&status,WNOHANG),此时若父进程执行waitpid时子进程已经先结束等待回收则waitpid直接回收成功,返回值是回收的子进程的PID;若父进程waitpid时子进程尚未结束则父进程立刻返回(非阻塞),但是返回值为0(表示回收不成功)。
(5)竟态全称是竞争状态,多进程环境下多个进程同时抢占系统资源(内存/CPU/文件IO);竞争状态对OS来说是很危险的,此时OS如果没处理好就会造成结果不确定;编写程序当然不希望程序运行的结果不确定,则我们写程序时要尽量消灭竞争状态;操作系统给我们提供了一系列的消灭竟态的机制,我们需要做的是在合适的地方使用合适的方法来消灭竟态。


10.4.为什么需要exec函数
(1)fork创建子进程是为了执行新程序,fork创建子进程后,子进程和父进程同时被OS调度执行,则子进程即可单独的执行某个程序,该程序宏观上将会和父进程程序同时运行。
(2)我们可以直接在子进程的if中写入新程序的代码,但此方式不够灵活(我们必须必须知道源代码且源代码太长了也不好控制),譬如说我们希望子进程执行ls-la命令就无法实现(没有源代码,只有编译好的可执行程序);我们可使用exec函数族运行新的可执行程序,exec族函数可直接把某个编译好的可执行程序直接加载运行。
(3)我们有了exec族函数后形成了典型的多进程项目架构,即子进程需要运行的程序被单独编写并单独编译链接成某个可执行程序a.out,主程序为父进程,fork创建子进程后在子进程中通过exec来执行a.out,达到父子进程同时运行不同程序的效果。


10.5.exec函数族介绍
(1)execl和execv这两个函数是最基本的exec函数,都可以用来执行某个程序,区别是传参的格式不同;execl是把参数列表(多个字符串,必须以NULL结尾)依次排列而成;execv是把参数列表事先放入字符串数组中,再把该字符串数组传给execv函数;该两个函数执行程序时必须指定可执行程序的全路径,若没有找到path文件则直接报错。
(2)execlp和execvp该两个函数传递的可以是file/path,该两个函数会首先去找file/path,若找到则执行,若没找到则会去环境变量PATH所指定的目录下去找,若找到则执行,若没找到则报错。
(3)execle和execvpe该两个函数的参数列表中多了字符串数组envp形参,特点是执行可执行程序时会多传环境变量的字符串数组给待执行的程序。
(4)main函数的原型可以是int_main(int_argc,char_**argv,char_**env),第3个参数是环境变量字符串数组;若用户在执行该程序时没有传递第3个参数,则程序会自动从父进程继承环境变量(默认来源于OS中的环境变量);若我们exec的时候使用execle/execvpe去传递envp数组,则程序中的实际环境变量就是我们传递的这份,即取代了默认的从父进程继承来的那份环境变量。


10.wait_waitpid
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:wait族函数和exec族函数
 * 功能:演示父进程调用waitwaitpid用于回收子进程。   
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char **argv)
{
    pid_t pid = -1, ret = -1;
    int status = -1;

    pid = fork();
    if (pid > 0)
    {   // 父进程入口
        printf("parent ID is %d.\n", getpid());
#if 0   
        ret = wait(&status);                    // 调用wait函数阻塞用于回收子进程
#endif

#if 0
        ret = waitpid(-1, &status, 0);          // 使用waitpid实现wait效果
#endif

#if 0
        ret = waitpid(pid, &status, 0);         // 调用waitpid阻塞用于回收特定ID子进程
#endif  

#if 0
        sleep(1);                               // 确保父进程慢于子进程消亡,保证回收子进程成功
        ret = waitpid(pid, &status,  WNOHANG);  // 非阻塞式回收子进程
#endif

#if 2
        ret = waitpid(pid, &status,  WNOHANG);  // 父进程非阻塞式回收子进程但子进程尚未消亡的返回值。
#endif      
        printf("The Recover of process ID is %d.\n", ret);
        printf("Whether the child process exits normally %d.\n", WIFEXITED(status));
        printf("Child process is abnormal exit %d.\n", WIFSIGNALED(status));
        printf("Value Normal termination %d.\n", WEXITSTATUS(status));
    }
    else if (0 == pid)
    {   // 子进程入口
#if 2
        sleep(1);                               // 父进程非阻塞式回收子进程但子进程尚未消亡的返回值。
#endif
        printf("child ID is %d.\n", getpid());
        printf("Now parent ID is %d.\n", getppid());
        exit(0);                                // 正常退出子进程
    }
    else
    {
        perror("fork error");
        exit(-1);
    }

    return 0;
}

10.exec/app
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:wait族函数和exec族函数
 * 功能:做为子进程中待执行的程序输出argc和argv和env的变量的值。 
 */

#include <stdio.h>

// env即我们给main函数额外传递的环境变量字符串数组
int main(int argc, char **argv, char **env)
{
    int i = 0;

    printf("argc = %d.\n", argc);

    for (i=0; NULL != argv[i]; i++)
    {
        printf("argv[%d] = %s.\n", i, argv[i]);
    }

    for (i=0; NULL != env[i]; i++)
    {
        printf("env[%d] = %s.\n", i, env[i]);
    }

    return 0;
}
*********
10.exec/exec
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:wait族函数和exec族函数
 * 功能:实例演示exec函数族的用法。   
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>  
#include <sys/wait.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    pid_t pid = -1;

    pid = fork();

    if (pid > 0)
    {   // 父进程入口
        printf("parent ID is %d.\n", getpid()); 
    }
    else if (0 == pid)
    {   // 子进程入口

#if 0   // 使用execl/execv函数运行ls-la程序
        execl("/bin/ls", "ls", "-l", "-a", NULL);   
#elif 0
        char *const arg[] = {"ls", "-l", "-a", NULL};
        execv("/bin/ls", arg);
#endif  

#if 0   // 使用execl/execv函数运行自己写的app程序
        execl("./app", "./app", "argv[1]", "argv[2]", NULL);    
#elif 0
        char *const arg[] = {"./app", "argv[1]", "argv[2]", NULL};
        execv("./app", arg);
#endif

#if 0   // 使用execlp/execvp函数运行ls-la程序
        execlp("ls", "ls", "-l", "-a", NULL);   
#elif 0
        char *const arg[] = {"ls", "-l", "-a", NULL};
        execvp("ls", arg);
#endif

#if 0   // 使用execlp/execvp函数运行自己写的app程序
        execlp("app", "./app", "argv[1]", "argv[2]", NULL); 
#elif 0
        char *const arg[] = {"./app", "argv[1]", "argv[2]", NULL};
        execvp("app", arg);
#endif

#if 0   // 使用execle/execve函数运行自己写的app程序
        char *const envp[] = {"AA=env[0]", "BB=env[1]", NULL};
        execle("./app", "./app", "argv[1]", "argv[2]", NULL, envp);
#elif 1
        char *const arg[] = {"./app", "argv[1]", "argv[2]", NULL};
        char *const env[] = {"AA=env[0]", "BB=env[1]", NULL};
        execvpe("./app", arg, env);
#endif

        exit(0);
    }
    else if (pid < 0)
    {
        perror("fork error");
        exit(-1);
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值