进程和线程

需熟练掌握进程和线程的概念,以及进程的状态转换和内存空间结构,是后续内容的基础,在选择题中会考察。也需了解进程间通信的方式、用户级线程和内核级线程的概念,可能在选择题中考察。

进程和线程

两者的对比

进程主要特点如下:

  • 系统资源分配的基本单位: 进程拥有独立的系统资源,包括内存空间、文件描述符、CPU 时间片等,这些资源的分配由操作系统负责。

线程主要特点如下:

  • 系统调度的基本单位: 多核处理器可以将系统中的不同线程调度到不同的CPU逻辑核心上,所以在同一个时刻,系统中的线程可能会在不同的逻辑核心上并行运行。

进程的状态

状态种类

  1. 创建状态(New):当进程被创建但还未分配资源或执行时,它处于创建状态。
  2. 就绪状态(Ready):在就绪状态中,进程已准备好执行,但由于操作系统调度算法或其他原因,尚未获得CPU时间片。
  3. 运行状态(Running):在运行状态中,进程正在执行指令并占用CPU。
  4. 阻塞状态(Blocked):当进程在等待某些事件发生时,如等待I/O操作完成或等待其他资源时,它会进入阻塞状态。在阻塞状态下,进程暂停执行,直到等待的事件发生。
  5. 终止状态(Terminated):当进程执行完毕或被操作系统终止时,它进入终止状态。

状态转化

新建
就绪
运行
终止
阻塞
创建
调度
时间到
事件等待
事件发生
退出
  1. 就绪状态到运行状态:
    • 调度: 当操作系统的调度器选择一个就绪状态的进程分配给处理器时,该进程就会从就绪状态转换到运行状态。
  2. 运行状态到就绪状态:
    • 时间片用完: 如果系统使用时间共享调度,当进程的时间片用完,它会被中断并放回就绪队列。
    • 优先级更高的进程就绪: 在优先级调度算法中,如果一个优先级更高的进程变为就绪状态,当前运行的进程可能会被挂起。
    • 自愿放弃CPU: 进程可能主动放弃CPU,比如它发出了一个系统调用请求其他资源。
  3. 运行状态到阻塞状态:
    • I/O请求: 进程进行I/O操作,由于I/O设备比CPU慢得多,进程会被挂起直到I/O完成。
    • 等待资源: 进程等待不可用的资源,如信号量、互斥锁等。
    • 等待事件: 如等待其他进程的信号、消息或者某个条件的发生。
  4. 阻塞状态到就绪状态:
    • I/O完成: 当I/O操作完成,相应的进程会被移至就绪队列。
    • 资源获得: 进程所等待的资源变得可用,如获得了互斥锁。
    • 事件发生: 进程所等待的事件发生了,如接收到了另一个进程发出的信号。

进程内存空间

  • 用户空间(User Space):包含进程执行的用户程序代码和数据。在用户空间中,进程可以执行各种任务,如运行应用程序、访问文件系统等。用户空间对于应用程序是可见的,但对于操作系统中的核心功能是不可见的。
    1. 代码区(Text Segment):也称为"可执行代码区",存储了进程的可执行代码,包括程序的指令和只读数据。这个区域通常是只读的,因为程序的指令在运行时不应该被修改。
    2. 数据区(Data Segment), 数据区分为两个子区域:
      • 初始化数据区(Initialized Data Segment):存储全局和静态变量以及初始化的数据。这些变量在程序运行前就已经分配了内存并初始化。
      • 未初始化数据区(Uninitialized Data Segment):也称为"BSS"(Block Started by Symbol)段,存储全局和静态变量,但这些变量没有显式的初始化值。操作系统会在程序启动时自动将这个区域初始化为零。
    3. 堆区(Heap):堆区是动态分配内存的地方,用于存储程序运行时需要的变量和数据结构。在堆中分配的内存需要手动释放,以避免内存泄漏。
    4. 栈区(Stack):栈区用于存储函数调用和局部变量。每个函数调用都会在栈上创建一个栈帧,栈帧包含了函数的参数、局部变量以及函数返回地址。栈是一种后进先出(LIFO)的数据结构,它的大小通常有限,由操作系统或编程语言定义。
    5. 内存映射区域(Memory Mapped Region):这是一些操作系统或运行时库的扩展,用于存储动态链接库(DLL)和共享库的信息以及其他系统数据结构。
  • 内核空间(Kernel Space):内核空间包含了操作系统的核心代码和数据结构,如页表、调度程序和系统调用接口等。内核空间具有更高的特权级别,可以执行特权指令并且访问系统的各种资源,内核空间对于用户程序是不可见的。
Process-specific data
structures
(e.g. page tables, tasks and kernel stack)
Physical Memory
Kernel code and data
User stack
Memory-mapped region
for shared libraries
Run-time heap
Uninitialized data (.bss)
Initialized data (.data)
Code (.text)
Different for
each process
Identical for
each process
Kernel
virtual
memory
Process
virtual
memory
low
address
space
high
address
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,这两者都是协程模型的实现,协程可以理解为更轻量的线程,协程库在用户空间内实现了一个调度机制,可以在系统执行一个线程的时间片内,创建多个协程并且在这个时间片内调度多个协程执行。由于协程的切换是在用户空间内实现的,不需要系统调用,所以效率更高,占用的资源更少。

用户级线程和内核级线程的映射方式(这里了解即可)包含多对一、一对一、多对多模型:

多对一模型
一对一模型
多对多模型
用户线程
内核线程

视频讲解