-
操作系统的中断和异常有什么区别?
中断是外部事件触发的,硬件设备发出的异步信号,用于向操作系统请求服务。中断事件发生时,会停止当前程序的运行,而转向中断处理程序的执行。在中断处理程序执行完成之后再回到原来的进程执行。
异常是cpu执行指令的时候遇到的错误和意外情况,是cpu内部的一种机制,所以异常是时钟同步的。比如除数为0等,他是在进程执行过程中遇到的问题。异常事件发生后,先进行异常处理程序中处理异常事件。执行完了再回来执行程序。
-
内存管理单元是硬件吗?他与页表有什么联系?
是的,MMU是一种硬件。集成在cpu或作为一种独立的芯片存在。它负责将虚拟地址转换为物理地址,并且提供内存保护与访问控制。
首先介绍页表:它是一种数据结构。假设我们有4GB虚拟地址空间与1GB的物理地址空间。将虚拟地址和物理地址分成大小4KB的页。并且虚拟地址和物理地址都从0开始编址。
a.页表长度:2^ 20个页表项(4GB/4KB)。每个页表项大小为4字节,因此页表长度为4MB。
b.页表项结构:有效位(valid bit):为1才有物理地址,物理页号(Pysical Frame Number,PFN),访问位(accessed bit),修改位(dirty bit)。访问权限(access control bit)。
有了页表,MMU的原理就是:
a. cpu发出读写请求,会讲逻辑地址放给MMU。
b. MMU将逻辑地址拆解成页号和页内偏移。
c. MMU在页表里找到页号和物理页编号。
d.找到了物理页号,将它与页内偏移拼在一起。发送给内存进行访问。
e.如果找不到相应物理页号,那就触发一个缺页中断,操作系统会讲相应的页掉入内存并且更新页表。然后再次进行上述步骤。
-
介绍一下linux进程调度算法。
进程调度算法是什么?进程调度算法是在就绪的进程队列里按照一定规则选择一些进程进行执行,以提升cpu利用率。现在主要分为三种调度器:O(1)调度器,O(n)调度器,CFS调度器。
a. O(n)调度器是Linux2.4中引入的调度器,维护的是一个全局队列。每次挑一个优先级最高的任务来执行(动态优先级),时间复杂度O(n)。找最高优先级的方法是goodness函数。struct task_struct中的nice(普通任务)和rt_priority(实时任务)值是静态优先级。动态优先级是根据静态优先级算出来的。大致逻辑是:普通任务goodness低于实时任务goodness。这一轮中没有时间片的被忽略,普通进程除了考虑nice值之外还要按照剩余时间进行奖励,根据cpu进行奖励(任务p继续运行在上次cpu中,能提升cpu缓存与TLB命中率)。按照内存空间进行调整,内存空间一样(mm_struct一样)说明是同一进程的两个线程,!p->mm判断是否是内核线程,所有内核线程共享一个内存空间。调度器会把权重+1。具体细节:https://s3.shizhz.me/linux-sched/history/o(n)-logic。
b.O(1)调度器是Linux2.6引入的调度器。O(n)调度最大的问题是使用了全局的任务队列,这在当前这种多核的情况下,拓展性的问题越来越突出。主要就是并发访问要加锁,时间复杂度高,实时任务与普通任务公用一个队列,任务在不同cpu中来回转,cpu可能会空闲等问题。O(1)首先为每个cpu都要设立一个runqueue。为了降低时间复杂度,为每一个优先级(0~99实时,100~139普通进程优先级)单独弄了一个任务列表。靠一个bitmap来给出哪一个优先级不为空。O(1)避免每次重新计算时间片(与静态优先级有关),转而每次用完(每次时钟中断减去1的时间片)立马充值, 计算动态优先级,放入expire列表中。在所有任务都没时间片直接切换active与expired列表,这样可以避免cpu闲置。交互式任务要奖励之后扔到active里,但也不要一直占着,避免expired中进程被饿死。实时进程调度比较简单,FIFO和RR,先进先出是实时进程除非自己退出cpu,不然不会退出。RR是判断时间片是否用完,用完重新分配,并且放回active中。
c. CFS(Completely Fair Schedule)是Linux2.6.23引入的一个完全公平的进程调度算法。将进程组织为一颗红黑树,每个节点是一个进程。节点记录的是虚拟运行时间(vruntime),虚拟运行时间越小,相应节点越靠左。每次选最左叶子结点作为下一个要运行的任务。vruntime+=实际运行时间*1024/进程权重(不运行的实际运行时间不增加)。进程权重是根据事先确定的nice值(nice越小权重越大)来确定的,优先级越高的进程权重越大,vruntime增长的慢。CFS里面有调度周期的概念,这个是靠任务动态数量算出来的。一个调度周期内,vruntime增长是一样的,vruntime增长的慢。这个可以保证每个任务在一段时间内可以被执行一次。在一个周期内每个进程有自己的配额,用完了就会被踢出去。min_vruntime是一个新的进程加入时要初始化的值,或者cpu之间切换离开时要减去的值。
d.dl调度器:有三个参数runtime,period,deadline。用EDF与CBS进行调度。EDF会找距离deadline最近的算法。CBS主要思想就是,为每个任务预留固定的runtime,如果在自己的runtime无法交差,那就放到下个period中再分配时间。在有新进程加入,还要检查cpu的带宽。这种进程的优先级很高。
e.rt调度器:rt任务的优先级在[1,99]之间,数字大优先级小。调度器为每一个优先级都单独维护了一个任务列表。
调度组:CFS就是每个cpu上都有自己的cfsrq,每次找vruntime最小的那个进行运行。现在存在的一种情况是:同一个调度组可能分布在多个cpu上。这个时候,资源是按照组来分配的。组内再进行二次分配。
调度器的知识很多,还涉及到调度类(stop_sched_class,dl_sched_class,rt_sched_class,fair_sched_class,idle_sched_class),不同调度类的调度策略。调度组,cpu带宽控制,负载追踪,负载均衡等等问题。
-
多线程以及多进程实现互斥或者同步的方式。
a.互斥锁,对于多个线程共享的数据。通过互斥锁来实现互斥访问。pthread_mutex_t, std::mutex;https://blog.csdn.net/m0_53539646/article/details/115509348?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-115509348-blog-80559865.235^v29^pc_relevant_default_base3&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-115509348-blog-80559865.235^v29^pc_relevant_default_base3&utm_relevant_index=1
b. 条件变量。用于多个线程之间通信和同步。pthread_cond_t和std::condition_variable。
c. 原子操作:常用的std::atomic。
d.信号量:信号量通常维护一个计数器,记录访问共享资源的线程数的值。p_thread_sem_t和s t d::semaphore.
-
多进程之间实现互斥和同步的方式。
a.信号量。
b.管道:一种半双工方式,实现进程间的数据传输。匿名管道只能用于父子进程的通信,命名管道可以用于进程间通信。
c.共享内存。
d.互斥锁。
e.条件变量。
-
互斥锁和自旋锁区别:
a.互斥锁使用操作系统的原语。利用内核空间实现锁机制,一个线程尝试获取一个已经有锁的资源会被阻塞。从用户切换到内核,有较高开销。
b.自旋锁就是提供了更轻量级的同步机制。多个线程进入临界区,让线程忙等待(不停循环检查锁是否被释放)。在高并发的情况下,如果线程的锁一直没被释放,会导致其他线程一直等待,造成资源的浪费。
互斥锁使用于用于占用时间长,资源竞争不激烈,临界区较大。自旋锁用于占用时间短,临界区小,竞争激烈。线程数多的时候,支持hyper- threading和SMT技术使用自旋锁。