【操作系统】进程和线程是什么之间是如何通信的

1、进程

1.1、什么是进程

进程(process) 是计算机中正在运行的程序的实例,是操作系统资源分配的最小单位。在操作系统中,每个进程都有自己的地址空间、内存、数据栈和其他资源,它们与其他进程相互隔离。进程可以是应用程序、系统服务或操作系统本身。操作系统通过管理进程来协调多任务处理,使多个程序能够同时运行,并保证它们之间不会发生冲突或干扰。进程通信需要通过进程间通信机制(IPC)来实现。

1.2、进程的状态

在一个进程的活动期间至少具备三种基本状态,即运行状态、就绪状态、阻塞状态。

进程的三种基本状态

上图中各个状态的意义:

  • 运行状态(Running):该时刻进程占用 CPU;
  • 就绪状态(Ready):可运行,由于其他进程处于运行状态而暂时停止运行;
  • 阻塞状态(Blocked):该进程正在等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行,这时,即使给它CPU控制权,它也无法运行;

当然,进程还有另外两个基本状态:

  • 创建状态(new):进程正在被创建时的状态;
  • 结束状态(Exit):进程正在从系统中消失时的状态;

于是,一个完整的进程状态的变迁如下图:

进程状态变迁图

进程的状态变迁如下:

  • NULL -> 创建状态:一个新进程被创建时的第一个状态;
  • 创建状态 -> 就绪状态:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态,这个过程是很快的;
  • 就绪态 -> 运行状态:处于就绪状态的进程被操作系统的进程调度器选中后,就分配给 CPU 正式运行该进程;
  • 运行状态 -> 结束状态:当进程已经运行完成或出错时,会被操作系统作结束状态处理;
  • 运行状态 -> 就绪状态:处于运行状态的进程在运行过程中,由于分配给它的运行时间片用完,操作系统会把该进程变为就绪态,接着从就绪态选中另外一个进程运行;
  • 运行状态 -> 阻塞状态:当进程请求某个事件且必须等待时,例如请求 I/O 事件;
  • 阻塞状态 -> 就绪状态:当进程要等待的事件完成时,它从阻塞状态变到就绪状态;

1.3、进程的控制结构

在操作系统中,是用进程控制块process control block,PCB)数据结构来描述进程的,PCB是进程存在的唯一标识,这意味着一个进程的存在,必然会有一个 PCB,如果进程消失了,那么 PCB 也会随之消失。

在PCB中主要包含以下信息:

进程描述信息:

  • 进程标识符:标识各个进程,每个进程都有一个并且唯一的标识符;
  • 用户标识符:进程归属的用户,用户标识符主要为共享和保护服务;

进程控制和管理信息:

  • 进程当前状态,如 new、ready、running、waiting 或 blocked 等;
  • 进程优先级:进程抢占 CPU 时的优先级;

资源分配清单:

  • 有关内存地址空间或虚拟地址空间的信息,所打开文件的列表和所使用的 I/O 设备信息。

CPU 相关信息:

  • CPU 中各个寄存器的值,当进程被切换时,CPU 的状态信息都会被保存在相应的 PCB 中,以便进程重新执行时,能从断点处继续执行。

那么每个PCB是如何组织的呢?

通常是通过链表的方式进行组织,把具有相同状态的进程链在一起,组成各种队列。比如:

  • 所有处于就绪状态的进程链在一起,称为就绪队列
  • 所有因等待某事件而处于等待状态的进程链在一起就组成各种阻塞队列
  • 另外,对于运行队列在单核 CPU 系统中则只有一个运行指针了,因为单核 CPU 在某个时间,只能运行一个程序。

那么,就绪队列和阻塞队列链表的组织形式如下图:

就绪队列和阻塞队列

1.4、进程的控制

进程的创建、终止、阻塞、唤醒的过程也就是进程的控制

01 创建进程

操作系统允许一个进程创建另一个进程,而且允许子进程继承父进程所拥有的资源

