进程和线程
需熟练掌握进程和线程的概念,以及进程的状态转换和内存空间结构,是后续内容的基础,在选择题中会考察。也需了解进程间通信的方式、用户级线程和内核级线程的概念,可能在选择题中考察。
进程和线程
两者的对比
进程主要特点如下:
- 系统资源分配的基本单位: 进程拥有独立的系统资源,包括内存空间、文件描述符、CPU 时间片等,这些资源的分配由操作系统负责。
线程主要特点如下:
- 系统调度的基本单位: 多核处理器可以将系统中的不同线程调度到不同的CPU逻辑核心上,所以在同一个时刻,系统中的线程可能会在不同的逻辑核心上并行运行。
进程的状态
状态种类
- 创建状态(New):当进程被创建但还未分配资源或执行时,它处于创建状态。
- 就绪状态(Ready):在就绪状态中,进程已准备好执行,但由于操作系统调度算法或其他原因,尚未获得CPU时间片。
- 运行状态(Running):在运行状态中,进程正在执行指令并占用CPU。
- 阻塞状态(Blocked):当进程在等待某些事件发生时,如等待I/O操作完成或等待其他资源时,它会进入阻塞状态。在阻塞状态下,进程暂停执行,直到等待的事件发生。
- 终止状态(Terminated):当进程执行完毕或被操作系统终止时,它进入终止状态。
状态转化
- 就绪状态到运行状态:
- 调度: 当操作系统的调度器选择一个就绪状态的进程分配给处理器时,该进程就会从就绪状态转换到运行状态。
- 运行状态到就绪状态:
- 时间片用完: 如果系统使用时间共享调度,当进程的时间片用完,它会被中断并放回就绪队列。
- 优先级更高的进程就绪: 在优先级调度算法中,如果一个优先级更高的进程变为就绪状态,当前运行的进程可能会被挂起。
- 自愿放弃CPU: 进程可能主动放弃CPU,比如它发出了一个系统调用请求其他资源。
- 运行状态到阻塞状态:
- I/O请求: 进程进行I/O操作,由于I/O设备比CPU慢得多,进程会被挂起直到I/O完成。
- 等待资源: 进程等待不可用的资源,如信号量、互斥锁等。
- 等待事件: 如等待其他进程的信号、消息或者某个条件的发生。
- 阻塞状态到就绪状态:
- I/O完成: 当I/O操作完成,相应的进程会被移至就绪队列。
- 资源获得: 进程所等待的资源变得可用,如获得了互斥锁。
- 事件发生: 进程所等待的事件发生了,如接收到了另一个进程发出的信号。
进程内存空间
- 用户空间(User Space):包含进程执行的用户程序代码和数据。在用户空间中,进程可以执行各种任务,如运行应用程序、访问文件系统等。用户空间对于应用程序是可见的,但对于操作系统中的核心功能是不可见的。
- 代码区(Text Segment):也称为"可执行代码区",存储了进程的可执行代码,包括程序的指令和只读数据。这个区域通常是只读的,因为程序的指令在运行时不应该被修改。
- 数据区(Data Segment), 数据区分为两个子区域:
- 初始化数据区(Initialized Data Segment):存储全局和静态变量以及初始化的数据。这些变量在程序运行前就已经分配了内存并初始化。
- 未初始化数据区(Uninitialized Data Segment):也称为"BSS"(Block Started by Symbol)段,存储全局和静态变量,但这些变量没有显式的初始化值。操作系统会在程序启动时自动将这个区域初始化为零。
- 堆区(Heap):堆区是动态分配内存的地方,用于存储程序运行时需要的变量和数据结构。在堆中分配的内存需要手动释放,以避免内存泄漏。
- 栈区(Stack):栈区用于存储函数调用和局部变量。每个函数调用都会在栈上创建一个栈帧,栈帧包含了函数的参数、局部变量以及函数返回地址。栈是一种后进先出(LIFO)的数据结构,它的大小通常有限,由操作系统或编程语言定义。
- 内存映射区域(Memory Mapped Region):这是一些操作系统或运行时库的扩展,用于存储动态链接库(DLL)和共享库的信息以及其他系统数据结构。
- 内核空间(Kernel Space):内核空间包含了操作系统的核心代码和数据结构,如页表、调度程序和系统调用接口等。内核空间具有更高的特权级别,可以执行特权指令并且访问系统的各种资源,内核空间对于用户程序是不可见的。
进程间通信
- 共享内存(Shared Memory):共享内存允许多个进程访问相同的物理内存区域,这样它们可以直接共享数据,而不需要复制数据。这是一种高效的通信方式,但需要谨慎管理共享数据以避免竞态条件。
- 管道(Pipes):管道是一种单向通信方式,通常用于父子进程之间或兄弟进程之间的通信。有命名管道和匿名管道两种,匿名管道只能在有亲缘关系的进程之间使用。
- 消息队列(Message Queues):消息队列是一种进程间通信的机制,允许进程通过消息来进行异步通信。消息队列通常由操作系统维护,可以支持多个读者和写者。
- 信号(Signals):信号是一种轻量级的通信机制,用于通知进程发生了某些事件。进程可以发送信号给其他进程,比如终止信号、挂起信号等。
- 套接字(Sockets):套接字是一种通用的进程间通信方式,通常用于不同计算机之间的网络通信。它支持多种协议,如TCP和UDP,可以实现客户端-服务器通信。
- 信号量(Semaphores):信号量是一种计数器,用于控制多个进程对共享资源的访问。信号量可以用于解决竞争条件和进程同步的问题。
用户级线程和内核级线程
- 用户级线程(user level thread):用户级线程是在用户空间中创建和管理的线程,不依赖于操作系统的内核支持。这些线程由用户程序或用户级线程库管理,而不需要操作系统内核的介入。
- 内核级线程(kernel level thread):内核级线程是由操作系统内核管理和调度的线程。每个内核级线程都有自己的内核数据结构(例如,进程控制块,PCB),由操作系统内核负责创建、销毁和调度。
这个东西其实不太好理解,以真实的应用场景为例,当我们使用linux的pthread library创建线程时,这个线程其实是由操作系统进行管理和调度的,并不需要用户程序进行管理,所以当我们使用系统调用创建线程时,可以理解为创建的实际是内核级线程。
那么用户级线程在实际应用中到底是怎样的呢,根据其定义,用户级线程由程序或用户级线程库管理,这里举实际的例子的话,就是go语言中的goroutine或者python异步库asyncio中的的coroutine,这两者都是协程模型的实现,协程可以理解为更轻量的线程,协程库在用户空间内实现了一个调度机制,可以在系统执行一个线程的时间片内,创建多个协程并且在这个时间片内调度多个协程执行。由于协程的切换是在用户空间内实现的,不需要系统调用,所以效率更高,占用的资源更少。
用户级线程和内核级线程的映射方式(这里了解即可)包含多对一、一对一、多对多模型: