操作系统组织数据的方法详解版

下面讨论操作系统实现的一个核心问题:系统如何组织数据。本节简要讨论多个基本数据结构,它们在操作系统中用得很多。

列表、堆栈及队列

数组是个简单的数据结构,它的元素可被直接访问。例如,内存就是一个数组。如果所存的数据项大于一字节,那么可用多个字节来保存数据项,并可按项码 x 项大小(item number X item size)来寻址。不过,如何保存可变大小的项?再者,如何删除一项而不影响其他项的相对位置?对于这些情况,数组不如其他数据结构。

在计算机科学中,除了数组,列表可能是最为重要的数据结构。不过,数组的项可以直接访问,而列表的项需要按特定次序来访问。即列表(list)将一组数据表示成序列。实现这种结构的最常用方法是链表(linked list),项与项是链接起来的。链表包括多个类型:
  • 单向链表(singly linked list)的每项指向它的后继,如图 1 所示:


图 1 单向链表
 
  • 双向链表(doubly linked list)的每项指向它的前驱与后继,如图 2 所示:


图 2 双向链表
 
  • 循环链表(circularly linked list)的最后一项指向第一项(而不是设为 null),如图 3 所示:


图 3 循环链表

链表允许不同大小的项,各项的插入与删除也很方便。链表的使用有一个潜在缺点:在大小为 n 的链表中,获得某一特定项的性能是线性的,即 O(n),这是由于在最坏情况下需要遍历所有的 n 个元素。列表有时直接用于内核算法,不过,更多的是用于构造更为强大的数据结构,如堆栈和队列等。

堆栈(stack)作为有序数据结构,在增加和删除数据项时采用后进先出(Last In First Out,LIFO)的原则,即最后增加到堆栈的项是第一个被删除的。堆栈的项的插入和删除,分别称为压入(push)弹出(pop)

操作系统在执行函数调用时,经常采用堆栈。当调用函数时,参数、局部变量及返回地址首先压人堆栈;当从函数调用返回时,会从堆栈上弹出这些项。

相反,队列(queue)作为有序数据结构,采用先进先出(First in First Out,FIFO)的原则:删除队列的项的顺序与插入的顺序一致。

日常生活的队列样例有很多,如商店客户排队等待结账,汽车排队等待信号灯。操作系统的队列也有很多,例如,送交打印机的作业通常按递交顺序来打印,等待 CPU 的任务通常按队列来组织。

树(tree)是一种数据结构,可以表示数据层次。树结构的数据值可按父-子关系连接起来。对于一般树(general tree),父结点可有多个子结点。对于二叉树(binary tree),父结点最多可有两个子结点,即左子结点(left child)和右子结点(right child)。二叉查找树(binary search tree)还要求对两个子结点进行排序,如左子结点 ≤ 右子结点。


图 4 二叉查找树

图 4 为一个二叉查找树的例子。当需要对一个二叉查找树进行查找时,最坏性能为 O(n)(请想一想这是为什么)。为了纠正这种情况,我们可以通过算法来创建平衡二叉查找树(balanced binary search tree)。这样,包含 n 个项的树最多只有 lgn 层,这可确保最坏性能为 O(lgn)。在后续章节中,我们还将会看到,Linux 在 CPU 调度算法中就使用了平衡二叉查找树。

哈希函数与哈希表

哈希函数(hash function)将一个数据作为输入,对此进行数值运算,然后返回一个数值。该值可用作一个表(通常为数据组)的索引,以快速获得数据。虽然在最坏情况下从大小为n的列表中查找数据项所需的比较会是 O(n),但是采用哈希函数来从表中获得数据可能只有 O(1),这与具体实现有关。由于性能关系,哈希函数在操作系统中用得很广。

哈希函数有一潜在问题:两个输入可能产生同样的输出值,即它们会链接到列表的同一位置。哈希碰撞(hash collision)可以这样处理:在列表位置上可以存放一个链表,以便将具有相同哈希值的所有项链接起来。当然,碰撞越多,哈希函数的效率越低。

哈希函数的另一用途是实现哈希表(hash map),即利用哈希函数将键(key)和值 (value)关联起来。例如,可将键 operating 映射到值 system。有了这个映射,就可将哈希函数应用于键,进而从哈希表中获得对应值(图 5)。


