【Linux系统编程】通过系统调用获取进程标识符 及 创建子进程(fork)

文章目录

  • 1. 通过系统调用获取进程标示符(PID)
    • 1.1 进程id(PID)
    • 1.2 父进程id(PPID)
  • 2. bash也是一个进程
  • 3. 通过系统调用创建进程-fork初识
    • 3.1 批量化注释
    • 3.2 取消注释
    • 3.3 fork创建子进程
    • 3.4 fork的返回值
    • 3.5 fork 之后通常要用 if 进行分流
    • 3.6 父子进程代码共享,数据写时拷贝(实现相互独立)
    • 3.7 如何理解fork两个返回值的问题

1. 通过系统调用获取进程标示符(PID)

上一篇文章我们了解了进程的概念,并学会了创建进程和查看进程,在查看进程的时候,我们重点了解了一个属性叫做PID,即进程标识符。

1.1 进程id(PID)

那我们能否单独获取到一个进程的PID呢?可以的:

我们可以通过一个系统调用来获取,这个系统调用叫做getpid
我们可以通过man手册学习一下
在这里插入图片描述
getpid没有参数,直接调用即可获取(返回)当前进程的pid,返回值是pid_t类型,其实就是一个有符号整数类型

那我们来试一下:

首先给我们的源文件修改一下
在这里插入图片描述
保存退出
然后我们重新make,接着运行生成的可执行程序
在这里插入图片描述
就成功打印了PID是19490
另外我们也可以通过命令查看一下
在这里插入图片描述
没问题,就是19490

然后:

在这里插入图片描述
我把它终止掉,再反复多启动终止几次
在这里插入图片描述
我们发现,它每次的PID可能都是不同的,是会变化的,进程的PID是由操作系统维护的。

1.2 父进程id(PPID)

我们再来看一下这张图:

在这里插入图片描述
除了上面我们讨论的PID之外,前面还有一个PPID,这个是什么呢?
🆗,PPID,第一个P表示parent的意思,PPID代表当前进程的父进程的PID。
是的,进程也是有父子关系的。

那我们如何获取父进程的PID即PPID呢?

用另一个系统调用——getppid
在这里插入图片描述

我们来试一下:

在这里插入图片描述
重新make运行
在这里插入图片描述
然后我们再多运行几次
在这里插入图片描述
我们会发现当前进程的PID每次都是不一样的,但是其父进程的PID是一直不变的

那它的父进程是谁呢,为什么PID一直不变呢?

那我们可以查一下:
上面父进程的PID是18791
在这里插入图片描述
我们看到PID为18791的对应的是-bash
那bash是啥?
是不是Linux上的命令行解释器啊,这个我们之前学过。

2. bash也是一个进程

所以,我们可以得出一些结论:

命令行解释器bash也是一个进程!
其次,我们发现上面每次运行起来进程的父进程都是bash,所以,结论2:
命令行启动的所有程序,最后变成进程其对应的父进程都是bash(也有特殊情况,我们目前先不考虑)。
至于如何做到得,我们后面再说。

那为什么bash启动的程序,最终生成的进程它们的父进程都是bash呢?

🆗,大家还记不记得之前在讲解shell的那篇文章里面,我们举了一个王婆说媒的例子( link)
那在文章最后,我们就提出了——shell执行命令时,是创建子进程去执行的
所以上面我们发现进程的父进程都是bash。

那它为什么要这样做呢?

原因很简单,因为bash怕我们自己写到程序有问题,有bug。
所以bash就创建子进程去执行来保证自己的安全。
就对应我们之前讲的王婆自己去给小帅说媒怕不成功影响了自己的名声,所以找实习生去说。

那既然bash也是一个进程,那我们能不能把它干掉呢?

我们知道一个进程运行的时候我们可以输入CTRL+c终止这个进程。
那除此之外,还有一个命令——kill -9 PID可以强制杀死进程或者说强制终止进程。
试一下
在这里插入图片描述
在这里插入图片描述
那我们把bashkill掉呢?
在这里插入图片描述
我们kill之后会发现bash就不能正常工作了
那出现这种情况的话我们把xshell关掉重新登陆就行了。

bash创建子进程去帮它执行命令,那下一个问题,如何创建子进程呢?

3. 通过系统调用创建进程-fork初识

