@ TOC
概念
进程
1.进程有独立的地址空间
进程是系统为了执行一个程序而分配的资源的总称。
每个进程创建的时候,操作系统会为其分配相应的资源,包括内存资源、CPU资源等。
每个进程有自己独立的地址空间,其中代码、数据
每个进程只能访问自己地址空间中的代码和数据
每个进程除了存放代码和数据之外,还包括系统数,包括PCB(进程控制块)、各种寄存器的值、堆栈。
2.Linux 为每个进程创建 task_struct
对于 Linux 来说,每个进程会在内和空间创建一个结构体 task_struct (任务结构体)
这个结构体就是用来描述一个任务,其属性就存放在该结构体中,比如进程号、进程的状态,进程的优先级,进程打开的文件等
3.每个进程都参与内核调度,互不影响
每个进程创建好以后都需要内核的调度,才能执行,并且一个进程结束了,并不会影响到其他进程的执行。
线程
进程在切换时系统开销比较大
为了匹配一个高速设备CPU和一个低速设备RAM, 在中间会加上 高速缓存 cache 和 转换检测缓冲区 TLB ( 一般也是通过静态缓存来实现的)
cache 的成本比较高,所以容量比较小
CPU 实际运作时会先将一批缓存加载到 cache 中,然后再从 cache 中去读取数据。如果 cache 中没有数据了, 则再去RAM中加载一批指令和数据。
cache 也分为指令 cache 和 数据 cache
TLB 旁路转换缓冲/页表缓冲,其中每一行都保存着一个由单个PTE(Page Table Entry,页表项)组成的块。
页表是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表,PCB表中有指针指向页表
即每个进程都地址空间都不一样,访问的物理内存也不一样,所以会用到不同页表项
综上所述,如果CPU频繁的切换进程,cache 中的数据和 tlb 的缓存也都需要频繁的重新刷新,所以开销会比较大
所以,很多操作系统引入了轻量级进程LWP(线程)线程相比于进程会节省大量开销,因为同一进程中的线程共享相同地址空间,切换的时候 cache 和 TLB 不需要刷新或者只需要更新一小部分。
当然,不同进程间的进程互相切换也需要做同样工作。
对于 Linux 而言不区分进程、线程,都会创建一个 task_struct 来描述这个任务
总结
通常线程指的是共享相同地址空间的多个任务
使用多线程的好处在于大大提高了任务切换的效率,避免了额外的TLB & cache 的刷新
线程相比于进程可,资源的开销更小,可以更好的利用CUP的资源
原理:时间转轮算法
并发:看起来同时发生,实际一次只有一条指令执行
并行:同一时刻有多条指令在多个处理器上执行
同步:阻止依赖关系的系统调用同时发生
异步:任意两个彼此独立的操作是异步的,表现了事
件的独立性
线程的四种状态:
就绪:等待CUP资源
运行:执行过程中
堵塞:等待其他资源
终止:执行完成,或者被取消
线程的分离属性:
如果线程是非分离的,在线程结束时系统会保留他的系统资源和虚拟内存。有时可以被称为僵尸进程。
具有了分离属性,在线程解释的时候系统会自动释放掉未释放的系统资源。包括内存空间、堆栈、保存寄存器的内存空间。
注意在线程结束前要销毁互斥量。
头文件:
#include<pthread.h>
链接库:
-pthread
注意:exit ,_Exit,_exit 都是用来退出进程的。
共享资源
一个进程中的多个线程共享以下资源
可执行的指令
静态数据
进程中打开的文件描述符
当前工作目录
用户ID
用户组ID
私有资源
每个线程私有的资源包括
线程ID (TID)
PC(程序计数器)和相关寄存器
堆栈
错误号 (errno)
优先级
执行状态和属性
API
创建线程:
pthread_t pthread_creat( pthread_t *restrict attr , const pthread_attr_t *restrict attr , void *( *start_routine )( void *) , void *restrict arg )
通信
线程共享同一进程的的地址空间,所以线程间的通信通过全局变量来交换数据实现的非常方便。
但如果多个线程对用一个数据的访问存在先后顺序,那么,就需要引入同步或者互斥机制
定义信号
sem_t sem
初始化
sem_init(&sem,0,0)
初始化什么信号
0是线程通信,非0是进程通信
初始化的值为0,相当于 sem=0
P操作,堵塞
sem_wait()
V操作,唤醒
sem_post()
头文件
#include<semap[hore.h>
使用了线程的话,注意编译时加上 -lpthread
只有信号值为0的时候才能堵塞
同步机制
多个任务按照约定的先后次次序相互配合完成一件事情
1968年,基于信号量的概念提出了一种同步机制,即信号灯。
由信号量来决定一个线程是继续运行还是堵塞等待
信号量(灯)
信号量代表某一类资源,其值表示系统中的该资源的数量,(因此必定是一个非负数)
信号量是一个后保护的变量,只能通过3中操作来访问
初始化
P操作(申请资源)
V操作(释放资源)
P操作的本质
if (信号量的值 > 0)
{
申请资源的任务继续运行;
信号量的值减一;
}
else
{
申请资源的任务堵塞。
}
V操作的本质
信号量的值加一;
if ( 有任务在等待资源)
{
唤醒等待的任务,让其继续运行
}
Posix 中定义了两类信号量
无名信号量 用于线程间通信,不会创建文件
有名信号量 用于进程间通信,需要创建文件
互斥机制
临界资源
是一种特殊的资源,一般来说临界资源是一种共享资源,能够被多个任务访问到。
比如全局的一些数组,缓冲区,变量等,都应当认为是临界资源
一次只允许一个任务(进程、线程)访问的资源。
否则可能造成数据的混乱。
比如一个任务读数据,还没有读完就被另一个任务写入了,就会出现错误。
临界资源不光包括数据,还包括硬件等
临界区
访问临界资源的代码叫做临界区,其他的代码叫做非临界区
只有实现临界区的互斥才能保护临界资源
互斥机制
互斥锁
mutex 互斥锁
互斥锁只能被一个任务所持有,即使此时其他任务此时申请这个锁,依旧无法获得这个锁,必须等到这个锁空闲,不被任何任务所持有时才能获得这个锁
利用互斥所实现临界区的互斥
任务访问临界资源之前申请锁,访问完后释放锁
只有申请到锁的任务才能执行,其他没有申请到锁的任务都要堵塞,直到持有锁为止
锁的操作包括 初始化锁、申请锁和释放锁
如果临界区同时访问多个临界资源,一个互斥所就可以了,如果是不同的时候访问临界资源,则需要两个锁,这是一些最基本的原则
头文件
#include<pthread.h>