操作系统 1:进程与线程
进程(Process)是由一份代码段,一个当前状态和一组相关系统资源所刻画的活动单元,是程序在操作系统上运行的实体。在操作系统的帮助下,每个进程都产生了「我独占整个硬件,我独享所有内存」的假象,从而能更好地利用系统资源。
htop
工具可以直观地显示 *nix 系统下的进程和资源情况:
说明:Linux 不区分进程和线程,二者视为同一种东西称为 task,由调度器统一调度。进程和线程的区别体现在创建时对内存空间的处理上。
线程(Thread,又译「执行绪」)是一个进程之下的相对独立的可调度的执行单元。一个进程之下的所有线程可以并发执行,共享进程的资源。例如,在 Java 中,我们利用 Runnable
接口可以轻松地创建多个线程,不同线程共享着 Java 虚拟机下的资源,由 Java 虚拟机进行调度。
进程的描述
一个进程由下面几部分「描述」,或者说组成:
进程控制块(PCB)
进程控制块(Process Control Block, PCB)是一个结构体,用来描述进程与其他系统资源的关系。它包含进程的标识符(PID),进程相关用户的标识符(UID),进程的状态,资源(如文件描述符),处理器信息(寄存器的值,称为「栈帧」)等。
在 xv6 中,PCB 就是 kernel/proc.h
中的 proc
结构体;在 Linux 中,PCB 就是 include/linux/sched.h
中的 task_struct
结构体(Linux 将进程称为 task)。
上下文
进程的上下文,包含进程的物理实体(代码段、数据段)和支持它运行的环境。具体地:
进程的代码段、数据段、堆栈等组成用户级上下文。
进程标识、现场、控制信息、内核栈组成系统级上下文。
处理器中寄存器的值构成硬件上下文。
地址空间
几乎所有的现代操作系统都使用虚拟内存技术。在 Linux 平台下,每个用户进程都具有独立的私有地址空间。虚拟内存让每个进程「独享」一大段假想的内存,再由操作系统利用「页表」及硬件上的「内存管理单元」(Memory Management Unit, MMU)将每个进程实际使用的内存「映射」到真实的内存上(下图来自 IBM Linux 开发文档):
进程的特性
动态性——因创建而产生,由调度而执行,因得不到资源而暂停执行,最后因完成或撤销而消亡。
并发性——引入进程的目的就是为了使多个程序并发执行。
独立性——进程是一个能独立运行的基本单位,也是系统进行资源分配和调度的基本单位。
异步性——进程以各自独立的、不可预知的速度向前推进。
结构性——进程 = 程序 + 数据 + PCB
进程的状态、转换与控制
进程的状态
就绪态:已经获取了必要的除 CPU 之外的资源,等待获得 CPU 来执行。
运行态:进程已经获取到 CPU,正在 CPU 上执行。
阻塞态:进程正在等待某一事件而放弃 CPU,暂停运行。
新建:至少已经建立 PCB,但资源还没有调入主存。
终止:进程已经终止,等待回收。
进程的创建和回收
使用系统调用
fork()
创建子进程。子进程拥有父进程的所有资源。「调用一次返回两次」:在父子进程中各返回一次。
在子进程中返回 0,在父进程中返回子进程的 PID。
使用系统调用
wait()
或者waitpid()
主动回收子进程。wait()
等待某一个子进程结束。函数原型为
pid_t wait(int *status)
,返回值是此次结束的子进程的 PID,并将子进程的退出状态写到*status
指针所在处。可以使用宏
WIFEXITED(status)
来从status
中读出进程是否是正常退出的。可以使用宏
WEXITSTATUS(status)
来从status
中读出进程的退出码。waitpid()
等待一个特定的进程结束。函数原型为
pid_t waitpid(pid_t pid, int *status, int options)
。options
一般指定为 0。它可以是下面参数的位或:WNOHANG
:如果没有可以退出的子进程,就立即返回。WUNTRACED
:当一个进程不被ptrace
追踪但结束时也返回。WCOUNTINUED
:当一个停止的进程被信号SIGCONT
恢复时返回。
进程间通信
共享内存系统
消息传递系统
管道
套接字
远程过程调用
线程的描述
与进程类似,线程可以用「线程控制块」(TCB)+ 堆栈 + 寄存器进行描述。在引入了线程的概念之后,我们有:
进程 = 线程 + 代码段、数据段、内核上下文,由 PCB 控制
线程 = 寄存器 + 栈,由 TCB 控制
线程和进程的关系有:
线程是基本的 CPU 执行单元。
线程不单独拥有系统资源,只拥有其在运行中必不可少的资源。
一个进程可以关联多个线程。
每个线程有自己的控制流。
属于同一个进程的线程共享所属进程的地址空间。
每个线程有自己局部变量的堆栈。
POSIX 线程模型
POSIX 线程模型 POSIX Threads 简称 pthreads,是几乎所有 *nix 平台上都使用的多线程模型。
线程的创建
使用 pthread_create()
函数。原型:
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg);
线程的终止
当线程所属进程返回时,线程会隐式地终止。
如果需要显式地终止线程,使用 pthread_exit()
函数。原型:
noreturn void pthread_exit(void *retval);
线程的结合和分离
所谓线程的结合类似进程的 wait()
,使用 pthread_join()
函数阻塞当前线程,直到指定的线程终止。
线程的分离指的是,将一个指定的线程标记为 detached。当一个 detached 的线程终止时,它的资源会被系统自动回收,而无须其他进程来结合。使用 pthread_detach()
函数完成这个操作。