经过之前的学习我们知道我们可以通过运行一个程序使之变成进程,那有没有其它产生新进程的方法呢?

有的,我们可以通过系统调用来创建进程。
这个系统调用叫做fork

那我们先来学习一个fork怎么用:

man fork
在这里插入图片描述
它在当前进程的基础上创建一个新的子进程

3.1 批量化注释

那我们再重新写一段代码

把之前的注释掉,那这里再教大家一下如何批量化注释

怎么做呢?
我们用vim打开代码文件,进入之后默认在命令模式下,然后我们按CTRL+V
在这里插入图片描述
会看到下面显示一个V-BLOCK
然后我们按j就可以向下选中下面的行
在这里插入图片描述
选中完要注释的代码之后将输入切成大写
在这里插入图片描述
然后输入I
在这里插入图片描述
然后输入//注释第一行在这里插入图片描述
接着按Esc
在这里插入图片描述
批量化注释就完成了

3.2 取消注释

那如何取消批量化注释呢?

首先还是CTRL+V(要在命令模式下)
在这里插入图片描述
然后按l,按一次选中一列,那我们这里按两次就可以了
在这里插入图片描述
接着再按j向下选择行
在这里插入图片描述
选好之后按d就可以取消注释
在这里插入图片描述

3.3 fork创建子进程

然后我们写一下新的代码:

我们来写这样一个代码
在这里插入图片描述
fork也没有参数,我们直接调
如果不加fork的话,那这个程序运行就是打印两个字符串,这没什么好说的,很简单

然后我们运行一下:

在这里插入图片描述
看一下结果,我们发现我们代码里的第二个字符串被打印了两次。
在这里插入图片描述
而我们的代码里只打印了一次,但是它前面有一个fork的调用
为什么会这样呢?
我们测试第二个打印应该被执行了两次,因为fork又创建了一个子进程,所以有两个进程,那就有两个执行流去执行第二个打印,所以打印了两次。

我们可以在打印一下当前进程和其父进程的PID观察一下:

在这里插入图片描述
在这里插入图片描述
我们发现这两个PID是不一样的。
那这也证明了两次执行第二个printf对应的不是一个进程,这里是有两个进程的
另外呢,我们还发现:
在这里插入图片描述
第一次打印对应的进程的PID刚好是第二次打印对应进程的PPID。
那这也证实了它们两个是父子进程关系,fork的作用就是创建当前进程的子进程,而PID为30455的这个进程就是被创建的子进程。

那大家再思考一下,如果我把第一个printf打印对应的PID也打印出来,它应该跟哪一个一样呢?

在这里插入图片描述
在这里插入图片描述
🆗,它肯定跟前面那个相同,因为执行第一个打印的时候还没有执行fork()创建子进程呢。
那当然这里19559对应的肯定就是bash了

那对上面做一个简单的总结:

如果没有fork的话,那程序运行起来就只有一个进程,这个进程是bash的子进程,那就只有一个执行流,所以两个printf就都只打印一次;但是现在第一个打印后面有一个fork,它去创建了一个当前进程的子进程,所以就变成两个执行流,第二个printf就被打印了两次。

3.4 fork的返回值

接下来我们再来研究一个东西——fork的返回值:

在这里插入图片描述
从man手册上看fork的返回值也是一个pid_t类型,这个我们上面说了,就是一个有符号整数类型
但是我们不能只看一个类型,我们来看一下他返回的到底是什么:
在这里插入图片描述
翻译一下就是:
fork成功的话,在父进程中返回子进程的PID,在子进程中返回0。失败的话,-1在父进程中返回,不会创建任何子进程,并且正确设置了errno(C语言中一个用于表示错误码的全局变量,Linux内核时C语言写的)。
也就是说fork成功的话,返回值会有两个。

大家可能还不是特别理解,我们再写这样一个代码:

在这里插入图片描述
其实还是上面那个代码,我们接收一下fork的返回值保存到变量ret,并打印一下ret和&ret

我们运行一下看看结果:

