【Linux】-- 结合内存与硬盘IO的进程概念

文章详细介绍了Linux内存管理中的关键概念,包括进程的组成、vm_area_struct结构体在描述地址空间细粒度划分中的作用,以及structPage如何表示和管理物理内存的4KB块。还讨论了缺页中断的过程,以及32位系统中分层页表的机制,强调页表实际上建立的是虚拟地址到页的映射关系。
摘要由CSDN通过智能技术生成

目录

概念

vm_area_struct

struct Page

缺页中断

总结

深入理解补充的概念


概念

复习:

#问:什么叫进程?

        进程 = 内核相关的数据结构 + 进程相关的代码和数据


1. 动态运行属性:

        进程不仅仅是将磁盘中的程序加载到内存,然后此时执行代码和数据就行了。实际上,因为操作系统要管理所谓的进程,所以其必须先描述再组织。

        即:每一个进程都要有其内核相关的数据结构。

----------------------------------------------------------------

1.1 学到现在的内核数据结构:

        进程的PCB、进程的虚拟地址空间(上下文)、进程的相关页表、进程CPU的上下文数据、进程打开的文件、进程的通讯、进程的信号、进程访问的各种资源(只要进程需要访问某种资源 -> 操作系统只要对进程本身或者是访问的资源进行管理 -> 就会有对应的数据结构来进行描述 -> 进程和某种资源的访问关系就变成了某种数据结构上的关系)

----------------------------------------------------------------

1.2 学到现在的进程代码和数据:

        代码和数据通过页表来进行对应的映射,而页表分为用户级页表、内核级页表。内核级页表每个进程共享,用户级页表每个进程独有一份。

        栈区(有寄存器的概念保存栈顶栈底 -> 容易区分起始和结尾)和代码区是被整体使用的。而堆区看起来是一个整体,但实际上是被零散化的(malloc / new的申请都在不同区域)。

        我们申请堆空间的时候,向来都是告述其我们申请多大,但是从来为告述其该堆空间什么时候结束。所以在这里其实还需要细粒度的控制 —— 即:堆区的哪一部分区域是属于谁的

        所以在内核结构中,针对于地址空间,组合式的还有一个结构:vm_area_struct

vm_area_struct

        结构体里对于地址空间当中的某一个区域,进行更细粒度的划分。

struct vm_area_struct
{
    /* The first cache line has the info for VMA tree walking.
    第一个缓存行具有VMA树移动的信息*/

    unsigned long vm_start; /* Our start address within vm_mm. */
    unsigned long vm_end;   /* The first byte after our end address within vm_mm. */

    /* linked list of VM areas per task, sorted by address
    每个任务的VM区域的链接列表,按地址排序*/
    struct vm_area_struct *vm_next, *vm_prev;

    struct rb_node vm_rb;

    /*
     此VMA左侧最大的可用内存间隙(以字节为单位)。
     在此VMA和vma-> vm_prev之间,
     或者在VMA rbtree中我们下面的一个VMA与其->vm_prev之间。
     这有助于get_unmapped_area找到合适大小的空闲区域。
     */
    unsigned long rb_subtree_gap;

    /* Second cache line starts here.
    第二个缓存行从这里开始*/

    struct mm_struct *vm_mm; /* 我们所属的address space*/
    pgprot_t vm_page_prot;   /* 此VMA的访问权限 */
    unsigned long vm_flags;  /* Flags, see mm.h. */

    /*
     对于具有地址空间(address apace)和后备存储(backing store)的区域,
     链接到address_space->i_mmap间隔树,或者链接到address_space-> i_mmap_nonlinear列表中的vma。
     */
    union
    {
        struct
        {
            struct rb_node rb;
            unsigned long rb_subtree_last;
        } linear;
        struct list_head nonlinear;
    } shared;

    /*
     在其中一个文件页面的COW之后,文件的MAP_PRIVATE vma可以在i_mmap树和anon_vma列表中。
     MAP_SHARED vma只能位于i_mmap树中。
     匿名MAP_PRIVATE,堆栈或brk vma(带有NULL文件)只能位于anon_vma列表中。
     */
    struct list_head anon_vma_chain; /* Serialized by mmap_sem & * page_table_lock
                                        由mmap_sem和* page_table_lock序列化*/
    struct anon_vma *anon_vma;       /* Serialized by page_table_lock 由page_table_lock序列化*/

    /* 用于处理此结构体的函数指针 */
    const struct vm_operations_struct *vm_ops;

