操作系统真相还原
整理原因
简历上写了该项目,准备不充分,被拷打,因此该项目需要复盘,同时倡议有该项目的应该优先注重。(不作详细介绍,只介绍各模块重点并联系专业所学知识)
开发环境
Ubuntu20.04,bochs(虚拟机中装虚拟机)
- 为什么使用这个开发环境?
bochs硬件调试相当方便,容易调试内核运行
(1)可查看页表,gdt,idt等数据结构等
(2)可以查看栈中数据
(3)反汇编任意内存
(4)实模式和保护模式互相变换时提醒
(5)中断时发生提醒
MBR
- 怎么载入你的操作系统的?
BOIS载入地址0x7c00,校验启动盘中位于0盘0道1扇区的内容,启动系统引导盘。 - 系统引导盘是什么,干了什么?
系统引导盘受限于512字节大小,这么小的空间没法准备好环境,更加没法将内核成功加载到内存并运行。 它只完成关键操作
(1)需要将编译起始地址设置为0x7c00
(2)初始化寄存器,就像ds,es,fs,gs之类段寄存器
(3)初始化栈指针
(4)通过gs寄存器直接向显示屏输出提示
(5)跳转到loader加载器,实现硬盘读取扇区功能
实模式和保护模式
- 实模式和保护模式区别
(1)实模式下操作系统和用户态属于同一特权级
(2)用户程序所引用的地址都是指向真实的物理地址,也就是说逻辑地址等于物理地址
(3)用户程序可以自由修改段基址,可以访问所有内存
(4)访问64k的内存区域时需要切换段基址
(5)一次只能运行一个程序
(6)共20条地址线,最大可用内存为1M - 怎么从实模式跳转到保护模式?
(1)打开A20
(2)加载gdt
(3)将cr0的pe位置1 - 物理内存和虚拟内存区别和关系
(1)物理地址是指计算机中实际存储数据的位置,虚拟内存是一种操作系统管理内存的机制,他将硬盘空间和物理内存组合成一个更大的内存池。虚拟内存的大小通常比物理内存大很多,可以根据需要动态调整。
(2)物理内存是不连续的,而虚拟内存是连续的,虚拟内存是一个连续的地址空间(这也只是进程认为),而实际上,它通常是被分隔成多个物理内存碎片,还有一部分存储在外部磁盘存储器上,在需要时进行数据交换。
(3)物理内存时有限的,当多进程需要执行的时候,可能会缺少内存资源,效率很低,而虚拟内存可以为每个进程分配4G虚拟内存。 - 虚拟内存怎么转换为物理内存
一级页表(页目录表),里面存着页表地址
二级页表(页表),里面存着物理页地址
(1)得到页表物理地址,页部件用虚拟地址高 10 位乘 4 的积与页目录表物理地址相加,得到页目录项,取里面的值页表物理地址
(2)得到物理页地址,页部件用虚拟地址中间10位乘4的积与页表物理地址相加,得到页表项物理地址,读取里面的值物理页地址
(3)用虚拟地址低 12 位作为页内偏移地址与物理页地址相加 - 内存分页机制怎么实现的?
(1)CPU从底层就支持了,CPU在硬件一级上添加了 GDTR LDTR 寄存器来支持全局描述符表和局部描述符表,段描述符是内存段的身份证.
(2)启用分页机制
a.创建好页目录表及页表,并且设置内核空间和用户空间
b.将页表地址写入控制寄存器cr3
c.寄存器cr0的PG位置1 - 用户空间和内核空间区别?
(1)在用户态下,进程运行在用户地址空间中,被执行的代码要受到 CPU 的很多检查,比如:进程只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址。
(2)在内核态下,进程运行在内核地址空间中,此时 CPU 可以执行任何指令。运行的代码也不受任何的限制,可以自由地访问任何有效地址,也可以直接进行端口的访问。
(3)内核所在的空间完全被所有进程共享,所有进程都可以使用内核提供的服务,内核若为任意一个用户进程在内核空间中创建了某些资源的话,其他进程都可以访问到该资源
中断
- 中断是什么?
CPU由于某些情况 , 暂停了正在执行的程序,转而去执行处理该事件的程序,当这段程序执行完毕后, CPU 继续执行刚才的程序。整个过程称为中断处理,也称为中断。 - 中断有什么用?
(1)如果只有一个CPU的话,任何程序都必须以串行的方式轮流调度到CPU上运行。然而,在 CPU 外的设备是独立于 CPU 的,它与 CPU 是同步运行的,所以, CPU 抽出一点时间来处理中断,外部设备就可以同 CPU 并行工作了,整个计算机系统可以大幅度地提升效率。
(2)“没有中断,操作系统几乎什么都做不了,操作系统是中断驱动的”。首先,操作系统是个死循环,以此保证操作系统能够周而复始地运行下去,而运行的目的是为了等候某些事情发生。,有事情发生它才会工作,所以它是被事件驱动的,而这个事件是以中断的形式通知操作系统的。 - 中断的分类
(1)外部中断
外部中断又叫做硬件中断,其中断源必须来自某个硬件。
(2)内部中断
内部中断可以分为软中断和异常
软中断,就是由软件主动发起的中断,是主观上的,并非是客观上的某种内部错误。
异常是另一种内部中断,是指令执行期间 CPU 内部产生的错误引起的。
一下是可以发起中断的指令:
(1)int 8位立即数,我们要通过它进行系统调用,8位可以标识256种中断,这与处理器所支持的中断数是相吻合的,(软中断)
(2)“int3”,是调试断点指令,(异常) - 硬中断和软中断区别
外部中断是可以屏蔽的中断,也就是说,利用中断控制器可以屏蔽这些外部设备 的中断请求。内部中断是不可屏蔽的中断。 软件中断其实并不是真正的中断,它们只是可被调用执行的一般程序,内部中断优先级会比外部中断高。 - 你了解中断描述表吗?
实现中断机制,我们需要使用中断描述符表存储中断处理程序入口的表,当CPU接受到一个中断时,需要用中断向量在此表中检索对应描述符,在该描述符中找到中断处理程序的起始地址,然后执行中断处理程序。
中断描述符不仅仅存着中断描述符,还可以有任务门描述符和陷阱门描述符。 - 中断处理过程
完整的中断过程分为CPU外和CPU内两部分
CPU外:外部设备的中断由中断代理芯片接受,处理后将中断的向量号发送到CPU
CPU内:CPU执行到该中断向量号对应的中断处理程序。
处理器内的过程:
(1)处理器根据中断向量号定位中断描述符
(2)处理器进行特权级检测,主要比较当前特权级(CPL)和门描述符(DPL)
(3)执行中断处理程序 - 中断怎么跳转,又是怎么恢复原来环境的?
中断在发生后,处理器收到一个中断向量号,会根据此在中断描述符表中找到相应的中断门描述符,门描述符中保存的时中断处理程序所在的选择子以及段内偏移量,处理器从该描述符中加载目标代码段选择子到CS以及EIP。
为了从中断返回后能继续运行该进程,处理器自动把 ESP,EIP 的当前值保存到中断处理程序使用的栈中。由于不同特权级别下处理器使用不同的栈,同时是否由特权级变换,我们栈存入的数据也不同。中断程序完成后,使用iret指令返回。


