Linux 进程控制 :进程创建,进程终止,进程等待,程序替换

文章目录

  • 进程创建
  • 进程等待
  • 程序替换
  • 进程终止


进程创建

fork函数: 操作系统提供的创建新进程的方法,父进程通过调用 fork函数 创建一个子进程,父子进程代码共享,数据独有。

在这里插入图片描述
当调用 fork函数 时,通过 写时拷贝技术 来拷贝父进程的信息。

写时拷贝技术(copy on write): 子进程通过复制父进程的 PCB,使得父子进程指向同一块物理内存,运行位置和代码也相同。但又因为进程的独立性,所以当某一个进程数据发生改变的时候会重新给子进程开辟物理内存,将数据拷贝过去。(之所以这样使用是因为如果数据不修改的话还开辟空间拷贝数据会使效率降低)这也就是数据独有的原因。

代码共享: 通过页表来实现访问控制,使代码段是只读的,不可修改。


fork函数 的运用:

#include<iostream>
#include<unistd.h>
#include<stdlib.h>using namespace std;int main()
{cout << "hello world" << getpid() << endl;int id = fork();if(id < 0){cerr << "fork failed" << endl;}else if(id == 0){cout << "I am child process, id = " << getpid() << endl;}else{cout << "I am parent process, id = " << getpid() << endl;}return 0;
}

运行结果:
在这里插入图片描述
父进程调用 fork函数 后,对操作系统来说,这时看起来有两个完全一样的 test程序 在运行,并都从 fork() 系统调用中返回。区别在于,子进程不会从 main()函数 开始执行(因此 hello world 信息只输出了一次),而是直接从 fork() 系统调用返回,就好像是它自己调用了fork() ,只是返回值和父进程不同罢了。

实际上,上图的输出结果并不是唯一答案,也有可能 子进程先于父进程执行完毕


vfork函数: 创建一个子进程,并且阻塞父进程,直到子进程退出或者程序替换,父进程才继续运行。

#include <unistd.h>
pid_t vfork(void);
返回值:自进程中返回0,父进程返回子进程id.出错返回1

vfork 创建子进程的效率比 fork 要高,因为 vfork 所创建的子进程和父进程共用同一个虚拟地址空间。

但也因为这样,进程之间就不具备独立性,父子进程不能同时访问代码段和数据段,所以当子进程运行的时候必须要阻塞父进程,防止产生冲突。

虽然 vfork 效率高,但是 fork 因为实现了写时拷贝技术,效率提高了不少,所以 vfork 已经很少使用了。


进程等待

之前讲过,如果子进程退出时没有给父进程返回退出的信息,父进程就会以为他并没有退出,所以一直不释放他的资源,使子进程进入僵死状态。

之前的解决方法是退出父进程,但是那个不是一个合理的解决方法,这里有更好的方法,就是进程等待。

  • wait: 阻塞等待任意一个进程退出,获取退出子进程的 pid ,并且释放子进程资源。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置为NULL
  • 阻塞:为了完成某个功能发起调用,如果不具备完成功能的条件,则调用不返回一直等待。
  • 非阻塞:为了完成某个功能发起调用,如果不具备完成功能的条件,则立即报错返回

wait函数 的运用:

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>using namespace std;int main()
{cout << "hello world, My id = " << getpid() << endl;int id = fork();if(id < 0){cerr << "fork failed" << endl;}else if(id == 0){cout << "I am child process, id = " << getpid() << endl;}else{int wc = wait(NULL);cout << "I am parent process, id = " << getpid() << ". My child id = " << id << ". wc: " << wc << endl;}return 0;
}

运行结果:
在这里插入图片描述
对比 fork函数 的运行实例可以看到,本次运行 子进程 要先于 父进程 完成,这是因为 父进程 调用了 wait() ,延迟了自己的的执行(阻塞),直到 子进程 执行完毕,wait() 才返回父进程。

但与 fork函数 的运行实例不同,本实例的运行结果是唯一的:

  • 如果子进程先运行,父进程调用 wait() 时子进程早已执行完毕,父进程无需阻塞自己,那么输出结果如上没什么可多说的。
  • 如果父进程先运行,那么到调用 wait() 的时候必须等待子进程运行结束后才能返回,接着输出(父进程)自己的信息。

  • waitpid: 可以指定等待一个子进程的退出。