创建进程的过程如下:

  • 申请一个空白的 PCB,并向 PCB 中填写一些控制和管理进程的信息,比如进程的唯一标识等;
  • 为该进程分配运行时所必需的资源,比如内存资源;
  • 将 PCB 插入到就绪队列,等待被调度运行;

02 终止进程

进程可以有 3 种终止方式:正常结束、异常结束以及外界干预(信号 kill 掉)。

当子进程被终止时,其在父进程处继承的资源应当还给父进程。而当父进程被终止时,该父进程的子进程就变为孤儿进程,会被 1 号进程收养,并由 1 号进程对它们完成状态收集工作。

终止进程的过程如下:

  • 查找需要终止的进程的 PCB;
  • 如果处于执行状态,则立即终止该进程的执行,然后将 CPU 资源分配给其他进程;
  • 如果其还有子进程,则应将该进程的子进程交给 1 号进程接管;
  • 将该进程所拥有的全部资源都归还给操作系统;
  • 将其从 PCB 所在队列中删除;

03 阻塞进程

当进程需要等待某一事件完成时,它可以调用阻塞语句把自己阻塞等待。而一旦被阻塞等待,它只能由另一个进程唤醒。

阻塞进程的过程如下:

  • 找到将要被阻塞进程标识号对应的 PCB;
  • 如果该进程为运行状态,则保护其现场,将其状态转为阻塞状态,停止运行;
  • 将该 PCB 插入到阻塞队列中去;

04 唤醒进程

进程由「运行」转变为「阻塞」状态是由于进程必须等待某一事件的完成,所以处于阻塞状态的进程是绝对不可能叫醒自己的。如果某进程正在等待 I/O 事件,需由别的进程发消息给它,则只有当该进程所期待的事件出现时,才由发现者进程用唤醒语句叫醒它。

唤醒进程的过程如下:

  • 在该事件的阻塞队列中找到相应进程的 PCB;
  • 将其从阻塞队列中移出,并置其状态为就绪状态;
  • 把该 PCB 插入到就绪队列中,等待调度程序调度;

1.5、进程的上下文切换

各个进程之间是共享 CPU 资源的,在不同的时候进程之间需要切换,让不同的进程可以在 CPU 执行,那么这个一个进程切换到另一个进程运行,称为进程的上下文切换。而CPU 寄存器和程序计数是 CPU 在运行任何任务前,所必须依赖的环境,这些环境就叫做 CPU 上下文

