Java多程序运行:同一进程下的实现

1.进程的定义

可以把一个运行的程序看成一个进程。

同一进程运行多个java 一个进程执行多个程序_子进程


   

  • 进程与程序的联系
    1)程序是产生进程的基础
    2)程序的每次运行构成不同的进程
    3)进程是程序功能的体现
    4)通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。


  • 进程与程序的区别
    1)进程是动态的,程序是静态的程序是有序代码的集合;进程是程序的执行,进程有核心态/用户态。
    2)进程是暂时的,程序是永久的 进程是一个状态变化的过程;程序可长久保存。
    3)进程与程序的组成不同进程的组成包括程序、数据和进程控制块。(即 进程状态信息)



2.进程的组成

进程包含了正在运行的一个程序的所有状态信息:程序的代码、程序处理的数据、程序计数器中的值(指示下一条将运行的指令)、一组通用的寄存器的当前值、一组系统资源(如打开的文件)。



3.进程的特点

1)动态性:可动态地创建、结束进程
2)并发性:进程可以被独立调度并占用处理机运行
3)独立性:不同进程的工作不相互影响 (页表 是 保证独立性的重要机制。)
4)制约性:因访问共享数据/资源或进程间同步而产生制约



4.进程控制块(PCB,Process Control Block)

进程控制块 PCB 是操作系统管理控制进程运行所用的信息集合,是进程存在的唯一标识,每个进程都在操作系统中有一个对应的PCB。进程终止的话,PCB 也应该随之被回收。

  • PCB 包含以下三大类信息


1)进程标识信息

本进程的标识、本进程的产生者标识(父进程标识);用户标识。


2)处理机状态信息保存区
保存进程的进行现场信息。
① 用户可见寄存器:用户程序可以使用的数据、地址等寄存器。
② 控制和状态寄存器,如 程序计数器 PC、程序状态字 PSW。
③ 栈指针。过程调用 / 系统调用 / 中断处理和返回时 需要用到它。

5.生命期原理

进程的生命周期管理:

  • 进程创建
    引入进程创建的情况
    1)系统初始化时,创建 init 进程
    2)用户请求创建一个新进程
    3)正在运行的进程执行了创建进程的系统调用
     

 

  • 进程运行
    内核选择一个就绪的进程,让它占用处理机并执行,即 从就绪态 变为 运行态。
       


  • 进程等待
    进程进入等待(阻塞)的情况:
    1)请求并等待系统服务,无法马上完成;
    2)启动某种操作,无法马上完成;
    3)需要的数据没有到达。()
    比如要读取硬盘上的文件,这个过程很慢,就可能发生等待。)
    进程只能自己阻塞自己。因为只有进程自身才能知道何时需要等待某种时间的发生。
       


  • 进程唤醒
    唤醒进程的情况:
    1)被阻塞进程需要的资源可被满足
    2)被阻塞进程等待的事件到达
    3)将该进程的 PCB 插入到就绪队列
    进程只能被别的进程 或 操作系统 唤醒。
       


  • 进程结束
    进程结束的情况:
    1)正常退出(自愿的)
    2)错误退出(自愿的,如 未完成某种功能)
    3)致命错误(强制性的)
    4)被其他进程所杀 kill (强制性的)

6.进程状态变化模型

不考虑生命结束的话,进程有三种基本状态:运行(Running ,当一个进程正在处理机上运行时)、就绪(Ready ,一个进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行 ) 和 等待(又称 阻塞 Blocked,一个进程等待某一时间而暂停运行时。如等待某资源;等待输入 / 输出完成)。
同一进程运行多个java 一个进程执行多个程序_父进程_02


如果考虑创建态 和 结束态:
同一进程运行多个java 一个进程执行多个程序_父进程_03

  • Null ➡ New :
    一个新进程被产生出来执行一个程序。
  • New ➡ Ready:
    当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态。
  • Ready ➡ Running:
    处于就绪状态的进程被进程调度程序选中后,就分配到处理机上来运行。
  • Running ➡ Exit:
    当进程表示它已经完成或者因出错,当前运行进程会由操作系统作结束处理。
  • Running ➡ Ready:
    处于运行状态的进程在其运行过程中,由于分配给它的处理机时间片用完而让出处理机。
  • Running ➡ Blocked:
    当进程请求某样东西且必须等待时。
  • Blocked ➡ Ready:
    当进程要等待某事件到来时,它从阻塞

7.进程挂起

进程挂起 意味着 进程没有占用内存空间。进程挂起 Suspend 是指 把一个进程从内存转到外存中。 之前说虚拟内存的时候,运行的程序可能把一部分空间导到硬盘上,腾出更多的空间给需要的程序使用。”运行的程序“ 就是指进程。

挂起有两种:
1)等待 / 阻塞挂起状态(Blocked-suspend):进程在外存并等待某事件的出现。
2)就绪挂起状态(Ready-suspend): 进程在外存,但只要进入内存,即可运行。
   


  • 与挂起有关的状态转换:

    (1) 等待 ➡ 等待挂起:
    没有进程处于就绪状态或就绪进程要求更多内存资源。
    (2) 就绪 ➡ 就绪挂起:
    当有高优先级等待(系统认为会很快就绪的)进程和低优先级就绪进程。
    (3) 运行 ➡ 就绪挂起:
    对抢先式分时系统,当有高优先级等待挂起进程因事件出现而进入就绪挂起。
  • 在外存的状态转换
    (1) 阻塞挂起 ➡ 就绪挂起:
    当有阻塞挂起进程 因 相关事件出现时,系统会把阻塞挂起进程转换为就绪挂起进程(资源都还是暂存在外存中的)。
    (2) 解挂 / 激活
    把一个进程从外存转到内存,可能有以下情况:
    ① 就绪挂起 ➡ 就绪:
    没有就绪进程 或 挂起就绪进程 优先级 高于 就绪进程时,会进行这种转换。
    ② 阻塞挂起 ➡ 阻塞:
    当一个进程释放足够内存时,系统会把一个高优先级阻塞挂起(系统认为会很快出现所等待的事件)进程转换为阻塞进程。

8.状态队列

由操作系统来维护一组队列,表示系统中所有进程的当前状态。不同队列表示不同状态,如就绪队列、阻塞队列、各种等待队列,根据进程状态不同,进程PCB加入相应队列,进程状态变化时,它所在的PCB会从一个队列换到另一个。由操作系统来维护一组队列,表示系统中所有进程的当前状态。

同一进程运行多个java 一个进程执行多个程序_寄存器_04

9. 线程管理

1)为什么要有线程

例子:
编写一个MP3播放软件。核心功能模块有三个:
① 从MP3音频文件当中读取数据
② 对数据进行解压缩
③ 把解压缩后的音频数据播放出来
单进程的实现方法:

main( )
{
 while(true)
  {
   //IO 读文件
   Read( );
   // CPU 解压
   Decompress( );
   //播放
   Play( );
     }
}
 Read( ) { … }
 Decompress( ) { … }
 Play( ) { … }
 
 

这样写的问题:播放出来的声音能否连贯?而且各个函数之间不是并发执行,影响资源的使用效率。

那就用多进程:

同一进程运行多个java 一个进程执行多个程序_同一进程运行多个java_05


但是这样写又有了新的问题:进程之间如何通信,共享数据?而且系统开销较大:创建进程、进程结束、进程切换。


  • 解决思路
    在进程内部增加一类实体,满足以下特性:
    1)实体之间可以并发执行
    2)实体之间共享相同的地址空间
    这种实体就是  。

2)线程的概念

描述指令流执行状态。它是进程中的指令执行流的最小单元,是CPU调度的基本单位。


  • 进程的资源分配角色:
    进程由一组相关资源构成,包括地址空间(代码段、数据段)、打开的文件等各种资源。


  • 线程的处理机调度角色:
    线程描述在进程资源环境中的指令流执行状态。


  • 线程的优点:
    1)一个进程中可以同时存在多个线程
    2)各个线程之间可以并发地执行
    3)各个线程之间可以共享地址空间和文件等资源


  • 线程的缺点:

一个线程崩溃,会导致其所属进程的所有线程崩溃 (因为共享啦)

使用浏览器,可以用线程的方式实现,但是这时候发现,某一个网页崩溃,整个浏览器都崩溃了,所以现在的浏览器很多都是采取进程的方式实现,因为性能不再是瓶颈,而安全性更重要(比如 Chrome 就是用进程打开网页)。
   

  • 不同操作系统对线程的支持
✨ 进程与线程的比较

1)进程是资源分配单位,线程是CPU调度单位
2)进程拥有一个完整的资源平台,而线程只独享指令流执行的必要资源,如寄存器和栈
3)线程具有就绪、等待和运行三种基本状态和状态间的转换关系

4)线程能减少并发执行的时间和空间开销

① 线程的创建时间比进程短(因为进程创建还需要创建其他管理信息等,而线程直接使用进程所属的进程的资源即可)

② 线程的终止时间比进程短

③ 同一进程内的线程切换时间比进程短(因为同一进程的线程具有同一个地址空间,即具有同一页表,而进程的切换还需要切换页表,切换页表的开销是比较大的,因为涉及到访问的地址不同、缓存信息不同、TLB信息等都需要重新加载。)

④ 由于同一进程的各线程间共享内存和文件资源,可不通过内核进行直接通信

3)线程的实现



① 用户线程:

同一进程运行多个java 一个进程执行多个程序_同一进程运行多个java_06

操作系统看不到的线程,(操作系统只能看到整个进程的信息,但是看不到其中的线程;而如果进程被操作系统设置为等待状态,那么进程中的所有线程就不能执行了)在用户空间实现。

由一组用户级的线程库函数来完成线程的管理,包括线程的创建、终止、同步和调度等。
   


  • 用户线程的特征
    不依赖于操作系统的内核:内核不了解用户线程的存在;可用于不支持线程的多进程操作系统。
    在用户空间实现的线程机制:每个进程有私有的线程控制块(TCB)列表;TCB由线程库函数维护。
    同一进程内的用户线程切换速度快:无需用户态/核心态切换。
    允许每个进程拥有自已的线程调度算法。


  • 用户线程的缺点
    线程发起系统调用而阻塞时,则整个进程进入等待。
    不支持基于线程的处理机抢占:除非当前运行线程主动放弃,否则它所在进程的其他线程无法抢占 CPU 。
    只能按进程分配CPU时间:多个线程进程中,每个线程的时间片较少。


② 内核线程:

由操作系统管理起来的线程,操作系统能看到的;

在内核中实现由内核通过系统调用实现的线程机制,由内核完成线程的创建、终止和管理。

同一进程运行多个java 一个进程执行多个程序_子进程_07

  • 内核线程的特征
    由内核维护PCB和TCB。
    线程执行系统调用而被阻塞不影响其他线程。
    线程的创建、终止和切换相对较大。
    以线程为单位进行CPU时间分配:多线程的进程可获得更多CPU时间。



用户线程和内核线程的对应关系:

  • 一对一
  • 多对一
  • 多对多


③轻量级进程:

在内核中实现,支持用户线程。

内核支持的用户线程。一个进程可有一个或多个轻量级进程,每个轻权进程由一个单独的内核线程来支持。(Solaris/Linux)
   

10. 上下文切换


  • 进程切换:

暂停当前运行进程,从运行状态变成其他状态,调度另一个进程从就绪状态变成运行状态。

同一进程运行多个java 一个进程执行多个程序_父进程_08

 

  • 进程切换的要求:
    切换前,保存进程上下文;切换后,恢复进程上下文;快速切换。
       
  • 需要存储的上下文:
    寄存器 (PC, SP, …);CPU状态;内存地址空间… …

