《操作系统课程设计实验报告proj2.docx》由会员分享,可在线阅读,更多相关《操作系统课程设计实验报告proj2.docx(31页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、操作系统课程设计报告操作系统课程设计报告班级:班级:团队成员团队成员:目录.错误!未定义书签。错误!未定义书签。一、实验要求:建立线程系统.错误!未定义书签。错误!未定义书签。1.1 Task 2.1 实现文件系统调用.31.1.1 题目要求.31.1.2 题目分析与实现方案.31.1.3 关键点与难点.41.1.4 实现代码.41.2Task 2.2 完成对多道程序的支持.51.2.1 题目要求.51.2.2 题目分析与实现方案.51.2.3 关键点与难点.61.2.4 实现代码.71.3Task 2.3 实现系统调用.71.3.1 题目要求.71.3.2 题目分析与实现方案.81.3.3
2、关键点与难点.91.3.4 实现代码.91.4Task 2.4 实现彩票调度.101.4.1 题目要求.101.4.2 题目分析与实现方案.101.4.3 关键点与难点.111.4.4 实现代码.11二、测试结果.2 错误!未定义书签。错误!未定义书签。一、实验要求:一、实验要求:多道程序设计多道程序设计1 1.1.1 TaskTask 2 2.1.1 实现文件系统调用实现文件系统调用1.1.1 题目要求实现文件系统调用(创建,打开,读,写,关闭,unlink),在syscall.h 中有详细的记录,可以看一下 UserProcess.java 中的代码,最好把你自己写的系统调用的代码也放在这
3、里。记住你不是在实现一个文件系统,而是在给用户进程获取我们给你定义好的文件系统的能力。我们在 start.s 里为你提供了用户进程进行系统调用所必需的汇编代码。(SYSCALL 会为每个系统调用产生汇编代码)。其中 Unlink 为删除目录相并减少一个连接数,如果链接数为 0 并且没有任何进程打开该文件,该文件内容才能被真正删除,但是若又进程打开了该文件,则文件暂时不删除直到所有打开该文件的进程都结束时文件才能被删除。1.1.2 题目分析与实现方案系统共提供了七个系统调用:halt(停机,已经提供),creat(创建并打开磁盘文件),open(打开磁盘文件),read(读 IO,可以是磁盘或屏
4、幕),write(写 IO),close(关闭 IO),unlink(删除磁盘文件)。要确保如下几点:1)稳定性,不能因为一个进程的非法系统调用就使操作系统崩溃,而应该 返回错误代码。2)halt()调用只能由第一个进程(root process)执行。3)系 统 调 用 需 要 读 写 内 存 时,通 过 readVirtualMemory 和writeVirtualMemory 进行。4)文件名以 null 结尾,不超过 256 字符。5)如果系统调用出错,应返回-1。6)为每个打开的 IO 文件分配一个“文件描述符”,用整数表示.每个进 程最多可以拥有 16 个。其中 0 和 1 应分配
5、给标准输入和标准输出(即屏幕),这由 SynchConsole 类管理。不同进程可以用相同的文件描述符处理不同的文件。7)Nachos 已经提供了一个简单的文件系统 FileSystem(Machine 包中),通过 ThreadedKernel.fileSystem 访问。8)系统不需要考虑文件访问的互斥等问题。方案:1)create 系统调用:1.根据传入的 fileAddress,从虚拟内存中读出文件名2.判断文件名是否存在,即 fileAddress 处是否有文件名3.如果不存在,直接创建。先去找合适的位置,如果已经有大于 16个,退出,否则创建,创建只需调用 filesystem 的
6、 open 即可(传入的布尔值 create 为 true)2)open 系统调用:1.根据传入的 fileAddress,从虚拟内存中读出文件名2.判断文件名是否存在,即 fileAddress 处是否有文件名3.先去找合适的位置,如果已经有大于 16 个,退出,否则创建只需调用 filesystem 的 open 即可(传入的布尔值 create 为 false)4.维护本地描述符表3)read 系统调用Read 系统调用的三个参数依次为:文件描述符,写入的内存地址,读取的字节数。在 Read 系统调用中进行如下操作:1.如果打开文件大于 16 个或者小于 0 个,或者打开文件列表为空,直
7、接返回。2.创建数组 temp,把文件内容读入 temp,求出读出数据长度3.若读出数据小于 0 即为出错,返回。4.把该数据写入虚拟内存。5.返回写入内存的字节数4)write 系统调用Write系统调用的三个参数依次为:文件描述符,读内存的地址,写入文件的字节数。在 Write 系统调用中进行如下操作:1.如果打开文件大于 16 个或者小于 0 个,或者打开文件列表为空,直接返回。2.创建数组 temp,把虚拟内存中的内容读入 temp,得到读出数据长度3.若读出数据小于 0 即为出错,返回。4.把该数据写入磁盘。5.若写的数据长度小于读出的数据长度,则错误,返回错误代码5)close 系
8、统调用Close 系统调用的唯一一个参数为文件描述符。在 Close 系统调用中进行如下操作:1.如果打开文件大于 16 个或者小于 0 个,或者打开文件列表为空,直接返回。2.如果不存在,关闭出错3.否则关闭,把 openfile 数组中 fileDescriptor 中的位置置为 06)unlink 系统调用:1.根据传入的 fileAddress,从虚拟内存中读出文件名。2.如果不存在,不必删除3.如果存在,就删除,出错返回错误代码一般地,在 Unlink 调用中只需读取文件名并执行 fileSystem.remove方法删除文件即可。但是,一个文件可能被多个进程打开而不能立即删除,必须
9、等所有打开这个文件的进程都关闭该文件后才能删除。因此,在 Close 系统调用中还要增加如下内容:若文件关闭后它在全局文件表中已经不存在且文件名在删除队列中,则此时执行删除文件操作,并将文件从删除队列中移出。1.1.3 关键点与难点注意:以上系统调用只是在一般情况下函数的执行流程。为了提高系统的健壮性,在系统调用中还要进行下列错误检查:a)文件名长度不得超过 256 字符,不得含有非法字符或空。b)打开,创建文件时,局部描述符表不能满。c)fileSystem 的操作返回值必须正确。d)readVirtualMemory 和 writeVirtualMemory 的返回值必须正确。1.1.4
10、实现代码private int handleCreate(int fileAddress)/限定文件名长度,readVirtualMemoryString 读取文件名String filename=readVirtualMemoryString(fileAddress,256);if(filename=null)return-1;int fileDescriptor=findEmpty();if(fileDescriptor=-1)return-1;else/ThreadedKernel.fileSystem.open()打开文件(第二个参数为true表示创建新文件)openfilefileD
11、escriptor=ThreadedKernel.fileSystem.open(filename,true);return fileDescriptor;private int handleOpen(int fileAddress)/限定文件名长度String filename=readVirtualMemoryString(fileAddress,256);if(filename=null)return-1;int fileDescriptor=findEmpty();/为空,报错。if(fileDescriptor=-1)return-1;else openfilefileDescrip
12、tor=ThreadedKernel.fileSystem.open(filename,false);return fileDescriptor;private int handleRead(int fileDescriptor,int bufferAddress,int length)/如果打开文件大于16个或者小于0个,或者打开文件列表为空,直接返回。if(fileDescriptor 15|fileDescriptor 0|openfilefileDescriptor=null)return-1;byte temp=new bytelength;/通过OpenFile.read读取文件内
13、容int readNumber=openfilefileDescriptor.read(temp,0,length);if(readNumber 15|fileDescriptor 0|openfilefileDescriptor=null)return-1;/文件未打开,出错/创建数组temp,把虚拟内存中的内容读入temp,得到读出数据长度byte temp=new bytelength;int readNumber=readVirtualMemory(bufferAddress,temp);/若读出数据小于0即为出错,返回。if(readNumber=0)return 0;/把该数据写入
14、磁盘。int writeNumber=openfilefileDescriptor.write(temp,0,length);/若写的数据长度小于读出的数据长度,则错误,返回错误代码if(writeNumber 15|fileDescriptor 0|openfilefileDescriptor=null)/文件不存在,关闭出错return-1;/否则关闭,把openfile数组中fileDescriptor中的位置置为0openfilefileDescriptor.close();openfilefileDescriptor=null;return 0;private int handleU
15、nlink(int fileAddress)/根据传入的fileAddress,从虚拟内存中读出文件名。String filename=readVirtualMemoryString(fileAddress,256);/如果不存在,不必删除if(filename=null)return 0;/如果存在,就删除,出错返回错误代码if(ThreadedKernel.fileSystem.remove(filename)return 0;elsereturn-1;1 1.2 2 TaskTask 2 2.2.2 完成对多道程序的支持完成对多道程序的支持1.2.1 题目要求完成对多道程序的支持。已给出
16、的代码只能一次运行一个进程,而你要使它支持多道程序。从分配物理内存入手,不同的进程在在内存使用上不会重叠。用户程序不会使用 malloc()或 free(),既用户没有动态内存分配的需求。也就是说当进程创建,你完全知道它需要的内存大小,你要为进程栈分配合适的页大小,8 页就足够了。建议保持空闲物理页的全局链接列表(UserKernel class 的一部分),访问列表时会用到同步。解决方案为新进程分配页时务必有效的使用内存。只在连续块上分配内存是不可取的的,你的方案必须用到空闲内存池中的缝隙。保持每个进程的页表,页表保持了用户虚拟地址到物理地址的映射。TranslationEntry clas
17、s 代表了一个虚拟到物理的页转换。如果页来自 COFF section,那么在 TranslationEntry.readOnly 文件中要将其标记为只读。这个方法失败不会抛出异常,它总会返回传送的位数,即使是零。修改 UserProcess.loadSections(),根据所需为进程分配页数。用你决定的分配规则。这个方法还要为进程开启页表 structure,这样进程就被加载到当前物理内存页中。1.2.2 题目分析与实现方案Nachos 为不同进程应分配完全不同的物理内存,但不支持动态内存分配,所以需要分配的内存在装入程序时就可以确定了(代码,数据,堆栈部分)。故在装入程序时就为每个进程一
18、次性分配固定的物理内存,在进程结束时收回它们。这里还需要实现如下简单的虚拟内存方案:每个进程的地址空间是连续的虚拟内存,但这些连续的虚拟页面在物理内存中却不一定是连续的。这个方案的简单之处在于,虚拟空间的总容量和物理空间的总容量相等,映射机制只是从虚拟内存到物理内存的一一映射。除了内存的分配,内存的读写也要体现出映射机制。方案:页表 pageTable 以虚拟页号为下标,其中的每个项目是一个machine.TranslationEntry 类型的对象,存放了一个页的下列信息:物理页号,虚拟页号,是否有效,是否只读,是否被用过,是否脏。在UserKernel 中定义一个全局队列 memoryLi
19、nkedList 用于存放当前空闲的物理页号,一个保护内存互斥访问的锁 allocateMemoryLock。开始时,memoryLinkedList 包括所有的物理页面。当启动新进程时,UserProcess.load 负责从磁盘装入进程。过程 UserProcess.loadSections 完成如下操作:先获得 allocateMemoryLock,锁定内存。该进程需要的页面数已知,保存在 numPage 变量中。与 memoryLinkedList 比较,判断空余存储空 间 是 否 足 够。分 配 物 理 页(UserKernel.allocatePages)时 从memoryLink
20、edList中出队相应数量的页面(返回的是物理页号的数组)。2)整个需要装入的进程是一个 machine.CoffSection 类型的对象,包括若干个页。在 loadSection 中,在释放 allocateMemoryLock 前应该创建一个页表。3)得到可用的物理页号后,就装入所有的页,为段中每个虚拟页在pageTable 中创建一个新的 TranslationEntry 对象并为其赋上相应的虚拟页号和物理页号等信息,然后将原来 section 对象的读写权限等转移,然后并调用 CoffSection.loadPage 将页的内容读入。读入时进行物理地址和逻辑地址的关联,然后把程序的每
21、一块按照顺序对应于物理地址导入到内存中。完成了题目的“保持物理页的全局链接表”的要求。4)在进程结束时,对于 页表中所有的页面,只要将其虚拟页号放入memoryLinkedList 队列。UserProcess.readVirtualMemory 读虚拟内存。读内存时,要先利用页表将逻辑地址转换为物理地址然后再将内存数据复制到数组中。类似地,UserProcess.writeVirtualMemory 写 虚 拟 内 存,其 方 法 与readVirtualMemory 类似,要先利用页表将逻辑地址转换为物理地址然后再将数组数据复制到内存中,并注意取页面时要检查页面是否为只读。1.2.3 关键
22、点与难点在 loadSection 中,在导入 coff 之前应该创建一个页表,进行物理地址和逻辑地址的关联,然后把程序的每一块按照顺序对应于物理地址导入到内存中。这样就完成了题目的“保持物理页的全局链接表”的要求。读写内存时,要先利用页表将逻辑地址转换为物理地址然后再将内存数据复制到数组中,取页面时要检查页面是否为只读。读写内存时要注意判断内存是否已满,数组是否已满,是否有错误参数(比如写入数据的长度0),另外题目要求无论读写是否成功都不报错,而是返回读写数据的长度。1.2.4 实现代码UserKernel:/保护内存互斥访问的锁public static Lock allocateMemo
23、ryLock;/全局队列,用于存放当前空闲的物理页号public static LinkedList memoryLinkedList;UserProcess:protected boolean loadSections()/获取分配内存的锁,锁定内存UserKernel.allocateMemoryLock.acquire();/判断能否装载if(numPages UserKernel.memoryLinkedList.size()coff.close();Lib.debug(dbgProcess,tinsufficient physical memory);UserKernel.alloc
24、ateMemoryLock.release();/返回装载失败return false;/创建页表pageTable=new TranslationEntrynumPages;for(int i=0;i numPages;i+)int nextPage=UserKernel.memoryLinkedList.remove();pageTablei=new TranslationEntry(i,nextPage,true,false,false,false);/释放锁UserKernel.allocateMemoryLock.release();for(int s=0;s coff.getNum
25、Sections();s+)CoffSection section=coff.getSection(s);/获得sectionLib.debug(dbgProcess,tinitializing +section.getName()+section(+section.getLength()+pages);for(int i=0;i section.getLength();i+)int vpn=section.getFirstVPN()+i;/标记只为已读pageTablevpn.readOnly=section.isReadOnly();section.loadPage(i,pageTable
26、vpn.ppn);return true;public int readVirtualMemory(int vaddr,byte data,int offset,int length)/保证偏移量与长度都为正,偏移量+长度=0&length=0&offset+length (pageSize*numPages-vaddr)/判断能否读取length=pageSize*numPages-vaddr;if(data.length-offset length)length=data.length-offset;int transferredbyte=0;do int pageNum=Processo
27、r.pageFromAddress(vaddr+transferredbyte);if(pageNum=pageTable.length)return 0;int pageOffset=Processor.offsetFromAddress(vaddr+transferredbyte);int leftByte=pageSize-pageOffset;int amount=Math.min(leftByte,length-transferredbyte);int realAddress=pageTablepageNum.ppn*pageSize+pageOffset;System.arrayc
28、opy(memory,realAddress,data,offset+transferredbyte,amount);transferredbyte=transferredbyte+amount;while(transferredbyte=0&length=0&offset+length (pageSize*numPages-vaddr)length=pageSize*numPages-vaddr;if(data.length-offset length)length=data.length-offset;int transferredbyte=0;do int pageNum=Process
29、or.pageFromAddress(vaddr+transferredbyte);if(pageNum=pageTable.length)return 0;int pageOffset=Processor.offsetFromAddress(vaddr+transferredbyte);int leftByte=pageSize-pageOffset;int amount=Math.min(leftByte,length-transferredbyte);int realAddress=pageTablepageNum.ppn*pageSize+pageOffset;System.array
30、copy(data,offset+transferredbyte,memory,realAddress,amount);transferredbyte=transferredbyte+amount;while(transferredbyte length);return transferredbyte;1 1.3 3 TaskTask 2 2.3.3 实现系统调用实现系统调用1.3.1 题目要求实现系统调用(exec,join,and exit,同样在 syscall.h)。此外,所有通过寄存器来 Exec 和加入的地址是虚拟地址。你应该使用readVirtualMemory 和 readVi
31、rtualMemoryString 在内核和用户进程之间传递内存。同样,你必须防止这些系统调用。注意,子过程的状态是进程完全私有的。这意味着,父进程和子进程不直接共享存储器或文件描述符。注意两个过程当然可以打开相同的文件;例如,所有的进程应该有的 0 和 1 映射到系统控制台的文件描述符。不像KThread.join(),只有一个进程的父进程可以调用join()。举例来说,如果 A 创建 B 并且 B 创建 C,A 不允许 join()到 C 中,但 B被允许 join()到 C。加入需要一个进程 ID 作为参数,用于唯一标识其父母希望加入的子进程。进程 ID 应该是全局唯一的正整数,在每个进
32、程创建时分配。(虽然在这个项目的唯一使用的进程 ID 是在 join(),进程 ID 是整个系统中的所有正在运行的进程是唯一的以后项目阶段很重要)。解决这个问题的最简单的方法就是保持一个表明接下来的进程 ID 分配的静止计数器。由于该进程 ID 是 int 类型,则有可能对是否存在在系统中的许多过程这个值溢出。对于这个项目,你不必要处理这种情况下,也就是说,假定该方法 ID 计数器不会溢出。当进程调用 exit(),它的线程应立即终止,而这个过程应该清理与它相关的任何状态(也就是释放内存,关闭打开的文件等)。如果一个进程异常退出执行相同的清理。在退出过程的退出状态应转移到父进程,如果父调用 j
33、oin()系统调用。一个进程退出状态的异常与否取决于你。对于 join()的目的,如果它任何状态下调用退出函数,那么一个子进程正常退出,如果内核杀死它(例如,由于未处理的异常)则是不正常的。最 后 一 个 过 程 调 用 exit()应 使 machine 类 暂 停,通 过Kernel.kernel.terminate()(请注意,只有根进程中应允许调用 halt()系统调用,但最后退出的过程中应该直接调用 Kernel.kernel.terminate()1.3.2 题目分析与实现方案在该系统调用中,第一个参数为文件名地址,第二个参数为参数个数,第三个参数为参数表指针.需要做的工作是:读虚
34、拟内存获得文件名2.处理参数。首先用第三个参数作为虚拟内存地址得到参数表数组的首址,然后用 readVirtualMemoryString 依次读出每个参数3.用 UserProcess.newUserProcess 创建子进程,将文件和参数表加载到子进程4.用 UserProcess.execute 方法执行子进程,同时将这个子进程的父进程置为这个进程,再将子进程加入子进程列表join:该系统调用中,第一个参数为子进程编号,第二个参数一个地址,用于保存子进程的返回值.需要做的工作是:1.利用进程号确定 join 的是哪一个进程,检查 childProcesses,遍历子进程链表,确定 joi
35、n 的进程是子进程。如果子进程编号不在其中则出错返回2.获得 join 锁,让该进程休眠,直到子进程唤醒3.子进程唤醒之后,将子进程的状态存入自己的内存中,判断当前进程是否正常结束。结束则返回。exit:首先关闭 coff,然后将所有的打开文件关闭,把退出的状态置入,如果该进程有父进程,看是否执行了 join 方法,如果执行就将其唤醒,同时将本进程从子进程链表中删除,释放内存,结束底层线程,如果是最后一个结束的进程则将系统关闭。该系统调用的唯一参数为返回值。需要做的工作为:1.关闭 coff,关闭所有打开的文件,把退出的状态置入2.如果该进程有父进程,看是否执行了 join 方法,如果执行就将
36、其唤醒,同 时将此进程从子进程链表中删除3.调用 unloadsections 释放内存,调用 KThread.finish 方法结束当前线程4.如果是最后一个进程,调用 Machine.halt()停机,修改运行的进程总数1.3.3 关键点与难点exec()实现创建子进程并执行功能,涉及执行程序地址,参数数量,参数地址相关参数,并返回子进程 id。join()阻塞等待某子进程执行完毕;涉及参数子进程 id,子进程地址,并返回-1/0/1exit()关闭进程,清除相关信息,涉及进程状态参数,返回 0.应注意:1)父进程和子进程不共享任何的内存,文件或其它状态。2)只有父进程能对子进程进行 jo
37、in 操作。例如 A 执行 B,B 执行C,则 A 不允许 join C,而 B 允许 join C。3)需要为每个进程分配一个唯一的进程编号。4)exit 操作将使当前进程立即结束,如果父进程对其进行 join 操作,返回代码这时应返回。5)最后一个调用 exit 的进程将使系统停机1.3.4 实现代码UserProcess parentProcess;LinkedList childProcess;private KThread thread;private Lock joinLock;Condition joinCondition;int numOfRunningProcess;priv
38、ate boolean normalExit;private int status;private int pid;private int handleExec(int fileAddress,int argc,int argvAddress)/文件名大小,读虚拟内存获得文件名String filename=readVirtualMemoryString(fileAddress,256);/文件出错if(filename=null|argc 0|argvAddress numPages*pageSize)return-1;String args=new Stringargc;/将文件内容读入虚
39、拟内存for(int i=0;i 0)argsi=readVirtualMemoryString(Lib.bytesToInt(argsAddress,0),256);/创建子进程,将文件和参数表加载到子进程UserProcess process=UserProcess.newUserProcess();/文件打开失败,退出if(!process.execute(filename,args)return-1;/将当前父进程信息赋予子进程process.parentProcess=this;childProcess.add(process);/返回子进程编号return process.pid;
40、private int handleJoin(int pid,int statusAddress)UserProcess process=null;/利用进程号确定join的是哪一个进程for(int i=0;i childProcess.size();i+)/如果子进程是当前运行程序,则返回if(pid=childProcess.get(i).pid)process=childProcess.get(i);break;if(process=null|process.thread=null)return-1;/得到锁process.joinLock.acquire();/进程睡眠process
41、.joinCondition.sleep();process.joinLock.release();byte childstat=new byte4;Lib.bytesFromInt(childstat,0,process.status);/将要写入虚拟内存大小int numWriteByte=writeVirtualMemory(statusAddress,childstat);if(process.normalExit&numWriteByte=4)return 1;return 0;private int handleExit(int status)/关闭coff区coff.close(
42、);/关闭所有打开的文件for(int i=0;i 调度引入一个 ThreadState 类用来把进程和彩票数量联系在一起,但是在得到有效优先级时,是把等待自己的队列中的进程的优先级和自己的优先级加起来,总的彩票数才是自己的优先级。在 getEffectivePriorityNachos 中将选取最大优先级的过程改成将所有等待线程的优先级(彩票数)加到 lockHolder 上(即等待者把自己拥有的彩票给获得锁的线程),其次将 nextThread 修改如下:首先遍历一遍队列,计算出当前队列中的所有队列的彩票总数(考虑过 donation后的)numEffective,其次生成一个 0 到 n
43、umEffective-1 之间的随机数 randomLottery 表示抽取的彩票,最后再次遍历一遍队列,用累加器 t 计算彩票总数,当 t 对应的当前值大于randomLottery 时说明当前循环到的线程正好落在目标区间内,即选出该线程。由于不能保存彩票的指针,所以只能遍历队列,累加线程的有效优先级,当有效优先级的总和大于等于中奖号码表示这个线程中奖,这是利用概率中的随机原则,将中奖转换为了累加。找到线程后将线程从队列中取出,将其返回。1.4.3 关键点与难点彩票调度与优先级调度非常类似,只是将“优先级”的概念变成了“彩票”,即表示该线程下次被运行的几率。而且在调度过程中,并不是选择彩票
44、数最多的线程运行,而是随机抽取一彩票,让彩票的主人运行。这样,彩票越多,下次得到的运行机会就越大。类 LotteryScheduler 继承自类 PriorityScheduler,可以直接利用类PriorityScheduler 的特性把进程和彩票数量联系在一起,并且彩票的“抽奖”过程只能遍历线程队列。1.4.4 实现代码public int getEffectivePriority()/implement meif(effectivePriority!=expiredEffectivePriority)return effectivePriority;effectivePriority=p
45、riority;/等待队列为空if(waitors=null)return effectivePriority;/将所有等待线程的优先级(彩票数)加到lockHolder上for(Iterator i=waitors.waitQueue.iterator();i.hasNext();)ThreadState ts=(ThreadState)getThreadState(KThread)i.next();effectivePriority+=ts.priority;return effectivePriority;protected ThreadState pickNextThread()/im
46、plement meint numEffective=0;if(waitQueue.isEmpty()return null;/计算彩票总数for(Iterator i=waitQueue.iterator();i.hasNext();)ThreadState ts=(ThreadState)getThreadState(KThread)i.next();numEffective+=ts.getEffectivePriority();Random random=new Random();/随机出一个0到numEffective-1之间的随机数表示抽取的彩票int randomLottery=random.nextInt(numEffective);int t=0;ThreadState toPick=null;for(Iterator i=waitQueue.iterator();i.hasNext();)ThreadState n=(ThreadState)getThreadState(KThread)i.next();t+=n.getEffectivePriority();if(t randomLottery)/当t的当前值大于randomLottery时说明当前循环到的线程正好落在目标区间内,即选出该线程toPick=n;return toPick;二、二、测试结果测试结果