【Linux】进程概念 PCB结构体 fork创建子进程

news2024/11/26 4:28:09

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:青果大战linux

总有光环在陨落,总有新星在闪烁


每日小感慨:

         最近很羡慕学校小登,感觉他们还刚上大学,有无穷的潜力与可能性,而我已经行将就木了


在学这节课前请先观看上节课冯诺依曼体系结构 & OS的概念

进程的概念 

或许有人在教材上发现了这样的定义:

一段正在运行的程序就是进程。

我承认他的话并没有错,但这只是进程的直观表现,想真正了解它我们得进入linux更深层次去看。

我们知道上节课学到OS是一款负责软硬件资源管理的软件,程序显然属于软件,那OS就要对其进行管理,我们上节课又学到管理的本质不是管理你本身(这个看的见摸得着的存在),而是对你的数据进行管理分析,然后做出决策。所以OS管理程序是必须要获取程序的数据(创建时间、创建路径,创建人等等)。在获取硬件信息时直接加载他的驱动就好了,可是驱动本身就是程序,所以在获取程序数据时,OS最后一定是从要和程序进行了交互,但上节课的冯诺依曼体系结构告诉我们,OS与外设上(即硬盘)的程序(即代码)打交道是要先把程序加载到内存才可以的。

与此同时,OS要去管理的程序显然不止一份,要管理如此多的程序要怎么办呢?

      具体过程就是先用结构体存储数据以便描述程序,接着依靠数据结构把多个结构体组织起来,完成管理操作。

我们称这个结构体为PCB结构体,在linux中PCB的名称是task_truct。这个结构体放了很多东西,

我们进一步思考OS的管理操作,

例如cpu的使用,cpu只有一个,但程序有很多,这就像你去医院看病要排队一样,程序想去cpu上执行自己也要排队,但是我们并不需要程序本身去排队(因为它里面没有指针无法有效管理),只需要让他对应的PCB去排队就好了,把他们对应的next指针连接起来,就得到了一份链表,然后依次向后遍历,到那个节点就让他去cpu运行一会。

 比如等待队列也是一个链表,他把要等待同一个设备(如键盘输出)的进程按照一定的顺序以链表指针的方式链接(在task_struck里加一个next指针即可),然后依次向后遍历,到那个节点就让他去接受键盘输出。注意我们比不需要新建一份PCB,只需要在PCB中放置对应链表就可以

即产生新的数据结构只需要在PCB加指针,而不需要重新拷贝一份PCB

       如此,OS先是通过task_struct描述出了所有程序,接着又根据具体需求加入不同指针来对他们进行管理,也就符合了“先描述,再组织”的核心。

这时我们就知道了,在数据层次上,

进程=task_struct(内核数据结构)+自身代码和数据

进程查看

ps指令

我们自己写一段程序让他运行试试。

我们写了一个死循环,接着每隔一秒输出一个“666”。

 运行起来后,结果符合逾期

       接着我们用ps -ajx可以查看进程的各种信息(等同于windows系统下的任务管理器),由于杂乱信息太多于是我们grep一下

显然上面的就是我们要找的myproc3进程,而grep要查找myproc3,所以自己也带有myproc3,于是也被选中了,放在下面。

第二列的数字是每个进程的PID,进程的唯一识别符

我们可以通过getpid的方式获取进程的PID,getpid是一个库函数,包含在<unistd.h>,他的返回值PID的类型是pid_t,本质是还是int,包含在<sys/types.h>

代码如下

 #include<iostream>
   #include <sys/types.h>
   #include <unistd.h>
   using namespace std;
   int main()
  {
      pid_t id = getpid();
  
      while(1)
     {
         sleep(1);
          cout<<"pid ="<<id<<endl;                                                                                                                                                                                                
      }
  }

我们通过ps查看该程序的PID,发现确实是7038

  • PPID指的是父进程,也就是创建该进程的进程

  • PID是该进程的唯一标识符

  • COMMAND是运行的可执行文件的名字

接下来我们可以通过getppid进行查找父进程的PPID

 #include<iostream>  
   #include <sys/types.h>  
   #include <unistd.h>  
   using namespace std;  
   int main()  
   {  
       pid_t id1 = getpid();  
         
       pid_t id2 = getppid();  
      while(1)  
      {  
          sleep(1);  
          cout<<"pid ="<<id1<<endl;  
          cout<<"ppid ="<<id2<<endl;                                                                                                                                                                                              
      }                                                                                                                                              
  } 

通过ps查找2698这个PID,我们发先它对应的可执行程序叫做bash 

这里先提出一个观点:

命令行调用的一切进程,都是bash的子进程