图 5 哈希映射

例如,现有用户名称映射到用户密码。 用户认证可以这样进行:用户输入他的用户名称和密码;将哈希函数应用于用户名称,以获取密码;获取密码再与用户输入的密码进行比较,以便认证。

位图

位图(bitmap)为 n 个二进制位的串,用于表示 n 项的状态。例如,假设有若干资源,每个资源的可用性可用二进制数字来表示:0 表示资源可用,而 1 表示资源不可用(或相反)。位图的第 i 个位置的值与第 i 个资源相关联。

例如,现有如下位图:

001011101

第 2、4、5、6 和 8 个资源是不可用的,第 0、1、3 和 7 个资源是可用的。

当考虑空间效率时,位图优势明显。如果所用的布尔值是 8 位的而不是 1 位的,那么最终的数据结构将会是原来的 8 倍。因此,当需要表示大量资源的可用性时,通常采用位图。磁盘驱动器就是这么工作的。一个中等大小的磁盘可以分成数千个单元,称为磁盘块(disk block)。每个磁盘块的可用性就可通过位图来表示。

数据结构广泛用于实现操作系统。因此,除了这里讨论的一些数据结构,在分析内核算法与实现时,也会讨论其他的数据结构。

知识扩展:Linux 内核所用的数据结构有源码。头文件 <linux/list.h> 包括内核所用的链表数据结构的实现细节。Linux 的队列称为 kfifo,源代码的目录 kernel 的文件 kfifo.c 包含它的实现。Linux 通过红黑树提供了平衡二分查找树的实现,头文件 <linux/rbtree.h> 包括它的细节。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


