计算机操作系统处理机调度读后感:
笔者在看操作系统西安电子科技大学那本书的时候,初次感觉本科教的不会太难,所以没有认真的看,但是随后这本书讲的刷新了我的世界观。这本书居然是ring0级别的,这时不禁吐槽一下。。如果没调试过程序,没接触过ring0的同学,这本书就和马原一样。全背完还不知道学了啥。
由于笔者之前做过逆向工程。而调试的大都是ring3级别的,这本书是ring0级别的。我必须要把这些知识和之前学的连接起来,以便以后接触ring0的时候能更轻松一些。
1、创建进程。
在这个模块我会从一个创建进程的函数开始,将我知道的进程线程等内核对象与目前看的这本书联系起来。
ps:当你双击一个桌面一个exe执行文件的时候,实际上是一个名叫explorer.exe的进程帮你去创建一个进程。如果你把这个进程终结了的话你会发现图标及任务栏全没了。刚刚启动的窗口却还在。没了父进程的我们一般称作孤儿进程。(笑)
这是win32api给出的创建进程的方法。下面是对于创建进程需要的参数。
1、作业调度参数
IpApplicationName 一般填入文件路径。指向一个NULL结尾的、用来指定可执行模块的字符串。说白了告诉程序要从哪把PE文件拽入内存中(这个过程一般称为作业调度)。
lpCommandLine指向一个以NULL结尾的字符串,该字符串指定要执行的命令行。 可以把他理解为main方法中的参数argv[]。
dwCreationFlags一般设置进程的优先级,和其他细节操作。有很多参数网上都有。没啥用。
2、进程通信参数
lpProcessAttributesh、lpThreadAttributesh和bInheritHandles
这三个参数分别是子进程是否继承父进程的句柄表。如果继承了的话,子类可以访问其他父类创建的进程。话说回来我觉得这个就是属于操作系统进程通信中的共享存储器系统 。通过共享句柄表,来进行子进程和父进程的通信。
而线程在笔者心中和进程一样,都是属于内核对象。原因很简单,他们都有自己的句柄。而且在程序中都叫Handle。很多人认为线程比进程要矮一级,笔者认为这是错的。进程是提供资源的,例如:PE文件在装入内存后(作业调度)在内存中分配各种代码段,数据段,堆栈,还有各种其他信息。而线程则是执行这些代码段,享用这些堆栈的。
一个进程如果没了线程就会被杀死,因为进程的存在是没有意义的,空占内存!
3、各种作业调度的各种细节函数(没啥用,一般给NULL就行)
lpEnvironment
指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。
lpCurrentDirectory
指定子进程的工作路径。
lpStartupInfo
启动信息。
lpProcessInformation
进程信息
2、进程调度
1、虚拟内存
笔者这里需要引入一个虚拟内存的概念,这个虚拟内存不同于这本书后面的虚拟内存存储器,(那个虚拟内存为了解决有的作业比较大,内存一时不够用了而创建的一种分配机制)。
这里借用一下0day安全那本书的一张图
虽然每个进程都“相信”自己拥有 4GB 的空间,但实际上它们运行时真正能用到的空间根本没有那么多。内存管理器只是分给进程了一片“假地址”,或者说是“虚拟地址”,让进程们“认为”这些“虚拟地址”都是可以访问的。如果进程不使用这些“虚拟地址”,它们对进程来说就只是一笔“无形的数字财富”;当需要进行实际的内存操作时,内存管理器才会把“虚 拟地址”和“物理地址”联系起来
2、而为什么是4GB的虚拟内存呢?
这里我给出计算虚拟内存的方法
我们日常的操作系统是32位的,这个4GB的虚拟内存就是32位操作系统的特性。
32位最小的地址是0000 0000 最大是 FFFF FFFF 这里是十六进制表示的!!
可以看到十进制是4,294,967,295 加上0000 0000这个地址,总共就是4294967296
而4294967296 代表的是可以存的Byte字节。1024B=1KB
1024KB=1M 1024M=1G
根据这个我们可以算出结果位4GB。所以这不是定义的,而是算出来的。
3、高2G和低2G
在内存中7FFFF FFFF之前的我们称之为低2G也就是所谓的ring3用户态
而从8000 0000开始就是所谓的ring0内核级了,普通调试器例如Ollydbg是无法对ring0进行调试的。
而ring0就保存了处理机的现场信息,操作系统内核代码、设备驱动程序、设备I/O高速缓存、非页面内存池的分配、页表等一切后面的章节所讲的知识。
(笔者对于之前的自大有点羞愧,之前认为本科教材很简单,思想是有点不准确的)
4、进程调度(原书3.3)
下面就是完完全全的ring0级别的了,是普通程序员在开发时完全体会不到的,因为无论是windows编程还是Java,Linux编程那些都是应用层面的开发。ring0级别的是完全透明的无法感知的。
因为我这次是以我最熟悉的windows的平台进行的讲解。而windows属于多用户多任务型操作系统,进程调度相对复杂,而微软的封闭性导致我学习的困难,因此我对进程调度的学习还是以书上的相对简单的实现方式进行讲解。(这本书关于算法讲的也太简单了。连个数据结构都没有,差评。)
(0)PCB进程控制块的介绍:
(1)进程调度的任务(原书3.3.1)
1.保存处理机的现场信息。—这个比较好理解,把各种结构体压进系统堆栈(push)中即可实现保存。
2.按照某种算法选取进程。—-笔者认为这里是个坑。因为没有数据结构哪来的算法,所以还是往后再深究。
3.把处理器分配给进程。—-笔者也不知道如何分配的,书上也没有具体介绍,姑且理解为弹栈(pop)的过程把。
三、进程调度算法
在这里,我主要介绍俩种进程调度算法的实现、轮转法和优先级调度算法。
一、轮转法:
一.轮转法的基本原理:
根据先来先服务的原则,将需要执行的所有进程按照到达时间的大小排成一个升序的序列,每次都给一个进程同样大小的时间片,在这个时间片内如果进程执行结束了,那么把进程从进程队列中删去,如果进程没有结束,那么把该进程停止然后改为等待状态,放到进程队列的尾部,直到所有的进程都已执行完毕
二.进程的切换
时间片够用:意思就是在该时间片内,进程可以运行至结束,进程运行结束之后,将进程从进程队列中删除,然后启动新的时间片
时间片不够用:意思是在该时间片内,进程只能完成它的一部分任务,在时间片用完之后,将进程的状态改为等待状态,将进程放到进程队列的尾部,等待cpu的调用
三.关于时间片大小的选择
时间片过小,则进程频繁切换,会造成cpu资源的浪费
时间片过大,则轮转调度算法就退化成了先来先服务算法
二、优先级调度算法:
一、优先级调度算法的基本原理:
优先级进程调度算法,是把处理机分配给就绪队列种优先级最高的进程。即根据优先级来确定排名的算法。
二、根据新的更高优先级进程能否抢占正在执行的进程,可将该调度算法分为:
- 非剥夺式优先级调度算法。当某一个进程正在处理机上运行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在运行的进程继续运行,直到由于其自身的原因而主动让出处理机时(任务完成或等待事件),才把处理机分配给更为重要或紧迫的进程。
- 剥夺式优先级调度算法。当一个进程正在处理机上运行时,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。
三、根据进程创建后其优先级是否可以改变,可以将进程优先级分为以下两种:
- 静态优先级。优先级是在创建进程时确定的,且在进程的整个运行期间保持不变。确定静态优先级的主要依据有进程类型、进程对资源的要求、用户要求。
- 动态优先级。在进程运行过程中,根据进程情况的变化动态调整优先级。动态调整优先级的主要依据为进程占有CPU时间的长短、就绪进程等待CPU时间的长短。
下面就是笔者使用Java语言对俩种调度算法的实现:
package process; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import java.util.Queue; import java.util.Scanner; public class ProcessHandling { /** * @author godoforange * @PCB数据结构 */ class PCB { PCB(int id, int priority, int timeuse, int timeround) { this.id = id; this.priority = priority; this.timeuse = timeuse; this.timeround = timeround; } int id = 0;// 进程id int priority = 0;// 优先级 int timeuse = 0;// 需要的运行的时间为 int timeround = 0;// 轮转时间片数为 int cpuuse = 0; @Override public String toString() { return "[+]进程ID:" + id + " 优先级为:" + priority + " 需要运行的时间:" + timeuse + " 轮转时间片数为:" + timeround + " 已经占用CPU:" + cpuuse; } } /** * @author godoforange * @优先级比较器 */ class PriorityComparator implements Comparator<PCB> { @Override public int compare(PCB o1, PCB o2) { if (o1.priority < o2.priority) { return 1; } else if (o1.priority > o2.priority) { return -1; } else { return 0; } } } /** * @author godoforange * @param N 总数 * @return 包含了PCB的链表 */ public List<PCB> createProcess(int N) { System.out.println("[+]创建进程中。。。"); List<PCB> allPCB = new LinkedList<>(); for (int i = 0; i < N; i++) { int p = (int) (1 + Math.random() * 10); int c = (int) (1 + Math.random() * 10); int b = (int) (1 + Math.random() * 10); PCB pcb = new PCB(i, p, c, b); System.out.println("[+]创建第" + i + "个进程,优先级为:" + p + " 需要运行的时间为:" + c + "轮转时间片数为:" + b); allPCB.add(pcb); } return allPCB; } /** * @author godoforange * @轮转法 * @param N 进程数 */ public void round(int N) { List<PCB> allPCB = createProcess(N); System.out.println("[+]轮转法调度执行中"); System.out.println("[+]创建队列"); Queue<PCB> que = new LinkedList<>(); for(PCB pcb : allPCB) { que.offer(pcb); } System.out.println("[+]就绪队列创建完毕"); while(!que.isEmpty()) { PCB pcb = que.element(); pcb.timeuse--; pcb.cpuuse++; try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.println("[+]"+pcb.id + "运行了"+pcb.cpuuse+"次"+"轮转时间片数为"+pcb.timeround); if (pcb.timeuse < 1) { System.out.println("[+]"+pcb.id + "运行时间到被移除"); que.remove(); }else if(pcb.cpuuse%pcb.timeround==0) { que.offer(que.remove()); } } } /** * @author godoforange * @优先权法 * @param N 进程数 */ public void priority(int N) { List<PCB> allPCB = createProcess(N); Collections.sort(allPCB, new PriorityComparator()); System.out.println("[+]优先权调度执行中"); System.out.println("[+]创建队列:"); for (PCB pcb : allPCB) { System.out.println(pcb.toString()); } System.out.println("[+]就绪队列创建完毕"); while (!allPCB.isEmpty()) { PCB pcb = allPCB.get(0); pcb.priority -= 3; pcb.timeuse--; pcb.cpuuse++; try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.println("[+]"+pcb.id + "运行了"+pcb.cpuuse+"次"+"优先级为"+pcb.priority); if (pcb.timeuse < 1) { System.out.println("[+]进程"+pcb.id + "运行时间到被移除"); allPCB.remove(pcb); } Collections.sort(allPCB, new PriorityComparator()); } System.out.println("[-]优先级调度结束"); } public static void main(String[] args) { System.out.print("[+]请输入要创建的进程数:"); int N;// 进程数 Scanner sc = new Scanner(System.in); N = sc.nextInt(); System.out.println("[+]需要创建:" +N+ "个进程"); System.out.print("[+]输入 分别执行优先级调度和轮转法调度 [Y/N]:"); if(sc.nextLine().equals("Y")||sc.nextLine().equals("y")) { new ProcessHandling().priority(N); }else{ new ProcessHandling().round(N); } sc.close(); } }
运行结果如下:
四、总结
实话来说、这本书其实并不适合让大家真正去理解何为操作系统,因为这里面涉及到的知识很多很多,很多语言十分生涩,即便是有多年经验的开发者也难以对其进行总结。还是希望本科教育能够重视起来,真正的让学生去理解何为操作系统,而非只是生涩的去介绍这些知识。