为什么?之后会讲


proc目录

我们在根目录下可以找到proc这个文件夹

接着我们打开根目录下的proc文件,我们也确实找到了2698和7650这个PID

接着我们打开7650,而这文件里装的就是该进程的各种信息

我们选个重点的讲一下

  1. cwd:表示当前进程的工作目录。它指向进程的当前目录,影响文件操作的相对路径。

  2. exe:表示进程执行的可执行文件的路径。

在了解这个后我们在看下面的代码

我们直到fopen的第一个参数可以是绝对路径,也可以是相对路径,绝对路径很好理解,可是相对路径呢?程序执行的时候怎么找到这个相对路径呢?就是依靠这个进程参数中的cwd


  #include<stdio.h>
   #include<unistd.h>
   #include<sys/types.h>
   int main(){
   pid_t p=getpid();
   FILE *f=fopen("test.txt","w");                                                                                                                                                                                                  
   if(f==NULL)                   
       printf("failure\n");
   while(1)                
     { printf("pid:%d\n",p);
          sleep(1);           
      }              
  return 0;          
  } 

根据上图发现这个文件也确实是在该进程被启动的目录下所创建的 

然后我们在kill -9 PID,直接删了该进程,再去查找这个文件夹就发现该文件不存在了

 说到这里大家就懂了,当程序(可执行文件)被启动时,OS会在proc下给他创建一个文件,里面存储他的种种信息,而当该文件结束时,这个文件会被删除。事实上,如果你在启动程序后直接关闭Xshell,当你在打开时会发现该程序自动结束了,而他在proc中的文件也被删除了。这其实说明了该文件掉电就丢失,即:proc文件是放在内存中的! 

为什么不方到硬盘呢?因为放硬盘的话

  • 加入程序意外停止(比如拔电源),那这份文件就会一直保存在硬盘中,可是下次再启动时会建立新的文件,所以他已经毫无用处了,那我们的磁盘空间就被浪费了。

  • 假如ps这些获取进程数据的指令启动了,那cpu就需要从磁盘取数据给他们用,而由于冯诺依曼结构,cpu又要先把数据加载到内存,那我们不如直接把该文件放到内存,省去了从硬盘到内存的时间消耗,至于空间,反正内粗现在都是8G,16G,正常状况下还是完全够的。

于是我们得出结论,在创建一个进程时,OS会把他的信息加载到内存文件中

小结,在启动程序时,OS先创建的ask_struct,然后去访问task_struct得到对应数据,填充给/proc创建对应的文件夹,当使用ps这种查找进程信息的指令,则会到对应的文件目录下进行查找。当程序关闭时,该文件会被删除。


 fork函数

 fork函数可以在进程内部创建一个子进程,他被包含在头文件<unistd.h>中,直接调用即可。

可是,fork的返回值很奇怪,他是这么介绍的

  • 如果创建失败,就给父进程返回-1(这没问题)
  • 如果创建成功,就给子进程返回0,给父进程返回子进程的PID

what?请注意,我们用的fork是一个函数,而现在他说“如果成功,会有两个返回值

 这怎么可能,匪夷所思。于是我们来测试一下

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
using namespace std;
int main(){
    pid_t p=fork();
    if(p>0)
    { pid_t p1=getpid();
    pid_t p2=getppid();
        cout<<"我是父进程:我的pid是"<<p1<<"我的父进程是"<<p2<<endl;
    }
    else if(p==0)
    { pid_t p1=getpid();
    pid_t p2=getppid();
        cout<<"我是子进程我的pid是"<<p1<<"我的父进程是"<<p2<<endl;
    }
    else 
        cout<<"创建失败"<<endl;
}

可以看到我们确实创建了两个进程,根据PID和PPID也确实是一父一子

但是,if语句和else if都跑了起来。

我们可以开始思考了,fork函数可以创建一个进程,我们现在知道了进程就是task_struct+代码和数据,taskstruct暂且不管,请问子进程的代码和数据怎么来呢,显然只能是来自一份父进程的代码和数据,我们有知道代码加载到内存后无法修改,所以父子进程可以都使用父进程的代码,所以只要让子进程的taskstruct里的指向代码的指针,指向父进程的代码即可 。可是数据不行,在代码运行时数据会不断变化,如果父子进程公用一份数据,那相互直接就会影响,于是子进程需要把父进程的数据拷贝一份,至于怎么拷贝,拷贝到哪里,我们之后再说。

于是我们就知道,fork函数会把他后面的代码和数据当作子进程的代码和数据

进一步思考,虽然我们还不知道fork怎么创建的子进程,但是我们可以粗略的将其分为两部分