起步 处理器架构,参考 x86是指intel的开发的一种32位指令集 intel和amd早期的cpu都支持这种指令集 AMD比Intel率先制造出了商用的兼容x86的CPU,AMD称之为AMD64 Intel选择了设计一种不兼容x86的全新64为指令集,称之为IA-64,后来支持AMD64的指令集,
pscp pscp -P 22 C:\work\test.txt root@192.168.1.5:/home/data pscp -P 22 root@192.168.1.5:/home/data/test.txt C://work// 检索 find / -name default.config
文件处理 ls -a # 显示所有文件 ls -l # 显示详细信息 ls -d # 显示路径 mkdir /目录名称 # 创建目录 cd /目录名称 # 切换目录 pwd # 显示当前路径 rmdir /目录名称 # 删除目录 cp -rp [目录名称] [目标目录] # 复制目录到目标目录 cp
准备一台电脑(我就用联想拯救者r7000演示) 参考博客制作启动盘 插上U盘,启动电脑,一直按F2 进入如下页面后,将U盘设置为第一启动项,点击exit,保存并退出 之后进入如下页面,选择第三项 进入如下页面,选择第四项 进入如下页面,选择第一项,选中后,先不要点Enter 按e键,将inst.st
认识 Linux系统是参考了UNIX系统作为模板开发的,但没有使用UNIX的代码;是UNIX的一种,但不是衍生版 在Linux内核的基础上开发是发行版 分区 逻辑分区永远从5开始 步骤 挂载:可理解为分配盘符,挂载点即是盘符名;不同之处:Linux中是以空目录名称作为盘符 Hda 第一块硬盘 Hda
文件处理命令 以 . 开头的文件是隐藏文件 以 - 开头表示这是一个文件 以 d 开头表示是一个目录 以 l 开头表示是一个软链接 第一个root是所有者,第二个root是所属组 ls -h 以文件默认大小后缀 显示 ls -i 查看i节点(唯一标识) 所有者:只能有一个,可变更 所属组:只能有一个
参考 01 02 03 前提环境 本地安装VirtualBox,并安装CentOS8,配置网络后,window系统上putty能连接到CentOS8服务器 配置步骤 右键服务器复制 启动复制后的服务器,查看ip和hostname发现和原来的服务器一样,需要修改 hostname # 查看主机名 vi
文件搜索命令 星号匹配任意字符,问号匹配任意单个字符 -iname 根据文件名查找且不区分大小写 -ok 命名会有一个询问的步骤 如果没有找到指定文件,可输入命令:updatedb 更新文件资料库;除tmp目录不在文件资料库收录范围之内 locate -i 文件名 # 检索时不区分大小写 which
安装环境 安装最新版的Virtual Box,点击安装 下载centos8镜像 创建虚拟机,可参考 选择下载到本地的镜像 设置启动顺序 点击启动 启动过程中报错:“FATAL:No bootable medium found!” 1.没有选择iso镜像 2.光驱没有排在第一位置 3.镜像只能选择x8
Linux严格区分大小写 所有内容文件形式保存,包括硬件 Linux不靠扩展名区分文件类型 挂载:将设备文件名和挂载点(盘符)连接的过程 Linux各个目录的作用 bin表示二进制 服务器注意事项 远程服务器不允许关机,只能重启 重启时应该关闭服务 不要在服务器访问高峰运行高负载命令 远程配置防火墙
IDE连接Linux,上传下载文件 参考1 参考2 连接Linux 上传下载文件 本地项目打包后上传 查看是否上传成功,右键下载 补充 后端项目开发完成后,需clean掉临时文件target文件夹,且只推送修改过的文件 前端项目开发的过程中,需要在每个子组件中使用scoped,确保每个子组件中的编码
起步 LTS与普通版本的区别 LTS版本的发布周期更长,更加稳定 安装jdk sudo mkdir /usr/lib/jvm # 在Ubuntu中创建目录 pscp D:\安装包\linux源码包\jdk-8u291-linux-x64.tar.gz chnq@192.168.0.102:/tmp
前言 最近在b站上看了兄弟连老师的Linux教程,非常适合入门:https://www.bilibili.com/video/BV1mW411i7Qf 看完后就自己来试着玩下,正好手上有台空闲的电脑就尝试不使用虚拟机的方式安装Linux系统 安装步骤 制作启动盘 下载ISO镜像,我这里下载的是Cen
新建虚拟电脑 设置内存和处理器 设置硬盘大小 完成 设置 查看光驱 设置启动顺序 点击启动 选择第1项 进入图形安装界面 选择安装位置,开始安装 设置root密码 重启 登录 查看本地文件夹 配置网络,点击设置 查看宿主机ip C:\Users\ychen λ ipconfig 无线局域网适配器 W
源码包安装需手动下载后安装 二进制包则在package目录下 rpm命令管理rpm包 若某个rpm包依赖于某个模块,需要到网站www.rpmfind.net查询该模块依赖的包,安装这个包后自动安装模块,之后就能安装rpm包了 安装升级时使用包全名 查询卸载时使用包名 虚拟机中的Linux系统安装rp
首先进入命令模式,再输入以下命令 命令模式用于输入命令 插入模式可对文件编写操作 编辑模式下的命令是在冒号后输入 :12, 15d # 删除指定范围的行,这里是删除12到15行 :n1,n2s/old/new/g ## 表示从n1行到n2行,old表示旧的字符串 vim使用小技巧:自定义快捷键,如快
使用源码包安装,需要自己指定安装位置,通常是 /usr/local/软件名/ linux中要想启动执行文件,应使用绝对路径 /绝对路径/rpm包名 start ## 执行方式一 service rpm包名 start ## 执行方式二 使用源码包安装后,由于自定义安装路径,就不能使用service命
网络命令 在收邮件的用户中,输入 mail 可查看邮件信息,输入序列号查看详细信息 在mail命令下,输入h 查看所有邮件的列表 输入:d 序列号 # 删除邮件 last # 统计所有用户登录或重启时间,用于日志查询 lastlog # 显示包括未登录用户的登录时间 lastlog -u 用户id
若要使用yum管理,必须能连接网络,首先配置网络IP 进入yum源文件中启动容器 使用yum源头安装rpm包不需要进入package路径,同时也不需要使用包全名,会有yum自动管理 安装软件组
简介 client即是本机安装的docker,相当于git Docker_host相当于centos系统 registry则是docker仓库,相当于GitHub 镜像用于创建docker容器,一个镜像可以创建多个docker容器 容器是由镜像创建的运行实例,(镜像相当于类,容器相当于类创建的对象)