操作系统第一弹——进程和线程
在没有线程概念的系统中,进程是:
- 资源分配的单位:资源的控制或所有权属于进程,这些资源包括:内存、I/O通道、I/O设备和文件等。操作系统提供保护功能,以防止进程之间发生不必要的与资源相关的冲突。
- 调度/执行的单位:进程沿着一条执行路径执行,其执行过程可能与其它进程的执行过程交替运行,即一个进程具有一个执行状态(如就绪、运行等)和一个被分配的优先级,它是一个可被操作系统调度和分派的实体。
传统的只存在进程会有以下两个主要问题(线程的存在得以解决):
- 进程切换的开销大——每次切换都需要保存和恢复进程所拥有的全部信息(PCB、有关程序段和相应的数据集等)
- 进程占用的资源多——多个同类进程需占用多份资源,而一个进程中的多个同类线程则共享一份资源
再回头看看上面的两个特点,这两个特点本质上独立,可分开处理,为区分这两个特点,拥有资源所有权的单位称为进程,分派的单位称为线程。
即:
- 进程:资源分配和保护的单位,实现操作系统的并发——1.拥有用于保存进程映像的虚地址空间 2.受保护地访问处理器、其它进程、文件和I/O资源
- 线程:分派的执行单位,实现进程内部的并发——执行状态(运行、就绪等),保存的线程上下文(非运行时),对线程的内存和其他资源的访问(与同一进程内的其他线程共享这些资源)
线程的优点(与进程比较):
- 共享进程的代码、数据和资源
- 创建速度快:在一个已有进程中创建一个新线程比创建一个全新进程所需时间要少很多。
- 终止所用的时间少:终止一个线程要比终止一个进程花费的时间少
- 切换时间少(保存和恢复工作量小):同一进程内线程间切换比进程间切换时间花费少
- 提高了不同的执行程序间通信的效率:独立进程间的通信需要内核的介入,已提供保护和通信所需要的机制。但是,由于在同一个进程中的线程共享内存和文件,它们无需调用内核就可以互相通信。
进程和线程的区别:
- 不同的操作系统资源管理方式:进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响;而线程只是一个进程中的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
- 一个程序至少有一个进程,一个进程至少有一个线程,线程依赖进程而存在
- 进程在执行过程中拥有独立的内存单元,所以多进程的程序更加健壮;而多个线程共享内存,从而能极大的提高程序的运行效率
- 线程执行开销小,但是不利于资源的管理和保护;进程则相反。
进程控制块(Process Control Block,PCB):用来描述进程,用于进程的管理和调度。
包含以下信息:
- 标识符:跟这个进程相关的唯一标识符,用来区别其他进程
- 状态:进程的当前状态(运行/就绪/等待)
- 优先级:相对于其他进程的优先级别
- 程序计数器:程序中即将被执行的下一跳程序指令的地址
- 内存指针:包括只想程序代码、相关数据和共享内存的指针
- 上下文数据:进程被中断时处理器寄存器中的数据
- I/O状态信息:包括显式I/O请求、分配给进程的I/O设备、被解除使用的文件列表等
- 记账信息:包括占用处理器时间、时钟数总和、时间限制、账号等
进程的状态:
两状态模型:运行态,未运行态
三状态模型:运行态,就绪态,阻塞态(三个基本状态)
五状态模型:运行态,就绪态,阻塞态,新建态,退出态
六/七状态模型:有挂起态。为节省内存,可将部分处于阻塞态或就绪态的进程,从内存中换出到磁盘的挂起队列中,增加了阻塞挂起态和就绪挂起态。
- 新建态:进程控制块已经创建,关于该进程的信息已经被保存进内存的进程表中,但是还没有被加载进内存的新进程。可能原因是:操作系统可能基于性能或者内存局限性,限制系统中的进程数量,确保不会因为就绪态中活跃的进程数过多导致性能下降。
- 退出态:已经结束(自身停止或者进程被操作系统释放),进程不再被执行,但是可能系统还需要为了分析性能或者利用率,需要提取进程的历史信息,所以需要退出态,当所需要信息等操作被完成,操作系统不需要再保留任何跟该进程相关的数据,该进程就会被从操作系统中删除。
所以这两个状态是很有必要的。
主要内容为三种基本状态:
- 就绪状态:进程已获得除处理机以外的所需资源,等待分配处理机资源,即进程做好了准备,只要有机会就开始执行。
- 运行状态:占用处理机资源运行,处于此状态的进程数小于等于CPU数。
- 阻塞状态:进程等待某些条件满足,事件发生前不能执行,如I/O操作完成。
状态转变:
运行→就绪:可能原因
1:正在运行的进程达到“允许不被中断执行”的最大时间段,需要将cpu让出给别的进程
2:有更高优先级的进程由阻塞变为就绪,进程抢占
3:进程自愿释放资源,如一个周期性的进程,到达一定时间后会自动是释放占有资源
运行→阻塞:可能原因:等待输入,或者等待另一个进程的信息等,都会陷入阻塞。
就绪→退出,阻塞→退出:可能原因:父进程结束,则处于这两种状态的子进程都将被终止,直接退出。
挂起状态:
交换:进程映像整体地或部分地从主存转移到辅存中(挂起),或从辅助转移到主存中(换入)。交换式一种I/O操作,过于频繁地交换可能导致系统整体性能的恶化。当操作系统执行了一个换出操作之后,可以接纳一个新创建的进程,或调入一个以前被挂起的进程。通常倾向于调入一个以前被挂起的进程给它服务,而不是增加系统的负载总数。
引入原因:
- 实存不足:没有使用虚存的系统中,多个进程完全进入主存(主存价格高)
- CPU时间浪费:I/O速度比计算速度慢很多——可能出现主存中的多个进程全部阻塞等待I/O
- 调度策略:其他作业因没有主存空间而不能投入运行
线程的状态:
线程的关键状态也是运行态、就绪态和阻塞态。挂起状态和终止状态是进程级的概念,不属于线程:
- 挂起一个进程,则该进程的所有线程也被挂起(共享地址空间)
- 终止一个进程,则该进程的所有线程也终止(共享代码段)
与线程状态变化有关的操作:
- 派生:线程可派生新线程
- 阻塞:等待事件发生
- 唤醒/解除阻塞:事件发生后被唤醒,转换到就绪态
- 调度:有操作系统将就绪线程调度到处理器中执行
- 结束:释放寄存器上下文和栈
线程分类:
一、用户级线程(User-level Threads,ULT)
线程管理均由应用程序完成(线程库);
内核不知道现成的存在;
优点:
- 线程切换不需要模式切换:由于所有线程管理数据结构都在一个进程的用户地址空间中,线程切换不需要内核态权限,因此进程不需要为了线程管理切换到内核态,者节省了两次状态转换(从用户态到内核态,从内核态返回到用户态)的开销。
- 调度算法可应用程序专用:一个程序可能更适合简单的轮转调度算法,而另一个应用程序可能更适合基于优先级的调度算法。可以做到为应用程序量身定做调度算法而不扰乱底层的操作系统调度程序。
- UTL不需内核支持,线程库可在任何OS上运行
缺点:
- 一个线程阻塞会导致整个线程阻塞
- 不能利用多核和多处理器技术:一个多线程应用程序不能利用多处理技术。内核一次只把1一个进程分配给一个处理器,因此一个进程中只有一个线程可以执行。
使用内核级多线程技术会比使用单线程的进程速度有所提高,使用用户级线程比内核级线程又有额外的提高,不过这个额外的提高能否真的实现,取决于应用程序的性质,如果应用程序中大多数线程切换都需要内核态的访问,那么基于用户级线程的方案不会比基于内核级线程的方案好多少。
二、内核级线程(Kernel-level Threads,KLT)
线程管理由内核完成(提供API):应用程序部分没有线程管理的代码,只有一个到内核线程设施的应用程序编程接口(API);
内核基于线程进行;
优点:
- 线程阻塞不会导致进程阻塞:如果进程中的一个线程被阻塞,内核可以调度同一个进程中的另一个线程。
- 可以利用多核和多处理器技术:同一个进程中的多个线程调度到多个处理器中
- 内核例程本身也可以使用多线程
缺点:线程切换需要模式切换
三、混合方法(Combined Approaches)
线程创建在用户空间完成;
线程调度和同步在用户空间完成;
应用程序的m个ULT被映射到n(≤m)个KLT;
在混合方法中,同一个应用程序中的多个线程可以在多个处理器上并行的运行,某个会引起阻塞的系统调用不会阻塞整个进程。如果设计正确,该方法会结合纯粹用户级线程方法和内核级线程方法的优点,同时克服他们的缺点。
程序员可以为特定的应用程序和处理器调节内核级线程的数目,以达到整体最佳效果。