进程和线程
进程
进程是资源分配的基本单位
进程控制块描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作
下图是 4 个程序创建了 4 个进程,这四个进程可以并发执行

线程
线程是独立调度的基本单位
一个进程中可以有多个线程,他们共享进程资源
网易云和浏览器是两个进程,浏览器进程里面可以有多个线程,比如 HTTP 请求线程,事件响应线程,渲染线程等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,还可以相应其他线程
区别
- 拥有资源
进程是资源调度的基本单位,但是线程不拥有资源,线程可以访问隶属于该进程的资源
- 调度
线程是独立调度的基本单位。在同一进程中,线程的切换不会引起进程切换,从一个进程的线程切换到另一个进程的线程时,会引发线程切换
- 系统开销
由于创建或撤销进程时,系统都要为其分配或回收资源(内存空间,I/O等),所付出的开销远大于创建或撤销线程时的开销。
在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换只需保存和设置少量寄存器内容,开销很小
- 通信
线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC(进程间通信)
线程通信
- 消息队列
- 利用系统提供的事件,信号等通知机制
-
通过管道
- 套接字
等待唤醒机制(wait(), notify()):在一个线程进行规定操作后,就进入等待状态,等待其他线程执行完他们的指定代码过后,再将其唤醒
进程通信
- 管道
- FIFO
- 消息队列
- 信号量
-
共享存储
允许多个进程共享一个给定的存储区,这样数据不需要在进程之间复制,所以这是最快的一种 IPC
需要使用信号量来同步对共享存储的访问
多个进程可以将同一个文件映射到他们的地址空间从而实现共享内存
- 套接字 用于不同机器间的进程通信
进程之间共享内存

共享内存是在内存中单独开辟的一段内存空间,这段内存空间有自己的数据结构,包括访问权限、大小、和最近访问的时间等
两个进程在使用此共享内存空间的时候,需要在进程地址空间与共享内存空间之间建立联系,即把共享内存空间挂载到进程中
在使用完毕后,需要调用 shmdt() 函数将其与当前进程分离
在Linux 中可以通过 mmap 来创建一个新的虚拟内存区域并通过内存映射关联到磁盘上的对象
如何查看哪些进程在使用共享内存
使用ipcs命令的 -m(内存) 和 -p(进程)选项
进程线程切换的代价
切换步骤:
- 切换页目录以使用新的地址空间
- 切换内核栈和硬件上下文
对于 Linux 来说,线程和进程最大区别在于地址空间,对于线程切换,第一步是不用做的
切换的性能消耗:
- 线程上下文切换和进程最主要的区别就是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文的切换都是通过切换到内核态来完成的。内核的这种切换过程最显著的性能消耗是将寄存器中的内容切换出去。
- 另外一个隐藏的消耗就是这种上下文的会扰乱处理器的缓存机制。简单来说,一旦切换上下文,处理器中所有已经缓存的内存地址就已经失效了。当进程切换需要改变虚拟内存空间的时候,处理的页表缓冲等会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程切换中就不会出现这个问题。
进程状态

互斥和信号量
-
互斥量 Mutex
是表现互斥现象的数据结构,也被当作二元信号灯。他能用作同步多任务的行为,常用作保护从中断来的临界段代码并且共享同步使用的资源

Mutex 本质上是一把锁,提供对资源的独占访问,所以 Mutex 主要的作用是用于互斥。Mutex 对象的值只有 0 和 1。0 代表锁定状态,当前对象被锁定,其他线程无法访问,若此时试图 lock 该资源,则进入排队等待;1 代表当前对象空闲,用户进程/线程可以 lock 该临界资源,之后 Mutex 值减 1 变为 0
在同一个线程中,为了防止死锁,系统不允许连续两次对 Mutex 加锁。也就是说,*加锁和解锁所对应的操作需要在同一个线程中完成
-
信号量 Semaphore
是在多线程环境下使用的一种设施,它负责协调各个线程,以保证它们能够合理正确使用公共资源
信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减 1。如果计数器大于 0,则访问被允许,若为 0,则访问被禁止,所有试图通过它的线程都将处于等待状态;当此线程不需要再访问该资源时,释放该信号量,此时计数器加 1
- 区别:
- 互斥量用于线程的互斥,信号量用于线程的同步
这是互斥量和信号量的根本区别,也就是互斥和同步的区别
互斥:某一资源同时只允许一个访问者对其进行访问,具有唯一性和排他性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:在互斥的基础上(大多数情况),通过其他机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少时情况可以允许多个访问者同时访问资源
- 互斥量值只能为 0/1,信号量值可为非负整数
- 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到
调度算法
https://www.cnblogs.com/xiaolincoding/p/13631224.html