    /* 后备存储(backing store)的信息: */
    unsigned long vm_pgoff; /* 以PAGE_SIZE为单位的偏移量(在vm_file中),*不是* PAGE_CACHE_SIZE*/
    struct file *vm_file;   /* 我们映射到文件(可以为NULL)*/
    void *vm_private_data;  /* 是vm_pte(共享内存) */

#ifndef CONFIG_MMU
    struct vm_region *vm_region; /* NOMMU映射区域 */
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *vm_policy; /* 针对VMA的NUMA政策 */
#endif
};

        与mm_struct中含有大量的start与end是类似的,只不过mm_struct中是描述大空间的,而堆区有很多我们申请的小空间(第一次new、第二次new……)。堆区的空间就会划分的很细,所以使用vm_area_struct结构体。

        其中,此处不讲全,只讲解一些重点理解。

        见上图,vm_startvm_end即是小空间细致的划分。vm_nextvm_prev即是经典的双链表整理结构。

        如此mm_struct中的堆区只表示堆区整体的起始和结束,但是堆区的详细信息,比如从哪里开始哪里结束。其中就可以每一次取一个vm_area_struct。当我们上层malloc然后调用系统调用(malloc -系统调用-> brk),其就帮我们申请一个vm_area_struct。其中就有start表示虚拟地址起始,end表示虚拟地址结束,然后将其对应的虚拟地址经过页表映射到内存当中。

        而堆区的特点就是连续,只要我们申请了就是连续的。所以当我们需要使用申请的堆空间,只需要在vm_area_struct构成的双链表结构中查找到对应的start,便可知道end,即可知道堆的范围。

Note:

        地址空间内的地址划分是粗粒度的,而其是可以使用再补充数据结构来保证可以进行细粒度的划分。

        操作系统是可以做到让进程进行资源的细粒度划分。

        上层所拿的所谓的地址来进行操作,该地址就是虚拟地址,而虚拟地址通过 -> 用户级页表 + MMU内存管理单元(Memory Manager Unit,硬件)-> 映射物理内存。(MMU是集成于CPU内部 -> 从CPU出来访问的就是物理地址)CPU读取到的都是虚拟地址,出来的时候就到了访问的物理内存当中。

        以前我们说所就是页表映射到物理内存,现在我们需要探讨到底:

#问:如何从虚拟地址映射到物理内存的?

  1. .exe就是一个文件(二进制文件)
  2. 我们的可执行程序本质就是按照地址空间方式进行编译的。
  3. 可执行程序,其实在磁盘中按照区域也已经被划分成为了以4KB为单位。(可执行程序编译形成的二进制文件的格式)

        如同,一个工厂生产商品一样,在工厂里就将商品包装好了,放在厂库里。每一个商品的大小就是 "4KB" ,一次提货的量就可以说是n * 4KB

        同样的物理内存其实也早被划为了4KB为单位的块(软件上的划分)。这也就是文件系统中,操作系统IO的时候是以4KB为单位的。

        假如物理内存的大小为4GB,我们将其划分成为了4KB为单位的空间。操作系统如何知晓那些空间(4KB)被占,哪些没有被占用。哪些空间的访问时间是什么时候、装载的是代码还是数据等 —— 操作系统需要知道物理内存当中每个内存块对应的属性 

        而4GB的分割为4KB:4GB / 4KB = 104.8576w,并且每个内存块都具有其自己的属性,所以操作系统对于内存块的管理是十分重要的 —— 先描述,再组织。

        对4KB内存块管理的结构:struct Page

struct Page

        struct page中使用了大量的联合体union。

struct page {
    /* First double word block */
    unsigned long flags;        /* Atomic flags, some possibly
                     * updated asynchronously */
    struct address_space *mapping;    /* If low bit clear, points to
                     * inode address_space, or NULL.
                     * If page mapped as anonymous
                     * memory, low bit is set, and
                     * it points to anon_vma object:
                     * see PAGE_MAPPING_ANON below.
                     */
    /* Second double word */
    struct {
        union {
            pgoff_t index;        /* Our offset within mapping. */
            void *freelist;        /* slub first free object */
        };
 
        union {
            /* Used for cmpxchg_double in slub */
            unsigned long counters;
 
            struct {
 
                union {
                    atomic_t _mapcount;
 
                    struct {
                        unsigned inuse:16;
                        unsigned objects:15;
                        unsigned frozen:1;
                    };
                };
                atomic_t _count;        /* Usage count, see below. */
            };
        };
    };
 