CPU 上下文切换就是先把前一个任务的 CPU 上下文(CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

系统内核会存储保持下来的上下文信息,当此任务再次被分配给 CPU 运行时,CPU 会重新加载这些上下文,这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

上面说到所谓的「任务」,主要包含进程、线程和中断。所以,可以根据任务的不同,把 CPU 上下文切换分成:进程上下文切换、线程上下文切换和中断上下文切换

进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。

通常,会把交换的信息保存在进程的 PCB,当要运行另外一个进程的时候,我们需要从这个进程的 PCB 取出上下文,然后恢复到 CPU 中,这使得这个进程可以继续执行,如下图所示:

进程上下文切换

1.6、进程上下文切换场景

  • 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,进程就从运行状态变为就绪状态,系统从就绪队列选择另外一个进程运行;
  • 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行;
  • 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度;
  • 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行;
  • 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。

1.7、进程间通信

01 管道

管道是一种单向的数据传输方式,实现线程间的通信需要两个管道。管道这种通信方式的缺点就是通信效率低,好处就是实现简单。

02 消息队列

为了解决管道通信效率低,可以使用消息队列。消息队列这种通信方式就是有一个生产者,一个消费者,运送数据的效率取决于消息队列的容量。

消息队列的缺点就是,不适合传输较大的数据。 另外传输数据要进行内核态与用户态之间的拷贝,效率比较低。

03 共享内存

为了解决内核态与用户态之间数据拷贝带来的开销,可以使用共享内存,共享内存就是通过指针的指向改变来完成数据的访问。

优点就在于可以省去拷贝开销,但是随之而来的问题就是对共享资源互斥访问需要控制,不然会带来安全性问题。

04 信号量

为了解决对共享资源访问的同步、互斥问题,可以使用信号量。信号量其实就是一个整型计数器,用来记录资源的数量,通过PV操作来实现进程间的同步、互斥流程。

05 信号

对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。信号跟信号量虽然名字相似度很高,但两者用途完全不一样。信号是通过固定的信号标志来传达信息。

06 Socket

前面涉及到的都是同一台主机间通信方式,还有不同主机间的通信,那就要用到Socket.创建Socket的系统调用:

int socket(int domain, int type, int protocal)

domain 参数用来指定协议族 type 参数用来指定通信特性 protocal 参数原本是用来指定通信协议的,但现在基本废弃。

2、线程

2.1、什么是线程

线程是计算机中能够被操作系统独立调度和执行的最小单位。一个进程可以包含多个线程,每个线程负责执行进程中的一部分任务。同一个进程内多个线程之间可以共享代码段、数据段、打开的文件等资源,但每个线程各自都有一套独立的寄存器和栈,这样可以确保线程的控制流是相对独立的。

不同的线程可以并行执行,从而提高程序的性能和响应速度。线程之间共享进程的内存空间,因此它们可以更方便地访问相同的数据。线程通常被用于处理多个任务或执行耗时的操作,例如图形界面事件处理、网络请求等等。

进程和线程简单关系

2.2、线程的上下文切换

线程的上下文切换主要看线程是不是属于同一个进程:

  • 当两个线程不是属于同一个进程,则切换的过程就跟进程上下文切换一样;
  • 当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据

所以,线程的上下文切换相比进程,开销要小很多。

2.3、线程间通信

在线程间常见的通信方式有:

  • 互斥锁提供了以排他方式防止数据结构被并发修改的方法;
  • 信号量 和互斥锁的区别在于:互斥锁只允许一个线程进入临界区,信号量允许多个线程同时进入临界区 互斥锁使用对同一个资源的互斥的方式达到线程同步的目的,信号量可以同步多个资源以达到线程同步;
  • 读写锁允许多个线程同时读共享数据,而对写操作是互斥的;
  • 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用;
  • 自旋锁与互斥量类似,也是只有解锁和加锁两种状态,它与互斥量的区别在于,它不会阻塞线程;
  • 信号机制(Signal) 类似进程间的信号处理,主要用于处理异常状况,用特定的码代指信息。

在Java中,常见的线程间通信操作是通过 wait()notify() 方法,这是Java中最基本的线程通信机制。wait() 方法会使当前线程等待,直到其他线程调用 notify() 方法唤醒它。notify() 方法会随机选择一个在等待该对象的线程进行唤醒。

3、线程与进程的联系

进程是操作系统资源分配的最小单位,线程是程序执行的最小单位,两者的区别和联系如下:

  • 资源分配:进程拥有自己的内存空间等系统资源,而线程共享进程的资源;
  • 独立性:进程之间相互独立,互不干扰,而线程是进程的一部分,线程之间共享进程的资源;
  • 调度:进程间调度的开销比线程大,线程的调度开销小,可以并发执行;
  • 并发性:多个进程之间相互独立,多个线程可以并发执行;
  • 同步:进程间通信需要通过IPC机制,线程间同步可以通过共享内存、信号量等机制实现。

在Java中,当我们启动main函数时,其实就是启动了一个 JVM 进程,而main函数所在的线程就是这个进程中的一个线程,也称之为主线程。多个线程共享进程的堆和方法区

而也有一些东西是线程私有的,例如程序计数器私有是为了保存线程的执行记录便于线程切换后能恢复到正确的执行位置;虚拟机栈和本地方法栈私有是为了保证线程中的局部变量不被别的线程访问。

线程与进程的联系

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈宝子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值