用户态和内核态
概念

为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围 ————《CSAPP》
一般来说,操作系统将执行权限进行分级,分成了用户态和内核态。用户态相较于内核态执行权限比较低,而内核态相当于一个连接应用和硬件的层,内核有 ring 0 的权限,可以执行所有 cpu 指令,也可以引用任何内存地址。
运行用户程序时,初始状态下是在用户态下,当出现中断,故障或系统调用这样的异常情况,就会切换到内核态。
用户态和内核态切换的代价
Linux 下每个进程的栈有两个,一个是用户态栈,一个是内核态栈。在需要从用户态切换到内核态的时候,需要执行栈的切换,保存用户态的状态(包括寄存器的状态),然后执行内核态操作,操作完毕之后还要再恢复到用户态。这个过程是耗时的。
虚拟内存
虚拟内存的目的是为了能让物理内存扩容成更大的逻辑内存,从而让程序获得更多的可用内存。
CPU 通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前需要由内存管理单元(MMU)通过查询表来动态翻译虚拟地址将其转换为物理内存地址。
概念上而言,虚拟地址被组织成为一个存放在硬盘上的 N 个连续字节大小的单元组成的数组,每个字节都有一个唯一对应的虚拟地址,作为到数组的索引。

虚拟地址空间有哪些部分

每个进程都会分配到自己独有的虚拟地址空间,对于 32 位的系统,虚拟地址空间有 2^32 大小,也就是 4G 空间。将最高的 1G 空间给内核使用,称为内核空间,将较低的 3G 空间给进程使用,称为用户空间。
内核空间
存放内核的代码和数据,所有进程的内核代码段都映射到同样的物理内存,并且在内存中持续存在,是操作系统的一部分。
用户空间
-
栈空间
由编译器自动分配释放,存放局部变量,函数参数值,是一块连续的空间
-
共享区
内存映射以及共享库(动态库)所在的内存
-
堆空间
用户通过 new/malloc 开辟的空间,存放进程运行时动态分配的内存段
-
bss区
未初始化或初始值为 0 的全局变量和静态局部变量
-
data区
已初始化且初值不为 0 的全部变量和静态局部变量
-
代码段
存放程序执行代码
-
保留区
位于虚拟地址空间的最低部分,未赋予物理地址。任何对他的引用都是非法的,用于捕捉使用空指针和小整形值指针引用内存的异常情况
死锁
概念
多个进程在运行过程中争夺资源时产生的僵局。当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
产生死锁原因
- 竞争资源
- 竞争不可剥夺资源(当系统把这类资源分配给某进程后,不能再强行收回,只能再进程使用完之后自行释放)
- 竞争临时资源(包括硬件中断、信号、消息、缓冲区内的信息等)通常消息通信顺序进行不当,会产生死锁
- 进程间推进顺序非法
- 若 P1 保持了资源 R1,P2 保持了资源 R2,系统处于不安全状态,因为这两个进程再向前推进,可能就会产生死锁
- 例如:当 P1 运行到 P1:request R2时,将因 R2 被 P2 占用而阻塞;当 P2 运行到 P2:request R1 时,也将因 R1 被 P1 占用而阻塞,因此发生进程死锁
四个必要条件
- 互斥条件:进程要求堆锁分配的资源进行排他性控制,即在一段时间内某资源仅为一进程所占用
- 请求和保持条件:当进程因请求资源而阻塞时,对以获得的资源保持不放
- 不剥夺条件:进程以获得的资源在未使用完之前,不能剥夺, 只能在使用完之后自行释放
- 环路等待条件:在发生死锁时,必然存在一个进程-资源的环形链
如何解决死锁
预防死锁
- 资源一次性分配(破坏请求条件)
- 只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏请求保持条件)
- 可剥夺资源:当某进程获得了部分资源,但得不到其他资源,则释放已占有的资源(破坏不可剥夺条件)
- 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)