在这里插入图片描述
大家先自己看一下这个结果。
然后这里再补充一下就是:
操作系统中,fork成功之后,父进程和子进程哪一个先运行完全是随机的,是不清楚的,因为fork成功创建子进程之后,父子进程谁先运行是取决于操作系统的调度策略
然后我们来分析一下这个结果:
那根据fork的返回结果这里第一次打印BBB…这个字符串调用printf的是父进程,后面打印调用printf的就是fork创建出来的子进程
那我们看到fork的两个返回值是不一样的,但是它们的地址&ret却是一样的。那这个问题呢我们现在还说不清楚,等到后面学进程地址空间的时候我们会再谈这个问题。

3.5 fork 之后通常要用 if 进行分流

fork 之后通常要用 if 进行分流,这样可以根据需要在父子进程中执行不同的操作。

所以我们一般要这样写:

在这里插入图片描述
通过ifelse语句让父子进程执行不同的操作

然后我们运行一下看看:

在这里插入图片描述
我们看到,父进程和子进程都是在执行的。
在这里插入图片描述
我们也能查看到当前是有两个myprocess进程的。
但是我们之前写的代码出现过if和elseif两个条件同时满足的吗?
并没有,但是这里if和elseif里面的语句都执行了,两个while循环同时在执行。
那为什么可以这样呢?
因为fork成功的话有两个返回值。
所以在多执行流的情况下if和elseif是可以同时执行的。

那简单总结一下上面的内容,可以得出一些结论:

fork成功之后,执行流会变成两个(父进程和子进程同时执行)
fork成功之后,父进程和子进程的执行顺序是不确定的,取决于操作系统的调度策略。
fork成功之后,父进程和子进程代码共享(我们上面fork之后父子进程都执行了第二个打印就可以证实这一点),通常我们要使用if语句进行代码块分流。

3.6 父子进程代码共享,数据写时拷贝(实现相互独立)

通过前面的学习,我们可以得出:

fork成功之后,父子进程是共享一份代码的。比如我们上面演示的fork之后父子进程都执行了同一句printf语句。

那我们再看这样的代码:

在这里插入图片描述
来运行一下
在这里插入图片描述

我们看到:

两个进程打印对应的x的值和x的地址都是一样的,所以我们可以暂且认为父子进程的数据也是共享的。

然后问大家一个问题:

在这里插入图片描述
就比如我们现在电脑上打开了这么多应用,那就对应了这么多的进程。
那如果现在我们把QQ退出了,会影响我的xshell吗。
这当然是不会的,凭我们平时的使用经验我们也知道。

所以呢:

程序的运行是具有独立性的!每个进程在执行时都相对独立,不会相互干扰或影响彼此的运行状态。
那同样的,对于父子进程也是这样,我们可以验证一下:
我们在再把这个程序跑起来在这里插入图片描述
我们看到现在父子进程是都在运行的,然后我们把子进程杀掉在这里插入图片描述
我们看到后面就只剩父进程在运行了,它们互相不会影响。

但是呢,有一个问题:

对于父子进程来说,按照我们上面的分析,父子进程共享一份代码和数据,那他是如何做到相互独立呢?
那首先对于代码来说,好像是没什么问题的。
虽然父子进程共享一份代码,但是可以实现独立啊,就算其中一个进程被干掉了,那代码还是在的啊(在程序运行时,代码段通常被视为只读的,以确保程序的完整性和安全性)。所以你不会影响我另一个进程的执行啊。
这没什么问题。
但是数据呢?
我一个进程在自己的执行流里执行代码的时候是可以修改代码里面的数据的(比如某个变量的值)
像这样
在这里插入图片描述
那我们运行一下看看
在这里插入图片描述
我们看到修改之后呢,它们打印的x的值确实是不一样了,但是我们看到两个x的地址依然是一样的。
那这里如何做到同一个变量地址相同但是值不同的,我们目前还不能解释,后面再说。

但是我们现在要说的是:

对于父子进程的数据,并不是真正的共享一份,而是写时拷贝
那写时拷贝的概念我们其实之前在C++里面string模拟实现那篇文章提到过。
其实就是只有在修改数据的时候才进行拷贝,然后修改你自己拷贝的数据,而不会影响原始数据。
这样就做到了在数据层面上也可以实现进程间的独立性

所以,可以理解为:

当子进程被创建时,起初操作系统只为其分配一个新的进程控制块(PCB),用于维护子进程的相关信息。
并不会立即复制父进程的整个地址空间,包括代码段和数据段。相反,父进程的地址空间会被标记为共享,并且只有在子进程或父进程试图修改共享数据时,才会进行写时拷贝。
这时,操作系统会将要修改的内存页复制到一个新的物理页中,然后对于的进程将修改后的数据写入这个新的页中,使得子进程和父进程的数据相互独立。