第一:先创建子进程,第二:返回一个值。

显然返回值这条return语句是整个函数最后一条语句,此时子进程已经建立好了,所以return语句也是父子共享代码的一部分,而这里的返回值也是一份数据,是数据那子进程就要拷贝一份,所以fork有两个返回值的本质就是向原来的数据和这个拷贝的数据分别进行写入!于是乎,我们就理解了fork的双返回值实现原理。

为什么返回值一个是0,一个是子进程的PID呢

以为对于子进程来说,他想找到父进程直接getppid就可以,但是父进程想找到子进程无法直接调用函数找到,根本原因是一个儿子只有一个父亲,但一个父亲可以有多个儿子,所以为了让父进程知道自己创建的子进程是谁,就需要用返回值的形式告诉他。

总结

  1. 进程就是task_struct(内核数据结构)+代码和数据

  2. ps -ajx可以查看进程信息

  3. 进程的信息都放在/proc这个文件夹里,这个文件夹用的是内存空间,进程退出,其信息会被删除

  4. fork函数可以创建子进程,和父进程共用代码,拷贝一份父进程的数据

  5. 我们可以通过fork返回值分别控制父子进程,

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

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

相关文章

UDP/TCP协议详解

目录 一,自定义应用层协议: 1)xml 2),JSON 3),yml 4),google protobuffer 二,传输层UDP/TCP: UDP协议: TCP协议: TCP的核心机制一:确认应答 TCP核心机制二:超时重传 TCP核心机制三:连接管理 TCP核心机制四:滑动窗口 TCP核心机制五:流量控制 TCP核心机制六:拥塞控制…

c++ pdf文件提取txt文本示例

最近抽空采用之前封装的接口将pdf文件提取出txt文本&#xff0c;顺利完成&#xff0c;界面如下所示&#xff1a; 提起的效果如下所示&#xff1a; 输出的txt文本内容如下&#xff1a; 下载链接&#xff1a;https://download.csdn.net/download/u011269801/89905548

AI 3D拣选系统行业分析:物流行业是最主要的需求来源

AI 3D拣选系统是一种集成了先进传感技术、机器人技术和计算机视觉技术的自动化分拣解决方案。它能够在三维空间内快速、准确地识别和分拣各种形状、大小和材质的物品&#xff0c;大大提高了物流效率和准确性。该系统通过高精度的3D传感器和先进的视觉算法&#xff0c;能够实时捕…

【某农业大学计算机网络实验报告】实验五 TCP 运输连接管理

实验目的&#xff1a; 熟悉 TCP 通信的三个阶段&#xff1a;通过此次实验&#xff0c;结合理论课知识深入理解并熟悉 TCP 通信的三个主要阶段&#xff0c;即连接建立&#xff08;SYN-SYN&#xff09;&#xff0c;数据传输&#xff08;DATA&#xff09;&#xff0c;以及连接释放…

【论文速读】Prompt Tuning:The Power of Scale for Parameter-Effificient Prompt Tuning

arxiv&#xff1a;2104.08691v2 摘要 在这项工作中&#xff0c;我们探索了“prompt tuning&#xff08;提示调优&#xff09;”&#xff0c;这是一种简单而有效的机制&#xff0c;用于学习“soft prompts&#xff08;软提示&#xff09;”&#xff0c;以条件下冻结的语言模型…

Golang | Leetcode Golang题解之第485题最大连续1的个数

题目&#xff1a; 题解&#xff1a; func findMaxConsecutiveOnes(nums []int) (maxCnt int) {cnt : 0for _, v : range nums {if v 1 {cnt} else {maxCnt max(maxCnt, cnt)cnt 0}}maxCnt max(maxCnt, cnt)return }func max(a, b int) int {if a > b {return a}return …

矩阵matrix

点积 在 NumPy 中&#xff0c;dot 是矩阵或向量的点积&#xff08;dot product&#xff09;操作。 假设有两个向量a和 b&#xff0c;它们的点积定义为对应元素相乘&#xff0c;然后求和。公式如下&#xff1a; 例子&#xff1a; 点积的计算步骤是&#xff1a; 因此&#xf…

【动态规划】【路径问题】下降路经最小和、最小路径和、地下城游戏

4. 下降路径最小和 931. 下降路径最小和 算法原理 确定状态表示 dp[i][j] 表示&#xff1a;到达 [i, j] 位置&#xff0c;最小的下降路径 状态转移方程 dp[i][j] 从 [i-1, j-1] 到达 [i, j] > dp[i-1][j-1] m[i][j]从 [i-1, j] 到达 [i, j] > dp[i-1][j] m[i][j]从 …

