漫谈进程和线程 Bertha 。 2022-05-26 00:10 266阅读 0赞 > 为了帮助大家理解什么是进程,以厨师做蛋糕为例。厨师做蛋糕,首先需要厨师(CPU),其次,需要食谱(程序)和原料(输入数据),而用原料做蛋糕的一些列动作的总和就是进程。某天厨师正在后厨做着蛋糕,突来听到儿子哭着跑进后厨,说自己被蜜蜂蛰了 ,厨师放下手中工具,并记录下当前做到哪一步了(保存上下文信息) ,然后拿出急救手册,按其中的说明为儿子进行处理(开始另外一个进程)。 ## 进程概览 ## 我们知道文件是对I/O设备的抽象,虚拟存储器是对文件和主存的抽象,指令集是对CPU的抽象,**进程是对指令集和虚拟存储器的抽象**。如下图所示 。 ![image.png][] ## 进程在内存的逻辑布局 ## 从上可知,进程包括指令集和虚拟存储器。我们着重介绍进程在虚拟存储器中的逻辑布局,它包括用户栈、堆、程序数据和程序代码,其中,用户栈从上往下生长,堆从下往上生长,程序数据和程序代码从可执行文件加载而来,将程序代码改写成汇编指令就是类似于`movl、imul、addl等指令`。如下图所示 ![image.png][image.png 1] 此时,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][image.png 2] ## 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][image.png 3] * **进程间同步的本质** 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][image.png 4] ## 线程的实现 ## 1. 完全在用户层实现(当用户要执行硬件设备,必须从用户空间到内核空间,这是一种保护措施,保护操作系统不被恶意程序所破坏),**线程在应用层实现有一个优点就是线程切换不用内核介入,线程切换会非常的快**。也就是说线程的调度策略是自己实现的。但是这里也有一个巨大的缺陷:由于内核只知道进程而不知道线程,那么进程1中的任何一个线程被阻塞,导致进程1中的其他线程也被阻塞 2. 内核实现线程和用户空间一一对应,可以有效的解决方案一中的缺点,但是由于在内核中实现用户空间相同数量的线程数,开销比较大 3. **用户空间中多个线程映射到内核中的一个线程**,这样一来,内核中的线程就不用创建那么多, 而且阻塞的概率也降低了,这是一种平衡和折中的方式。**JVM就是实现了这种方式** 。JVM本身就是一个进程,JVM可以创建很多线程,然后对应内核中的线程,内核中的线程调度CPU。 -------------------- ![image][] 欢迎关注微信公众号:木可大大,所有文章都将同步在公众号上。 [image.png]: /images/20220526/cdbb7b7e67f0438aaca6ab3d96874061.png [image.png 1]: /images/20220526/69e4b4db86d34b0b8781372f509f1efa.png [image.png 2]: /images/20220526/213220a67aa944259b3a75e69b6de5ac.png [image.png 3]: /images/20220526/78035337b7c5451eacf5aecae0580d28.png [image.png 4]: /images/20220526/93810d24078a4ed98c942415b40e60f0.png [image]: /images/20220526/52699573c6dd49b0afaa35f32919465c.png
相关 线程和进程 目录 进程 线程 实现多线程的方法 方式一:继承Thread类 设置和获取线程名称 线程调度 线程控制 线程的状态(生命周期) 方式二:实现Runnab 曾经终败给现在/ 2024年04月01日 01:38/ 0 赞/ 79 阅读
相关 线程和进程 一、概念: 进程:进程是一个具有一定独立功能的程序在一个数据集合上依次动态执行的过程。进程是一个正在执行的程序的实例,包括程序计数器、寄存器和程序变量的当前值。 线程:进程 刺骨的言语ヽ痛彻心扉/ 2024年03月27日 11:50/ 0 赞/ 79 阅读
相关 线程和进程 目录 一、线程和进程的区别 二、线程 三、分时操作系统 四、如何减少上下文切换 五、多线程和单线程 六、多线程的使用场景 -------------------- 亦凉/ 2023年10月01日 21:23/ 0 赞/ 22 阅读
相关 线程和进程 线程和进程 一、线程和进程 线程是CPU调度和分派的基本单位。 进程对于操作系统来说就是一个任务。 听起来挺难懂的一句话,我们举一个实际例子:比如现在有一条需要 迈不过友情╰/ 2023年05月29日 14:29/ 0 赞/ 25 阅读
相关 进程和线程 1.概念 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位 我就是我/ 2022年07月16日 08:11/ 0 赞/ 258 阅读
相关 漫谈进程和线程 > 为了帮助大家理解什么是进程,以厨师做蛋糕为例。厨师做蛋糕,首先需要厨师(CPU),其次,需要食谱(程序)和原料(输入数据),而用原料做蛋糕的一些列动作的总和就是进程。某天厨 Bertha 。/ 2022年05月26日 00:10/ 0 赞/ 267 阅读
相关 进程和线程 进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。 线程:是进程的一个执行单元,是进程内调度实体。比进程更小的独立运行 ╰半夏微凉°/ 2022年02月19日 21:13/ 0 赞/ 347 阅读
相关 进程和线程 进程和线程 开发工具与关键技术:java 撰写时间:2019/5/15 进程和线程 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间 ╰半夏微凉°/ 2022年01月31日 06:47/ 0 赞/ 278 阅读
相关 进程和线程 进程和线程 概念 进程就是操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、 数据栈以及其他用于跟踪进程执行的 川长思鸟来/ 2021年10月30日 05:08/ 0 赞/ 425 阅读
相关 线程和进程 1.定义 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它 红太狼/ 2021年06月24日 14:36/ 0 赞/ 539 阅读
还没有评论,来说两句吧...