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

Linux系统 —— 进程系列 - 进程的概念,PCB与PID和fork

目录

1. 进程的基本概念与基本操作

1.1 总结:什么才是进程

2. 描述进程-PCB(process control block)

2.1  task_ struct

2.2 task_ struct内容分类

2.3 组织进程

3. PID 

获取当前进程PID - getpid

获取父进程PID - getppid

如何查看进程

1 进程的信息可以通过 /proc 系统⽂件夹查看

2 我们还可以使⽤top和ps这些⼯具来获取

4. fork - 创建子进程

4.1 为什么fork要给父进程返回子进程的pid, 给子进程返回0呢?

4.2 fork函数为什么会返回两次?

总结:


1. 进程的基本概念与基本操作

课本概念:程序的⼀个执⾏实例,正在执⾏的程序等

   
内核观点:担当分配系统资源(CPU时间,内存)的实体

但是,上面的内容对于我们初学者来说是非常难以理解的,所以我们可以画图来进行理解 

一个程序运行起来叫做进程,未运行起来就是一个二进制文件,存储在磁盘当中,程序运行之前要加载到内存,在操作上就是(./cmd) ,而真正意义当我们执行时cmd就会加载到内存当中去

  

 
这里我们是把一个程序加载到内存,有没有可能我们在操作系统里,在同一时刻我们可以把成百上千给程序加载到内存里呢?答案是一定的

  

而有一款软件是最开始就加载进来了,叫做操作系统

  

  

如上图,我们可以看到在内存中,有很多加载的程序,那么我们就有一个问题:这些程序在内存中的什么位置被加载?代码和数据是否已经被cpu已经执行完毕呢?有没有可能其中的程序需要暂停运行?有没有可能其中内存不够了,需要扩容呢?

  

所以在内存中有这么多加载的内存,它们都需要申请内存和加载内存,所以操作系统要对这些进程,这些代码进行管理,但是我们可以做管理吗?答案是不能,因为操作系统虽然知道里面有代码,但是操作系统并不知道是那个进程

  

但是我们有没有解决方法呢?答案是先描述,再组织

  

     

  

操作系统会在操作系统内部(内核)给每一个代码和数据构建一个struct结构体,然后每加载进来一个程序,操作系统就为该程序创建一个同类型的对象,然后把当前这个进程按照创造的对象填写好信息,填好之后就有了一个对应的节点,而这里每一个节点都有对应的指针可以指向对应的代码和数据

  

与此同时,我们对应的每个节点当中,它的指针还可以指向它的下一个节点,最终在操作系统内我们形成了一个程序列表,我们把这个程序列表叫做进程列表

  

1.1 总结:什么才是进程

  ​​​​​​


2. 描述进程-PCB(process control block)

其实我们上面已经见过PCB了,就是我们上面创建的结构体,在操作系统中所有表示进程的都叫PCB,而这个结构体叫做进程控制块

  

  

进程控制块里包含进程的所有属性,进程的所有属性都可以直接或者间接通过 task_ struct找到

    

进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合

  

所以为什么一个进程加载的时候操作系统要给它创建一个对应的PCB即task_ struct对象呢?

      

因为操作系统要管理进程,所以要先描述,再组织,所以必须要有描述进程的task_ struct再组织管理成数据结构,操作系统就会转化成对应数据结构的增删查改

  

操作系统要对进程做管理,那么就必须要对进程的PCB做管理


2.1  task_ struct

Linux中进程控制块PCB-------task_struct结构体结构 - 童嫣 - 博客园icon-default.png?t=O83Ahttps://www.cnblogs.com/tongyan2/p/5544887.html

1. 在Linux中描述进程的结构体叫做task_struct

   

2. task_struct是Linux内核的⼀种数据结构,它会被装载到RAM(内存)⾥并且包含着进程的信息


2.2 task_ struct内容分类

   
1. 标示符: 描述本进程的唯⼀标⽰符,用来和其他进程进行区别

    
2. 状态: 任务状态,退出代码,退出信号等

    
3. 优先级: 相对于其他进程的优先级

   
4. 程序计数器: 程序中即将被执⾏的下⼀条指令的地址

   
5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

    
6. 上下文数据: 进程执⾏时处理器的寄存器中的数据[休学例⼦,要加图CPU,寄存器]

    
7. I/O状态信息: 包括显⽰的I/O请求,分配给进程的I∕O设备和被进程使⽤的⽂件列表

    
8. 记账信息: 可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等

    
9. 其他信息


2.3 组织进程

所有运⾏在系统⾥的进程都以task_struct链表的形式存在内核里

  

在linux内核中, 最基本的组织进程task_struct的方式, 是采用双向链表进行组织的


3. PID 

获取当前进程PID - getpid

   

进程创建的时候, 里面都有一个自己的PID,我们如何在一个程序运行时获取这个程序的PID呢?

如图所示,操作系统里面上层是系统调用, 下层是内存缓冲区, 这个时候内存中已经缓存了两个进程,PCB对象里面含有PID,我们可以使用ps axj和管道来获取进程的PID

  

但是我们都知道,操作系统不相信我们用户, 所以我们就不能直接访问PCB(task_struct)也就是里面的PID, 状态等, 想要获取这些字段就必须使用系统调用接口

  

如果我们想要获取当前进程的PID的系统调用接口的话,我们就要使用getpid(), 这个函数在哪个进程里被调用, 就会返回哪个进程的PID

   

注意:pid是一个整形, 下面是我们自己定义的一个获取系统调用接口的程序

当前进程为: 


获取父进程PID - getppid

对上面的概念进行试验之后, 我们再来看一下父进程, 也就是PPID 

我们发现,父进程ppid都是不改变的,而pid每次都是变化的,这是为什么呢?

  

