【进程控制上】创建、终止、等待、程序替换

2023-05-16

★进程的创建、终止、等待、程序替换、以及popen/system与fork之间的区别。

一、进程的创建

init进程将系统启动后,init将成为此后所有进程的祖先,此后的进程都是直接或间接从init进程“复制”而来。完成该“复制”功能的函数有fork()和clone()等。
1.fork

#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程的id;出错父进程返回-1,子进程创建失败。

2.进程调用fork,当控制转移到内核中的fork代码后,内核做了四件事情。
①分配新的内存块和内核数据结构给子进程。
②将父进程部分数据结构内容拷贝至子进程。
③添加子进程到系统进程列表当中。
④fork返回,开始调度器调度。

3.当一个进程调用fork之后,就会有两个二进制代码相同的进程,而且他们都运行到相同地方。
4.实现代码

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    pid_t pid;
    printf("Before is %d\n",getpid());
    if((pid=fork())<0){
            perror("fork");
            exit(1);
    }
    printf("After is %d,return is %d\n",getpid(),pid);
    sleep(1);
    return 0;
}

这里写图片描述
注:父进程打印了两行,为什么子进程只打印了一行?
因为fork之前父进程独立运行,fork之后,父子两个执行流分别执行,但是,父子进程谁先执行完全由调度器决定,没有固定顺序。

5.什么时候使用fork呢?
1)一个父进程希望子进程同时执行不同的代码段,这在网络服务器中常见——父进程等待客户端的服务请求,当请求到达时,父进程调用fork,使子进程处理此请求。
2)一个进程要执行一个不同的程序,一般fork之后立即调用exec

6.写时拷贝
新进程得到的是父进程的副本,所以,父子进程count变量不会相互影响。
fork函数将复制父进程的地址空间给子进程,但为了提高效率,复制过程并不会真正的进行物理内存的完整复制,而是采用“写时拷贝(copy-on-write)”技术让父子进程尽可能地长久地共享该物理内存,仅仅是复制内存页入口地址并标记写拷贝对应的页面,当修改真正发生时才真正复制。

7.进程状态
这里写图片描述
8.fork调用失败的原因
1)系统中太多的进程。
2)实际用户的进程超过了限制。

9.进程的创建还有另一种方式:vfork
1)函数

#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
//成功:子进程返回 0,父进程返回子进程ID。pid_t为无符号整型。
//失败:返回 -1。

2)vfork特点
子进程和父进程共享地址空间。也就意味着子进程可以直接改变父进程的变量值。
vfork保证子进程先运行,在它调用exec(进程替换)或exit(退出进程)之后父进程才可能被调度执行。

二、进程的终止

1.进程的退出场景
1)代码运行完毕,代码正确。
2)代码运行完毕,结果不正确。
3)代码异常终止
2.进程的退出方法
①正常终止(可以使用命令:echo $? 查看进程退出码)
从main返回
调用exit
调用_exit
②异常终止
Ctrl+c,信号终止
③_exit函数

#include <unistd.h>
void _exit(int status);
//参数:status定义了进程的终止状态,父进程通过wait来获取该值。
//虽然status是int,但是仅有低八位可以被父进程所用,所以_exit(-1)时,终端执行
echo $?查看退出码,会发现,返回值是255

④exit函数

//函数
#include <stdlib.h>
void exit(int staus);
//exit示例代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> 
int main()
{
    printf("hello");//结果显示
    exit(0);                                                              
}
//_exit示例代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    printf("hello");//结果不显示
    _exit(0);
}              

exit最后也会调用_exit,但在调用_exit之前,还做了其他工作,所以才会显示结果:
1、执行用户通过atexit或on_exit定义的清理函数。
2、关闭所有打开的流,所有的缓存数据均被写入
3、调用_exit

三、进程的等待

进程一旦变为僵尸进程就会造成内存泄漏,kill -9也无法杀死一个已经死去的进程,子进程任务完成的怎么样,也需要给父进程一个交代,所以父进程可以通过进程等待的方式,回收子进程资源,获取子进程退出信息
1.进程等待的方式
1)wait方法

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
返回值:成功返回被等待进程的pid,失败返回-1
参数:输出型参数,获取子进程退出时的信息,不用关心时,可以设置为NULL。

