小林coding 图解系统 chapter4

chapter 4 进程与线程

 

(先mark住开题之前的小故事,看完之后再来看这个小故事可能会有不同的收获!!!)

 

4.1 进程

我们编写的代码只是⼀个存储在硬盘的静态⽂件,通过编译后就会⽣成⼆进制可执⾏⽂件,当我们运⾏这个可执⾏⽂件后,它会被装载到内存中,接着 CPU 会执⾏程序中的每⼀条指令,那么这个运⾏中的程序,就被称为【进程】(Process)。
 
编写的代码(静态文件) ---> 编译(二进制可执行文件) ----->  运行这个二进制可执行文件(被装载到内存中) ----> cpu会执行每一条指令
 
上述描述中,这个正在执行的命令就是进程!!!

 

对于⼀个⽀持多进程的系统,CPU 会从⼀个进程快速切换⾄另⼀个进程,其间每个进程各运⾏⼏⼗或⼏百个毫秒。
虽然单核的 CPU 在某⼀个瞬间,只能运⾏⼀个进程。但在 1 秒钟期间,它可能会运⾏多个进程,这样就产⽣并⾏的错觉,实际上这是并发

 

1个cpu交替处理不同的进程,称作并发;

好几个cpu做着不同的任务,称为并行!!!

 
如果有大量处于阻塞状态的进程,会导致内存的浪费,进程会占用储存空间;所以在虚拟内存管理操作系统中,会将阻塞状态的进程换到硬盘当中,需要再次运行的时候,才换入到物理内存中去!!!
 
所以,描述进程没有占用实际物理内存的状态就是------> 挂起状态!!!
另外,挂起可以分成两种:一个是就绪态挂起,一个是阻塞态挂起;
 
阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
就绪挂起状态:进程在外存(硬盘),但只要进⼊内存,即刻⽴刻运⾏;
 
 
进程的控制结构:
在OS中,用PCB(processing control block)来描述进程
 
那么PCB中有什么信息呢?
进程描述信息,进程控制和管理信息,资源分配清单,CPU 相关信息
 
通常PCB使用链表的形式串在一起,把相同状态的PCB串在一起

 

但是,除了链表的方式之外,还有索引的方式;不过为了方便数据的增删改查,还是使用链表比较方便!!!

 

进程的控制 :1、创建进程,2、终止进程,3、阻塞进程,4、唤醒进程

创建进程:为新的进程申请一个新的进程标识符,再申请一个空白的PCB(PCB是有限的,若申请失败则创建进程失败)

终止进程:有三种方式终止进程:正常结束,异常结束和被kill掉

                先找到想要结束的进程的PCB

                若处于执行状态,则立即终止他的运行状态,把资源还给cpu

                若其还有子进程,则需要立即终止其子进程,将资源全部返还给操作系统或者父进程

                将PCB从所在队列中剔除

阻塞进程:找到想要阻塞的进程的PCB 

                  若其正在运行,保护其现场,将其状态改为阻塞态

                  将PCB插入到阻塞队列当中去

唤醒进程:找到想唤醒的进程的PCB

                  从阻塞队列中移除,标志状态改为就绪态

                  将PCB插入到就绪队列当中去

 

进程的上下文切换:表示的是PCB的切换,因为进程的环境就是PCB。通常进程会把信息保存在PCB中,通过PCB来实现环境的切换!!

cpu的上下文切换:表示的是cpu寄存器和程序计数器(PC)所依赖的环境,这两个代表了cpu运行的环境------> 环境的切换,就是cpu上下文的切换!!!

 

 

线程:线程就是进程的一条执行流程!!

线程的优点:

1、不同线程可以并发执行

2、一个进程当中可以有多个线程

3、各个线程之间可以共享资源

线程的缺点:

一个线程挂了,所有线程都挂了!!!

 

如果两个线程不是同一个进程的话,那么线程切换就是上下文切换;

如果两个线程是一个进程的话,那么线程的切换就是进程内部的切换:只需要切换私有数据就可以了。

 

线程主要有三类:

用户级线程,内核级线程和轻量级线程

 

调度算法:

调度算法分成两类:一种是抢占式调度算法,一种式非抢占式调度算法

抢占式调度算法是指:挑选一个进程,设置一定长时间的时间片,如果时间片结束之后该进程依然没有执行完毕,则该进程被挂起,OS选择其他的进程来运行

非抢占式调度算法:运行一个进程,如果他的运行时间片用完或者是运行结束之后,才会调用另外的进程!!!

为了实现一个优秀的调度类算法,我们需要做的就是考虑上述5个问题的实现效率!!!

