重学OS 第一章/第二章 引论/线程与进程
主要记录一些细节和思考
通过库过程系统调用,感觉和过程调用的区别主要在6,9,即不会陷入内核,性能上的区别在之后的博客会讨论
典型的进程表项中的内容,具体实现可能有不同,进程和线程切换的开销有一部分出自这里,之后讨论
这是书中的一种陷入中断的处理流程,和计组中学到的有点不同在于:第三步汇编语言过程会将寄存器值保存入进程表项,这里保存的包括第一步硬件压栈的寄存器和无法用硬件压栈的一些寄存器,而机组中不存入进程表项只压栈。对这个不同的理解是可能保护现场只是一个概念,并没有绝对的要求,只要保护了现场就行。
同时有一个细节,进入中断处理例程之后使用的堆栈与之前堆栈不同,使用的是一个临时堆栈(第四步),有两点感受:
1.查了一下,大多操作系统中断处理例程没有线程化,没有自己的进程表项也没有自己的堆栈
2.进入中断处理例程后的操作和之前被中断的进程解耦了,因为中断处理是一致性操作
补充一个,在网上查了之后发现一般情况下中断处理过程中是不会被挂起的,总结了下主要原因:
1.直接原因是之前说的中断处理例程没有线程化,没有自己的进程表项,挂起之后就回不来了,但这个设计出现的原因是不需要中断被挂起,所以也不用给他进程表项。
2.根本一些的原因是中断处理的优先级高于普通进程调度,出现中断代表有一些事相对迫在眉睫(试想一个缺页中断)。另外中断是可嵌套的,因此挂起一个中断可能导致很多进程陷入中断无法运行,而这些中断相对紧迫,这时去调度执行一个不那么紧迫的进程显得违背常理。
3.据说Linux可以中断线程化,不太懂
这些只是我的一些感受,欢迎大家补充
关于线程,书中有几个值得注意的地方:
1.由于各个线程可以访问进程地址空间的每个内存地址,所以一个线程可以读写甚至清除另个线程的堆栈,没有办法也没有必要在线程间保护(线程间不胡乱操作应该由写进程的人确认)
2.每个线程都有自己的堆栈
3.不同于进程,线程库没有办法利用时钟中断强制线程让出CPU
用户线程:每个进程在其用户空间由自己的线程表,线程切换时不需要陷入内核,上下文切换(这里其实需要一个线程的上下文切换,但保存的只是线程自己的寄存器不用保存进程控制块),高缓刷新,速度很快。但同时也存在很多问题,这种情况下内核甚至不知道线程的存在,这就导致一个线程阻塞时往往会阻塞这个进程,即使其他线程可用。另外,内核无法主动中断一个线程,如果线程想无限运行,就会占据这个进程的所有时间片。这两个问题有一些解法,但都不尽人意
内核线程:在内核空间有一个线程表,线程切换陷入内核,因此要慢一些。由于在内核切换也就解决了上面的问题。同时,内核切换时可以选择切换到同一进程的其他线程或者其他进程的线程(这就变成进程切换了)
所以目前的解决方案是混合线程,权衡二者的优势
为什么线程切换快?
1.线程切换的上下文是进程切换的上下文的子集
2.进程切换刷新Cache(比如TLB),导致命中率降低,其实从直觉来讲Cache应该也不用总刷新,查了一下果然https://blog.****.net/zdy0_2004/article/details/54956787 (后面讲的看不懂,但是可以优化是明白了)。另外硬件TLB就是寄存器,应该也可以保存入进程表项,但软件TLB的保存可能就慢了。同时进程可能被换出到磁盘,这时保存的TLB按理来说就完全失效了。总的来说切换时Cache会受到影响。
3.用户线程之间切换,不需要陷入内核
关于这个线程进程切换的消耗还是不太清晰,欢迎大家挑错
不同于进程切换,陷入内核的开销:暂停当前进程,保存上下文,切换至内核堆栈
进程间竞争
忙等待的互斥:屏蔽中断,锁变量,严格轮换算法,Peterson解法,TSL指令
忙等待在用户线程中可能出现由于一个线程一直循环导致这个进程一致等待
注:自旋锁是忙等待
非忙等待:
睡眠与唤醒,
信号量:如果有多个CPU,每个CPU要有一个锁变量保护,使用TSL或XCHG确保同时只有一个CPU操作信号量。使用TSL和XCHG保护信号量和忙等待的TSL不同,前者非常快只需几毫秒,后者可能用任意长的时间,
互斥量:futex:没有锁竞争则不陷入内核(由于up,down是系统调用,这里减少这两者从而减少陷入内核)
pthread:如下图,pthread_cond_wait等待时会原子性的释放互斥量,等待结束后会锁住互斥量
注:while(buffer!=0) pthread_cond_wait 这句的原因是可能出现意外唤醒,这时的buffer不能使用,所以要用while再次挂起
管程:是编程语言的一部分,因此没有管程机制的语言(如C/C++)实现管程相当困难
消息传递:缓冲区使用地址编码或信箱。需要解决消息是否正确传递的问题和身份认证,这一部分很像网络传输
屏障
避免锁:可以使用读--复制--更新(RCU)来避免锁。有个问题:不能保证读到的是新版本还是旧版本,但可以保证不会读到新旧中间态(即一定是正确的数据)。
进程调度
这里也提到了调度开销,这是很重要的部分
批处理调度
先来先服务:周转时间可能不好(先来的很多大任务耽误了后面的小任务)
最短作业优先:只有在所有作业同时可运行时,最短作业优先算法才是最优的,因为每个时刻不能知道以后的情况,所以只能做当前最优解。
最短剩余时间优先:可以使新来的短作业有较号的服务,但问题在于很难预测剩余时间
交互式系统调度
轮转调度:时间片的设置很重要,设置太短会经常切换进程开销较大,设置太长会导致后来的服务相应时间过长
优先级调度:动态优先级可以在调度运行一个进程后降低它的优先级。也可以使用静态优先级,但这样可能饿死低优先级。
多级队列:队内先来先服务,队间轮转,最低优先级内部也是轮转。各个队列时间片不同,越低越长,调度后降低优先级。
最短进程优先:使用老化预测下次运行时间
保证调度:向用户保证每个进程CPU的分配,之后按照保证分配
**调度:为每个进程发放一定数量**,中奖的进程运行,这种方式出奇的有效,但是有时不可靠
公平分享调度:按照用户分配CPU
实时系统调度
使用负载判断是否理论可调度
不出意外今天开始恢复写博客,以后稳定
重学《现代操作系统》,这东西太有用了