哪些进程可以做切换?能够在 CPU 上执行的进程是放在就绪队列中的,是个链表。内核为每个进程维护了对应的进程控制块(PCB);内核将相同状态的进程的PCB放置在同一队列:

同一进程运行多个java 一个进程执行多个程序_父进程_09



11.进程控制创建进程

  • Unix进程创建系统调用: fork/exec


1)fork() 创建一个继承的子进程复制父进程的所有变量和内存;复制父进程的所有CPU寄存器(有一个寄存器例外)。 fork()执行过程对于子进程而言,是在调用时间对父进程地址空间的一次复制。


  • 返回值
    子进程的fork()返回0;父进程的fork()返回子进程标识符;fork() 返回值可方便后续使用,子进程可使用 getpid() 获取 PID。


  • 实现开销
    对子进程分配内存;复制父进程的内存和CPU寄存器到子进程里。在99%的情况里,我们在调用 fork() 之后调用 exec(),exec 会去加载新的程序,那么 fork 之前复制的就没有用了,会被覆盖掉,所以需要优化:
  • 法一:vfork() ,创建进程时,不再创建一个同样的内存映像,一些时候称为轻量级fork() ,只是完成了一小部分的复制,子进程应该几乎立即调用 exec()。
    法二:通过虚拟内存管理,Copy on Write (COW) 写时复制 技术,当父进程创建子进程时,并不是把整个地址空间真实地复制,只是复制父进程地址空间所需要的元数据,即 页表,当父进程或子进程对某个地址单元进行写操作时,会触发异常,使父进程或子进程需要把异常的页复制两份,使得父进程和子进程分别有不同的地址。可以实现 ”按需写“。

(2)parent (old PID), child (new PID)
(3)exec()用新程序来重写当前进程 / 让当前进程执行新的程序
PID 没有改变。
用 fork 和 exec 创建进程的示例:


// 创建子进程
int pid = fork();		
if(pid == 0) 
  {		
     // 子进程在这里继续	
     // Do anything (unmap memory, close net connections…)
     //系统调用 exec() 加载程序取代当前运行的程序
	exec(“program”, argc, argv0, argv1, …);
	
	//正常来说,执行了 exec 后,子进程的地址空间里的代码段被覆盖,接下来这句 printf...应该是被覆盖了的,不会执行到的
	printf("Why would I execute?")
   }
else
   {  
      if(pid>0)
      {
         //父进程在这里继续
         pringtf("Whose your daddy?");
         ...
         //等待子进程结束wait,返回说明子进程结束
         child_status=wait(pid);
      }
      else 
         //如果 pid<0,说明是失败的系统调用
         pringtf("error occurred");
   }
   
   
  • Windows进程创建API: CreateProcess(filename)
  • 1)创建时关闭所有在子进程里的文件描述符

CreateProcess(filename, CLOSE_FD)


2)创建时改变子进程的环境

CreateProcess(filename, CLOSE_FD, new_envp)

12.等待和终止线程

为什么要有 wait()?父进程为什么需要等子进程结束呢?(子进程直接 exit 退出不就行了嘛?)问题在于,调用了 exit() 后,进程所需要的资源是否都会被回收到操作系统中呢?有一个资源很难回收——就是在操作系统内部 代表进程存在的 ”进程控制块“。当子进程执行 exit() 后,会通过操作系统通知父线程,如果这时父进程在执行 wait() ,那么父进程就可以帮助子进程完成资源的释放,主要是 PCB 资源。
当子进程执行过 exit() 且 父进程还没有执行完 wait() ,子进程并没有释放,也不属于就绪 / 等待 / 运行态,它属于 僵尸态?【要死还没有死】。
如果父进程先于子进程死亡,那子进程不会再有一个进程在 wait 它,那这个子进程的 PCB 该由谁回收呢? 最早的进程称为祖宗进程,祖宗进程会定期扫描进程控制块链表,代替僵尸状态的进程的父进程完成回收。

               

免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空