关于存储管理中的一些概念

作者: QuietHeart | 来源:发表于2019-11-21 10:47 被阅读0次

    +AUTHOR:QuietHeart

    +EMAIL: quiet_heart000@126.com

    +DATE: [2011-11-17 四 17:45]

    前言

    在编写程序的时候,在学习操作系统以及编写驱动的时候,尤其是在Linux内核空间中编程的时候,经常会被一些与存储相关的概念所困扰,而这也经常是我们程序出现错误概率很大的一个原因(指针相关的错误)。

    我们经常遇到的问题,例如:什么是页?什么是段?什么是扇区?什么是块?什么是簇?什么是磁道?什么是物理地址?什么是线性地址?什么是虚拟地址?什么是逻辑地址?它们之间究竟有什么关系?……这些问题,这里暂时归结为存储管理中涉及到的问题,而存储管理,又可分为内存管理,外存管理。本文通过对外存、内存的管理的简单叙述,尝试达到理清对这些概念以及它们之间的关系的简单理解。更为具体的内容,可以参见列出的参考资料,或者其他更好的资料。

    主要内容:
    一、非易失存储(例如磁盘)的管理
    二、易失性存储(例如内存)的管理
    三、其他

    一、非易失存储(例如磁盘)的管理

    1、关于磁盘物理结构与寻址

    关键之处在于关于磁盘物理结构,首先理解了磁盘的柱面(磁道),扇区,以及盘面之后,再知道如下信息,就掌握了关于磁盘寻址基本的知识。

    传统的大致情况就是,一个盘面上面有多个磁道(每个磁道就是一圈,这些磁道组成盘面上的同心圆),一个磁道上面有多个扇区(就是一个同心圆上面的一个弧线部分,每个扇区实际物理大小和硬件相关,但是内核内部默认和驱动交互时候采用扇区是512字节的逻辑扇区,所以物理扇区一定是512字节的整数倍),而多个盘片上的同一位置的磁道组成的圆柱就是柱面。综上,寻址磁盘,可以通过“(盘片,磁道,扇区)”达到目的,而这样的磁盘的大小为:盘片数*每盘片上的磁道数*每磁道上的扇区数*每扇区的字节数。

    另外,磁盘的分区是以磁道为边界的,所以如果只有2个磁道,因此最多只能创建2个分区。

    传统的磁盘使用8个位表示盘面数、6个位表示每磁道扇区数、10个位表示磁道数,因此盘面、每磁道扇区、磁道的最大数值分别为255、63和1023。这也是传说中启动操作系统时的1024柱面(磁道)和硬盘容量8G限制的根源。

    现代磁盘采用线性寻址方式突破了这一限制,从本质上说,如果你的机器还没生锈,那么你的硬盘无论是内部结构还是访问方式都与常识中的盘面、每磁道扇区、磁道无关。但为了与原先的理解兼容,对于现代磁盘,我们在访问时还是假设它具有传统的结构。目前比较通用的假设是:所有磁盘具有最大数目的(也就是恒定的)盘面和每磁道扇区数,而磁盘大小与磁道数与成正比。

    因此,对于一块80G的硬盘,根据假设,这块磁盘的盘面和每磁道扇区数肯定是255和63,磁道数为:80*1024*1024*1024/512(字节每扇区)/255(盘面数)/63(每磁道扇区数)=10043(小数部分看作不完整的磁道被丢弃)。 假设写磁盘驱动程序中我们指定了磁盘大小为16M,共包含16*1024*1024/512=32768个扇区。假设这块磁盘具有最大盘面和每磁道扇区数后,那么它的磁道数就是:32768/255/63=2。

    2、关于扇区(sector)、块(block)和簇(cluster)

    扇区是硬件的磁盘的最小存储单位,而块是文件系统中数据存储的最小单元。这里,文件系统是用来规范数据文件在磁盘上以什么方式进行存储的,以便操作系统可以通过文件系统中定义好的规范,访问到磁盘上的文件。

    一个磁盘扇区一般512个字节(现在有4K的了), 磁盘块应该是类似FAT的簇大小的概念,是操作系统中分配磁盘容量的最小单位了,一般是512B*2^n。扇区是硬件上的单位,块一般是针对上层的,块一般要比扇区大。有些地方的说法,块和扇区都无什么区别了,关心逻辑和物理的就行了。也就是说,设备驱动的相关结构中,有两个地方,一个表示物理的扇区,一个表示逻辑扇区;物理的扇区就是实际物理扇区的大小,为512字节的整数倍;而逻辑扇区,就是512字节,操作系统认为所有扇区就是512字节,使用统一的逻辑扇区大小做为操作单位,和驱动进行交互,简化了写驱动的繁琐;而具体内部是如何转化两者之间关系的,写驱动的时候不用关心,我们只要告诉物理扇区大小,逻辑扇区大小,然后在驱动里面使用逻辑扇区(512字节)就行了。

    而对于簇,在fat文件系统中,fat上面簇是多个磁道,当然不同的文件系统有所不同。

    二、易失性存储(例如内存)的管理

    1、关于实模式和保护模式

    说到内存管理,就不能不提到实模式和保护模式。处理器的两种工作方式:保护模式和实模式。早期的dos就是运行在实模式下,而现在的windows则运行在保护模式下。实模式使用的逻辑地址直接转换成物理地址,只能访问1M多一点的内存空间,在拥有32根地址线的cpu中访问1M以上的空间则变得很困难。为了满足计算机对资源(存储资源和cpu资源等等)的管理,由此产生了新的管理方式–保护模式。

    80386及以上的处理器功能要大大超过其先前的处理器,但只有在保护模式下,处理器才能发挥作用:

    • 在保护模式下,全部32根地址线有效,可寻址4G的物理地址空间;
    • 扩充的存储分段机制和可选的存储器分页机制,不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持;
    • 支持多任务;
    • 4个特权级和完善的特权级检查机制,实现了数据的安全和保密。

    计算机启动后首先进入的就是实模式,通过设置相应的寄存器才能进入保护模式。

    2、关于页(page)和段(segment)

    和扇区和块等一般是针对于非易失存储介质(如磁盘)不同,段和页的概念一般是对于易失性存储而言的。也就是主要体现在内存访问方式上的存储方式。从逻辑地址到线性地址的转换由80386分段机制管理,分页机制是在段机制之后进行的,它进一步将线性地址转换为物理地址。

    1. 段是信息的逻辑单位。

      分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。细言之:段式分段由用户设计划分,每段对应一个相应的的程序模块,有完整的逻辑意义。分段的目的是为了能更好的满足用户的需要。

    2. 页是信息的物理单位。

      分页的作业地址空间是维一的,即单一的线性空间,程序员只须利用一个记忆符,即可表示一地址。分页的目的是为实现离散分配方式,以消减内存的外零头,提高内存的利用率;或者说,分页仅仅是由于系统管理的需要,而不是用户的需要。

    3. 页和段的大小。

      页的大小固定且由系统确定,把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而一个系统只能有一种大小的页面。段的长度却不固定,决定于用户所编写的程序,通常由编辑程序在对源程序进行编辑时,根据信息的性质来划分。

    3、分页机制和分段机制

    处理器在得到逻辑地址后首先通过分段机制转换为线性地址,线性地址再通过分页机制转换为物理地址最后读取数据。分段机制是必须的,分页机制是可选的,当不使用分页的时候线性地址将直接映射为物理地址,设立分页机制的目的主要是为了实现虚拟存储。

    4、分段机制和逻辑地址

    分段机制中,将逻辑地址转换成线性地址的细节就省略了,总的思想就是首先通过段选择子在描述符表中找到相应段的描述符,根据描述符中的段基址首先确定段的位置,再通过 OFFSET 加上段基址计算出线性地址。进一步解释,一个任务会涉及多个段(代码段,数据段……),每个段需要一个描述符来描述,为了便于组织管理,80386及以后处理器把描述符组织成表,即描述符表。逻辑地址结构形式一般为: seg:offset 形式,用描述表中记录的段基址加上逻辑地址 sel:offset 中的 offset 部分,即转换成线性地址。

    5、分页机制和线性地址

    通过分段机制,将逻辑地址转换成的线性地址,简单的说就是 0000000h~ffffffffh (即0~4G)的线性结构,是32个bite位能表示的一段连续的地址,但它是一个概念上的、抽象的地址,并不存在在现实之中。线性地址地址主要是为分页机制而产生的。

    分页机制是在段机制之后进行的,它进一步将线性地址转换为物理地址。80386使用4K字节大小的页,且每页的起始地址都被4K整除;因此,80386把4GB字节的“线性地址”空间划分为1M个页面,采用了两级页表结构进行转换。具体转换过程也省略了,大致如下:

    1. 第一级表称为页目录,存储在一个4K字节的页中,每个表项为4个字节,线性地址最高的10位(22-31)对第一级表进行索引,索引得到的表项内容定位了二级表中的一个表的地址(即下级页表所在的内存块号)。
    2. 第二级表称为页表,存储在一个4K字节页中,包含了1K字节的表项,线性地址的中间10位(12-21)位对二级页表进行索引,索引得到的表项包含了一个页的物理地址。
    3. 页物理地址的高20位与线性地址的低12位形成最后的物理地址。

    6、Linux内核中的内存管理

    在Linux内核中,内存分为内核空间和用户空间。

    (1)用户空间

    在Linux中,每个用户进程都可以访问4GB的线性虚拟内存空间。其中从0到3GB的虚存地址是用户空间,用户进程可以直接访问。

    (2)内核空间

    从3GB到4GB的虚存地址为内核态空间,存放供内核访问的代码和数据,用户态进程不能访问。所有进程从3GB到4GB的虚拟空间都是一样的,linux以此方式让内核态进程共享代码段和数据段。

    (3)内核虚拟地址,用户空间虚拟地址

    这里的虚拟地址,实际上就是分页机制用来转化成物理地址的线性地址。讲述这里的时候,使用下面三个地址描述内存:

    • 物理地址( phyaddr ): 对应真实的内存.
    • 内核虚拟地址( kervir ): 内核的虚地址空间(3g-4g),例如 _get_free_pages 等就是从这里分配。
    • 用户虚拟地址( usrvir ): 用户的虚拟空间地址(0g-3g).例如 malloc 等返回的就是这里的地址。

    (4)内存地址空间之间的转换:

    • phyaddr <-> kervir: 有类似 _pa , _va 这样的宏。
    • kervir -> usrvir: 有类似 remap_pfn_range 这样的函数,一般在驱动里面调用,返回内核地址给用户空间。
    • phyaddr -> usrvir: 知道 phyaddr 的基地址,与usrvir的基地址,然后计算偏移量即可。

    (5)关于分配内存:

    1. 内核空间内存分配

      • _get_free_pages: 连续物理地址,且连续最大页为 2^PAGE_SHIFT*2^MAXORDER ,宏可以配置。
      • kmalloc: 连续物理地址,不过分配的空间太小了,只有128k,也有一个可以配置的宏。
      • vmalloc: 分配的地址空间物理上不连续。

      想要知道更具体的信息,内核源代码中的 kmalloc.h/c , kmalloc_size.h/c, slab.h/c 等文件会有助于了解。

    2. 用户空间内存分配

      • malloc: 从用户堆中动态分配内存。

    三、其他

    这里,是一些补充性的内容。

    1,块设备的bio

    编写块设备驱动,最终用户请求的数据(读或者写)都会通过 bio 这个结构反应出来,也就是说, bio 代表一次请求。这里就用到了 page 。对于 page ,一般各种cpu操作 page 的最小单位是4k,当然有的设成8k等,但是最小是4k。

    当块设备请求到来的时候,会为用户请求数据分配一块虚拟地址,存放在请求结构( request )中的 bio 结构中,而 bio 结构中的 bi_io_vec 数组存放实际的数据。数组元素为:

    struct bio_vec
    {
        struct page* bv_page;
        unsigned int bv_len;
        unsigned int bv_offset;
    }
    

    实际上,分配给用户请求数据的虚拟地址不一定以 page 进行对齐,所以要对其 align ,如下:

    |--###|#####|#####|##---|
    

    这里,分配了4个页给用户请求数据,这四个页都存放在一个 bio_vec 中的 bv_page 列表中。而由于需要 align ,所以'#'中的才是实际的数据,而'-'的可能是别人的或者没有用的数据等。这里, bv_offset 就是第一个 bv_page 中第一个'#'中的偏移,而 bv_len 就是从第1个'#'到最后一个'#'的长度。这一点要注意,不要从 bv_page 开始的页对应的虚拟地址访问 page

    获取一个 page 对应的起始地址方式是使用 page_address 宏,这样返回 page 的起始地址,再加上 bv_offset 就得到整个 bio 结构中数据的起始地址了。获取 bio 数据对应的虚拟地址的函数的实现就是如下:

    //include/linux/bio.h
    static inline void *bio_data(struct bio *bio)
    {
        if (bio->bi_vcnt)
             return page_address(bio_page(bio)) + bio_offset(bio);
    
        return NULL;
    }
    

    可知,通过 bio_data 就可以获得 bio 数据的虚拟地址了,通过内核代码发现,这个虚拟地址只是 bio 数据的当前 vec 索引地址,而不一定是整个的。

    2,通过io映射将外设映射到内存空间

    我们使用 ioremap 来将外设的空间映射到内存空间,借以访问外设,而这里所映射得到的就是物理地址,物理地址是一个固定的常量,而不是我们以为的随意的一个地址。

    参考

    以上,是本人根据理解,综合所参考的资料,书上所学,以及工作时候的时间,所做的总结。尽量只用文字的形式描述,只通过文本文件便可以学习。如其中有不准或者更好的建议,可以联系我,谢谢!

    相关文章

      网友评论

        本文标题:关于存储管理中的一些概念

        本文链接:https://www.haomeiwen.com/subject/wwvzictx.html