leetcode_887_鸡蛋掉落___循序渐进的分析

分析&#xff1a;对于一组[n,k] 在一次尝试中选择了在dep层测试 其可以分为 如果在dep层炸了: 则变成了[dep-1,k-1]读作在dep-1层用k-1个鸡蛋来找鸡蛋的极限所需次数如果在dep层没炸: 则变成了[n-dep,k]读作在n-dep层用k个鸡蛋来找鸡蛋的极限所需次数可以发现这都是子问题的…

【Javaee】网络编程-TCP Socket

前言 前文中我们介绍了UDP Socket相关的构造方法和方法&#xff0c;并实现了UDP的回显服务器和客户端。 本篇将介绍TCP Socket&#xff0c;并使用TCP Socket api实现服务器和客户端的通信 一.TCP Socket的常见方法 1.ServerSocket ServerSocket是创建TCP服务端Socket的API …

线程池:高效管理并发任务的利器

线程池&#xff1a;高效管理并发任务的利器 什么是线程池&#xff1f; 线程池&#xff08;Thread Pool&#xff09;是Java并发编程中的一种设计模式&#xff0c;旨在通过重复利用线程资源&#xff0c;来提高程序执行效率。线程池的主要思想是提前创建一组可供使用的线程&#…

归一化输入

当输入的不同的特征取值范围差异过大&#xff0c;取得对应参数差别也会很大&#xff0c;在对参数进行优化的过程中&#xff0c;参数小的维度步长较小&#xff0c;参数大的维度步长较大&#xff0c;优化过程中路径曲折&#xff0c;将输入归一化&#xff0c;使特征取值范围差别小…

相控阵雷达电特性matlab模拟与仿真,带GUI界面,对比有限扫描阵,稀疏阵,多波束阵,共形阵等

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 相控阵雷达作为一种先进的雷达技术&#xff0c;具有高分辨率、多功能、快速扫描等优点&#xff0c;在军事和民用领域都有着广泛的应用。相控阵雷达的天线系统是其核心组成部分…

C#线性变换——缩放

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在C#开发中经常需要对平面中的坐标进行一些变换&#xff0c;比如缩放、旋转等&…

数据结构:二叉树、堆

目录 一.树的概念 二、二叉树 1.二叉树的概念 2.特殊类型的二叉树 3.二叉树的性质 4.二叉树存储的结构 三、堆 1.堆的概念 2.堆的实现 Heap.h Heap.c 一.树的概念 注意&#xff0c;树的同一层中不能有关联&#xff0c;否侧就不是树了&#xff0c;就变成图了&#xff…

PCL 点云配准 Trimed-ICP算法(精配准

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 perform_standard_icp 函数 2.1.2 perform_trimmed_icp 函数 2.1.3 visualize_registration 函数 2.2完整代码 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算…

国庆旅游高峰期,如何利用可视化报表来展现景区、游客及消费数据

国庆黄金周&#xff0c;作为国内旅游市场的年度盛宴&#xff0c;总是吸引着无数游客的目光。今年&#xff0c;随着旅游市场的强劲复苏&#xff0c;各大景区又再次迎来游客流量的高峰。全国国内出游7.65亿人次&#xff0c;同比增长5.9%&#xff0c;国内游客出游总花费7008.17亿元…

大型企业软件开发是什么样子的? - Web Dev Cody

引用自大型企业软件开发是什么样子的&#xff1f; - Web Dev Cody_哔哩哔哩_bilibili 一般来说 学技术的时候 我们会关注 开发语言特性 &#xff0c;各种高级语法糖&#xff0c;底层技术 但是很少有关注到企业里面的开发流程&#xff0c;本着以终为始&#xff08;以就业为导向…

界面控件Telerik UI for WPF 2024 Q3亮点 - 支持禁用数据过滤等

Telerik UI for WPF拥有超过100个控件来创建美观、高性能的桌面应用程序&#xff0c;同时还能快速构建企业级办公WPF应用程序。UI for WPF支持MVVM、触摸等&#xff0c;创建的应用程序可靠且结构良好&#xff0c;非常容易维护&#xff0c;其直观的API将无缝地集成Visual Studio…

[Linux网络编程]03-TCP协议

一.TCP协议数据通信的过程 TCP数据报如下&#xff0c;数据报中的标志位双端通信的关键。 三次握手: 1.客户端向服务端发送SYN标志位&#xff0c;请求建立连接&#xff0c;同时发送空包 2.服务端向客户端回发ACK标志位(即确认标志位&#xff0c;任何一端发送数据后都需要另一端…