#include<sys/wait.h>
pid_ t waitpid(pid_t pid, int *status, int options);返回值:
当正常返回的时候 waitpid 返回收集到的子进程的进程ID;
如果设置了选项 WNOHANG ,而调用中 waitpid 发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在;参数:
- pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
- status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
- options:
WNOHANG: 若 pid 指定的子进程没有结束,则 waitpid() 函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

程序替换

创建子进程必定是想让 子进程做与父进程不一样的事情 ,如果采用判断 pid 的方法来进行代码分流,这样的程序会非常庞大,所以还有更好的方法,就是通过 exec()函数 来实现 程序替换

exec: 是创建进程 API 的重要组成部分。可以让 子进程执行与父进程不同的程序

程序替换: exec() 加载另一个程序的代码和静态数据到内存中,覆盖自己的代码段(以及静态数据),堆、栈及其他控件也会被重新初始化。PCB 不再调度原来的程序,而调度这个新的程序。(只是改变了映射关系,所以原本的 PCB 和程序还在)

#include <unistd.h>`
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[]);
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[]);

乍一看这么多接口很不容易记,其实是有规律的:

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

exec()函数 的运用:

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<string.h>using namespace std;int main(int argc, char *argv[])
{cout << "hello world, My id = " << getpid() << endl;int id = fork();if(id < 0){cerr << "fork failed" << endl;exit(1);}else if(id == 0){cout << "I am child process, id = " << getpid() << endl;char* s[3];s[0] = strdup("wc");s[1] = strdup("test");s[2] = NULL;execvp(s[0], s);cout << "This shouldn't print out." << endl;}else{int wt = wait(NULL);cout << "I am parent process, id = " << getpid() << ". My child id = " << id << ". wt: " << wt << endl;}return 0;}

运行结果:
在这里插入图片描述

在本例中,子进程调用 execvp() 来运行字符计数程序 wc 。将计数程序 wc 作为可执行文件 test 的执行参数,输出该文件有多少行、多少单词、多少字节。exec() 从可执行程序 wc 中加载代码和静态数据以覆盖运来的代码段(及静态数据),并重新初始化堆、栈及其他内存空间,然后操作系统执行该程序,将参数通过 argv 传递给该进程。exec() 并不创建新进程,而是直接将当前运行的程序(test)替换为不同的运行程序(wc),子程序执行 exec() 后,几乎就像 test 从未运行过一样,对 exec() 的成功调用永远不会返回。

fork()exec() 的组合既简单又极其强大,fork 使用 父进程 的各种资源迅速创建一个 子进程 ,在修改 子进程资源/环境 而保证 父进程 能执行 原来的需求工作 ,再将 子进程 exec 为另一个程序,从而 创建两个并行的功能不同的进程


shell 也可以通过 fork()exec() 方便地实现很多有用的功能。比如,上面的例子可以写成:

prompt> wc p3.c > newfile.txt

在上面的 shell 命令中,wc 的输出结果被 重定向(redirect) 到文件 newfile.txt 中(通过 newfile.txt 之前的大于号来指明重定向)。shell 实现结果重定向的方式也很简单,当完成子进程的创建后,shell 在调用 exec() 之前先关闭了标准输出(standard output ),打开了文件 newfile.txt 。这样,即将运行的程序 wc 的输出结果就被发送到该文件,而不是打印在屏幕上。