CPU 利⽤率:调度程序应确保 CPU 是始终匆忙的状态,这可提⾼ CPU 的利⽤率;
系统吞吐量:吞吐量表示的是单位时间内 CPU 完成进程的数量,⻓作业的进程会占⽤较
                      ⻓的 CPU 资源,因此会降低吞吐量,相反,短作业的进程会提升系统吞吐量;
周转时间:周转时间是进程运⾏和阻塞时间总和,⼀个进程的周转时间越⼩越好;
等待时间:这个等待时间不是阻塞状态的时间,⽽是进程处于就绪队列的时间,等待的时
                   间越⻓,⽤户越不满意;
响应时间:⽤户提交请求到系统第⼀次产⽣响应所花费的时间,在交互式系统中,响应时
                   间是衡量调度算法好坏的主要标准。

 调度算法的分类:

1、FCFS(first come first serve)----> 先来先服务

这很简单,就是先来的进程会优先服务,运行处理完之后,再运行or处理下一个进程

2、SJF(shortest job first)-----> 短作业优先法则

这也很简单,就是谁的运行时间最短,就优先服务谁。

3、HRRN(highest response radio next)(为什么不用first,不理解TAT)高相应比优先

相应比 = (等待时间+运行时间)/(运行时间),相对的,运行时间越少,或者等待时间越多则相应比就越高!!!

所以这种方法是 短任务优先+会服务长进程的算法(因为长进程肯定要等待很久来能轮到,所以客观上有利于长进程的运行!!!)

4、RR(Round Robin) ----> 时间片轮转调度法

每一个进程分配一个时间片(最理想的时间片应该是20ms--50ms)

如果一个进程在时间片所规定的时间之内用完了时间片,但是进程依然没有处理完成的话,则进程被挂起把cpu的性能释放出来,为下一个进程做服务!!!

如果在规定的时间片的时间内,进程已经运行完毕了,那么操作系统会将cpu立即切换到其他的进程!!!

5、HPF(highest processing first)最高优先级调度法

进程的优先级:静态进程优先级+动态进程优先级

静态优先级是创建进程的时候已经分配好的,优先级是不变的

动态优先级是会随着时间的流逝而改变的;根据进程的动态变化调整优先级,⽐如如果进程运⾏时间增加,则降低其优先级,如果进程等待时间(就绪队列的等待时间)增加,则升⾼其优先级,也就是随着时间的推移增加等待进程的优先级。

最高优先调度法依然会有抢占式和非抢占式

抢占式:当就绪队列中出现优先级较高的进程的时候,优先处理,将当前运行的进程挂起,优先运行优先级较高的进程

非抢占式:依次按照顺序来处理

6、MFQ(Multilevel Feedback Queue)多级反馈队列

多级:就是有多个就绪队列,每个队列的优先级从高到低,优先级越高时间片越小

反馈:就是有优先级较高的进程加入队列,那么应该立即停止当前运行的进程,转而去运行优先级较高的进程!!!

 

上述的6种调度算法必须要记得挺牢!!!

 

4.2 进程之间的通信

 

我们知道,进程的用户空间是不能被独立的,一般而言是不能被访问的,但是内核态确实可以共享的!!!

 

Linux内核提供了不少的通信方法:

1、管道;2、消息;3、信号;4、信号量;5、共享内存;6、socket(插座)(现在的我只是把他背下来了)23333

 

1)管道:管道的传输是单向的,如果要想实现相互通信的话,需要两个管道!!!

效率太地下了

2)消息队列:消息队列是保存在内核中的消息链表!!!(类似于回邮件,你回我一封信,我回你一封信)

缺点:通信不及时,传输信息有大小要求!!!

3) 共享内存:消息队列会有用户态空间与内核态空间相互拷贝的问题(我也不太懂为什么)

拿出一块虚拟内存来来,映射到相同的物理地址当中去。这样就不需要互相拷贝了,就会共享了!!!

4)信号量:共享内存之后,如果同一时间多个进程同时修改一个共享内存,那么势必会引发冲突,所以信号量正是需要的所谓的保护机制!!!

P操作:用在共享资源之前;信号量减去1,如果信号量>=0,则还可以继续分配;若信号量<0则不可以进行分配

V操作:用在共享资源之后:信号量加上1,

5)信号:上述方式都是正常状态下进行的通信,那么不正常的状态下进行的通信该怎么去操作呢?

6)socket:表示跨网络在不同主机上进行通信!!!(socket没怎么看全,知道其中有tcp,udp和本机进程通信)

 

 

 

4.3多线程同步