内存管理系统
- 这么大的系统工程开发,你如何保证其正常运行,或是出错快速定位错误的?
我实现了断言(ASSERT),监督程序该有的条件状态,一旦条件不符合,就会报错并将程序挂起。 - 内存管理系统怎样实现的?
实现内存地址池,将内存池分为用户物理内存池和内核地址池,他们各自又有自己的虚拟内存池和物理地址池。申请内存时,从内存池的虚拟地址池中分配虚拟地址,再从内存池中物理内存池中分配物理内存,然后在自己的页表将这两种地址建立好映射关系。 - 怎么实现虚拟内存到物理内存的映射的?
前提:(1)首先的了解虚拟内存如何转化到物理内存的。
(2)我们在最后一个页目录项里写的是页目录表自己的物理地址
(3)我们是通过一个*(vaddr)转换为一个新(new_vaddr)
拼凑虚拟地址分为三步:
(1)需要高10位来定位 pde。
(2)凑出中间10位来定位pte索引。
(3)在页表中找到pte,乘4即为低十二位。
然后在页表中添加虚拟地址 vaddr 与物理地址 page_phyaddr 的映射 - 内存管理系统完成了什么功能?
(1)虚拟内存池申请虚拟地址
(2)物理内存池中申请物理页
(3)虚拟地址和物理地址在页表中完成映射
线程和进程
- 线程是什么?
在高级语言中,线程是运行函数的另一种方式,也就是说,构建一套线程方法,让函数在此线程中被调用,然后处理器区执行这个函数,因此线程的实际功能相当于调用或者函数,让函数执行。我们会给其所依赖的上下文环境(寄存器映像和栈等资源),使其具有独立性,成为执行流,即调度单位。
简而言之,就是使所运行的函数能够以调度单元的身份独立上处理器运行,这样我们能让多个函数(执行流)并行运行。 - 说说线程和进程?
对于处理器来说,进程是一种控制流集合,集合至少包含一条执行流,执行流之间是相互独立的,但他们共享进程所有资源(堆和方法区资源)。
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。 - 聊聊用户空间实现线程和内核空间实现线程
线程包括内核线程和用户空间中的线程,它俩的区别在于线程表在哪,由谁调度上处理器。
用户空间:操作系统是不会意识到线程的存在,因为操作系统调度器只会以整个进程的方式调度,处理器的使用权会交给进程,进程中的调度器自己去协调分配处理器时间。所以我们要实现进程内的线程调度器,以及在进程内维护线程表。
内核空间:由内核提供原生线程机制,用户进程中不再单独实现。
线程由内核来实现。优点:1.阻塞时,操作系统认识线程,进程的某一个线程阻塞后,进程内其他线程将不受影响。2.会让进程多占用处理器资源缺点:用户进程需要通过系统调用陷入内核,增加现场保护的栈操作。
我实现的内核空间实现线程 - 你如何实现线程的?
(1)实现线程需要进程,实现进程我们需要,构造PCB程序控制块,他表示进程的相关信息,比如进程状态,PID,优先级等,调度器可以根据这张表选择上处理器运行的进程。
(2)构建中断栈,保护线程或进程的上下文环境
(3)构造线程栈,用于存储线程中待执行的函数。
(4)创建线程,我们需要初始化线程栈thread_stack,将待执行的函数指针和参数放到thread_stack中相应位置,最后根据线程栈pop到寄存器,他就会开始执行 - 线程调度时需要注意些什么?
任务调度是由时间中断发起的,在调用switch_to函数时,中断处理程序又要被中断,因此我们需要保护好这任务两层执行流的上下文。分两部分完成:
(1)进入中断前的保护,保存的是任务的全部寄存器映像,相当于任务中用户代码的上下文。
(2)上下文保护的第二部分负责保存这4个寄存器 esi,edi,ebx和ebp ,目的是让任务恢复执行zai1任务切换发生时剩下尚未执行的内核代码, - 你如何实现多线程调度的?
完整的调度需要三部分的配合
(1)时间中断处理函数,每个线程都有一定时间片,优先级越大,时间片越多。
(2)调度器schedule,进行任务调度, 想如果某线程时间片到了,就将其加入就绪队列队尾,根据优先级重置时间片。然后弹出就绪队列链表第一个就绪线程,调度上CPU。
(3)任务切换函数switch_to,根据ABI(应用程序二进制接口),我们只需要将ebp,ebx,edi,esp和esi五个寄存器进行备份和恢复就行。
同步机制
- 多线程同步的四种方式
(1)互斥锁,需要满足,互斥,无死锁,无饥饿
(2)条件变量
(3)读写锁
(4)信号量,(生产者和消费者) - 如何实现同步机制的?
我是通过互斥锁实现多线程同步的,其核心思想是有线程访问进程空间中的公共资源时,该线程执行“加锁”操作(将资源“锁”起来),阻止其它线程访问。访问完成后,该线程负责完成“解锁”操作,将资源让给其它线程。当有多个线程想访问资源时,谁最先完成“加锁”操作,谁就最先访问资源。 - 如何阻塞线程的?
阻塞时线程自己发出的动作,他会把其线程状态调为阻塞态,然后换下CPU。
用户进程
- 你如何实现用户进程的?
进程与内核线程最大的区别是进程有单独的 4GB 空间,因此我们需要单独为每个进程维护一个虚拟内存池。同时因为进程有独立的地址空间,不同的地址空间就是不同的页表,我们在创建进程的过程中需要单独创建一个页表
因为在实现用户进程之前,我的代码一直运行在0特权级下,但用户进程代码工作在特权级3.我们需要借用中断返回的方式从0特权级进入3特权级。
进入三特权级之后,我们就可以开始创建用户进程
(1)申请内存创建进程pcb
(2)初始化进程
(3)为用户创建管理虚拟地址空间的位图
(4)创建内核线程,使其调用用户进程启动函数
(5)为进程创建页表。将进程pcb加入就绪队列和全部队列。 - 进程间的通信方式
signal,file,pipe,shm,sem,msg,socket
1.signal
信号又被称之为中断,需要处理什么对应的是中断处理函数,此时设置断点,形参入栈,保存现场信息,然后去执行中断处理函数,当处理完成之后,恢复现场信息,程序继续往下执行。
局限性:不能够传递复杂的、有效的、具体的数据
2.file
每打开一个文件,就会产生文件控制块,而文件控制块与文件描述符是一一对应的,通过对文件描述符的操作进而对文件进行操作。可以通过文件系统对文件描述符的读/写控制,进程间一方对文件写,一方对文件读,达到文件之间的通信;可以是不相关进程间的通信
局限性:文件通信没有访问规则,是低俗的
3.pipe
在通信的进程间构建一个单向的数据流动的通道,数据通过管道从一个进程流向另一个进程是具有时间先后顺序的,所以是半双工通信;管道文件是一种临时文件,不是磁盘上真真正正的文件,是一块内存区域
无名管道:只能用于亲缘关系的父子进程,fd = pipe(),得到的是管道文件描述符
有名管道:非父子进程间通信mkfifo(),mkfifo会在文件系统中创建一个管道文件,然后使其映射内存的一个特殊区域,凡是能够打开mkfifo创建的管道文件进程(通过这个文件描述符),都可以使用该文件实现FIFO的数据流动
特点:
(1)管道为空,读取数据的一方会阻塞,直到管道中有新的数据为止
(2)管道的数据通信有FIFO特性,这样可以避免数据混乱
(3)管道数据的读取与发送并没有次数限制,而是管道是否为空时最重要的指标
(4)这种管道的使用具有一个最大的局限性:只适用于父子进程,从程序的设计中可以看到,管道的创建是父进程完成的,而是在创建子进程之前,从而才使子进程拥有管道描述符,才能够使父子进程持有管道的入口和出口。
(5)一个管道只能实现单向的数据流
4.shm(共享内存)
共享内存使用:
(1)建立进程与共享内存的映射关系
(2)读/写
(3)如果对于共享内存的使用结束,此时就要断开与共享内存的映射
5.sem
信号量:进程在访问共享资源是存在冲突的,必须的有一种强制手段说明这些共享资源的访问规则。
信号量:表示的是一种共享资源(空闲)的个数,对共享资源的访问规则。
信号量使用:
(1)用一种数量去标识某一种共享资源的个数
(2)当有进程需要访问对应的共享资源的时候,则需要先进行查看,根据资源对应当前可用数量进行申请
(3)资源的管理者(操作系统内核),就使用当前的资源个数减去要申请的资源个数,结果 >=0,表示有可用资源,允许该进程继续访问;否则表示资源不可用,则告诉进程(暂停或者立即返回)
(4)资源数量的变化就表示资源的占用和释放。
特征:
(1)如果有进程通过信号量申请共享资源,而且此时资源个数已经小于0,则此时对于该进程,有两种可能性:等待资源,不等待
(2)如果此时进程选择等待资源,则操作系统内核会针对该信号量构建进程等待队列,将等待的进程加入到该队列之中
(3)如果此时有进程释放资源,则会:(1)、先将资源个数增加;(2)、从等待队列中抽取第一个进程;(3)、根据此时资源个数和第一个进程需要申请的资源个数进行比较,结果大于0,则唤醒该进程;结果小于0,则让该进程继续等待。
6.msg
消息队列就是就是在进程间架起通道,从宏观上看是一样的,但是管道在字节流上是连续的,消息队列在发送数据时,分为一个一个独立的数据单元,也就是消息体,每个消息体都是固定大小的存储块,在字节流上不连续。
消息队列与管道不同的地方在于:管道中的数据并没有分割为一个一个的数据独立单位,在字节流上是连续的。然而,消息队列却将数据分成了一个一个独立的数据单位,每一个数据单位被称为消息体。每一个消息体都是固定大小的存储块儿,在字节流上是不连续的。
注意:
消息结构体被发送的时候,只是发送了消息结构体中成员的值,如果结构体成员是指针,并不会将指针所指向的空间的值发送,而只是发送了指针变量所保存的地址值。数组作为消息体结构体成员是可以的。因为整个数组空间都在消息体结构体中
(7)socket
网络之间不同进程间通信
系统调用
- 你如何实现命令调用的?
(1)用中断门实现系统调用,效仿Linux用0x80号中断作为系统调用的入口
(2)IDT中安装0x80号中断对应的描述符,在该描述符中注册系统调用对应的中断处理例程
(3)建立系统调用子功能表syscall_table,利用eax寄存器中的子功能号在该表中索引对应的处理函数
(4)用宏实现用户空间系统调用接口_syscall,最大支持3个参数的系统调用,故只要完成_syscall[0-3],寄存器传递参数,eax为子功能号,ebx,ecx,edx分别保存第1-3个参数。
系统交互
- fork是什么?实现原理是什么?
fork 利用老进程克隆出 个新进程并使新进程执行,新进程之所以能够执行,本质上是它具备程序体,这其中包括代码和数据等资源。。因此 fork 就是把某个进程的全部资源复制了一份,然后让处理器的 cs:eip寄存器指向新进程的指令部分。故 实现 fork 也要分两步,先复制进程资源,然后再跳过去执行。
本文复盘操作系统真相还原项目,介绍各模块重点。涵盖开发环境(Ubuntu20.04、bochs),MBR载入,实模式与保护模式区别及转换,中断机制、内存管理、线程进程、同步机制、用户进程、系统调用和系统交互等内容,还提及进程间通信方式。
3318

被折叠的 条评论
为什么被折叠?



