漫谈进程和线程

简介:

为了帮助大家理解什么是进程,以厨师做蛋糕为例。厨师做蛋糕,首先需要厨师(CPU),其次,需要食谱(程序)和原料(输入数据),而用原料做蛋糕的一些列动作的总和就是进程。某天厨师正在后厨做着蛋糕,突来听到儿子哭着跑进后厨,说自己被蜜蜂蛰了 ,厨师放下手中工具,并记录下当前做到哪一步了(保存上下文信息) ,然后拿出急救手册,按其中的说明为儿子进行处理(开始另外一个进程)。

进程概览

我们知道文件是对I/O设备的抽象,虚拟存储器是对文件和主存的抽象,指令集是对CPU的抽象,进程是对指令集和虚拟存储器的抽象。如下图所示 。
image.png

进程在内存的逻辑布局

从上可知,进程包括指令集和虚拟存储器。我们着重介绍进程在虚拟存储器中的逻辑布局,它包括用户栈、堆、程序数据和程序代码,其中,用户栈从上往下生长,堆从下往上生长,程序数据和程序代码从可执行文件加载而来,将程序代码改写成汇编指令就是类似于movl、imul、addl等指令。如下图所示
image.png

此时,CPU运行到地址为304的指令, 假设CPU时间片刚好用完,就需要进行进程切换,在进行进程切换之前,需要保护现场,即保存寄存器信息、PC、打开的文件, 代码段地址、数据地址、堆栈信息等,这些信息称为进程的上下文。当操作系统切换到进程时,首先将进程2的上下文信息加载到操作系统中,找到PC,然后接着执行就可以了。

进程控制块(PCB)

进程的上下文信息是以某个数据结构保存在内存中的,而这种数据结构就是PCB。在Linux操作系统中PCB对应的数据结构就是task_struct,它保存着进程的重要信息。

struct task_struct{
  pid_t pid://进程号
  long state;//状态
  cputime_t utime,stime;//cpu在用户态和 核心态下执行的时间
  struct files_struct *files;//打开的文件
  struct   mm_struct  *mm;//进程使用的内存
  ...
}

进程的状态

  • 进程的状态包括新建态、就绪态、等待态、运行态、退出态
  • 流程:首先进程被新建,然后进入到就绪状态,此时,进程并没有进入到运行状态,而是等待CPU调度,如果被CPU调度则进入到运行态,而当时间片用完时,进程从运行态返回到就绪态,而当等待I/O操作时,则由运行态进入阻塞态。需要注意的是:只有运行态的进程拥有CPU,而处于就绪态和等待态的进程只是处于内存,等待CPU调度,因此CPU调度是一个很关键的流程。
    image.png

CPU调度

像上文描述的那样,CPU调度就是到底哪个进程占有CPU,它可以分为非抢占式抢占式。非抢占式是指调度程序一旦把CPU分配给某一进程后便让它一直运行下去,直到进程完成或发生某件事件而不能运行时,才将CPU分配给其他进程。它适合批处理系统,简单、系统开销小。抢占式是指当一个进程正在执行时,系统可以基于某种策略剥夺CPU给其他进程。剥夺的原则有优先权原则、端进程优先原则、时间片原则,它适用于交互式系统。

  • 评价标准
  1. 公平:合理的分配CPU
  2. 响应时间:从用户输入到产生反映的时间
  3. 吞吐量:单位时间完成的任务数量
  4. 但是这些目标是矛盾的,例如:我们希望前端进程能够快速得到响应,这样一来后端进程就不能得到快速响应。
  • 批处理系统中的调度
  1. 先来先服务(公平、FIFO队列、非抢占式)
  2. 最短作业优先(系统的平均等待时间最短,但是需要预先知道每个任务的运行时间)
  • 交互式调度策略
  1. 轮转,每个进程分配一个固定的时间片,但是定义时间片长度是个问题,假设进程切换一次的开销为1ms,如果时间片太短,那么很多时间都浪费在切换上,例如时间片为4ms,那么20%的时间浪费在切换上;如果时间片太长,浪费时间就减少了,但是最后一个经常等待的时间就非常久,譬如,时间片100ms,浪费的时间1%,假设有50个进程,最后一个需要等待5s。
  2. 静态优先级,给每个进程赋予优先级,优先级高的先执行,优先级低的后执行,但是该方法存在一定问题:低优先级的进程存在被饿死的情况,例如新来的进程的优先级都比原来的高,怎么办呢?我们根据等待时间的增加而调整优先级大小---多级反馈队列
  3. 动态优先级---多级反馈队列,即进程的优先级会随着等待时间的增长而增长。

进程间同步

我们知道,打印机有一个缓存,叫做打印队列,如下图所示,打印队列有5个空格,就是说这个打印队列最多可以容纳5个待打印文件,打印机进程就是消费者,而其他待打印进程是生产者,生产者不断地向队列中放数据,例如:A.java、B.doc等。

  • 临界区:多个进程需要互斥的访问共享资源,共享资源可以是变量、表和文件等,例如打印队列就是共享资源。
  • 当生产者将队列放满时,需要等待消费者;如果消费者把所有文件都打印完了,则需要等待生产者,这就是进程间的同步问题

image.png

  • 进程间同步的本质
  1. 进程调度是不可控的
  2. 在机器层面,count++,count--并不是原子操作,即一条代码,对应汇编层面多条指令。两者缺一不可,如果进程调度是可控的,那么,即使count++对应多条指令,当执行完第一条指令时,发生CPU切换,进程调度控制接下来的进程还是原来的进程控制CPU。
  • 解决方案
  1. 关闭中断
    缺点:把中断操作(CPU收到时钟中断以后,会检查当前进程的时间片是否用完,用完则切换)开放给应用程序,这是极其危险的事情,例如:当某个程序关闭中断之后,执行完毕之后,忘记打开中断,导致整个系统都终止了。
  2. 用硬件指令来实现锁