2)waitpid方法

pid_t   waitpid(pid_t  pid,int *status,int  options);
返回值:
正常返回时后waitpid收集到的子进程的id,如果设置了选项WNOHANG,而调用中waitpid发现
没有已退出的子进程可收集,则返回0.如果调用中出错,则返回-1,这时errno会被设置成相应
的值以指示错误所在。

//pid
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指
定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpidwait的作用一模一样。   
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会
对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。 

//status:
WIFEXITED(status):若为正常终止子进程返回状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

//options:
WNOHANG:若pid指定的子进程没有结束,则waitpid返回0,不予以等待。若正常结束,则返回
该子进程的id。

需要注意的一点得是,当子进程正常退出时,调用wait/waitpid时,函数会立即返回,并成功释放资源,获得子进程退出信息,如果在任意时刻调用wait/waitpid时,且子进程正在运行,会造成子进程的阻塞。如果不存在该子进程,则立即出错返回。
还有,wait就是经过包装的waitpid。
2.wait&waitpid的区别
waitpid提供了wait函数不能实现的3个功能:
1.waitpid等待特定的子进程, 而wait则返回任一终止状态的子进程;
2.waitpid提供了一个wait的非阻塞版本;
3.waitpid支持作业控制(以WUNTRACED选项). 用于检查wait和waitpid两个函数返回终止状态的宏: 这两个函数返回的子进程状态都保存在status指针中
3.获取子进程的status
1)wait和waitpid中都有一个status参数,是一个输出型参数,由操作系统填充。
2)如果传递NULL,那么意味着不关心子进程的退出信息。
3)否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
4)status不能简单的当做整型看待,可以看做位图,只看低16位
这里写图片描述

4.进程的阻塞与非阻塞式等待

阻塞
//waitpid(-1, &status, 0);
非阻塞
//waitpid(-1,&status, WNOHANG);

四、程序替换

何为程序替换?
如果操作系统正在执行某项程序,我们可以使用exec函数族进行程序替换,让系统去执行另一个程序,我们可以fork一个子进程,父子进程执行相同的代码,我们就可以使用程序替换,让子进程去干其他的事。

//shell脚本不相信任何人,它将任务交给子进程去执行。
//子进程用于执行命令,父进程用于返回!

1.替换函数

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);//自己维护
//带l的函数,参数表以空指针结尾

int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);//自己维护 
//成功返回信息,失败返回-1
//execve属于系统调用,而其他的五个函数都是在execve函数基础上经过包装而形成的库函数

替换函数提供了一个在进程中启动另一个程序执行的方法,将当前程序替换为一个新的程序。
2.函数命名

l(list):参数采用列表
v(vector):参数采用数组
p(path):自动搜索环境变量
e(env):自己维护环境变量

3.程序替换的作用
在一个正在运行的进程内部根据函数所指定的文件名和路径找到可执行的文件,并将进程执行的程序替换为新程序,新程序从main处开始执行。其中这里的文件必须是二进制文件或Linux下可执行的脚本文件,另外,exec函数族的函数只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆和栈。调用exec并不创建新进程,所以调用前后该进程的id并未改变.
4.代码示例

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    char *const argv[]={"ls","-l","-a","-i",NULL};//参数用数组
    char *const envp[]={"PATH=/bin:/usr/bin","TEMR=console",NULL};//自己维护
    pid_t id;
    id=fork();
    if(id<0)//unsucessful
    {
       perror("fork");
       return 0;
    }
    else if(id==0)//child
    {
       //execl("/bin/ls","ls","-l","-a","-i",NULL);
       //execlp("ls","ls","-l","-a","-i",NULL);  //带p的无需写全路径
       //execv("/bin/ls",argv);
       execle("ps","ps","-ef",NULL,envp);  //带e的自己组装环境变量
    }
    else//father
    {
    }
    return 0;
}