用代码实现重定向:

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<string.h>
#include<fcntl.h>using namespace std;int main(int argc, char *argv[])
{int id = fork();if(id < 0){cerr << "fork failed" << endl;exit(1);}else if(id == 0){close(STDOUT_FILENO);open("./test.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);// now exec "wc"...char* s[3];s[0] = strdup("wc");s[1] = strdup("test");s[2] = NULL;execvp(s[0], s);}else{int wt = wait(NULL);}return 0;}

在这里插入图片描述

上例中 重定向的工作原理: 重定向是 基于对操作系统管理文件描述符方式的假设 。具体来说,UNIX 系统从 0 开始寻找可以使用的文件描述符。在这个例子中,STDOUT_FILENO(标准输出文件描述符) 将成为第一个可用的文件描述符,因此在 open() 被调用时,得到赋值。然后子进程向 标准输出文件描述符 的写入(例如 wc 程序中 printf() 这样的函数),都会被透明地转向新打开的文件,而不是屏幕。

UNIX管道也是用类似的方式实现的,但用的是 pipe() 系统调用。


进程终止

Linux 下有三种终止进程的方法。

  • return: 只能在 main 函数中使用,退出后刷新缓冲区

  • exit: 库函数调用接口,退出刷新缓冲区

#include <unistd.h>
void exit(int status);
  • _exit: 系统函数调用接口,退出不刷新缓冲区
#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/443773.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 内存管理 | 连续分配方式 和 离散分配方式

文章目录前言连续分配单一连续分配分区式分配固定分区分配动态分区分配可重定位分区分配离散分配分段分页多级页表快表(TLB)段页式Linux前言 Linux 内存管理 | 虚拟内存管理&#xff1a;虚拟内存空间、虚拟内存分配 Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器…

操作系统 | 用户态和内核态的切换(中断、系统调用与过程(库函数)调用)

文章目录中断过程调用系统调用过程调用和系统调用的区别中断 用户态、内核态之间的切换是怎么实现的? 用户态→内核态 是通过中断实现的。并且 中断是唯一途径 。核心态→用户态 的切换是通过执行一个特权指令&#xff0c;将程序状态字 (PSW) 的标志位设置为 用户态 。 中断…

管道实现父子进程的信息传递(二)【标准流和其文件描述符、fwrite函数、perror函数】

文章目录代码实现标准流 和 标准流文件描述符代码中用到的函数fwrite()perror()在复习进程间的通信方式时又写了一遍&#xff0c;和 管道实现父子进程的信息传递&#xff08;一&#xff09;【fork函数、pipe函数、write/read操作、wait函数】 的区别不是特别大&#xff0c;只是…

命名管道实现进程的信息传递【mkfifo函数、open函数】

文章目录代码实现mkfifo函数open函数代码实现 #include<fcntl.h> // open() #include<sys/wait.h> // wait() #include<sys/types.h> // mkfifo() #include<sys/stat.h> // mkfifo() #include<iostream> #include<unistd.h> // fork()usi…

Linux 进程 | 进程间的通信方式

文章目录管道匿名管道 pipe命名管道 FIFO共享内存共享内存的使用流程&#xff1a;消息队列信号量套接字在之前的博客中讲过&#xff0c;虚拟空间出现的其中一个目的就是解决 进程没有独立性&#xff0c;可能访问同一块物理内存 的问题。因为这种独立性&#xff0c;进程之间无法…

Linux网络编程 | socket介绍、网络字节序与主机字节序概念与两者的转换、TCP/UDP 连接中常用的 socket 接口

文章目录套接字socket 地址通用 socket 地址专用 socket 地址网络字节序与主机字节序地址转换TCP/UDP 连接中常用的 socket 接口套接字 什么是套接字&#xff1f; 所谓 套接字 (Socket) &#xff0c;就是对网络中 不同主机 上的应用进程之间进行双向通信的端点的抽象。 UNIX/L…

网络协议分析 | 传输层 :史上最全UDP、TCP协议详解,一篇通~

文章目录UDP概念格式UDP如何实现可靠传输基于UDP的应用层知名协议TCP概念格式保证TCP可靠性的八种机制确认应答、延时应答与捎带应答超时重传滑动窗口滑动窗口协议后退n协议选择重传协议流量控制拥塞控制发送窗口、接收窗口、拥塞窗口快速重传和快速恢复连接管理机制三次握手连…

JDom,jdom解析xml文件

1.要解析的文件模板如下&#xff1a; <?xml version"1.0" encoding"GBK"?> <crsc> <data><举报信息反馈><R index"1"><举报编号>1</举报编号><状态>1</状态><答复意见>填写…

网络协议分析 | 应用层:HTTP协议详解、HTTP代理服务器

文章目录概念URLHTTP协议的特点HTTP协议版本格式请求报文首行头部空行正文响应报文首行头部空行正文Cookie与SessionHTTP代理服务器正向代理服务器反向代理服务器透明代理服务器概念 先了解一下 因特网&#xff08;Internet&#xff09; 与 万维网&#xff08;World Wide Web&…

MySQL命令(一)| 数据类型、常用命令一览、库的操作、表的操作

文章目录数据类型数值类型字符串类型日期/时间类型常用命令一览库的操作显示当前数据库创建数据库使用数据库删除数据库表的操作创建表显示当前库中所有表查看表结构删除表数据类型 mysql 的数据类型主要分为 数值类型、日期/时间类型、字符串类型 三种。 数值类型 数值类型可…

C++ 继承 | 对象切割、菱形继承、虚继承、对象组合

文章目录继承继承的概念继承方式及权限using改变成员的访问权限基类与派生类的赋值转换回避虚函数机制派生类的默认成员函数友元与静态成员多继承菱形继承虚继承组合继承 继承的概念 继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。 当创建一个类时&…

博弈论 | 博弈论简谈、常见的博弈定律、巴什博弈

文章目录博弈论什么是博弈论&#xff1f;博弈的前提博弈的要素博弈的分类非合作博弈——有限两人博弈囚徒困境合作博弈——无限多人博弈囚徒困境常见的博弈定律零和博弈重复博弈智猪博弈斗鸡博弈猎鹿博弈蜈蚣博弈酒吧博弈枪手博弈警匪博弈海盗分金巴什博弈博弈论 什么是博弈论…

MySQL命令(二)| 表的增删查改、聚合函数(复合函数)、联合查询

文章目录新增 (Create)全列插入指定列插入查询 (Retrieve)全列查询指定列查询条件查询关系元素运算符模糊查询分页查询去重&#xff1a;DISTINCT别名&#xff1a;AS升序 or 降序更新 (Update)删除 (Delete)分组&#xff08;GROUP BY&#xff09;联合查询内连接&#xff08;inne…

MySQL | 数据库的六种约束、表的关系、三大范式

文章目录数据库约束NOT NULL&#xff08;非空约束&#xff09;UNIQUE&#xff08;唯一约束&#xff09;DEFAULT&#xff08;缺省约束&#xff09;PRIMARY KEY&#xff08;主键约束&#xff09;AUTO_INCREMENT 自增FOREIGN KEY&#xff08;外键约束&#xff09;CHECK&#xff08…

哈希 :哈希冲突、负载因子、哈希函数、哈希表、哈希桶

文章目录哈希哈希&#xff08;散列&#xff09;函数常见的哈希函数字符串哈希函数哈希冲突闭散列&#xff08;开放地址法&#xff09;开散列&#xff08;链地址法/拉链法&#xff09;负载因子以及增容对于闭散列对于开散列结构具体实现哈希表&#xff08;闭散列&#xff09;创建…

C++ 泛型编程(一):模板基础:函数模板、类模板、模板推演成函数的机制、模板实例化、模板匹配规则

文章目录泛型编程函数模板函数模板实例化隐式实例化显式实例化函数模板的匹配规则类模板类模板的实例化泛型编程 泛型编程旨在削减重复工作&#xff0c;如&#xff1a; 将一个函数多次重载不如将他写成泛型。 void Swap(int& left, int& right) {int temp left;lef…

你真的了解静态变量、常量的存储位置吗?

文章目录引言C对内存的划分如何落实在Linux上自由存储区和堆之间的问题栈常量区静态存储区静态局部变量静态局部变量、静态全局变量、全局变量的异同macOS系统的测试结果总结引言 在动态内存的博客中&#xff0c;我提到&#xff1a; 在Linux 内存管理的博客中&#xff0c;我提…

C++ 泛型编程(二):非类型模板参数,模板特化,模板的分离编译

文章目录非类型模板参数函数模板的特化类模板的特化全特化偏特化部分参数特化参数修饰特化模板分离编译解决方法非类型模板参数 模板的参数分为两种&#xff1a; 类型参数&#xff1a; 则是我们通常使用的方式&#xff0c;就是在模板的参数列表中在 class 后面加上参数的类型…

数据结构 | B树、B+树、B*树

文章目录搜索结构B树B树的插入B树的遍历B树的性能B树B树的插入B树的遍历B*树B*树的插入总结搜索结构 如果我们有大量的数据需要永久存储&#xff0c;就需要存储到硬盘之中。但是硬盘的访问速度远远小于内存&#xff0c;并且由于数据量过大&#xff0c;无法一次性加载到内存中。…

MySQL 索引 :哈希索引、B+树索引、全文索引

文章目录索引引言常见的索引哈希索引自适应哈希索引B树索引聚集索引非聚集索引使用方法联合索引最左前缀匹配规则覆盖索引全文索引使用方法索引 引言 为什么需要索引&#xff1f; 倘若不使用索引&#xff0c;查找数据时&#xff0c;MySQL必须遍历整个表。而表越大&#xff0c;…