boolean TestAndSet(boolean *lock){
  boolean rv = *lock;
  *lock = TRUE;
  return rv;
}

// 使用TestAndSet
boolean lock = false;
do{
  while(TestAndSet(&lock)){
    ...//什么也不做
  }
  临界区
  lock = false;
  剩余区
}while(true);
  • 注意:操作系统会锁住系统总线,防止其他CPU访问内存变量
  • 注意TestAndSet函数中的三条指令是原子执行的
  1. 信号量
  • 信号量S是个整形变量,除了初始化外,有两个操作,wait()、signal()。
  • 为了确保信号量操作,需要用一种原子的方式实现它,操作系统来实现原子性。
wait(S){
  while(S<=0){
  ...//啥也不做
  }
  S--;
}
signal(S){
  S++;
}

//
semaphore mutext = 1;
wait(mutex);
进入临界区
signal(mutex);
剩余区
  1. 不能忙等问题

用硬件指令实现锁的方案和信号量方案都有忙等问题,即某个进程获得了CPU时间片,但是啥事干不了,while(S < = 0){...}

  • 新增进程队列,当发现value<0,将当前队列加入到阻塞队列中,同时,阻塞进程,而不像之前的方法那样无限等待下去
typedef struct{
  int value;
  struct process *list;
} semaphore;

wait(semaphore *s){
  s -> value--;
  if(s->value<0){
  //把当前进程加入到s->list中
  block();
}
signal(semaphore *s){
  s -> value++;
  if(s -> value <=0){
    //从s->list取出一个进程p
    wakeup(p);
  }
}

线程

由于进程之间是相互独立的,所以进程间数据共享只能通过内核实现,开销很麻烦,因此我们提出了线程这个概念。线程之间的数据是共享的;一个进程可以只有一个线程,也可以有多个线程(一个进程至少有一个线程);当一个进程有多个线程时,每个线程都有一套独立的寄存器和堆栈信息,而代码、数据和文件是共享的,如下图所示。
image.png

线程的实现

  1. 完全在用户层实现(当用户要执行硬件设备,必须从用户空间到内核空间,这是一种保护措施,保护操作系统不被恶意程序所破坏),线程在应用层实现有一个优点就是线程切换不用内核介入,线程切换会非常的快。也就是说线程的调度策略是自己实现的。但是这里也有一个巨大的缺陷:由于内核只知道进程而不知道线程,那么进程1中的任何一个线程被阻塞,导致进程1中的其他线程也被阻塞
  2. 内核实现线程和用户空间一一对应,可以有效的解决方案一中的缺点,但是由于在内核中实现用户空间相同数量的线程数,开销比较大
  3. 用户空间中多个线程映射到内核中的一个线程,这样一来,内核中的线程就不用创建那么多, 而且阻塞的概率也降低了,这是一种平衡和折中的方式。JVM就是实现了这种方式 。JVM本身就是一个进程,JVM可以创建很多线程,然后对应内核中的线程,内核中的线程调度CPU。
相关文章
|
20天前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第16天】进程、线程和协程是计算机程序执行的三种基本形式。进程是操作系统资源分配和调度的基本单位,具有独立的内存空间,稳定性高但资源消耗大。线程是进程内的执行单元,共享内存,轻量级且并发性好,但同步复杂。协程是用户态的轻量级调度单位,适用于高并发和IO密集型任务,资源消耗最小,但不支持多核并行。
37 1
|
3天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
6天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
16 2
|
7天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
1月前
|
存储 消息中间件 人工智能
进程,线程,协程 - 你了解多少?
本故事采用简洁明了的对话方式,尽洪荒之力让你在轻松无负担的氛围中,稍微深入地理解进程、线程和协程的相关原理知识
40 2
进程,线程,协程 - 你了解多少?
|
18天前
|
Python
Python中的多线程与多进程
本文将探讨Python中多线程和多进程的基本概念、使用场景以及实现方式。通过对比分析,我们将了解何时使用多线程或多进程更为合适,并提供一些实用的代码示例来帮助读者更好地理解这两种并发编程技术。
|
20天前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第15天】进程、线程和协程是操作系统中三种不同的执行单元。进程是资源分配和调度的基本单位,每个进程有独立的内存空间;线程是进程内的执行路径,共享进程资源,切换成本较低;协程则更轻量,由用户态调度,适合处理高并发和IO密集型任务。进程提供高隔离性和安全性,线程支持高并发,协程则在资源消耗和调度灵活性方面表现优异。
43 2
|
2月前
|
存储 消息中间件 资源调度
「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
该文章总结了操作系统基础知识中的十个关键知识点,涵盖了进程与线程的概念及区别、进程间通信方式、线程同步机制、死锁现象及其预防方法、进程状态等内容,并通过具体实例帮助理解这些概念。
「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
|
26天前
|
算法 安全 调度
深入理解操作系统:进程与线程的管理
【10月更文挑战第9天】在数字世界的心脏跳动着的,不是别的,正是操作系统。它如同一位无形的指挥家,协调着硬件与软件的和谐合作。本文将揭开操作系统中进程与线程管理的神秘面纱,通过浅显易懂的语言和生动的比喻,带你走进这一复杂而又精妙的世界。我们将从进程的诞生讲起,探索线程的微妙关系,直至深入内核,理解调度算法的智慧。让我们一起跟随代码的脚步,解锁操作系统的更多秘密。
34 1
|
1月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
29 3