3.7 如何理解fork两个返回值的问题

首先大家来思考一个问题:一个函数将要return的时候,它完成的主体功能是否已经执行完了?

当然是的!
比如有一个求和的函数,那当它return的时候,这个和肯定已经求出来了,而return是要把这个结果返回给函数调用的地方。

那对于fork来说:

它是一个系统调用,那其实就是操作系统提供的一个函数嘛。
那在fork最后将要return的时候,那它的主体功能即创建子进程当然已经完成了。所以此时的执行流就已经变成两个了,上面我们也说了,fork成之后,父子进程是共享代码的。
那对于fork的return,他也是一句代码,一个语句啊。
所以这个return语句就会被子进程和子进程都执行,被执行了两次,而在我们看来就好像是fork返回了两个值。

那还有一个问题:

这里return执行了两次,所以返回了两个值,但是:
在这里插入图片描述
我们接收返回值只用了一个变量接收啊。
一个变量怎么同时接收两个值的?
很简单,ret第二次接收的时候,相当于要对数据进行修改。
那这时会发生什么?
🆗,上面说过了,这时就会发生写时拷贝
所以呢?
在这里插入图片描述
虽然我们看到这两个x的地址是一样的,但是其实它们是两个不同的变量,占用不同的存储空间。
那为什么地址看到的是一样的呢?
那其实这里我们看到的地址并不是底层真实的物理地址,那关于这方面的问题我们后面也会讲到,大家现在先了解一下就行了。

在这里插入图片描述

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

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

相关文章

目标检测算法改进系列之Neck添加渐近特征金字塔网络(AFPN模块)

渐近特征金字塔网络(AFPN模块) 在目标检测任务中,多尺度特征对具有尺度差异的目标进行编码具有重要意义。多尺度特征提取的常用策略是采用经典的自顶向下和自底向上的特征金字塔网络。 然而,这些方法存在特征信息丢失或退化的问…

十四、流式编程(2)

本章概要 中间操作 跟踪和调试流元素排序移除元素应用函数到元素在 map() 中组合流 中间操作 中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。 跟踪和调试 peek() 操作的目的是帮助调试。它允许你无修改地查看…

Maven3.6.1下载和详细配置

1.下载maven 说明:以下载maven3.6.1为例 1.1网址 Maven – Welcome to Apache Maven 1.2点击下载 1.3点击Maven 3 archives 1.4 点击相应的版本 1.5 点击binaries下载 说明:binaries是二进制的意思 1.6点击zip格式 1.7 蓝奏云获取 说明&#xff1a…

gateway之断言的使用详解

文章目录 gateway产生的背景,为什么要是用gateway什么是网关gateway 带来的好处功能特征gateway在项目中使用的依赖 什么是断言断言分类内置自定义示例 断言和过滤器的不同 gateway产生的背景,为什么要是用gateway 一个系统会被拆分为多个微服务&#x…

软考 -- 计算机学习(2)

文章目录 一、安全性知识1.1 信息安全和信息系统安全1.2 信息安全技术1.3 网络安全技术 二、多媒体技术三、软件工程基础知识3.1 信息系统生命周期3.2 软件过程模型3.3 信息系统开发方法3.4 系统分析和设计概述3.5 结构化开发方法3.6 系统运行与维护 四、项目管理4.1 进度管理4…

说说hashCode() 和 equals() 之间的关系?

每天一道面试题,陪你突击金九银十! 上一篇关于介绍Object类下的几种方法时面试题时,提到equals()和hashCode()方法可能引出关于“hashCode() 和 equals() 之间的关系?”的面试题,本篇来解析一下这道基础面试题。 先祭一…

ESP-IDF学习——1.环境安装与hello-world

ESP-IDF学习——1.环境安装与hello-world 0.前言一、环境搭建1.官方IDE工具2.vscode图形化配置 二、示例工程三、自定义工程四、点灯五、总结 0.前言 最近在学习freertos,但由于买的书还没到,所以先捣鼓捣鼓ESP-IDF,因为这个比Arduino更接近底…

『贪吃蛇』AI 算法简易实现(中秋特别版)

