本文共 3551 字,大约阅读时间需要 11 分钟。
1)概念:管理硬件的软件
主要的核心就是:CPU管理、内存管理、磁盘管理、文件管理(硬盘管理),还有一个隐藏的进程管理
2)操作系统中的库函数和系统调用
1>概念:为什么要有库函数和系统调用函数,主要是为了保护操作系统。将内存分为了内核态和用户态,内核态能访问所有内容,包括硬件等,而用户态的权限有限,用户程序一般运行在用户态,当需要时候才会进入内核态。而在用户态所执行的函数正是库函数,当需要进入内核态的时候,就要执行系统调用进入内核态,也可以说系统调用函数就是一个接口。
硬件(内存条)提供了主动进入到内核态的方法,对于Intel X86来说,就是中断指令int,其中int指令将使上面的CS中的CPL改为0,“进入内核”
2>从一个程序来看
当我们在终端执行shell指令的时候,操作系统首先会fork()一个子进程来执行,(整个操作系统进程就是一个大的父进程),然后调用printf命令,最后经过系统调用函数write()进入到内核态。(相关的系统调用函数还有open(),fork(),read()等等),而在调用系统调用时采用的是中断处理通知system_call(),其中的一个重要的指令就是0x80
1)cpu工作原理:自动取指-执行的方式(cpu会根据我们设置的额PC(程序计数器)开始的位置,然后一步步往下执行),但这时就有问题了,当我们执行的一个进程中,存在读文件的操作,而读文件的操作需要访问到磁盘,也就是执行I/O操作,I/O操作就会阻塞,这样CPU就会空闲,导致CPU利用率低下。所以就有了操作系统是一个多进程执行的概念,就是为了提高CPU的利用率。
CPU单进程执行的情况
CPU多进程执行的情况,当遇到阻塞的时候,切换去执行另外的进程,这样就可以提高CPU的利用率
但是这时候又有另外一个问题,PC切换到另外一个进程的时候,如何保存当前进程的执行信息呢。这时候就出现了一个叫做PCB(进程控制块),所有进程执行的信息都保存在这个结构里面,当切换到这个进程的时候,在这个结构里面进行相关信息的查找和恢复。
因为CPU是一个多进程执行图像,所有就涉及到多进程调度。常见的算法有FCFS(先到执行),Priority(优先级),SCF(短作业优先)。多进程调度还涉及到另外一个问题也就是当多个进程同时处于内存空间时,如何防止他们访问同一块内存地址空间,这就涉及到操作系统的内存管理。(也是后面会提到的)
2)用户级线程
在一个UC浏览器中,打开UC浏览器就是打开一个进程,我们可以用这个浏览器做很多事情,这时候就是一个执行多线程的命令。
关键的一个概念就是线程共享全局变量,但拥有自己的执行栈。必须拥有自己的执行栈的原因是因为这样不会出现跳转错误。当两个线程中存在相同名称的时候,不会再执行线程1的时候,回调时跑到线程2中去。
这种情况就是当只有一个线程栈的时候
这种情况是每个线程都有自己的线程栈,这样就不会跑到别人的线程中去。主要关键的函数是Yield()函数,yield()进行线程的切换。所以当有两个线程时,就必须要有两个TCB表(线程控制块,保存线程信息),两个栈。当用户级线程经过调用而陷入内核的时候,就会调用核心级线程
3)核心级线程
和用户级线程一样,核心级线程对于栈也必须有两个,但它不单单是两个栈,而是两套。因为核心级线程还要关联到它在用户级下的线程,当核心级线程切换时,用户级线程也必须跟着切换。
一套内核栈
这就是两套内核级线程的栈,可以看出是采用Schedule()函数进行调度的
所以核心级线程的栈在创建的时候,会关联到用户级的线程栈。做到同步切换。
上面这张图说明了什么是两个进程同步,这时候问题来了,当有多个生产者和多个消费者的时候,当生产者的缓冲区刚好慢,这时候有多个生产者进程处于阻塞状态,而消费者进程判断到缓冲区有东西可以缓存,这时候如果我们采取的是一个信号的机制,就会出现一种情况,就是后面等待的进程永远无法被唤醒,
这时候就有了信号量了,信息只能表示两种状态,而信号量可以用来表示由多少个在等待。
这里应该注意的是这个-1表示当前有一个进程在等待,+1表示由一个空闲的可用资源。
这时候问题来了,当多个进程同时运行的时候,也就是并发对这个信号量进行修改,则会产生并发问题。这时候就要对信号量进行保护,也就有了临界区。
并且临界区要遵守以下原则
当然进入临界区有很多算法:轮换法、标记法、非对称标记法、Peterson算法 、硬件原子指令法(重要且简单)
1)应该解决的:如何让内存使用起来
通过图,我们可以看到,就是将程序加载到内存中去,然后pc指向开始位置,然后CPU就开始取指-执行的过程。
这个时候又有一个问题来了,通常我们的内存的大小是有限的,不可能每一个进程都全部加载到内存中去,这样明显内存空间不足。这时候就出来了一个概念了,SWAP。
(但是应该注意一点就是,SWAP是整个进程的换入换出,这样代价太大,现在很少这样做,而是采用了页面置换,也就是以进程中更小的单位,进行置换,提高效率)
进程的切换涉及到的PC的切换,而PC的切换时根据PCB进行切换。
2)分段的概念:前面我们提到了不可能采用将整个进程进行换入换出,而是根据页面进行置换。而在页和进程之间,还有一个重要的概念,就是段,也就是一个进程是分成不同的段进行载入到内存,每个段都有一个PC执行单元往下执行(可以对比Java中的JVM内存管理,也是分成不同的段,这样可以更好的进行管理,共享数据,只读数据,私有数据分的明明白白)
程序是分成段放到内存单元中去
这时我们知道了原来程序是分为多个段载入到内存中去,那么现在又有一个问题了,这些段是载入到内存中的哪些区域,是随意的还是有固定的位置,这就涉及到内存请求分配算法。
原来内存中采用两张表来记录,一张分配的空间表,一张剩余空间表,其中表中的记录比较详细,从哪个位置和有多大进行记录。这时候问题又来了,我们可以看到空闲分区表中有多个,那每次新的请求进来的时候要分配哪一个呢?这就涉及到了分配算法。具体有:
但是分区又会出现一个问题,就是当空闲区域的长度不够分配的时候怎么办。也就是如果我有一个进程需要160K,根据上面的空闲表,只有一个150K和50K,两个合起来就够用,但是分开就不够用,造成空间的浪费。所以这时就引入了一个分页的概念。
3)分页概念:通常一个页的大小是4K,这时候问题又来了,如果这样划分了话,这个页表的大小就要非常大了才能保存空闲和使用的表项
所以这时候可以采用一种办法,就是不记录空闲的区域,只记录使用过的区域。但是这时候又有一个问题,每次要分配的时候就要进行查找,时间效率低下,这时候就出现了多级页表,也就是我们书的目录索引(多级目录)
这样,空间利用率就高了,但是时间利用率呢,这时候就引入了快表的概念(TLB,Translation Lookaside Buffer(地址转换后援缓冲器)),作用就是页表的Cache其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本。只有在TLB无法完成地址翻译任务时,才会到内存中查询页表,这样就减少了页表查询导致的处理器性能下降。
现在好了,在内存中,实际使用的分配单元是页,但是我们程序员希望的是按照段进行分配,那要怎么样将这两者结合起来呢。
4)段页结合(虚拟内存):采用的就是虚拟内存,用来存储段跟内存之间的地址映射,内存的分配对用户是透明的,用户只需要关心段的分配
在每一个段中都有一个映射到实际的物理内存中的表。这样就可以将其关联起来。
所以整个进程的状态就是如上图所示。
这时候我们解决了段和页的映射,就是再硬盘中建一张虚拟内存表,而通常这张表的大小比实际的内存大小大,那么程序是如何进行执行的时候换入换出内存的。这就是页面置换算法。
其中换入比较简单,当要执行的时候必须将程序换入,但是如何换出就涉及到很多算法,要将哪个页给换出来呢。常见的算法有LRU,MIN算法,但是这些都是理论上的算法,实际在操作系统中很难实现。在操作系统中实际使用的是一个叫做CLOCK算法。
具体的CLOCK算法,可以网上查相关资料进行了解,主要是通过时钟的方式进行的,一个清楚标志位,一个用来选择淘汰页。
外设也可以理解成文件,在操作系统中,一切都以处理文件为基础。
那么外设是怎么工作的呢,这一切还是要归因于CPU的控制,外设只不过在设备驱动中,当使用外设,向CPU发送指令。
参考资料:操作系统-----李治军## 操作系统
转载地址:http://qxrai.baihongyu.com/