    /* Third double word block */
    union {
        struct list_head lru;    /* Pageout list, eg. active_list
                     * protected by zone->lru_lock !
                     */
        struct {        /* slub per cpu partial pages */
            struct page *next;    /* Next partial slab */
#ifdef CONFIG_64BIT
            int pages;    /* Nr of partial slabs left */
            int pobjects;    /* Approximate # of objects */
#else
            short int pages;
            short int pobjects;
#endif
        };
    };
 
    /* Remainder is not double word aligned */
    union {
        unsigned long private;        /* Mapping-private opaque data:
                          * usually used for buffer_heads
                         * if PagePrivate set; used for
                         * swp_entry_t if PageSwapCache;
                         * indicates order in the buddy
                         * system if PG_buddy is set.
                         */
#if USE_SPLIT_PTLOCKS
        spinlock_t ptl;
#endif
        struct kmem_cache *slab;    /* SLUB: Pointer to slab */
        struct page *first_page;    /* Compound tail pages */
    };
}

        其中,此处不讲全,只讲解一些重点理解。

        flags:是否有效、是否可以直接访问、是否有数据等,描述page的状态和其他信息。

        描述方式上可见,即组织:struct page mem[1048576]; 利用数组的方式组织起来,并且利用数组也就默认的为每一个内存块配上了编号。

        于是便将对于特定内存块管理,变为了特定数据结构的管理。

        其中我们将,磁盘中用4KB为单位划分的代码和数据(内容),称作为:页帧。物理内存中用4KB为单位划分的内容,称作为:页框

        操作系统(文件系统)和磁盘进行IO的基本单位是4KB:页帧装进页框

Note:

        如此的划分方式,对应的从外设加载到内存时,是以4KB为单位,就算只加载1Bit也是进行的4KB的加载。

缺页中断

        说白了就是:操作系统在通过页表进行寻址时,发现我们需要访问的对应的内存区域不在内存当中,所以就要引发缺页中断去:

  1. 先确认申请对应的内存。
  2. 在磁盘当中找到我们需要加载的目标数据对应的地址。
  3. 将磁盘中目标数据加载到指定的内存的位置。
  4. 重新填充页表。
  5. 返回到用户,让给用户继续进行访问。

#:需要访问的不在内存区域当中,在磁盘中

#:引发缺页中断

1. 先确认申请对应的内存。

2. 在磁盘当中找到我们需要加载的目标数据对应的地址。

3. 将磁盘中目标数据加载到指定的内存的位置。

4. 重新填充页表。

5. 返回到用户,让给用户继续进行访问。

(这个动作用户是零感知的,用户顶多感受到第一次访问速度与后面访问速度的快慢)

总结

磁盘与物理内存IO的大小为4KB并不是简单的一说,而是操作系统编译器的支持:

  • 编译器:编译的时候就必须将对应的代码和数据,按照4KB来划分好。
  • 操作系统:对内存进行管理的时候,就必须按照4KB为单位来进行管理。

深入理解补充的概念

        前面的是概念的铺垫,现在才是虚拟地址如何映射到物理地址的真正理解。 

        前面的学习我们只是说,虚拟地址映射到物理地址是通过页表的kv结构。但是其实这仔细看起来是十分荒谬的。如果我们的平台大小为32位,那么也就是说有:2^32的地址个数,那么页表的映射关系表就有2^32行。据我们简单的推测:一个物理地址对一个地址 + 一个字节的大小表示各种状态和其他信息

        也就是说:为了存储页表就需要消耗内存大小 = 2^32 * 9Byte = 36GB。

8bit = 1Byte
1024Byte = 1KB
1024KB = 1MB
1024MB = 1GB
1024GB = 1TB

        页表不是一个简单的结构。而是几个结构组合而成。

以32为操作系统为例:

        32位的页表分为:一级页表、二级页表。

#:一级页表

        内核层,将虚拟地址的前10个bit位进行映射,成一个一级页表,而前10bit映射的不是物理地址而是二级页表:

一级页表全使用所占空间:

        空间 = 2^10 * 9Byte = 36KB。

#:二级页表

二级页表全使用所占空间:

        空间 = 2^10 * 9Byte = 36KB。

#:页表结构的原理

        所以:所谓的页表,严格意义上建立的不是虚拟地址到物理地址的映射。更准确的说法是,所谓的页表,建立的是虚拟地址到特定页的映射

        这是能够保证通过这个方法可以找到对应的数据。因为编译器编译代码的时候,就是按照4KB为单位,把可执行程序(按虚拟地址)规制好的。                       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川入

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值