我的相关博文:

系统编程-进程-close-on-exec机制

PART1 

exec系列函数功能简介

exec系列函数登场

常规操作是先fork一个子进程,然后在子进程中调用exec系列函数执行新的目标程序,

虽然exec系列函数执行成功不返回,但是我们仍然i要使用wait或waitpid让父进程给该子进程收尸,否则将会产生一个僵尸进程(子进程死了,老爸没给收尸,子成为僵尸)!

并且,不论子进程内的exec系列函数执行成功或是失败,我们都要在父进程给对其收尸!

待实验,见实验1(实验1,使用execl),  思路:

让子进程内调用exec系列函数执行的新程序的生命周期大概是5秒,观察父进程wait成功且执行到wait后面的打印语句的时间,判断是否也为5秒。

该系列函数辨识方法

该系列函数都以“exec”为前缀,后面的字母有各自固定的含义,可以根据这点来进行区分,而无需强行记忆。看下图详解:

补充知识点:

读完上面的小结,我们可以分析出,例如execl,其第一个参数pathname,必须要求是绝对路径。

exec系列函数关系剖析

注意事项:

如果代码想下图这样写,因为exec函数执行出错,但是后续代码仍然会被执行,可是:当前进程的内存空间(堆、栈、数据区)可能已经被破坏,所以这种写法是不妥的!

上图代码不妥,应该修改为下图方式,即设置进程退出:

同时,若exec执行成功,则后续代码不会被执行。

PART2

实验部分

实验0

实验目的:execl错误使用展示

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h> char* elf_name = "do cat"; char* path1 = "ls"; // 相对路径
char* path2 = "/bin/ls"; // 绝对路径 char* argv1 = "-al";
char* argv2 = "a.out"; int main(void)
{
int status = 0;
pid_t pid;
printf("main-process-pid: %ld\n", (long)getpid()); pid = fork();
if (pid < 0) {
printf("fork error");
}
else if (pid == 0) { printf("son-process-pid: %ld\n", (long)getpid());
if(execl(path1, elf_name, argv1, argv2, NULL)<0){
perror("execl error");
exit(1);
}else{
printf("execl %s success\n", elf_name);
}
} wait(NULL); printf("-------main process ending------\n"); return 0;
}

编译运行:

错误原因:execl的第一个参数不支持是相对路径,所以上述实验中execl的第一个参数应该是path2 .

实验1

实验目的1: execl使用展示

实验目的2:

虽然exec系列函数执行成功不返回,但是我们仍然要使用wait或waitpid让父进程给该子进程收尸,否则将会产生一个僵尸进程(子进程死了,老爸没给收尸,子成为僵尸)!

并且,不论子进程内的exec系列函数执行成功或是失败,我们都要在父进程给对其收尸!

让子进程内调用exec系列函数执行的新程序的生命周期大概是5秒,观察父进程wait成功且执行到wait后面的打印语句的时间,判断是否也为5秒。

屏蔽父进程内的wait函数与否将产生不同的效果,可使用ps -aux查看子进程是否变成了僵尸进程。

为了承接本文章上下文的连续性,将实验1分为三个小实验,逐步加深理解。

实验1-1: 验证僵尸进程的产生

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h> char* elf_name = "do cat"; char* path1 = "ls"; // 相对路径
char* path2 = "/bin/ls"; // 绝对路径 char* argv1 = "-al";
char* argv2 = "a.out"; int main(void)
{
int status = 0;
pid_t pid;
printf("main-process-pid: %ld\n", (long)getpid()); pid = fork();
if (pid < 0) {
printf("fork error");
}
else if (pid == 0) { printf("son-process-pid: %ld\n", (long)getpid());
if(execl(path2, elf_name, argv1, argv2, NULL)<0){
perror("execl error");
exit(1);
}else{
printf("execl %s success\n", elf_name);
}
} //wait(NULL); 这里不回收子进程
while(1); return 0;
}

在一个终端内编译运行:

可见,子进程的进程ID是21018, 我们来看看在父进程不回收子进程,而子进程内又使用execl执行完毕了新程序后,是否会产生僵尸进程吧!

在另一个终端内查看:

实验证实,子进程21018成为了僵尸进程。