五、popen/system与fork之间的区别

1.popen

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
//功能:popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数
//command的指令。

int pclose(FILE * stream)
//功能:pclose()用来关闭由popen所建立的管道及文件指针

1)如果没有在调用popen后调用pclose那么这个子进程就可能变成”僵尸”。
2)popen执行命令并且通过管道和shell命令进行通信。
3)调用popen的进程为父进程,由popen启动的进程称为子进程。

2.system :Linux下使用system()函数需谨慎

#include <stdlib.h>
int system(const char *command);

1)system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令.
2) 在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【进程控制上】创建、终止、等待、程序替换 的相关文章

  • QT Signal and slot arguments are not compatible

    一 原因 信号和槽绑定的参数不同 signals span class token operator span span class token keyword void span span class token function run
  • QML入门----图形动画基础(二)

    文章目录 导语一 混合效果二 颜色效果1 亮度对比度 xff08 BrightnessContrast xff09 2 颜色叠加3 着色4 饱和度 三 渐变效果 xff08 Gradient xff09 四 阴影效果五 模糊效果六 动感模糊
  • EXCEL基本办公操作 (求和、相除、填充日期、交换列、排序)

    文章目录 一 求和二 相除三 自动填充日期四 交换列五 进行排序 一 求和 1 拖动鼠标选中 2 同时按住 alt 跟 61 二 相除 假如要计算A列除以B列 1 先选中显示结果的框 2 在上面的框输入 61 号 xff0c 然后点击A1位
  • Ubuntu的VirtualBox虚拟机怎么识别物理机的U盘?我教你。

    首先确保 你的VBOX虚拟机安装了扩展 1 到官网上下载扩展吧 2 用VBOX打开扩展包 3 打开VBOX管理器 xff0c 点击设置 4 新建一个usb筛选器 xff0c 名字随便起 最后点击确定 正常关闭 你的虚拟机 xff0c 然后重
  • QML入门----设计器详解(拖拽添加控件)

    文章目录 导语1 基本视图2 文件类型 一 界面说明1 库 xff08 Library xff09 2 导航 xff08 Navigator xff09 3 属性 xff08 Properties xff09 4 连接视图 导语 设计器的基
  • C++11 非常方便的特性

    文章目录 C 43 43 11一 nullptr1 含义2 作用3 NULL存在的问题 二 auto1 含义2 限制3 使用场景 三 lambda1 含义2 优点3 用法 四 基于范围的for循环1 作用2 用法3 循环内更改数组 C 43
  • QML入门----C++与QML交互快速应用

    文章目录 前言一 Qt中有关QML的C 43 43 类1 QQmlEngine2 QQmlContext3 QQmlComponent4 QQmlExpresssssion 二 其他1 使用C 43 43 属性 xff08 Q PROPER
  • QML错误:Component is not ready

    一 原因 终极原因 xff1a 组件没有构建好 xff0c 有可能是加载的QML路径不对 xff0c 或者是QML代码错误 xff0c 或者是QML组件还没有加载完 二 解决办法 打印详细错误 QQmlEngine engine span
  • QT 打开程序闪烁cmd窗口

    包含多种原因 xff0c 我的原因是Pro文件多写了一些其他的 xff0c 删除了下面这句OK了 DISTFILES span class token operator 43 span span class token operator 6
  • QT UTC(T和Z格式)时间转换为北京时间

    一 UTC 协调世界时 xff0c 又称世界统一时间 世界标准时间 国际协调时间 由于英文 xff08 CUT xff09 和法文 xff08 TUC xff09 的缩写不同 xff0c 作为妥协 xff0c 简称UTC 和北京时间相差八小
  • QT 文件操作大全

    文章目录 常用文件模式一 创建文件二 读文件三 写文件四 清空文件夹五 计算文件夹个数六 计算文件夹总大小七 转换大小为B KB M G八 批量修改文件名 常用文件模式 模式含义QIODevice ReadOnly只读方式QIODevice
  • QT QScrollArea 滑动到指定item位置

    一 QT自带的api QListWidget QTableWidget QTreeWidget都有自带的api可以调用 xff0c 如下示例 但是当自定义一个QScrollArea区域 xff0c 布局中插入多个item时 xff0c 就需
  • 马克飞象常用操作(markdown )

  • QT 移入控件展示卡片

    功能 xff1a 移入widget显示卡片 xff0c 并且可以进入卡片不消失 xff08 widget与卡片距离离得很近 xff09 xff0c 移出卡片才离开 span class token keyword bool span spa
  • 树莓派pico入门第一站:让主板上的小灯闪起来。(附代码)

    首先配置你的树莓派pico xff0c 把它插在你的电脑上 xff0c 你的电脑会多出来一个 U盘 xff0c 把这个文件复制 xff0c 粘贴 到你的树莓派pico里面 xff0c 你多出来的 U盘 会自动 消失 xff0c 这时候 xf
  • QT 网格布局插入固定列数的item

    一 场景 在网格布局插入固定列数的item xff0c 比如三列item xff0c 根据item的总数计算 span class token macro property span class token directive hash s
  • QT QMetaEnum枚举与字符串互转

    一 示例 span class token macro property span class token directive hash span span class token directive keyword include spa
  • QT 抓取widget转换为图片

    QString folder span class token operator 61 span span class token class name QStandardPaths span span class token operat
  • window11 安装linux子系统(一键安装)并连接到vs code

    文章目录 一 window 使用linux环境的几种方式二 安装wsl1 进入这个目录下 xff0c 将cmd exe已管理员身份运行2 命令行输入以下命令 xff0c 然后重启计算机3 再次已管理员身份打开 xff0c 执行命令 xff0
  • QT 利用URL Protocol实现网页调起本地程序

    一 QT 安装时脚本注入注册表或者自己添加 span class token comment 依次为目录 键 值 xff0c 34 URL Protocol 34 这个键必须有 span WriteRegStr HKCR span clas

