❶ 数据结构与算法分析
本文出自:
www点54manong点com
请尊重原创,转载请注明出处,谢谢!
什么是数据结构,为什么要学习数据结构?数据结构是否是一门纯数学课程?它在专业课程体系中起什么样的作用?我们要怎么才能学好数据结构?… 相信同学们在刚开始《数据结构》这门课的学习时,心里有着类似前面几个问题的这样那样的疑问。希望下面的内容能帮助大家消除疑惑,下定决心坚持学好这门课:
1 学习数据数据结构的意义
数据结构是计算机科学与技术专业、计算机信息管理与应用专业,电子商务等专业的基础课,是十分重要的核心课程。所有的计算机系统软件和应用软件都要用到各种类型的数据结构。因此,要想更好地运用计算机来解决实际问题,仅掌握几种计算机程序设计语言是难以应付当前众多复杂的课题。要想有效地使用计算机、充分发挥计算机的性能,还必须学习和掌握好数据结构的有关知识。打好“数据结构”这门课程的扎实基础,对于学习计算机专业的其他课程,如操作系统、数据库管理系统、软件工程、编译原理、人工智能、图视学等都是十分有益的。
2 为什么要学习数据结构
在计算机发展的初期,人们使用计算机的目的主要是处理数值计算问题。当我们使用计算机来解决一个具体问题时,一般需要经过下列几个步骤:首先要从该具体问题抽象出一个适当的数学模型,然后设计或选择一个解此数学模型的算法,最后编出程序进行调试、测试,直至得到最终的解答。例如,求解梁架结构中应力的数学模型的线性方程组,可以使用迭代算法来求解。
由于当时所涉及的运算对象是简单的整型、实型或布尔类型数据,所以程序设计者的主要精力是集中于程序设计的技巧上,而无须重视数据结构。随着计算机应用领域的扩大和软、硬件的发展,非数值计算问题越来越显得重要。据统计,当今处理非数值计算性问题占用了85%以上的机器时间。这类问题涉及到的数据结构更为复杂,数据元素之间的相互关系一般无法用数学方程式加以描述。因此,解决这类问题的关键不再是数学分析和计算方法,而是要设计出合适的数据结构,才能有效地解决问题。下面所列举的就是属于这一类的具体问题。
例1:图书馆信息检索系统。当我们根据书名查找某本书有关情况的时候;或者根据作者或某个出版社查找有关书籍的时候,或根据书刊号查找作者和出版社等有关情况的时候,只要我们建立了相关的数据结构,按照某种算法编写了相关程序,就可以实现计算机自动检索。由此,可以在图书馆信息检索系统中建立一张按书刊号顺序排列的图书信息表和分别按作者、书名、出版社顺序排列的索引表,如图1.1所示。由这四张表构成的文件便是图书信息检索的数学模型,计算机的主要操作便是按照某个特定要求(如给定书名)对图书馆藏书信息文件进行查询。
诸如此类的还有学生信息查询系统、商场商品管理系统、仓库物资管理系统等。在这类文档管理的数学模型中,计算机处理的对象之间通常存在着的是一种简单的线性关系,这类数学模型可称为线性的数据结构。
例2:八皇后问题。在八皇后问题中,处理过程不是根据某种确定的计算法则,而是利用试探和回溯的探索技术求解。为了求得合理布局,在计算机中要存储布局的当前状态。从最初的布局状态开始,一步步地进行试探,每试探一步形成一个新的状态,整个试探过程形成了一棵隐含的状态树。如图1.2所示(为了描述方便,将八皇后问题简化为四皇后问题)。回溯法求解过程实质上就是一个遍历状态树的过程。在这个问题中所出现的树也是一种数据结构,它可以应用在许多非数值计算的问题中。
例3:教学计划编排问题。一个教学计划包含许多课程,在教学计划包含的许多课程之间,有些必须按规定的先后次序进行,有些则没有次序要求。即有些课程之间有先修和后续的关系,有些课程可以任意安排次序。这种各个课程之间的次序关系可用一个称作图的数据结构来表示,如图1.3所示。有向图中的每个顶点表示一门课程,如果从顶点vi到vj之间存在有向边<vi,vj>,则表示课程i必须先于课程j进行。由以上三个例子可见,描述这类非数值计算问题的数学模型不再是数学方程,而是诸如线性表、树、图之类的数据结构。因此,可以说数据结构课程主要是研究非数值计算的程序设计问题中所出现的计算机操作对象以及它们之间的关系和操作的学科。
学习数据结构的目的是为了了解计算机处理对象的特性,将实际问题中所涉及的处理对象在计算机中表示出来并对它们进行处理。与此同时,通过算法训练来提高学生的思维能力,通过程序设计的技能训练来促进学生的综合应用能力和专业素质的提高。
3数据结构课程的内容
数据结构与数学、计算机硬件和软件有十分密切的关系,它是介于数学、计算机硬件和计算机软件之间的一门计算机专业的核心课程,是高级程序设计语言、操作系统、编译原理、数据库、人工智能、图视学等课程的基础。同时,数据结构技术也广泛应用于信息科学、系统工程、应用数学以及各种工程技术领域。
数据结构课程重在讨论软件开发过程中的方案设计阶段、同时设计编码和分析阶段的若干基本问题。此外,为了构造出好的数据结构及其实现,还需考虑数据结构及其实现的评价与选择。因此,数据结构的内容包括三个层次的五个“要素”,如图1.3所示。
数据结构的核心技术是分解与抽象。通过分解可以划分出数据的三个层次;再通过抽象,舍弃数据元素的具体内容,就得到逻辑结构。类似地,通过分解将处理要求划分成各种功能,再通过抽象舍弃实现细节,就得到运算的定义。上述两个方面的结合使我们将问题变换为数据结构。这是一个从具体(即具体问题)到抽象(即数据结构)的过程。然后,通过增加对实现细节的考虑进一步得到存储结构和实现运算,从而完成设计任务。这是一个从抽象(即数据结构)到具体(即具体实现)的过程。熟练地掌握这两个过程是数据结构课程在专业技能培养方面的基本目标。
结束语:数据结构作为一门独立的课程在国外是从1968年才开始的,但在此之前其有关内容已散见于编译原理及操作系统之中。20世纪60年代中期,美国的一些大学开始设立有关课程,但当时的课程名称并不叫数据结构。1968年美国唐.欧.克努特教授开创了数据结构的最初体系,他所着的《计算机程序设计技巧》第一卷《基本算法》是第一本较系统地阐述数据的逻辑结构和存储结构及其操作的着作。从20世纪60年代末到70年代初,出现了大型程序,软件也相对独立,结构程序设计成为程序设计方法学的主要内容,人们越来越重视数据结构。从70年代中期到80年代,各种版本的数据结构着作相继出现。目前,数据结构的发展并未终结,一方面,面向各专门领域中特殊问题的数据结构得到研究和发展,如多维图形数据结构等;另一方面,从抽象数据类型和面向对象的观点来讨论数据结构已成为一种新的趋势,越来越被人们所重视。
❷ 操作系统(四)文件管理
文件—就是一组有意义的信息/数据集合
文件属于抽象数据类型。为了恰当地定义文件,需要考虑有关文件的操作。操作系统提供系统调用,它对文件进行创建、写、读、重定位、搠除和截断等操作。
所谓的“逻辑结构”,就是指在用户看来,文件内部的数据应该是如何组织起来的。而“物理结构”指的是在操作系统看来,文件的数据是如何存放在外存中的。
无结构文件:文件内部的数据就是一系列二进制流或字符流组成。又称“流式文件”
文件内部的数据其实就是一系列字符流,没有明显的结构特性。因此也不用探讨无结构文件的“逻辑结构”问题。
有结构文件:由一组相似的记录组成,又称“记录式文件”。每条记录又若干个数据项组成。 [1] 一般来说,每条记录有一个数据项可作为关键字。根据各条记录的长度(占用的存储空间)是否相等,又可分为定长记录和可变长记录两种。有结构文件按记录的组织形式可以分为:
对于含有N条记录的顺序文件,查找某关键字值的记录时,平均需要查找N/2次。在索引顺序文件中,假设N条记录分为√N组,索引表中有√N个表项,每组有√N条记录,在查找某关键字值的记录时,先顺序查找索引表,需要查找√N /2次,然后在主文件中对应的组中顺序查找,也需要查找√N/2次,因此共需查找√N/2+√N/2=√N次。显然,索引顺序文件提高了查找效率,若记录数很多,则可采用两级或多级索引
FCB的有序集合称为“文件目录”,一个FCB就是一个文件目录项。FCB中包含了文件的基本信息(文件名、物理地址、逻辑结构、物理结构等),存取控制信息(是否可读/可写、禁止访问的用户名单等),使用信息(如文件的建立时间、修改时间等)。最重要,最基本的还是文件名、文件存放的物理地址。
对目录的操作如下:
操作的时候,可以有以下几种目录结构:
早期操作系统并不支持多级目录,整个系统中只建立一张目录表,每个文件占一个目录项。
单级目录实现了“按名存取”,但是不允许文件重名。在创建一个文件时,需要先检查目录表中有没有重名文件,确定不重名后才能允许建立文件,并将新文件对应的目录项插入目录表中。显然, 单级目录结构不适用于多用户操作系统。
早期的多用户操作系统,采用两级目录结构。分为主文件目录(MFD,Master File Directory)和用户文件目录(UFD,User Flie Directory)。
允许不同用户的文件重名。文件名虽然相同,但是对应的其实是不同的文件。两级目录结构允许不同用户的文件重名,也可以在目录上实现实现访问限制(检查此时登录的用户名是否匹配)。但是两级目录结构依然缺乏灵活性,用户不能对自己的文件进行分类
用户(或用户进程)要访问某个文件时要用文件路径名标识文件,文件路径名是个字符串。各级目录之间用“/”隔开。从根目录出发的路径称为绝对路径。
系统根据绝对路径一层一层地找到下一级目录。刚开始从外存读入根目录的目录表;找到目录的存放位置后,从外存读入对应的目录表;再找到目录的存放位置,再从外存读入对应目录表;最后才找到文件的存放位置。整个过程需要3次读磁盘I/O操作。
很多时候,用户会连续访问同一目录内的多个文件,显然,每次都从根目录开始查找,是很低效的。因此可以设置一个“当前目录”。此时已经打开了的目录文件,也就是说,这张目录表已调入内存,那么可以把它设置为“当前目录”。当用户想要访问某个文件时,可以使用从当前目录出发的“相对路径”
可见,引入“当前目录”和“相对路径”后,磁盘I/O的次数减少了。这就提升了访问文件的效率。
树形目录结构可以很方便地对文件进行分类,层次结构清晰,也能够更有效地进行文件的管理和保护。但是,树形结构不便于实现文件的共享。为此,提出了“无环图目录结构”。
可以用不同的文件名指向同一个文件,甚至可以指向同一个目录(共享同一目录下的所有内容)。需要为每个共享结点设置一个共享计数器,用于记录此时有多少个地方在共享该结点。用户提出删除结点的请求时,只是删除该用户的FCB、并使共享计数器减1,并不会直接删除共享结点。只有共享计数器减为0时,才删除结点。
其实在查找各级目录的过程中只需要用到“文件名”这个信息,只有文件名匹配时,才需要读出文件的其他信息。因此可以考虑让目录表“瘦身”来提升效率。
当找到文件名对应的目录项时,才需要将索引结点调入内存,索引结点中记录了文件的各种信息,包括文件在外存中的存放位置,根据“存放位置”即可找到文件。存放在外存中的索引结点称为“磁盘索引结点”,当索引结点放入内存后称为“内存索引结点”。相比之下内存索引结点中需要增加一些信息,比如:文件是否被修改、此时有几个进程正在访问该文件等。
为文件设置一个“口令”(如:abc112233),用户请求访问该文件时必须提供“口令”。
优点:保存口令的空间开销不多,验证口令的时间开销也很小。
缺点:正确的“口令”存放在系统内部,不够安全。
使用某个“密码”对文件进行加密,在访问文件时需要提供正确的“密码”才能对文件进行正确的解密。 [3]
优点:保密性强,不需要在系统中存储“密码”
缺点:编码/译码,或者说加密/解密要花费一定时间。
在每个文件的FCB(或索引结点)中增加一个访问控制列表(Access-Control List, ACL),该表中记录了各个用户可以对该文件执行哪些操作。
有的计算机可能会有很多个用户,因此访问控制列表可能会很大,可以用精简的访问列表解决这个问题
精简的访问列表:以“组”为单位,标记各“组”用户可以对文件执行哪些操作。当某用户想要访问文件时,系统会检查该用户所属的分组是否有相应的访问权限。
索引结点,是一种文件目录瘦身策略。由于检索文件时只需用到文件名,因此可以将除了文件名之外的其他信息放到索引结点中。这样目录项就只需要包含文件名、索引结点指针。
索引结点中设置一个链接计数变量count,用于表示链接到本索引结点上的用户目录项数。
当User3访问“ccc”时,操作系统判断文件“ccc”属于Link类型文件,于是会根据其中记录的路径层层查找目录,最终找到User1的目录表中的“aaa”表项,于是就找到了文件1的索引结点。
类似于内存分页,磁盘中的存储单元也会被分为一个个“块/磁盘块/物理块”。很多操作系统中,磁盘块的大小与内存块、页面的大小相同
内存与磁盘之间的数据交换(即读/写操作、磁盘I/O)都是以“块”为单位进行的。即每次读入一块,或每次写出一块
在内存管理中,进程的逻辑地址空间被分为一个一个页面同样的,在外存管理中,为了方便对文件数据的管理,文件的逻辑地址空间也被分为了一个一个的文件“块”。于是文件的逻辑地址也可以表示为(逻辑块号,块内地址)的形式。用户通过逻辑地址来操作自己的文件,操作系统要负责实现从逻辑地址到物理地址的映射
连续分配方式要求每个文件在磁盘上占有一组连续的块。用户给出要访问的逻辑块号,操作系统找到该文件对应的目录项(FCB)——可以直接算出逻辑块号对应的物理块号,物理块号=起始块号+逻辑块号。还需要检查用户提供的逻辑块号是否合法(逻辑块号≥ 长度就不合法)因此 连续分配支持顺序访问和直接访问 (即随机访问)
读取某个磁盘块时,需要移动磁头。访问的两个磁盘块相隔越远,移动磁头所需时间就越长。 连续分配的文件在顺序读/写时速度最快,物理上采用连续分配的文件不方便拓展,且存储空间利用率低,会产生难以利用的磁盘碎片可以用紧凑来处理碎片,但是需要耗费很大的时间代价。。
链接分配采取离散分配的方式,可以为文件分配离散的磁盘块。分为隐式链接和显式链接两种。
用户给出要访问的逻辑块号i,操作系统找到该文件对应的目录项(FCB)…从目录项中找到起始块号(即0号块),将0号逻辑块读入内存,由此知道1号逻辑块存放的物理块号,于是读入1号逻辑块,再找到2号逻辑块的存放位置……以此类推。因此,读入i号逻辑块,总共需要i+1次磁盘I/O。
采用链式分配(隐式链接)方式的文件,只支持顺序访问,不支持随机访问,查找效率低。另外,指向下一个盘块的指针也需要耗费少量的存储空间。但是,采用隐式链接的链接分配方式,很方便文件拓展。另外,所有的空闲磁盘块都可以被利用,不会有碎片问题,外存利用率高。
把用于链接文件各物理块的指针显式地存放在一张表中。即文件分配表(FAT,File Allocation Table)
一个磁盘仅设置一张FAT 。开机时,将FAT读入内存,并常驻内存。FAT的各个表项在物理上连续存储,且每一个表项长度相同,因此“物理块号”字段可以是隐含的。
从目录项中找到起始块号,若i>0,则查询内存中的文件分配表FAT,往后找到i号逻辑块对应的物理块号。 逻辑块号转换成物理块号的过程不需要读磁盘操作。
采用链式分配(显式链接)方式的文件,支持顺序访问,也支持随机访问 (想访问i号逻辑块时,并不需要依次访问之前的0 ~ i-1号逻辑块), 由于块号转换的过程不需要访问磁盘,因此相比于隐式链接来说,访问速度快很多。显然,显式链接也不会产生外部碎片,也可以很方便地对文件进行拓展。
索引分配允许文件离散地分配在各个磁盘块中,系统会为每个文件建立一张索引表,索引表中记录了文件的各个逻辑块对应的物理块(索引表的功能类似于内存管理中的页表——建立逻辑页面到物理页之间的映射关系)。索引表存放的磁盘块称为索引块。文件数据存放的磁盘块称为数据块。
在显式链接的链式分配方式中,文件分配表FAT是一个磁盘对应一张。而索引分配方式中,索引表是一个文件对应一张。可以用固定的长度表示物理块号 [4] ,因此,索引表中的“逻辑块号”可以是隐含的。
用户给出要访问的逻辑块号i,操作系统找到该文件对应的目录项(FCB)…从目录项中可知索引表存放位置,将索引表从外存读入内存,并查找索引表即可只i号逻辑块在外存中的存放位置。
可见, 索引分配方式可以支持随机访问。文件拓展也很容易实现 (只需要给文件分配一个空闲块,并增加一个索引表项即可)但是 索引表需要占用一定的存储空间
索引块的大小是一个重要的问题,每个文件必须有一个索引块,因此索引块应尽可能小,但索引块太小就无法支持大文件,可以采用以下机制:
空闲表法适用于“连续分配方式”。分配磁盘块:与内存管理中的动态分区分配很类似,为一个文件分配连续的存储空间。同样可采用首次适应、最佳适应、最坏适应等算法来决定要为文件分配哪个区间。回收磁盘块:与内存管理中的动态分区分配很类似,当回收某个存储区时需要有四种情况——①回收区的前后都没有相邻空闲区;②回收区的前后都是空闲区;③回收区前面是空闲区;④回收区后面是空闲区。总之,回收时需要注意表项的合并问题。
操作系统保存着链头、链尾指针。如何分配:若某文件申请K个盘块,则从链头开始依次摘下K个盘块分配,并修改空闲链的链头指针。如何回收:回收的盘块依次挂到链尾,并修改空闲链的链尾指针。适用于离散分配的物理结构。为文件分配多个盘块时可能要重复多次操作
操作系统保存着链头、链尾指针。如何分配:若某文件申请K个盘块,则可以采用首次适应、最佳适应等算法,从链头开始检索,按照算法规则找到一个大小符合要求的空闲盘区,分配给文件。若没有合适的连续空闲块,也可以将不同盘区的盘块同时分配给一个文件,注意分配后可能要修改相应的链指针、盘区大小等数据。如何回收:若回收区和某个空闲盘区相邻,则需要将回收区合并到空闲盘区中。若回收区没有和任何空闲区相邻,将回收区作为单独的一个空闲盘区挂到链尾。 离散分配、连续分配都适用。为一个文件分配多个盘块时效率更高
位示图:每个二进制位对应一个盘块。在本例中,“0”代表盘块空闲,“1”代表盘块已分配。位示图一般用连续的“字”来表示,如本例中一个字的字长是16位,字中的每一位对应一个盘块。因此可以用(字号,位号)对应一个盘块号。当然有的题目中也描述为(行号,列号)
盘块号、字号、位号从0开始,若n表示字长,则
如何分配:若文件需要K个块,①顺序扫描位示图,找到K个相邻或不相邻的“0”;②根据字号、位号算出对应的盘块号,将相应盘块分配给文件;③将相应位设置为“1”。如何回收:①根据回收的盘块号计算出对应的字号、位号;②将相应二进制位设为“0”
空闲表法、空闲链表法不适用于大型文件系统,因为空闲表或空闲链表可能过大。UNIX系统中采用了成组链接法对磁盘空闲块进行管理。文件卷的目录区中专门用一个磁盘块作为“超级块”,当系统启动时需要将超级块读入内存。并且要保证内存与外存中的“超级块”数据一致。
进行Create系统调用时,需要提供的几个主要参数:
操作系统在处理Create系统调用时,主要做了两件事:
进行Delete系统调用时,需要提供的几个主要参数:
操作系统在处理Delete系统调用时,主要做了几件
事:
在很多操作系统中,在对文件进行操作之前,要求用户先使用open系统调用“打开文件”,需要提供的几个主要参数:
操作系统在处理open系统调用时,主要做了几件事:
进程使用完文件后,要“关闭文件”
操作系统在处理Close系统调用时,主要做了几件事:
进程使用read系统调用完成写操作。需要指明是哪个文件(在支持“打开文件”操作的系统中,只需要提供文件在打开文件表中的索引号即可),还需要指明要读入多少数据(如:读入1KB)、指明读入的数据要放在内存中的什么位置。操作系统在处理read系统调用时,会从读指针指向的外存中,将用户指定大小的数据读入用户指定的内存区域中。
进程使用write系统调用完成写操作,需要指明是哪个文件(在支持“打开文件”操作的系统中,只需要提供文件在打开文件表中的索引号即可),还需要指明要写出多少数据(如:写出1KB)、写回外存的数据放在内存中的什么位置操作系统在处理write系统调用时,会从用户指定的内存区域中,将指定大小的数据写回写指针指向的外存。
寻找时间(寻道时间)T S :在读/写数据前,将磁头移动到指定磁道所花的时间。
延迟时间T R :通过旋转磁盘,使磁头定位到目标扇区所需要的时间。设磁盘转速为r(单位:转/秒,或转/分),则平均所需的延迟时间
传输时间T t :从磁盘读出或向磁盘写入数据所经历的时间,假设磁盘转速为r,此次读/写的字节数为b,每个磁道上的字节数为N。则
总的平均存取时间Ta
延迟时间和传输时间都与磁盘转速相关,且为线性相关。而转速是硬件的固有属性,因此操作系统也无法优化延迟时间和传输时间,但是操作系统的磁盘调度算法会直接影响寻道时间
根据进程请求访问磁盘的先后顺序进行调度。
优点:公平;如果请求访问的磁道比较集中的话,算法性能还算过的去
缺点:如果有大量进程竞争使用磁盘,请求访问的磁道很分散,则FCFS在性能上很差,寻道时间长。
SSTF算法会优先处理的磁道是与当前磁头最近的磁道。可以保证每次的寻道时间最短,但是并不能保证总的寻道时间最短。(其实就是贪心算法的思想,只是选择眼前最优,但是总体未必最优)
优点:性能较好,平均寻道时间短
缺点:可能产生“饥饿”现象
SSTF算法会产生饥饿的原因在于:磁头有可能在一个小区域内来回来去地移动。为了防止这个问题,可以规定,只有磁头移动到最外侧磁道的时候才能往内移动,移动到最内侧磁道的时候才能往外移动。这就是扫描算法(SCAN)的思想。由于磁头移动的方式很像电梯,因此也叫电梯算法。
优点:性能较好,平均寻道时间较短,不会产生饥饿现象
缺点:①只有到达最边上的磁道时才能改变磁头移动方向②SCAN算法对于各个位置磁道的响应频率不平均
扫描算法(SCAN)中,只有到达最边上的磁道时才能改变磁头移动方向,事实上,处理了184号磁道的访问请求之后就不需要再往右移动磁头了。LOOK调度算法就是为了解决这个问题,如果在磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向。(边移动边观察,因此叫LOOK)
优点:比起SCAN算法来,不需要每次都移动到最外侧或最内侧才改变磁头方向,使寻道时间进一步缩短
SCAN算法对于各个位置磁道的响应频率不平均,而C-SCAN算法就是为了解决这个问题。规定只有磁头朝某个特定方向移动时才处理磁道访问请求,而返回时直接快速移动至起始端而不处理任何请求。
优点:比起SCAN来,对于各个位置磁道的响应频率很平均。
缺点:只有到达最边上的磁道时才能改变磁头移动方向,另外,比起SCAN算法来,平均寻道时间更长。
C-SCAN算法的主要缺点是只有到达最边上的磁道时才能改变磁头移动方向,并且磁头返回时不一定需要返回到最边缘的磁道上。C-LOOK算法就是为了解决这个问题。如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。
优点:比起C-SCAN算法来,不需要每次都移动到最外侧或最内侧才改变磁头方向,使寻道时间进一步缩短
磁盘地址结构的设计:
Q:磁盘的物理地址是(柱面号,盘面号,扇区号)而不是(盘面号,柱面号,扇区号)
A:读取地址连续的磁盘块时,采用(柱面号,盘面号,扇区号)的地址结构可以减少磁头移动消耗的时间
减少延迟时间的方法:
Step 1:进行低级格式化(物理格式化),将磁盘的各个磁道划分为扇区。一个扇区通常可分为头、数据区域(如512B大小)、尾三个部分组成。管理扇区所需要的各种数据结构一般存放在头、尾两个部分,包括扇区校验码(如奇偶校验、CRC循环冗余校验码等,校验码用于校验扇区中的数据是否发生错误)
Step 2:将磁盘分区,每个分区由若干柱面组成(即分为我们熟悉的C盘、D盘、E盘)
Step 3:进行逻辑格式化,创建文件系统。包括创建文件系统的根目录、初始化存储空间管理所用的数据结构(如位示图、空闲分区表)
计算机开机时需要进行一系列初始化的工作,这些初始化工作是通过执行初始化程序(自举程序)完成的
初始化程序可以放在ROM(只读存储器)中。ROM中的数据在出厂时就写入了,并且以后不能再修改。ROM中只存放很小的“自举装入程序”,完整的自举程序放在磁盘的启动块(即引导块/启动分区)上,启动块位于磁盘的固定位置,开机时计算机先运行“自举装入程序”,通过执行该程序就可找到引导块,并将完整的“自举程序”读入内存,完成初始化。拥有启动分区的磁盘称为启动磁盘或系统磁盘(C:盘)
对于简单的磁盘,可以在逻辑格式化时(建立文件系统时)对整个磁盘进行坏块检查,标明哪些扇区是坏扇区,比如:在FAT表上标明。(在这种方式中,坏块对操作系统不透明)。
对于复杂的磁盘,磁盘控制器(磁盘设备内部的一个硬件部件)会维护一个坏块链表。在磁盘出厂前进行低级格式化(物理格式化)时就将坏块链进行初始化。会保留一些“备用扇区”,用于替换坏块。这种方案称为扇区备用。且这种处理方式中,坏块对操作系统透明
❸ 一道很难的算法题
只想到一种暴力方法,就是找到一个最短的+串进行枚举所有的匹配可能,由于长度最多是8,2^8不是很大还可以接受.然后对所有的+串进行一次改进,每当发现一个匹配串不符合某个+串,则进行添加,若无论如何都无法匹配,则否决.然后再对所有的-串进行一次检查,若匹配则否决,最后剩下的匹配串里面输出最短那个.
时间复杂度在O(2^m*n*m)级别,还算在接受范围之内.
不过的确不优美,最好是能找到更优的做法.
❹ 王者荣耀:系统怎么操作给我匹配这么多神坑队友,懂了
因为有大把的游戏玩家可以匹配到一起,就是很多几千场的高手玩家经常会遇到几十场甚至几场的菜鸟玩家,他们也许是因为你的出色发挥而获得胜利,也许是因为你的故意送分而崩盘,如果你玩的很差,不厉害,有一个很重要的东西叫matchmaking ranking(比赛匹配分级),以此寻找存在感
在众多玩家的一致呼唤下,MMR匹配分级系统应运而生
MMR匹配系统,会一直连胜过连败,直到MMR平衡至接近于0
这看上去很公平。但是问题是,当你一直连胜或连败后。这是一套成熟的,对手的MMR增加,你输了对手的MMR减少。当你的MMR达到一个极值,导致一边倒的碾压和屠杀。就是因为平衡被打破所造成的,玩家之间丛段含对局的匹配方式主要有两种:随机匹配和等级场次匹配。渐渐的人们发现按照随机的匹配方式对玩家进行分级来匹配会有一个弊端,也许是20,这将会打破平衡。也许是11。
但是这种靠系统强制干预玩家胜败的机制对于一些职业玩家和高手玩家来说简直是糟透了,也有无数大神亲测证实,至于原因要先讲解一下MMR(比赛匹配分级系统)
在竞技类游戏刚刚进入市场时王者连胜必连败,这对一些新手玩家会造成极差的游戏体验。至于另一种匹配模式按照等级和对战场次来匹配对手虽然避免了之前的问题,但是新的问题再次出现,死的也多,那么坑爹选手就出现了,就像钟摆一样,他总是希望回到它与地平线垂直的位置,MMR=0这个位置。
这渗笑样的匹配机制改善了玩家对局中的游戏体验,道理是一样的!
匹配系统—50%的平衡(胜率控制)
过于严苛的匹配计算方式会导致匹配等待时间的延长,也许是12,公正,平衡的匹配系统。
首先,反过来,那你匹配到的对手将会高于这个值,它有一个值初始为0 。
当你进燃羡行比赛的时候,每赢一场MMR+1,每输一场MMR-1
当你赢得越多,你的MMR越高,那你将面对的挑战也就越强,就是一些拥有几千场对战经验的高手会故意练小号,在低端局大杀特杀,大神分分钟登榜,你就有很大概率去坑这个强的玩家,匹配系统中,不同于一般的匹配,也不同于等级限制匹配,系统会把你和比较强的选手匹配在一起,这是许多玩家公认的,如果很低,那系统判断你弱,系统会把强的玩家和弱的玩家匹配在一起,根据你的击杀/死亡率、参团率输出比等等计算出一个综合分数,如果这个数值很高。你赢了,但是也维持一个相对的平衡,50%的平衡。从而保证了用户粘性。
那么很多人就会有疑问了,那系统会判断你是强,简称MMR,那么就造成了后面比赛的平衡将会打破,你的队友MMR也会变化,对手的MMR指数将会比你的高,相反的,如果你的MMR很低,那你匹配到的对手的MMR也将会很低,比如你的MMR为10,所以系统会允许差别在一定数值之内的玩家匹配到一起,那寻找游戏的速度将会加快,不会让大神觉得游戏索然无味也不会让菜鸟新手觉得游戏玩不下去,菜鸟分分钟输到退游,毫无趣味性可言,为什么有时候你会遇到3-4个比你菜的队友,或者是你遇到了3-4个比你厉害很多的队友,在连胜-连败中不断循环,系统怎么判断玩家的强弱程度呢?
其实除了MMR,系统还有一个内置数据,多数对局往往是一边倒的碾压和一边倒的被碾压,想要快速达到符合自己的段位往往需要超越此段位几倍的技术和意识,否则在此机制的干预下
❺ 操作系统第四章【2】内存空间管理---连续
内存分为系统区和用户区两部分:
系统区:仅提供给OS使用,通常放在内存低址部分
用户区:除系统区以外的全部内存空间,提供给用户使用。
最简单的一种存储管理方式,只能用于单用户、单任务的操作系统中。
优点:易于管理。
缺点:对要求内存空间少的程序,造成内存浪费;程序全部装入,很少使用的程序部分也占用内存。
把内存分为一些大小相等或不等的分区(partition),每个应用进程占用一个分区。操作系统占用其中一个分区。
u提高:支持多个程序并发执行,适用于多道程序系统和分时系统。最早的多道程序存储管理方式。
划分为几个分区,便只允许几道作业并发
1如何划分分区大小:
n分区大小相等:只适合于多个相同程序的并发执行(处理多个类型相同的对象)。缺乏灵活性。
n分区大小不等:多个小分区、适量的中等分区、少量的大分区。根据程序的大小,分配当前空闲的、适当大小的分区。
2需要的数据结构
建立一记录相关信息的分区表(或分区链表),表项有: 起始位置 大小 状态
分区表中,表项值随着内存的分配和释放而动态改变
3程序分配内存的过程:
也可将分区表分为两个表格:空闲分区表/占用分区表。从而减小每个表格长度。
检索算法:空闲分区表可能按不同分配算法采用不同方式对表项排序(将分区按大小排队或按分区地址高低排序)。
过程:检索空闲分区表;找出一个满足要求且尚未分配的分区,分配给请求程序;若未找到大小足够的分区,则拒绝为该用户程序分配内存。
固定分配的不足:
内碎片(一个分区内的剩余空间)造成浪费
分区总数固定,限制并发执行的程序数目。
(3)动态分区分配
分区的大小不固定:在装入程序时根据进程实际需要,动态分配内存空间,即——需要多少划分多少。
空闲分区表项:从1项到n项:
内存会从初始的一个大分区不断被划分、回收从而形成内存中的多个分区。
动态分区分配
优点:并发进程数没有固定数的限制,不产生内碎片。
缺点:有外碎片(分区间无法利用的空间)
1)数据结构
①空闲分区表:
•记录每个空闲分区的情况。
•每个空闲分区对应一个表目,包括分区序号、分区始址及分区的大小等数据项。
②空闲分区链:
•每个分区的起始部分,设置用于控制分区分配的信息,及用于链接各分区的前向指针;
•分区尾部则设置一后向指针,在分区末尾重复设置状态位和分区大小表目方便检索。
2)分区分配算法
动态分区方式,分区多、大小差异各不相同,此时把一个新作业装入内存,更需选择一个合适的分配算法,从空闲分区表/链中选出一合适分区
①首次适应算法FF
②循环首次适应算法
③最佳适应算法
④最差适应算法
⑤快速适应算法
①首次适应算法FF(first-fit)
1.空闲分区排序:以地址递增的次序链接。
2.检索:分配内存时,从链首开始顺序查找直至找到一个大小能满足要求的空闲分区;
3.分配:从该分区中划出一块作业要求大小的内存空间分配给请求者,余下的空闲分区大小改变仍留在空闲链中。
u若从头到尾检索不到满足要求的分区则分配失败
优点:优先利用内存低址部分,保留了高地址部分的大空闲区;
缺点:但低址部分不断划分,会产生较多小碎片;而且每次查找从低址部分开始,会逐渐增加查找开销。
②循环首次适应算法(next-fit)
1.空闲分区排序:按地址
2.检索:从上次找到的空闲分区的下一个空闲分区开始查找,直到找到一个能满足要求的空闲分区。为实现算法,需要:
©设置一个起始查寻指针
©采用循环查找方式
3.分配:分出需要的大小
优点:空闲分区分布均匀,减少查找开销
缺点:缺乏大的空闲分区
③最佳适应算法 (best-fit)
总是把能满足要求、又是最小的空闲分区分配给作业,避免“大材小用”。
1.空闲分区排序:所有空闲分区按容量从小到大排序成空闲分区表或链。
2.检索:从表或链的头开始,找到的第一个满足的就分配
3.分配:分出需要的大小
缺点:每次找到最合适大小的分区割下的空闲区也总是最小,会产生许多难以利用的小空闲区(外碎片)
④最差适应算法/最坏匹配法(worst-fit): 基本不留下小空闲分区,但会出现缺乏较大的空闲分区的情况。
⑤快速适应算法
n根据进程常用空间大小进行划分,相同大小的串成一个链,需管理多个各种不同大小的分区的链表。进程需要时,从最接近大小需求的链中摘一个分区。类似的:伙伴算法
n能快速找到合适分区,但链表信息会很多;实际上是空间换时间。
3)分区分配操作
分配内存
找到满足需要的合适分区,划出进程需要的空间
s<=size,将整个分区分配给请求者
s> size,按请求的大小划出一块内存空间分配出去,余下部分留在空闲链中,将分配区首址返回给调用者。
回收内存
进程运行完毕释放内存时,系统根据回收区首址a,在空闲分区链(表)中找到相应插入点,根据情况修改空闲分区信息,可能会进行空闲分区的合并:
(4)动态重定位分区分配
——有紧凑功能的动态分区分配
用户程序在内存中移动,将空闲空间紧凑起来提高空间利用率。但必然需要地址变化,增加“重定位”工作。
(5)内存空间管理之对换
当内存空间还是满足不了需求时,引入“对换”思想:
把内存中暂时不能运行、或暂时不用的程序和数据调到外存上,以腾出足够的内存;把已具备运行条件的进程和进程所需要的程序和数据,调入内存。
u按对换单位分类:
Ø整体对换(或进程对换):以整个进程为单位(连续分配)
Ø页面对换或分段对换:以页或段为单位(离散分配)
❻ Linux内存系统
维基网络——虚拟内存定义
All about Linux swap space
Linux将物理RAM (Random Access Memory) 划分为称为页面的内存块。交换是将一页内存复制到硬盘上的预配置空间(称为交换空间)以释放改内存页面上的过程。物理内存和交换空间的组合就是可用的虚拟内存量。
虚拟内存的那点事儿
进程是与其他进程共享CPU和内存资源的。为了有效的管理内存并减少出错,现代操作系统提供了一种对主存的抽象概念,即:虚拟内存( Virtual Memory )。 虚拟内存为每个进程提供一个一致的,私有的地址空间,每个进程拥有一片连续完整的内存空间。
正如 维基网络 所说,虚拟内存不只是“使用硬盘空间来扩展内存”的技术。 虚拟内存的重要意义是它定义了一个连续的虚拟地址空间, 使得程序编写难度降低。并且, 把内存扩展到硬盘空间只是使用虚拟内存的必然结果,虚拟内存空间会存在硬盘中,并且会被全部放入内存中缓冲(按需),有的操作系统还会在内存不够的情况下,将一进程的内存全部放入硬盘空间中,并在切换到进程时再从硬盘读取 (这也是Windows会经常假死的原因...)。
虚拟内存主要提供了如下三个重要的能力:
内存通常被组织为一个由M个连续的字节大小的单元组成的数组。每个字节都有一个唯一的物理地址 (Physical Address PA) ,作为到数组的索引。
CPU访问内存最简单直接的方法就是使用物理地址,这种寻址方式称为 物理寻址 。
现代计算机使用的是一种被称为虚拟寻址 (Virtual Addressing) 的寻址方式。 使用虚拟寻址,CPU需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。
虚拟寻址需要硬件与操作系统之间相互合作。 CPU中含有一个被称为内存管理单元 (Memory Management Unit,MMU) 的硬件,它的功能是将虚拟地址转换称为物理地址,MMU需要借助存放在内存中的 页表 来动态翻译虚拟地址,该页表由操作系统管理。
分页表是一种数据结构,它用于计算机操作系统中虚拟内存系统,其存储了虚拟地址到物理地址之间的映射。虚拟地址在访问进程中是唯一的,而物理地址在硬件(比如内存)中是唯一的。
在操作系统中使用 虚拟内存 ,每个进程会认为使用一块大的连续的内存,事实上,每个进程的内存散布在 物理内存 的不同区域。或者可能被调出到备份存储中(一般是硬盘)。当一个进程请求自己的内存,操作系统负责把程序生成的虚拟地址,映射到实际存储的物理内存上。操作系统在 分页表 中存储虚拟地址到物理地址的映射。每个映射被称为 分页表项(page table entry ,PTE) 。
在一个简单的地址空间方案中,由虚拟地址寻址的页与物理内存中的帧之间的关系。物理内存可以包含属于许多进程的页。如果不经常使用,或者物理内存已满,可以将页面分页到磁盘。在上图中,并非所有页面都在物理内存中。
虚拟地址到物理地址的转换(即虚拟内存的管理)、内存保护、CPU高速缓存的控制。
现代的内存管理单元是以 页 的方式,分割虚拟地址空间(处理器使用的地址范围)的;页的大小是2的n次方,通常为几KB(字节)。地址尾部的n位(页大小的2的次方数)作为页内的偏移量保持不变。其余的地址位(address)为(虚拟)页号。
内存管理单元通常借助一种叫做转译旁观缓冲器(Translation Lookaside Buffer,TLB)和相联高速缓存来将虚拟页号转换为物理页号。当后备缓冲器中没有转换记录时,则使用一种较慢的机制,其中包括专用硬件的数据结构或软件辅助手段。这个数据结构称为 分页表 ,页表中的数据叫做 分页表项 (page table entry PTE)。物理页号结合页偏移量便提供了完整的物理地址。
页表 或 转换后备缓冲器数据项应该包括的信息有:
有时候,TLB和PTE会 禁止对虚拟页访问 ,这可能是因为没有RAM与虚拟页相关联。如果是这种情况,MMU将向CPU发出页错误的信号,操作系统将进行处理,也许会寻找RAM的空白帧,同时建立一个新的PTE将之映射到所请求的虚拟地址。如果没有空闲的RAM,可能必须关闭一个已经存在的页面,使用一些替换算法,将之保存到磁盘中(这被称为页面调度)。
当需要将虚拟地址转换为物理地址时,首先搜索TLB,如果找到匹配(TLB)命中,则返回物理地址并继续存储器访问。然而,如果没有匹配(称为TLB未命中),则MMU或操作系统TLB未命中处理器通常会查找 页表 中的地址映射以查看是否存在映射(页面遍历),如果存在,则将其写回TLB(这必须完成,因为硬件通过虚拟存储器系统中的TLB访问存储器),并且重启错误指令(这也可以并行发生)。此后续转换找到TLB命中,并且内存访问将继续。
虚拟地址到物理地址的转换过程,如果虚拟内存不存在与TLB,转换会被重置并通过分页表和硬件寻找。
通常情况下,用于处理此中断的程序是操作系统的一部分。如果操作系统判断此次访问有效,那么 操作系统会尝试将相关的分页从硬盘上的虚拟内存文件调入内存。 而如果访问是不被允许的,那么操作系统通常会结束相关的进程。
虽然叫做“页缺失”错误,但实际上这并不一定是一种错误。而且这一机制是利用虚拟内存来增加程序可用内存空间。
发生这种情况的可能性:
当原程序再次需要该页内的数据时,如果这一页确实没有被分配出去,那么系统只需要重新为该页在MMU内注册映射即可。
操作系统需要:
硬性页缺失导致的性能损失是很大的。
另外,有些操作系统会将程序的一部分延迟到需要使用的时候再加载入内存执行,以此提升性能。这一特性也是通过捕获硬性页缺失达到的。
当硬性页缺失过于频繁发生时,称发生 系统颠簸。
具体动作与所使用的操作系统有关,比如Windows会使用异常机制向程序报告,而类Unix系统则使用信号机制。
尽管在整个运行过程中,程序引用不同的页面总数(也就是虚拟内存大小)可能超出了物理存储器(DRAM)总大小,但是程序常常在较小的活动页面上活动,这个集合叫做工作集或者常驻集。在工作集被缓存后,对它的反复调用会使程序命中提高,从而提高性能。
大部分的程序都可以在存储器获取数据和读取中达到稳定的状态,当程序达到稳定状态时,存储器的使用量通常都不会太大。虚拟内存虽然可以有效率控制存储器的使用, 但是大量的页缺失还是造成了系统迟缓的主要因素。 当工作集的大小超过物理存储器大小,程序将会发生一种不幸的情况,这种情况称为 “颠簸” ,页面将不停的写入、释放、读取,由于大量的丢失(而非命中)而损失极大性能。用户可以增加随机存取存储器的大小或是减少同时在系统里运行程序的数量来降低系统颠簸的记录。
推荐阅读:
操作系统--分页(一)
操作系统实现(二):分页和物理内存管理