进程和线程学习
计算机硬件
CPU
:
计算机的大脑,它从内存中取出指令,并执行之。
工作流程:从内存中取出指令 ---> 解码 --> 执行——>从内存中取出指令-->解码---> 执行
解码:用来确定指令的类型和操作数
每个CPU
有一套专门的指令集。由于用来访问内存以得到指令或数据的时间要比执行指令花费的时间要长,因此,每个CPU
内部都有一些用来保存关键变量和临时数据的寄存器
。
指令集中有一些执行如下操作:
1.将数据从内存中调入寄存器,或者从寄存器调入内存。
2.操作寄存器和内存中的数据,进行计算,然后把结果放到内存中或寄存器中。
寄存器:
通用寄存器:保存变量和临时结果
专门寄存器:
程序计数器:保存了将要取出的下一条指令的内存地址。指令执行后,该值就会更新。
堆栈指针:指向内存中当前栈的顶端。该栈含有已经进入但没有退出的每个过程的一个
框架。在一个过程的堆栈框架中,保存了有关输入参数,局部变量,以及那些没有
保存在寄存器中的临时变量。
程序状态字寄存器:包含:CPU
优先级,模式(用户态,内核态)等,在系统调用和I/O
中很有用。
操作系统必须知晓所有的寄存器,在时间多路复用的CPU
中,操作系统经常会中止正在运行的某个程序,并启动(或再启动)另外一个程序,每次停止一个运行者的程序时,操作系统必须保存所有的寄存器,这样在稍后再次运行该程序时,可以把这些寄存器重新装入。
存储系统
:
存储系统采用分层次存储结构。按速度从高到底排列如下:寄存器 高速缓存 内存 磁盘 磁带
他们的容量也是有小到达排列的。
系统调用
任何一个单CPU
计算机一次只能执行一条指令。如果一进程正在用户态中运行一个用户程序,并且需要一个系统服务,如:从一个文件中读取数据。那么它必须执行一个系统调用指令,讲控制移到操作系统,操作系统接着通过参数检查,找到所需要的调用进程(调用进程,名词),然后执行系统调用。并把控制权返回个在系统调用后面跟随这的指令。
从某种意义上讲:进行系统调用就像进行一个特殊的过程调用,但是只有系统调用才能进入内核,而过程调用则不能。
(个人理解:系统调用就是一些对系统进行操作的程序,例如 read系统调用,就有一个read库,
count = read(fd, buffer, nbytes) ,它是一个函数,需要三个参数,和我们的写的程序差不多,只是他更底层)
POSIX
有大约100多个过程调用,他们都是一些对如进程,读写文件,目录等等很底层进行操作的函数。
进程和线程
cpu
同一时刻只能处理一个进程。但在 1s 的时间内可以通过切换的方式运行多个进程,所以造成了并行的 错觉。
多处理器系统(有两个或多个cpu共享同一个物理内存)能实现真正的并行。
进程
:一个进程就是一个正在执行程序的实例。(包括程序计数器,寄存器,和变量当前值)
进程是程序的一次执行,是动态的。是cpu进行资源分配的最小单位。
进程的创建(4种主要事件会创建进程):
1.系统初始化
2.执行了正在运行的进程所调用的进程创建系统调用
3.用户请求创建一个新进程
4.一个批处理作业的初始化
unix中只有一个系统调用来创建一个新的进程fork
,windows中通过CreatePorcess
创建进程
进程的终止:
1.正常退出(自愿的)
2.出错退出 (自愿的)
3.严重错误(非自愿的)
4.被其他进程杀死(非自愿的)
unix中通过系统调用exit
来退出一个进程 windows中通过ExistPorcess
来退出一个进程。
杀死另一个进程,unix中用kill
,windows用TerminateProcess
。
进程可以只有以父进程,但是可以有0-多 个子进程。
在unix系统中,所有的进程都是以 init
为根的一根树 。
进程的三种状态:
1.就绪态(可运行,但是因为其他进程正在运行而暂时终止
2.运行态(该时刻该进程正在占用cpu)
3.阻塞态(除非某种外部时间发生,否则进程无法运行)
三种状态转换的可能关系:
运行-->阻塞 :进程等待输入而阻塞
运行-->就绪:调度程序选择了其他进程
就绪-->运行:调度程序选择了本程序
阻塞-->就绪:出现有效的输入(如果此时cpu空闲,那么就会立刻再次切换状态,进入运行状态)
调度程序:
决定应该运行哪个进程,何时运行以及它应该运行多长时间
进程表:
操作系统维护者一张表格,进程表。每个进程占用一个进程表项。该表项包含了进程状态的重要信息,包括:程序计数器,堆栈指针,内存分配情况,所打开的文件状态、账号,和调度信息以及其他进程状体切换必须保存的信息(保证该进程随后能够继续运行)
经测试:
如果进程花费80%的时间等待I/O
,为减少cpu的浪费,需要至少10个进程同时在内存中中。
如果进程只花费20%的时间用于等待I/O
,那么 两三个进程就可是使cpu得到极高的利用率。
线程
线程是cpu调度的最小单位。共享堆,有自己独立的栈。
线程也拥有一系列的东西,如
1.程序计数器
2.寄存器
3.栈
同一个进程内的所有线程:
1.有完全一样的地址空间,
2.共享全局变量
3.可以读/写/清除另外一个线程的堆栈。
线程状态:
新建 阻塞 就绪 运行 终止
线程通过 如thread_creat
等命令创建一个线程,当某个线程运行结束调用如thread_exit
等命令退出线程,
一个线程通过调用join
,等待(特定的)其他线程退出。在这个过程中,该线程是阻塞的,直到它等待的线程退出。
一个线程通过yield
来自动放弃cpu从而使其他的线程运行。
竞态条件:
多个进程(线程)对共享的资源进行读写操作
临界区:
对共享内存进行访问的程序片段叫做临界区也叫临界区域。
互斥:
采用某种手段,使一个进程(线程)访问某个共享资源时,其他进程(线程)不能做同样的操作。
几种互斥方案:
一.忙等待互斥
1.屏蔽中断。单系统处理器中,进程刚刚进入临界区之后,立刻屏蔽所有中断。并在就要离开的
的时候重新打开中断。(我的理解:禁止cpu切换进程,直至对共享区域的操作结束)
(中断:cpu只有发生中断的时候,才会进行进程切换)
缺点:1.用户负责该操作,可能会有遗忘等问题。2.如果是多cpu,那么只能屏蔽运行该
程序的那个cpu的中断,其他的cpu不受影响。可以继续访问共享资源。
2.锁变量。设置一个共享锁变量,初始值为0 ,当一个进程想进入临界区的时候,它先测试这把锁
如果该锁(也就是变量)为0,那么该进程将其设置为1,并进入临界区。如果该锁已经是1了
那么该进程等待,知道该锁变为0。
缺点:一个进程发现锁为0,在其恰好打算将锁置为1的这个空挡(嘿,好巧),另一个进程抢
先把锁置为1(怎么就这么巧),但是第一个进程没有发现啊,依旧把锁置为1,这个时
候,临界区内就有了两个进程。
3.严格轮换法
4.peterson 解法。
5.TSL指令
4.5解法的本质:一个进程打算进入临界区,先检查是否允许进入,如果不允许则进程原地等待
直到允许为止
缺点:浪费cpu或产生其他的错误
二:睡眠和唤醒
sleep
进程无法进入临界区的时候,不是忙等待而是睡眠(也叫挂起)。直到被其他进程唤醒
weekup
唤醒其他进程
依据上面两个产生如下几个方案:
1.生产者-消费者模型(又叫:有界缓冲区)
两个进程共享一个公共的固定大小的缓冲区。其中给m个是生产者,讲信息放入缓冲区,其他n
个是消费者,从缓冲区中取走信息。
当缓冲区已满,生产者就进入睡眠状态,当消费者从缓冲区取走数据之后,唤醒它。
当缓冲区已空,消费者就进入睡眠状态。当生产者在缓冲区放入数据之后,唤醒它。
三:信号量
一个整型变量来累计唤醒次数,以供以后使用。这个新的整型变量就是信号量。
信号量为 0或是正值
0:表示:没有保存下来的唤醒操作
正值:表示:一个或多个唤醒操作
两种操作:
down
up
对一个信号量执行down
操作即检查该信号量是否大于0,如果大于0 ,将其减1(即:用掉一
个保存的信号)并继续。如果信号量为0,则进程进入睡眠状态。
对信号量的操作是原子操作,(如检查值,修改值)一旦一个信号量操作开始,那么在完成操
作或者阻塞之前,其他进程都不能访问该信号量。
up
是对信号量的值加1,如果一个或多个进程在该信号量上睡眠,无法完成之前的down
操作
那么由系统在这些进程中随机选择一个,并允许它完成down
操作。于是对一个有进程在它上
面睡眠的信号量来说,执行一次up
操作之后,该信号量认为0,但是进程却少了一个。
二元信号量
:供两个或多个进程共用的信号量,其初始值为1,保证同时只有一个进程进入临
界区。每个进程进入临界区前执行一个down
操作,在刚刚退出的时候,执行一个up
操作,
从而实现互斥。
四:互斥量
信号量的简化版。取消了信号量的计数功能。
互斥量是一个处于加锁
和解锁
两个状态的变量
0 通常表示解锁,其他所有值表示加锁。
互斥量使用两个过程。当一个进程访问临界区时,它调用mutex_lock
(加锁)操作,如果该互斥
量是解锁状态(即临界区可用),则此调用(加锁)成功,进程进入临界区。另一方面,如果该互
斥量已经处于加锁状态,则调用线程进入阻塞状态,直到临界区中的进程调用unlock
(解锁)。
如果多个线程被阻塞在该互斥量,那么随机选择一个进程使其获得锁
五:管程
一个管程是一个由过程、变量、数据结构等组成的集合,他们组成一个特殊的模块或软件包。
进程可以在任何需要的时候调用管程中的过程,但它们不能在管程之外的声明的过程中直接访问
管程中的数据结构(双重否定表肯定:它们只能在管程内部访问管程里面的数据结构)
管程一个很重要的特性:任意时刻,管程中只能有一个活跃进程)
六:消息传递
进程间通信。send
和 receive
一个进程向一个给定的目标发消息,后一个从给定的源获取消息。如果没有消息,那么获取者就阻
塞,直到一个消息到达或者是带着错误信号返回。
七:屏障
#### 进程调度
进程切换:首先由用户态切换到内核态,然后保存当前进程,这运行调度算法选定一个进程,加载进程,进程执行。
不同的环境需要不同的调度算法:
批处理系统,交互式,实时。
批处理系统中的调度
1.先来先服务 ,非抢占式,顺序请求cpu
2.最短作业优先 非抢占式,要求运行时间可以预测。多个作业同时运行时才失效。
3.最短剩余时间优先 最短作业优先的抢占版本。调度程序总是调用剩余时间少的作业。
交互式系统:
1.轮转调度 每个进程分配一个时间片,到期就切换出去让其他的进程运行。
2.优先级调度 每个进程都被赋予了优先级 。为了防止高优先级的进程一直运行下去,每个进程赋予一个允许运行的最大时间片,到期之后,降低优先级。
优先级可以静态赋予,也可以动态赋予。可以系统动态确定,也可以认为确定。
3.多级队列 不同的优先级类运行不同的时间片,当一个进程运行完所有的时间片之后,进入下一级。
4.最短进程优先
5.保证调度
6.彩票调度 想进城发放系统资源(如cpu时间等等)的彩票,一旦需要调度了,就抽一个彩票,持有该彩票的进程获得资源
7.公平分享调度 根据用户分配进行调度(以上6中是根据进程调度)
实时系统中的调度
硬实时:到期必须满足
软实时:虽然有截止日期,但是要求并不严