同时,printf(“exec %s success\n”, elf_name)这句代码未打印,由此,我们也可以看出,通过execl执行的新程序正常执行是不会返回给主程序的,

实际上,通过整个exec系列函数成功执行新程序,都是不会返回给主程序的。

然而,父进程fork了子进程后就要遵循给其收尸的原则,即使使用了exec系列函数,也是如此。

相关知识点补充 - 进程状态及其标识 :

实验1-2

在实验1-1的代码基础上,父进程fork子进程后增加wait函数的使用,以用于对子进程的回收。此时我们重复实验1-1的操作过程,我们不会见到子进程成为僵尸进程。

本实验和1-1极其相似,故省略。

实验1-3

hello.c :

#include <stdio.h>
#include <unistd.h> int main(){ sleep(5); printf("---<hello>elf, is ending---\n"); return 0;
}

exec.c:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h> char* elf_name = "do hello";
char* path = "/home/lmw/MINE/Linux_C_exercise/fork/exec/hello"; // 绝对路径 int main(void)
{
int status = 0;
pid_t pid;
printf("main-process-pid: %ld\n", (long)getpid()); pid = fork();
if (pid < 0) {
printf("fork error");
}
else if (pid == 0) { printf("son-process-pid: %ld\n", (long)getpid());
if(execl(path, elf_name, NULL, NULL, NULL)<0){
perror("execl error");
exit(1);
}else{
printf("execl %s success\n", elf_name);
}
} wait(NULL);
printf("father waits son successfully \n"); return 0;
}

编译运行:

通过实验可见,可执行程序hello被成功执行起来了并且在5秒后退出(读者可以自行设置为6秒或者7秒或者8秒...),

同时,主进程内的wait调用也在5秒后成功返回(通过肉眼观察代码执行效果得出),表明在ecex函数装载的新程序结束后,父进程就对其展开收尸动作了。

本1-3实验可以进一步加深我们对fork exec wait等api进行混合使用的理解。

实验2

实验目的:execvp使用展示( 本质和execl一样,都是为了调用新程序去执行,只是使用的方式不一样而已 )

根据前面的介绍,exec后面的v表示argv,所以execvp有一个参数是char* argv[]. 后面的p表示path,且支持相对路径,但是该相对路径必须要在系统的环境表中,

Linux下查看系统环境表中的路径:

实验3  system介绍,自己编写功能更为强大的mysystem(自己手动调用exec系列函数可在需要时携带更多参数)。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h> char* test_cmd = "ps"; void mysystem(char* cmd){ pid_t pid;
//printf("main-process-pid: %ld\n", (long)getpid()); pid = fork();
if (pid < 0) {
printf("fork error");
}
else if (pid == 0) { //printf("son-process-pid: %ld\n", (long)getpid());
if(execlp("bash", "I am bash", "-c", cmd, NULL)<0){
perror("execl error");
exit(1);
}
//printf("mysystem success \n"); } wait(NULL);
} int main(void)
{
mysystem(test_cmd); return 0;
}

编译运行:

.

系统编程-进程-exec系列函数超级详解(带各种实操代码)的更多相关文章

  1. exec系列函数(execl,execlp,execle,execv,execvp)使用

    本节目标: exec替换进程映像 exec关联函数组(execl.execlp.execle.execv.execvp) 一,exec替换进程映像 在进程的创建上Unix采用了一个独特的方法,它将进程 ...

  2. linux系统编程之进程(五):exec系列函数(execl,execlp,execle,execv,execvp)使用

    本节目标: exec替换进程映像 exec关联函数组(execl.execlp.execle.execv.execvp) 一,exec替换进程映像 在进程的创建上Unix采用了一个独特的方法,它将进程 ...

  3. fork()和vfork()的区别,signal函数用法,exec()系列函数的用法小结

    一:fork()和vfork()的区别:    fork()函数可以创建子进程,有两个返回值,即调用一次返回两个值,一个是父进程调用fork()后的返回值,该返回值是刚刚创建的子进程的ID;另一个是子 ...

  4. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  5. exec系列函数和system函数

    一.exec替换进程映象 在进程的创建上Unix采用了一个独特的方法,它将进程创建与加载一个新进程映象分离.这样的好处是有更多的余地对两种操作进行管理.当我们创建 了一个进程之后,通常将子进程替换成新 ...

  6. linux系统编程-进程

    进程 现实生活中 在很多的场景中的事情都是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的: 如下是一段视频,迈克杰克逊的一段视频: http://v.youku.com ...

  7. WScript.Shell对象的 run()和exec()函数使用详解

    WScript.Shell对象的 run()和exec()函数使用详解 http://blog.sina.com.cn/s/blog_6e14a2050102v47g.html   vbScript ...

  8. nginx高性能WEB服务器系列之四配置文件详解

    nginx系列友情链接:nginx高性能WEB服务器系列之一简介及安装https://www.cnblogs.com/maxtgood/p/9597596.htmlnginx高性能WEB服务器系列之二 ...

  9. C#虚函数virtual详解

    在面向对象编程中,有两种截然不同的继承方式:实现继承和接口继承.在实现继承时候,在Java中,所有函数默认都是virtual的,而在C#中所有函数并不默认为virtual的,但可以在基类中通过声明关键 ...

  10. C语言对文件的操作函数用法详解1

    在ANSIC中,对文件的操作分为两种方式,即: 流式文件操作 I/O文件操作 一.流式文件操作 这种方式的文件操作有一个重要的结构FILE,FILE在stdio.h中定义如下: typedef str ...

随机推荐

  1. oeasy教您玩转vim - 49 - # 命令进阶

    ​ 命令进阶 回忆上节课内容 我们上次研究vim的历史 为什么会有行编辑器这种东西 竟然是当年 没有显示器只有纸的时代的无奈之举 vim进化到今天 依然还有好多人使用 而且ssh连接的时候直接vim就 ...

  2. for循环以及常用的遍历(迭代)用法

    for循环以及常用的遍历(迭代)用法   概念:(概念才是高楼的地基!) for循环是一个计次循环,一般运用在循环次数已知的情况下.通常适用于枚举或遍历序列,以及迭代序列中的元素. 注意*:迭代变量用 ...

  3. 在Django REST framework (DRF) 中,`request.query_params` 和 `request.data` 区别

    在Django REST framework (DRF) 中,request.query_params 和 request.data 都是用来获取请求中的数据,但是它们之间有一些关键的区别: requ ...

  4. 如何安装Ascend深度学习套件

    1. 驱动安装 1.1 驱动测试 输入测试命令: npu-smi info 结果如下: 1.2 Ascend驱动未安装 请参考Ascend驱动的安装文档,进行安装对应显卡的驱动,文档链接如下:http ...

  5. windows edge浏览器免费复制网页文字

    复制时,出现上面提示时候 使用edge浏览器打开链接,在http前面加入read: ,然后打开,即可复制 如果用js,可以参考https://www.cnblogs.com/rmticocean/p/ ...

  6. ansible 部署hadoop

    规划 ansible 节点 ansible controller 镜像rhel 8.2 镜像ansible hadoop 集群 master slave1 slave2 镜像centos 1810 0 ...

  7. 用了组合式 (Composition) API 后代码变得更乱了,怎么办?

    前言 组合式 (Composition) API 的一大特点是"非常灵活",但也因为非常灵活,每个开发都有自己的想法.加上项目的持续迭代导致我们的代码变得愈发混乱,最终到达无法维护 ...

  8. openAI的仿真环境Gym Retro的Python API接口

    如题,本文主要介绍仿真环境Gym Retro的Python API接口 . 官网地址: https://retro.readthedocs.io/en/latest/python.html ===== ...

  9. 如何在vscode中同时运行多个文件——server/client模式——在launch.json文件中设置多个configurations再compounds

    在vscode中我们一般都是同一时间只运行一个代码,但是这种设置并不适合server/client模式,甚至有很多分布式和并行的项目需要同一时间运行多个client,针对这种情况我们可以通过设置vsc ...

  10. 面试官:JDK中都用了哪些设计模式?

    设计模式是前辈们经过实践验证总结的解决方案,帮助我们构建出更具可维护性.可扩展性和可读性的代码.当然,在面试的过程中,也会或多或少的被问到.那么今天,我们就来看一道设计模式中的常见面试问题:JDK 中 ...