前言 一年一度的中秋节就快到了,平台也有各种各样的中秋发文活动,正在翻阅时偶然间我看到了这篇文章:《兔饼大作战》:吃月饼、见月亮,还能咬自己?| 欢庆中秋特制版 - 掘金 (juejin.cn) 大家肯定比较熟悉了…

python处理CSV文件

CSV库还有其他处理CSV的方法,这里只是介绍几个常用的,后面如果用到别的会进行更新 目录 1 生成一个新的csv文件,并向其中写一点东西 2 单纯往里面写几行 3 读取csv文件 1 生成一个新的csv文件,并向其中写一点东西 import…

Mybatis学习笔记11 缓存相关

Mybatis学习笔记10 高级映射及延迟加载_biubiubiu0706的博客-CSDN博客 缓存:cache 缓存的作用:通过减少IO的方式,来提高程序的执行效率 Mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库.一方面是减少了I…

vision transformer

一、网络构建 import torch from torch import nn from functools import partial# --------------------------------------- # # (1)patch embeddingimg_size224 : 输入图像的宽高 patch_size16 : 每个patch的宽高,也是卷积核的…

zabbix监控nginx

目录 一、实验准备 二、监控nginx 一、实验准备 zabbix-sever(192.168.115.4) zabbix-agent(192.168.115.5) 添加监控对象 二、监控nginx 安装NGINX在192.168.115.5上安装NGINX,开启status模块 yum -y install ep…

uniapp:OCR识别身份证上传原图失败,问题解决

1、上传普通图片成功 2、上传>4M | >5M图片失败检查&#xff1a;1、uni.uploadFile自身没有文件大小限制。然而&#xff0c;这仍然取决于你的应用程序所在的平台和存储空间容量。 2、上传照片后不在fail&#xff0c;在sucess 提交照片-3 {"data": "<h…

yolov5自动训练/预测-小白教程

文章目录 引言一、配置参数设置1、数据参数配置2、模型训练参数配置3、模型预测参数配置 二、一键训练/预测的sh介绍1、训练sh文件(train.sh)介绍2、预测sh文件(detect.sh)介绍 三、本文训练main代码解读1、训练main函数解读2、数据加工与参数替换 四、本文预测main代码解读1、…

单片机内存管理

源码说明 源码包含memory.h 和 memory.c 两个文件&#xff08;嵌入式C/C代码的“标配”&#xff09;&#xff0c;其源码中包含重要的注释。 memory.h文件包含结构体等定义&#xff0c;函数API申明等&#xff1b; memory.c文件是实现内存管理相关API函数的原型。 memory.h …

相机HAL

相机HAL 1、概览实现 HAL2、相机 HAL2.1 AIDL 相机 HAL2.2 相机 HAL3 功能2.3 Camera HAL1 概览 相机 HAL 相机 实现 HAL android12-release 1、概览实现 HAL HAL 位于 相机驱动程序 和 更高级别的 Android 框架 之间&#xff0c;它定义您必须实现的接口&#xff0c;以便应用…

城市管网污水监测方案,科技助力污水排放管理!

根据《国务院办公厅关于加强入河入海排污口监督管理工作的实施意见》各地要明确“水污染&#xff0c;谁治理”和政府兜底的原则&#xff0c;明确排污主体责任。根据排污口类型集中整治&#xff0c;划分主体。加大私设暗管借道排污的监察力度溯源主体责任。加强科技研发&#xf…

Java实现添加文字水印、图片水印功能实战

Java实现添加文字水印、图片水印功能实战 本文介绍java实现在图片上加文字水印的方法&#xff0c;水印可以是图片或者文字&#xff0c;操作方便。 java实现给图片添加水印实现步骤&#xff1a; 获取原图片对象信息&#xff08;本地图片或网络图片&#xff09;添加水印&#…

9月20日作业

时钟代码&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPainter> #include <QPaintEvent> #include <QTime> #include <QTimer> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class W…

初识canvas

对于一个前端人员来说&#xff0c;canvas是必须掌握的技能之一。如果你想像画画一样在浏览器上作画&#xff0c;那么canvas就可以做你的画布。 接下啦我们就以画画的标准来初步认识下canvas 1.画布 画画的第一步你得有一张画纸或者画布&#xff0c;canvas标签就是我们的画布…