当前位置: 首页 > news >正文

Linux-进程概念(3)

文章目录

  • 补充两个小知识
  • 研究父进程与子进程的关系
  • 代码创建子进程的方式
    • 1. fork 创建子进程
    • 2. fork 的返回值
  • 三个问题
    • 1. 为什么 fork 给父子返回各自不同的返回值?
    • 2. 为什么一个函数会返回两次
    • 3. 为什么一个变量,即等于0,又大于0?导致 if else 同时成立

补充两个小知识

在进程运行时,可以通过命令:ls -l /proc/进程ID,查看进程的相关信息。其中需要关注两个,一个是exe它指向的是进程对应的可执行文件),另外一个是cwdcurrent work dir,它指向的是当前工作路径

在这里插入图片描述

之前学习C语言时,用fopen函数打开文件有两种方式,一种是:fopen("a/b/c/d.txt", "w"),另外一种是:fopen("d.txt", "w")。那这两种打开方式有什么区别呢?

第一种打开方式,它是以绝对路径的方式找到指定文件,对文件进行各种操作
第二种打开方式,因为进程它会记录下来自己的当前路径,遇到这种打开方式后,就在当前路径下创建一个文件名为d.txt的文件,对该文件进行各种操作

这个当前路径就是上述cwd所指向的current work dir,可以通过代码来验证一下。只要在当前路径下出现了hello.txt文件,就没问题

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{fopen("hello.txt", "a");while (1){sleep(1);printf("我是一个进程!我的pid: %d\n", getpid());}return 0;
}

在这里插入图片描述

之前不是提到,在该进程运行时将可执行文件删除,进程还会继续跑吗。但是此刻去查进程属性,exe所指向的地方就会飘红,因为可执行文件被删除,系统找不到了

在这里插入图片描述

另外再分享一个系统调用:chdir,可以通过手册(man 2 chdir)进行查找,它能在当前进程启动时,将进程的当前路径改变为其传递的参数

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{chdir("/home/xiao");fopen("hello.txt", "a");while (1){sleep(1);printf("我是一个进程!我的pid: %d\n", getpid());}return 0;
}

在这里插入图片描述

研究父进程与子进程的关系

Linux系统里所有的进程都是被它的父进程创建的,Linux系统是一个单亲繁殖的系统,没有母进程,那么子进程由父进程创建

那么我们自己的程序是一个进程,它要有它自己的父进程,当然父进程也有他的一个父进程,一个父进程可能创建多个子进程,所以Linux当中所有的进程可以看成一棵进程树,从一号进程开始,然后依次有各种子进程,是一棵多叉树,每一个节点都是一个进程