随机推荐

  • PC 配置jenkins自动打包

    文章目录 一 下载jenkins运行环境二 下载jenkins三 安装 qt 5 12 2 和 VS 2017四 安装git并配置gitlab五 jenkins配置git 一 下载jenkins运行环境 java jdk 11 镜像下载地址
  • 心系Flyme

    我来自陕西省神木县 xff0c 大学我考入了陕西科技大学 xff0c 成为了一名信息与计算科学专业的学生 xff0c 希望在以后的道路中 xff0c 通过我自己的努力 xff0c 提升自己的价值 在大二大三学习编程 xff0c 希望自己可以
  • C语言的编译链接过程

    编写的一个C程序 xff08 源程序 xff09 xff0c 转换成可以在硬件上运行的程序 xff08 可执行程序 xff09 xff0c 需要进行翻译环境和运行环境 翻译环境则包括两大过程编译和链接 xff0c 经过编译和链接过程便可形成
  • 函数的调用过程(栈帧的创建和销毁)

    为了更好地认识函数的调用过程 xff0c 我们可以用反汇编代码去理解学习 一 基本概念 1 栈帧 xff08 过程活动记录 xff09 xff1a 是编译器用来实现函数调用的一种数据结构 xff0c 每个栈帧对应一个未运行完的函数 xff0
  • 树莓派pico刚买来怎么用?

    第一次使用 xff0c 首先按住主板上的白色按钮 xff0c 然后另一只手把数据线插在主板上 xff0c 直到你的电脑提示有新设备输入 xff0c 提示可以是声音 xff0c 可以是设备管理器多了一个U盘 要想得到提示 xff0c 你要打开
  • C语言动态顺序表

    顺序表是将表中的节点依次存放在计算机内存中一组地址连续的存储单元中 xff0c 表可以动态增长 xff0c 尾插元素 xff0c 尾删元素 xff0c 头插元素 xff0c 头删元素 xff0c 打印元素 xff0c 查找元素 xff0c
  • C语言笔记1

    假定程序运行环境为VC6 0 xff0c 缺省为四字节对齐 xff0c CPU xff08 32小字节序处理器 xff09 1 char x 61 34 ab0defg 34 char y 61 39 a 39 39 b 39 39 0 3
  • 【C++三大特性】继承

    如有疑问 xff0c 欢迎讨论 xff0c QQ xff1a 1140004920 一 继承的概念 1 原有的类为基类 xff0c 又称父类 xff0c 对基类进行扩展产生的新类称为派生类 xff0c 又称子类 xff0c 继承可以使代码复
  • C++实现顺序表及双向链表

    顺序表 include lt iostream gt include lt assert h gt using namespace std typedef int DataType class SeqList public 默认的构造函数
  • 二叉树

    一 二叉树 是结点的一个有限集合 xff0c 每个根结点最多只有两颗子树 xff0c 二叉树有左右之分 xff0c 子树的次序不能颠倒 二 二叉树的种类 1 满二叉树 xff1a 每个结点都有左右子树 xff0c 且叶结点都在同一层 2 完
  • 进程间通信----管道、消息队列、共享内存、信号量

    一 进程间通信 xff08 Inter Process Communication xff09 1 目的 1 数据传输 2 资源共享 3 通知事件 4 进程控制 注 xff1a 每个进程都有各自不同的用户地址空间 xff0c 进程之间要交换
  • 进程基本概念、进程地址空间

    强调内容今天来谈一谈进程的一些基本概念 xff0c 认识一些进程状态 xff0c 重新认识一下程序地址空间 xff08 进程地址空间 xff09 xff0c 进程调度算法 xff0c 环境变量等属性 一 进程 1 什么是进程 xff1f 程
  • 何为缓存?

    一 缓存 xff08 cache xff09 1 概念 xff1a 数据交换的缓冲区 xff08 称作Cache xff09 缓存是一块内存芯片 xff0c 具有极快的存取速率 xff0c 它是硬盘内部存储和外界接口之间的缓冲器 xff0c
  • 计算机的组成

    一 冯诺依曼系统 1 计算机硬件 由运算器 控制器 存储器 输入设备 输出设备组成 2 计算机内部采用二进制表示指令和数据 3 注 xff1a 1 输入设备 xff1a 键盘和鼠标等 2 输出设备 xff1a 显示屏 xff0c 打印机等
  • fd与FILE以及fork缓冲问题

    一 文件描述符 fd 1 文件描述符其实就是一个非负的小整数 是文件指针数组的下标 2 让我们看一看0 xff0c 1 xff0c 2 xff0c 代表什么 xff1f span class hljs preprocessor includ
  • Kali Linux使用体验简述

    在以前的版本里Kali Linux默认用户是root用户 xff0c 这样设计的目的是避免每次都要输入root密码 xff0c 而如今需要root密码的程序明显少于从前 xff0c Kali Linux也做出了相应的改革 xff0c 默认用
  • 随身WiFi410的板子刷Debian安装青龙面板+狗东脚本最详细教程

    前几天 xff0c 我发布了一个410刷入debian的教程 很多老哥可能觉得刷入debian没有什么用 xff0c 今天我就教大家如何安装青龙面板 xff0c 并且安装脚本实现自动白嫖狗东的豆子 青龙面板 43 狗东脚本 自动领豆子红包
  • inode以及软硬链接

    一 inode 使用ls l查看文件元数据 xff0c 用来描述数据属性 模式 硬链接数 文件所有组 组 大小 最后修改时间 文件名 使用stat查看 xff0c 查看文件信息 span class hljs comment Access
  • 静态库与动态库

    一 库 由于版权原因 xff0c 库函数的源代码一般是不可见的 xff0c 但在暴露的头文件中你可以看到它对外的接口 库函数简介 xff0c 使用的时候 xff0c 直接引入头文件 include lt gt 即可 二 静态库 1 概念 程
  • 【进程控制上】创建、终止、等待、程序替换

    进程的创建 终止 等待 程序替换 以及popen system与fork之间的区别 一 进程的创建 init进程将系统启动后 xff0c init将成为此后所有进程的祖先 xff0c 此后的进程都是直接或间接从init进程 复制 而来 完成