多线程当中,如果共享资源没有上锁的话,那么多线程之间的竞争会非常的可怕,同时可能会让进程崩溃!!!

所以多线程同步本质上就是解决共享资源分配的问题!

互斥进程 :

互斥不只是针对对线程的,也可以对多进程竞争共享资源的时候,可以使用,避免造成资源混乱!!!

上述图片就是互斥的本质!!!

线程同步:

在一些关键点上需要线程之间相互通信,相互等待。这种相互通信,相互等待就称为线程同步!!!

 

加锁 or 设置信号量!!!

原子操作:原子操作是要么全部执行,要么全不执行,不可以出现执行一半的情况!!!

如果互斥信号量为 1,表示没有线程进⼊临界区;
如果互斥信号量为 0,表示有⼀个线程进⼊临界区;
如果互斥信号量为 -1,表示⼀个线程进⼊临界区,另⼀个线程等待进⼊。
很有味道,可以仔细品读一下!!!
 
生产者---消费者问题
生产者在生成数据之后放在一个缓冲区之内
消费者在缓冲区之内取出数据
当且仅当只有一个生产者和消费者可以访问缓冲区!!!
 
当且仅当在同一时间只有1个生产者和消费者可以访问缓冲区,所以操作缓冲区是临界代码,需要进行互斥操作
缓冲区空的时候,需要进行生产操作(加数据),在缓冲区满之后,需要进行消费者操作(减数据)!!则说明生产者和消费者需要进行同步操作
 
则,我们需要三个标志量:
互斥信号 mutex;资源信号量 fullBuffers;资源信号量 emptyBuffers
fullBuffers 用于生产者询问缓冲区内是否有数据,初始化为0
emptyBuffers 用于消费者询问缓冲区是否有空位,初始化为 n (缓冲区本来的大小)
 

 

没理解哲学家就餐问题(TAT)

哲学家就餐问题放到之后回答!!!

 

 

 

 

4.4 死锁

两个线程一直都在等待对方释放锁,没有外力的作用下,这些线程就会一直等待,这就是死锁!!!

死锁只有同时满⾜以下四个条件才会发⽣:
互斥条件;
持有并等待条件;
不可剥夺条件;
环路等待条件;
 
互斥条件是指:多个线程不可以同时获得一个资源
持有并等待条件:一个线程已经拥有了资源1,但是线程还想拥有资源2,但是资源2被另一个线程持有的时候,这个线程就必须持有并等待
不可剥夺条件:线程在拥有资源时,会保证线程在完成之前,手里的资源不会被其他线程所占有
环路等待条件:线程1拥有资源1,但是还想要资源2,线程2拥有资源2,还想要资源1,形成环路,卡住并等待
 
如何避免死锁问题的发生:
因为死锁的产生需要四个条件:互斥条件,持有并等待条件,不可剥夺条件,环路等待条件
所以,我们只需要剥夺上述四个条件中的任何一个条件即可!!!
我们可以采用有序资源分配法,来进行处理
意思就是,如果线程1和线程2都需要资源A和B的话,可以让线程1和线程2需要的资源顺序保持一致!!!即线程1需要的资源顺序为A、B,线程2需要的资源顺序也为A、B
 
4.5 乐观锁&悲观锁
互斥锁,自旋锁,读写锁,乐观锁,悲观锁
互斥锁加锁失败之后会释放掉cpu的资源,线程会被堵塞
自旋锁加锁失败之后,会一直忙等待,知道重新发现了锁。利用CPU周期,直到锁可用
 
 
读写锁:在读的过程中,用读锁加锁;在写的过程中,用写锁加锁。这样,我们可以非常清晰的判断出线程再进行读操作还是写操作了
读锁可以被好几个线程触发,也就是说,在读操作过程中,可以有很多的线程进行读操作;
但是写锁只可以被一个线程触发,在进行写操作的时候,只有一个线程可以进行写操作。一旦写锁被触发的话,读操作的进程就会被堵塞,其他进程获取写锁的操作也会被堵塞!!!!
(还有读优先锁和写优先锁)!!!
 
上述的互斥锁,自旋锁,读写锁都是悲观锁 ----- > 很容易出现冲突的情况下,采用悲观锁
但是,有一部分进程是不容易出现冲突的,这时候就需要采用乐观锁了:先修改共享资源,如果修改完之后,其他的线程修改过,那么此次修改是成功的;但是,如果修改完之后,其他的线程已经修改过这个资源,那么此次的修改失败了,放弃本次操作!!!!
 
 
posted @ 2022-06-03 14:34  Dyral_HAN  阅读(402)  评论(0编辑  收藏  举报