一个进程是通过父进程创建的,父进程怎么创建子进程现在不知道,但我能不能先获取一下我的父进程呢?可以通过系统调用getppid来获取父进程ID

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{while (1){sleep(1);printf("我是一个进程!我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}return 0;
}

在这里插入图片描述

其实上面的父进程bash就是一个命令行解释器,它本身就是一个进程。而OS会给每一个登录用户,都分配一个bash,可以这么理解[xiao@hcss-ecs-28ce dir_PCB]$,它就是bashprintf打印出的字符串,等待着你输入命令,就是bashscanf等待读取你输入的内容

代码创建子进程的方式

1. fork 创建子进程

可以通过系统调用fork去创建一个子进程,下面那段代码可以尝试着去猜一下运行结果

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{printf("父进程开始运行,pid: %d\n", getpid());fork();printf("进程开始运行,pid: %d\n", getpid());return 0;
}

在这里插入图片描述

通过上面结果,能直观看出来的一点是,该程序的进程(父进程)执行了整段代码,两个printf都进行了打印,而fork()创建子进程后,子进程估摸着只执行了后一个printf

可以这么去理解,进程=PCB(task struct) + 自己的代码和数据!那子进程它压根就没有自己的代码和数据,它是被fork出来的,目前根本就没有程序加载

所以可以得出一个结论,该子进程和父进程共享一套代码和数据,子进程会执行fork之后的代码,而子进程的PCB大部分的内容都是拷贝父进程的

在这里插入图片描述

2. fork 的返回值

可以使用man手册查看fork的返回值,可以输入:/return value

在这里插入图片描述

成功时,父进程中返回子进程的PID(进程标识符),子进程中返回0。失败时,父进程中返回-1,不会创建子进程,并且errno会被适当地设置

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{printf("父进程开始运行,pid: %d\n", getpid());pid_t id = fork();if (id < 0){perror("fork fail");return 1;}else if (id == 0){// child,给子进程留的while (1){sleep(1);printf("我是一个子进程!我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}}else {// parent,给父进程留的while (1){sleep(1);printf("我是一个父进程!我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}}return 0;
}

父进程fork返回的id值为子进程的PID,所以会走else语句。其次该父进程会去创建一个子进程,子进程会执行fork后的代码,而对于子进程来说,子进程fork返回的id值为0,会去执行else if语句

这段代码对于父进程和子进程都是共享的,只不过会因为fork的返回值不同而执行不同的逻辑

在这里插入图片描述

三个问题

1. 为什么 fork 给父子返回各自不同的返回值?

实际上在我们的系统里,对于父进程和子进程,他们两个的比例是1:n,任何一个父进程可以有一个或多个孩子或者零个孩子,任何一个子进程它就只有一个父进程,所以一定要把子进程的PID返回给父进程,为什么?

因为父进程要通过不同的PID来区分它不同的子进程,而子进程就不需要获得父进程的PID。因为可以通过getppid函数获得父进程的PID,所以子进程只要表明自己是否成功建立就行了,所以他不需要标识父亲的PID。而父亲可能因为有多个孩子,所以必须拿到每一个孩子的PID,未来方便对自己的工作进行管理

2. 为什么一个函数会返回两次

在这里插入图片描述
我是这么理解的,父进程执行了fork函数后,创建了子进程,子进程甚至被调度了。此时父进程的fork函数内部走到了return id语句,而子进程被调度后也会执行后面的代码,同样会走到fork函数内部的return id语句,那这条语句不就执行了两遍,返回了两次

返回两次的身份不同,一个是父进程,返回的是子进程的PID。另外一个是子进程,返回的是0(创建子进程成功的情形下)→返回值不同,最后有阐述

3. 为什么一个变量,即等于0,又大于0?导致 if else 同时成立

这里会牵扯到虚拟地址空间,所以只阐述一半原因。先展示代码和结果,再进行分析 + 说明

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int gval = 100;int main()
{printf("父进程开始运行,pid: %d\n", getpid());pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){printf("我是一个子进程 !, 我的pid: %d, 我的父进程id: %d, gval: %d\n", getpid(), getppid(), gval);sleep(5);// childwhile(1){sleep(1);printf("子进程修改变量: %d->%d", gval, gval+10);gval+=10; // 修改printf("我是一个子进程 !, 我的pid: %d, 我的父进程id: %d\n", getpid(), getppid());}}else{//fatherwhile(1){sleep(1);printf("我是一个父进程 !, 我的pid: %d, 我的父进程id: %d, gval: %d\n", getpid(), getppid(), gval);}}printf("进程开始运行,pid: %d\n", getpid());}

在这里插入图片描述

其实归根结底要讨论清楚,子进程和父进程的全局变量gval为啥会显示出两个不同的值

下面先说一个结论,这是理解第三个问题前半部分的一个必要因素。先举个例子,今天我在运行抖音时,抖音直接挂掉了,那会不会影响我刚刚启动的微信?并不会

我想说的是,每一款软件启动的时候都是进程,但你会发现在现实生活中,如果一款进程挂掉了,并不会影响另一个进程。由此就能得出一个结论:进程具有独立性

那进程存在PCB,可是PCB独立性怎么体现的?这个父进程的PCB,子进程的PCB,他们的内核数据结构(PCB),你是你,我是我,所以你父进程挂掉,跟我子进程没关系

而且共享的代码是只读的,没有任何父进程与子进程能修改。那么父进程和子进程在执行代码中,各自执行各自的,两者也是互不影响的

可是数据呢?父进程有一个quit全局变量,进程要根据这个全局变量来判定父进程是否退出。如果我们子进程看到了父进程的数据,子进程更改父进程的quit的变量,把quit由真改成假,那不就影响到父进程了吗?

这样的话,进程的独立性就不能保证了。所以父子在数据层面上默认是共享的,但一旦有任何一个人想尝试去修改对应的数据,也就是说不管是父进程还是子进程,只要有任何一个人尝试把quit这个变量修改掉,那么操作系统自动就会把这个变量在底层给你拷贝一份,让目标进程去修改这个拷贝变量

比如子进程要写,父进程依旧访问老的,子进程要访问新的,这种技术叫写时拷贝

在这里插入图片描述
父进程与子进程哪个先return,谁就先修改这个id变量,所以底层发生了写时拷贝之后,父与子就拿到了不同的变量
在这里插入图片描述

http://www.dtcms.com/a/266805.html

相关文章:

  • 在HP暗影精灵Ubuntu20.04上修复IntelAX211Wi-Fi不可用的全过程记录——系统安装以后没有WIFI图标无法使用无线网
  • RabbitMQ 高级特性之 TTL
  • Spring Boot 应用启动时,端口 8080 已被其他进程占用,怎么办
  • 物联网中的Unity/Unreal引擎集成:数字孪生与可视化控制
  • 【Spring Boot】HikariCP 与 Druid 连接池全面对比
  • OpenCV中超分辨率(Super Resolution)模块类cv::dnn_superres::DnnSuperResImpl
  • 数字工厂的核心引擎:物联网驱动生产智能化升级
  • 前端查询条件加密传输方案(SM2加解密)
  • Flink SQLServer CDC 环境配置与验证
  • vue3 el-table 行筛选 设置为单选
  • Oreacle(SQL语言基础)
  • 【问题解决】VSCode终端中看不到Git-Bash
  • XILINX Kintex 7系列FPGA的全局时钟缓冲器(BUFG)和区域时钟缓冲器(BUFR/BUFH)的区别
  • 【PyTorch】PyTorch预训练模型缓存位置迁移,也可拓展应用于其他文件的迁移
  • HTTP协议利用TCP的特性来实现长连接
  • Compose笔记(三十)--图片选择器
  • 【Spring Boot】HikariCP 连接池 YAML 配置详解
  • 洛谷P1941 [NOIP 2014 提高组] 飞扬的小鸟
  • vue3 获取选中的el-table行数据
  • MySQL 查询进阶指南:子查询、多表连接与 UNION 操作全解析
  • SQL 快速参考手册-SQL001
  • Swagger 安装使用教程
  • 高效的在Vue3中使用Vuex
  • Android-自定义View的实战学习总结
  • python训练day49 CBAM
  • 流程分类框架体系设计应该梳理到L5还是L6?
  • DePIN 普惠结构的缺失拼图,为什么是 UBI Network?
  • js中的捕获阶段和冒泡阶段
  • vue2/3安装依赖报错,终极解决方案
  • Kuberrnetes 服务发布