我们联想到王婆的例子

  

  

王婆不想给自己的牌子砸了,所以就找了些实习生去解决,不需要王婆亲自去解决了,派实习生去解决,所以我们的-bash就是王婆(父进程)

  

1.运行一个进程时,系统会自动创建bash进程

   
2.命令行再执行所有的程序或者指令时,它所对应的进程,所对应的父进程就是bash本身。我们自己执行的程序或者指令都是bash进程的子进程

   
3.执行出问题的时候,只会是子进程出问题,不会影响bash进程

   
4.我们启动xshell时候,系统自动生成bash进程,显示命令行

如何查看进程

1 进程的信息可以通过 /proc 系统⽂件夹查看

   
如:要获取PID为1的进程信息,你需要查看 /proc/1 这个⽂件夹

proc文件夹里面的目录都是临时文件,当进程开始就会创建一个以这个进程的pid作为名字的文件夹,进程结束的时候就会删除这个文件夹  

我们发现: 这些进程都是目录, 并且这些目录的名字都是数字 

我们可以通过proc文件来查看这个进程更详细的信息

 


2 我们还可以使⽤top和ps这些⼯具来获取

 ps:

top:

在命令行中,执行命令/执行程序,本质是bash的进程,创建子进程,来执行我们的代码


4. fork - 创建子进程

fork是一个系统调用,fork没有参数,有两个返回值

   
父子进程代码共享,数据各⾃开辟空间,私有⼀份(采用写时拷贝)

  

fork函数的本质就是是一个系统调用

上面那张图意思就是说如果fork函数成功了, 那么给父进程返回子进程的pid, 0返回给子进程。 如果失败了, 就返回 -1 给父进程。 并且没有子进程被创建

   

也就是说, fork有两个返回值, 并且这两个返回值的类型都是pid_t, 也就是有符号整形

 运行结果:

我们可以看到每一秒打印一条父进程, 打印一条子进程,这说明父进程和子进程是同时进行的,并且id > 0,   和 id == 0同时成立, 如果在其他的代码中, 这两种情况不可能同时存在,但是在调用的fork下就可以

  

所以在我们fork之后所有的代码都是共享的,只不过父进程认为自己>0,子进程认为自己=0,所以父进程会进入第三个,子进程会进入第二个,所以我们就可以做到父子执行不同的代码块

  


到了这里,我们有几个问题


4.1 为什么fork要给父进程返回子进程的pid, 给子进程返回0呢?

在我们的操作系统里,我们的父进程比上子进程是1:N的,简单来说就是任何一个父进程可以有一个或者多个子进程

   

所以我们在创建子进程时,一定要把子进程的pid返回给父进程,因为父进程需要通过返回的不同的pid来区分不同的子进程,而子进程不需要获得父进程的pid,因为子进程已经能够获得getppid了,所以子进程只需要表明自己成功建立就可以了

  


4.2 fork函数为什么会返回两次?

在这个问题之前我们先讨论另一个问题:一个函数执行到return时,函数的核心功能做完了吗?答案是核心功能已经完成了

  

fork函数的本质就是是一个系统调用

 

如图所示:fork函数创建子进程后, 函数后面的代码就会被子进程和父进程所共享

    

当fork函数里面创建好子进程后(绿色方框部分) 子进程就被创建出来了, 然后执行流就变成了两个(可以使用if或者else或者else if来进行分流), 一个子进程的执行流, 一个父进程的执行流

   

也就是说, 这个时候的return语句, 其实就是由两个执行流会执行它,因为return也是语句,所以会被执行两次,而这两个执行流都会返回一个值,所以这就是为什么fork会有两个返回值, 并且返回值给一个给子进程, 一个给父进程的原因


总结:

  

1. 进程具有独立性,简单来说就是一个进程挂掉了并不会影响其他进程,哪怕是父进程挂掉了也不会影响子进程

  

如果子进程和父进程公用一个数据块, 当子进程改变数据的时候, 父进程也会改变数据,所以 不能让父进程和子进程共享一份数据

   

对于子进程来说数据是独立的,所以当创建子进程的时候要拷贝一份父进程的数据独立出来

  

这个时候父进程崩溃或者子进程崩溃都不会影响对方

 


未完待续~


http://www.kler.cn/a/428103.html

相关文章:

  • ping命令详解Type 8和0 或者Type 3
  • 基于Springboot的智能学习平台系统【附源码】
  • Unity 粒子特效在UI中使用裁剪效果
  • 【云安全】云原生-K8S-搭建/安装/部署
  • 机器人基础深度学习基础
  • [MySQL]事务的理论、属性与常见操作
  • Redis与缓存
  • 如何解决 docker 容器中 “-bash: ping: command not found” 错误 ?
  • vue3父子组件通信
  • Asp.net Mvc在VSCore中如何将增删改查的增改添加数据传输到页面(需配合上一篇Mvc的增删改查一起)
  • IDEA社区版创建新模块时,无Spring Initializr选项
  • ES-DSL查询
  • 如何通过自学成长为一名后端开发工程师?
  • 【在Linux世界中追寻伟大的One Piece】HTTP Session
  • 运维工程师.云计算工程师指令集锦
  • Kubernetes架构原则和对象设计(二)
  • 利用ipmi工具设置ip、用户等设置
  • TCP/UDP
  • 如何利用Java爬虫获得商品类目
  • matlab finv()函数解释 F分布 和 逆累积分布函数 卡方分布
  • 彻底理解ThreadLocal的应用场景和底层实现
  • C++多态性
  • 项目页面渲染学习总结
  • 【JavaWeb后端学习笔记】Spring全局异常处理器
  • 【论文笔记】Compact Language Models via Pruning and Knowledge Distillation
  • R155 VTA 认证对汽车入侵检测系统(IDS)合规要求