精华内容
下载资源
问答
  • segmentation保护模式(一)上文讲到了segment descriptor,把这些descriptors放在一起(在内存里连续分布),就构成了GDT(Global Descriptor Table),所以GDT也可以被称为(描述符)表。页表的首地址是存储在...

    beabdf483fd8a4d9a12a028894b7981f.png

    segmentation和保护模式(一)

    上文讲到了segment descriptor,把这些descriptors放在一起(在内存里连续分布),就构成了GDT(Global Descriptor Table),所以GDT也可以被称为段(描述符)表。

    页表的首地址是存储在CR3寄存器中的,类似的,GDT的首地址存在GDTR寄存器中。页表的查找是通过使用虚拟地址作为index,而GDT的查找使用的是segment selector。

    26c624ba4eee69452a78a9ecf34a7288.png

    要找到某个page中的一个byte,得先通过虚拟地址的前面20位(以32位系统2级页表,4KB的page为例),查找页表得到物理页面号,再通过虚拟地址的后12位作为偏移(offset),在物理页面中找到对应的byte。

    类似的,要找到某个segment的一个byte,得先通过segment selector,查找GDT得到segment的基址(base),再通过一个offset,在该segment中找到对应的byte。那segment selector又是从哪些获取的呢?

    x86中有6个专门的寄存器CS, DS, ES, FS, GS, SS来存放这些selectors(拿专门的寄存器来放selector,主要是为了加快获取selector的速度),CS里存放的selector是指向GDT中code段的描述符,SS里存放的selector是指向GDT中stack段的描述符,DS里存放的selector是指向GDT中data段的描述符。

    ES(Extra), FS, GS(FS和GS是80386引入的,是ES的克隆产品)既可以指向code,也可以指向data,但实际应用中大多是指向data,等同于DS(从属性上来说,heap, bss都算是data段,所以可能data需要的段比较多吧)。

    一个segment seletor的构成如下(16个bit):

    149fd7998871b06f0be31e828ec99f0d.png

    index就是在GDT中查找的索引,占13位,因此GDT最大可以有

    个entries。

    TI表示要查的表是GDT还是LDT(Local Descriptor Table)。GDT是所有task共享的,是必须要有的,而LDT是task单独拥有的,是可选的,有点类似于linux中进程的page table是单独拥有的,而kernel的page table是所有进程共享的,但在segmentation的实际应用中,LDT较少被使用。

    说到RPL (Requested Privilege Level),还得提到另一个概念,叫CPL (Current Privilege Level) 。CPL是放在CS和SS中的,它只会在当前运行的特权级别更改的时候(比如从用户空间trap到内核空间)被更改,而RPL是软件设置的。

    如果要访问的数据也在当前segment,就不需要切换segment,这时的jmp跳转被称作near jump(直接给出offst即可),与之相对的是要重新查找GDT,切换segment的far jmp(需要给出segment和offset)。

        jmp eax      ; Near jump
        jmp 0x10:eax ; Far jump

    对于CS,只有RPL和CPL的值都小于上文介绍到的segment descriptor里的DPL,也就是特权级别高于要访问的segment要求的级别才可以。在切换segment的时候,需要将新的segment selector放入segment register,此时CPU会进行根据公式

    来进行这一检查,

    ed4093462c6702f0df33956c297daebf.png

    出于一些特殊用途的考虑,RPL的值可以设置的比DPL低,但是在访问 segment的时候是无效的,还得听CPL的。只有RPL的值大于DPL,也就是主动降低级别时(也是出于其他用途,在此不展开),RPL才有效。下图中,实线部分的访问(A和B访问E)是被允许的,而虚线部分的访问(C和D访问E)是被禁止的。

    bf9d6c982392bdc546eaedc156cdf407.png

    对于SS, RPL, DPL, CPL的值必须完全一样。

    介绍完这些bit field的含义,来看一段实际的设置代码:

    mov ax, 0x10
    mov ds, ax

    x86里不能把立即数直接往DS里送,得通过AX中转一下(好在AX这个中间商没有赚差价)。代码执行完后DS寄存器中的值应该为0x10,表达为二进制的对应关系如下:

    20a4b8d32126c67cccd34775091c6435.png

    则DS中的selector指向的是GDT中的第三个entry(index从0开始算)。

    同样的segement:offset的logical address格式,这时表达的linear address就不再是"segment<<4 + offset",而是"segment base (found from GDT[segment selector]) + offset"了。

    80286中的segment register不再直接指向segment,而是要经过一个GDT查表的过程,而GDT表中的的segment descriptor有limit和各种权限检测位,起到了对地址访问的保护作用,所以这种内存访问被称作保护模式(protected mode)。

    ee2d9097b803775ace721d1a4f964795.png

    所有task共用GDT,岂不是task A也可以访问到task B的segment了?理论上是可以的,但是每个task用的segment descriptor,都是操作系统来建立的,建立好之后某个task只能访问为它分配的那几个segments。所以,保护模式下的segmentation机制既提供了不同属性的segment之间的隔离,也提供了多任务环境下不同task之间的隔离。

    番外 - segmentation中的TLB

    Paging机制里有为了加快查找速度,缓存页表项的TLB,同样,在segmentation里也有类似的硬件单元。每当一个segment seletor被载入segment register中, 表明该segment当前正在被访问,则将该segment seletor指向的segment descriptor存入专门的nonprogrammable register中(可理解位描述符缓存)。

    一个segment register对应一个nonprogrammable register,所以只要segment register中的内容没有发生变化,CPU就不需要去查找GDT,直接从nonprogrammable register获取segment的地址。下图展示的是80286的描述符缓存机制,事实上,这个东东在8086上就有了。

    segmentation的机制介绍完了,那它和paging机制是怎么共存和交互的呢?请看下文分解。

    原创文章,转载请注明出处。

    展开全文
  • 存放函数中的局部变量、临时变量、函数参数、返回地址、寄存器值等(文字常量静态变量除外)。所有申请销毁的操作由编译器负责。 2. HEAP 在BSS之上,向高地址增长,通过malloc/free等申请/销毁。内部实现时...

    Linux将运行着的C程序分成不同的段,有些段的大小是固定的,有些是可变动的。

    1. STACK

    在HEAP之上,唯一一个由高地址向低地址增长的段。存放函数中的局部变量、临时变量、函数参数、返回地址、寄存器值等(文字常量静态变量除外)。所有申请和销毁的操作由编译器负责。

    2. HEAP

    在BSS之上,向高地址增长,通过malloc/free等申请/销毁。内部实现时所有空闲空间由链表串联起来,频繁的申请和销毁容易形成碎片。所有申请和销毁的操作由应用程序负责。

    3. BSS(Blocked Started by Symbol)

    存放未初始化的全局变量static变量,整个区段在程序启动时被初始化为0。

    4. DATA

    存放已初始化的全局变量和static变量文字常量全局常量。数据段所包含的数据的生命周期持续到程序结束。Linux将数据段分为已初始化的只读数据段(Read-only)和已初始化的读写数据段(Read-write)。

    5. CODE/TEXT

    代码段存放可执行代码,一般来说大小固定,并且只读。通常处于最底部,即最低地址部分。

     -------------------
    |       STACK      |  <-高地址
     -------------------
    |        HEAP       |
     -------------------
    |         BSS         |
     -------------------
    |        DATA        |
     -------------------
    |   CODE/TEXT   |  <-低地址
     -------------------

    #include <stdio.h>
    
    //
    // Name          Size      VMA       LMA       File off  Algn
    // .text         0000057c  08048390  08048390  00000390  2**4
    //                   CONTENTS, ALLOC, LOAD, READONLY, CODE
    // .rodata       000002d2  08048928  08048928  00000928  2**2
    //                   CONTENTS, ALLOC, LOAD, READONLY, DATA
    // .data         00000020  0804a014  0804a014  00001014  2**2
    //                   CONTENTS, ALLOC, LOAD, DATA
    // .bss          00000024  0804a034  0804a034  00001034  2**2
    //                   ALLOC
    //
    
    int g_int_1;
    // ADDRESS(g_int_1) = 0x804a044(bss)
    int g_int_2 = 10;
    // ADDRESS(g_int_2) = 0x804a01c(data)
    
    static int g_static_1;
    // ADDRESS(g_static_1) = 0x804a03c(bss)
    static int g_static_2 = 10;
    // ADDRESS(g_static_2) = 0x804a020(data)
    
    const int g_const_1 = 10;
    // ADDRESS(g_const_1) = 0x8048930(read-only data)
    /* const int g_const_2; Compile Error: const object must be initialized if not extern*/
    
    char g_ch_array_1[10];
    // VALUE(g_ch_array_1) = 0x804a048(bss)
    char g_ch_array_2[] = "abc";
    // VALUE(g_ch_array_2) = 0x804a024("abc" in data).
    // ADDRESS(g_ch_array_2) = 0x804a024(g_ch_array_2 in data).
    
    char *g_p_string_1;
    // VALUE(g_p_string_1) = NULL(g_p_string_1 in bss)
    char *g_p_string_2 = "abc";
    // VALUE(g_p_string_2) = 0x8048934("abc" in read-only data).
    // ADDRESS(g_p_string_2) = 0x804a028(g_p_string_2 in data segment).
    
    const char g_c_ch_array_1[] = "abc";
    // VALUE(g_c_ch_array_1) = 0x8048938("abc" in read-only data).
    // ADDRESS(g_c_ch_array_1) = 0x8048938(g_c_ch_array_1 in read-only data).
    char const g_c_ch_array_2[] = "abc";
    // VALUE(g_c_ch_array_2) = 0x804893c("abc" in read-only data).
    // ADDRESS(g_c_ch_array_2) = 0x804893c(g_c_ch_array_2 in read-only data).
    
    const char *g_c_p_string_1 = "abc";
    // VALUE(g_c_p_string_1) = 0x8048934("abc" in read-only data).
    // ADDRESS(g_c_p_string_1) = 0x804a02c(g_c_p_string_1 in data segment).
    char * const g_c_p_string_2 = "abc";
    // VALUE(g_c_p_string_2) = 0x8048934("abc" in read-only data).
    // ADDRESS(g_c_p_string_2) = 0x8048940(g_c_p_string_2 in read-only data).
    
    void main()
    {
    	int l_int_1;
    	// ADDRESS(l_int_1) = 0xbf8be29c(stack)
    	int l_int_2 = 10;
    	// ADDRESS(l_int_2) = 0xbf8be298(stack)
    
    	static int l_static_1;
    	// ADDRESS(l_static_1) = 0x804a040(bss)
    	static int l_static_2 = 10;
    	// ADDRESS(l_static_2) = 0x804a030(data)
    
    	const int l_const_1 = 10;
    	// ADDRESS(l_const_1) = 0xbf8be294(stack)
    
    	char l_ch_array_1[10];
    	// VALUE(l_ch_array_1) = 0xbf8be2a6(stack)
    	char l_ch_array_2[] = "abc";
    	// VALUE(l_ch_array_2) = 0xbf8be2b8("abc" in stack).
    	// ADDRESS(l_ch_array_2) = 0xbf8be2b8(l_ch_array_2 in stack)
    
    	char *l_p_string_1;
    	// VALUE(l_p_string_1) = random(l_p_string_1 in stack)
    	char *l_p_string_2 = "abc";
    	// VALUE(l_p_string_2) = 0x8048934("abc" in read-only data).
    	// ADDRESS(l_p_string_2) = 0xbf8be290(l_p_string_2 in stack)
    
    	const char l_c_ch_array_1[] = "abc";
    	// VALUE(l_c_ch_array_1) = 0xbf8be2b4("abc" in stack).
    	// ADDRESS(l_c_ch_array_1) = 0xbf8be2b4(l_c_ch_array_1 in stack).
    	char const l_c_ch_array_2[] = "abc";
    	// VALUE(l_c_ch_array_2) = 0xbf8be2b0("abc" in stack).
    	// ADDRESS(l_c_ch_array_2) = 0xbf8be2b0(l_c_ch_array_2 in stack).
    
    	const char *l_c_p_string_1 = "abc";
    	// VALUE(l_c_p_string_1) = 0x8048934("abc" in read-only data).
    	// ADDRESS(l_c_p_string_1) = 0xbf8be28c(l_c_p_string_1 in stack).
    	char * const l_c_p_string_2 = "abc";
    	// VALUE(l_c_p_string_2) = 0x8048934("abc" in read-only data).
    	// ADDRESS(l_c_p_string_2) = 0xbf8be288(l_c_p_string_2 in stack).
    }

    • Note 1:区分字符数组(Char Array)和文字常量(String Literal)。考虑两种情况char s[] = "abc"和char *s = "abc",其中前者"abc"是字符数组,后者"abc"是文字常量。两者的区别是字符数组存放在stack/data segment,其内容可修改。而文字常量存放在read-only data segment,内容不可修改。
    • Note 2:字符数组的地址。字符数组存放的位置与当前作用域相关,如果作用域在全局,则存放在data segment,如果作用域在局部,则存放在stack。定义char s[] = "abc",那么s == &s。
    • Note 3:函数中的常量存放在stack。
    • Note 4:函数中的文字常量存放在read-only data segment。
    • Note 5:函数中的静态变量存放在bss/data segment。

    展开全文
  • u-boot的段分布.vsdx

    2020-09-14 23:17:41
    所以拷贝之前是用不到.bss的,但是要用.__rel_dyn_start,而拷贝之后用不到.__rel_dyn_start,但是要用.bss,所以为了节省内存,这两个是可以重合的。另外拷贝的目的地址处,我们已经为.bss留出了空间,...
  • C/C++内存管理

    2018-11-27 12:14:21
    C/C++内存分布 C/C++内存分为栈、内存映射、堆、数据、代码。 1.栈又叫堆栈,非静态局部变量/函数参数/返回值等等 ,还有每次调用函数时保存的信息。每当调用一个函数时,返回到的地址关于调用者环境的...

    C/C++内存分布

    C/C++内存分为内存映射段数据段代码段

    1.栈又叫堆栈,非静态局部变量/函数参数/返回值等等 ,还有每次调用函数时保存的信息。每当调用一个函数时,返回到的地址和关于调用者环境的某些信息的地址,比如一些机器寄存器,就会被保存在栈中。然后,新调用的函数在栈上分配空间,用于自动和临时变量。

    2.内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。

     3.堆用于程序运行时动态内存分配,堆是可以上增长的。堆区域从BSS段的末尾开始,并从那里逐渐增加到更大的地址。堆是由程序员自己分配的。堆区域由所有共享库和进程中动态加载的模块共享。

    4.数据段分为初始化数据段和未初始化数据段。初始化的数据段,通常称为数据段,是程序的虚拟地址空间的一部分,它包含有程序员初始化的全局变量和静态变量,可以进一步划分为只读区域和读写区域。未初始化的数据段,通常称为bss段,这个段的数据在程序开始之前有内核初始化为0,包含所有初始化为0和没有显示初始化的全局变量和静态变量。

    5.代码段也叫文本段,是对象文件或内存中程序的一部分,其中包含可执行代码和只读常量。文本段在堆栈的下面,是防止堆栈溢出覆盖它。,通常代码段是共享的,对于经常执行的程序,只有一个副本需要存储在内存中,代码段是只读的,以防止程序以外修改指令。

    #include <stdio.h>
    int a = 100;        //全局变量---初始化数据段(数据段)
    int i;             //全局变量---未初始化数据段(bbs段)
    char s1[] = "nshcbh";   //s1在数据段,“nshcbh”在s1内部
    const char* p1 = "abc"; //p1在数据段,“abc”在代码段。可以通过指针p1获取
    int main()
    {
         int b;                 //局部变量---栈
         char* p = "123";        //p:局部变量---栈   123:代码段
         char arr[10] = "ads";   //栈,“ads”也在栈上
         char s[] = "dbh";       //s在栈上,“dbh”在代码段
         static int c =1;        //静态变量---初始化数据段                  
         p = (char*)malloc(10*sizeof(char));//动态分配的的内存在堆区
         return 0;
     }
    

    C语言在动态内存管理方式

    malloc/calloc//realloc和free

    //函数原型
    void* malloc(size_t size);
    void *calloc( size_t num, size_t size );
    void *realloc( void *memblock, size_t size );
    
    

    malloc在内存的动态存储区中分配一块长度为size字节的连续区域,返回该区域的地址。

    calloc与malloc类似,参数size为申请地址的单元元素长度,num是元素个数。

    realloc是给一个已经分配了的地址的指针重新分配空间,参数memblock为原有空间的地址,size是重新申请的地址空间。

    相同点:malloc/calloc/realloc都是进行动态内存管理的,均在堆上开辟空间,必须用free将申请的空间释放。返回类型都是void*,申请空间成功返回空间首地址,失败则返回NULL,所以,他们申请空间之后都需要判空。

    不同点:

    malloc不能初始化所分配的内存空间,需要用memset初始化。如果这块空间被初始化过,则可能遗留各种各样的数据。
    calloc会将所分配的空间中的每一位都初始化为0。
    realloc可以对给定的指针所指向的空间进行扩大或缩小,原有的内存中内容将保持不变。realloc并不保存新的内存空间和原来的内存空间保持同一内存地址,返回的指针可能指向新的地址。

    realloc申请过程:

    注意:在堆上开辟的内存空间,用完之后必须使用free释放,否则会造成内存泄露的问题。但是在栈上开辟的空间,栈上空间具有作用域,在函数结束时会自动释放掉,由编译器自动维护,不需要我们程序员进行手动释放。

    C++内存管理方式

     C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

    操作内置类型

    void Test()
    {
     // 动态申请一个int类型的空间
     int* ptr4 = new int;
     
     // 动态申请一个int类型的空间并初始化为10
     int* ptr5 = new int(10);
     
     // 动态申请10个int类型的空间
     int* ptr6 = new int[3];
     
     delete ptr4;
     delete ptr5;
     delete[] ptr6;
    }

    操作自定义类型

    class Test
    {
    public:
     Test()
     : _data(0)
     {
     cout<<"Test():"<<this<<endl;
     }
     ~Test()
     {
     cout<<"~Test():"<<this<<endl;
     }
     
    private:
     int _data;
    };
    void Test2()
    {
     // 申请单个Test类型的对象
     Test* p1 = new Test;
     delete p1;
     
     // 申请10个Test类型的对象
     Test* p2 = new Test[10];
     delete[] p2;
    }

    注意:

    1.new/delete是关键字不是函数。

    2.malloc只负责申请空间,不调用构造函数,free只负责释放空间,不调用析构函数。new会申请空间,且调用构造函数,delete会释放空间且调用析构函数。

    3.new/delete    new[]/delete[]    malloc/free:最好匹配使用。

    内置类型:如果没有匹配使用,程序也不会出现问题。自定义类型:如果没有匹配使用,则会内存泄露,程序崩溃。

    operator new与operator delete

    new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

    /*
    operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试
    执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
    */
    void* _CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
    {
        void* p;
        while((p = malloc(size)) == 0)
            if(_callnewh(size) == 0)
            {
                // report no memory
                _THROW_NCEE(_XSTD bad_alloc, ); //抛出异常
            }
        return (p);
    }

    operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。

    /*
    operator delete: 该函数最终是通过free来释放空间的
    */
    void operator delete(void *pUserData)
    {
         _CrtMemBlockHeader * pHead;
    
         RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    
         if (pUserData == NULL)
             return;
    
         _mlock(_HEAP_LOCK); /* block other threads */
    
         __TRY
    
             /* get a pointer to memory block header */
             pHead = pHdr(pUserData);
    
             /* verify block type */
             _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
    
             _free_dbg( pUserData, pHead->nBlockUse );
    
         __FINALLY
             _munlock(_HEAP_LOCK); /* release other threads */
         __END_TRY_FINALLY
    
         return;
    }

    operator delete 最终是通过free来释放空间的。

    注意:operator new和operator delete用户也可以自己实现,用户实现时即可实现成全局函数,也可实现成类的成员函数,但是一般情况下不需要实现,除非有特殊需求。

     new和delete的实现

    内置类型:

    如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

    自定义类型:

    • new的原理

    1.调用operator new函数申请空间

    2. 在申请的空间上执行构造函数,完成对象的

    • delete的原理

    1. 在空间上执行析构函数,完成对象中资源的清理

    2. 调用operator delete函数释放对象的

    • new T[N]的原理

    1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间请

    2. 在申请的空间上执行N次构造

    • delete[]的原理

    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的的清理

    2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放

    定位new表达式(placement-new)

    定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

    使用格式:
    new (place_address) type或者new (place_address) type(initializer-list)
    place_address必须是一个指针,initializer-list是类型的初始化列表
    使用场景:
    定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

    class Test
    {
    public:
        Test()
          : _data(0)
         {
             cout<<"Test():"<<this<<endl;
         } 
        ~Test()
        {
            cout<<"~Test():"<<this<<endl;
        }
     
    private:
        int _data;
    };
    void Test()
    {
         // pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
         Test* pt = (Test*)malloc(sizeof(Test));
         //定位new:void* operator new(size_t size, void* p)
         //         {return p}  
         new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
    }

    内存泄漏

    内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

    void MemoryLeaks()
     {
     // 1.内存申请了忘记释放
     int* p1 = (int*)malloc(sizeof(int));
     int* p2 = new int;
     
     // 2.异常安全问题
     int* p3 = new int[10];
     
     Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
     
     delete[] p3;
     }
    

    C/C++程序中一般我们关心两种方面的内存泄漏:

    • 堆内存泄漏(Heap leak)

    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

    • 系统资源泄漏

    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

    内存泄露检测:

    在linux下内存泄漏检测:linux下几款内存泄漏检测工具
    在windows下使用第三方工具:VLD工具说明
    其他工具:内存泄漏工具比较

     

    展开全文
  • 内存分页

    千次阅读 2010-09-20 22:22:00
    其出发点打破了程序装入的整体性存储分配的连续性允许进程逻辑地址空间不必分布内存的一连续空间中,而是可以分布在若干非连续的内存块中。即内存分配时,以页为单位,并按用户进程包含的页数多少进行分配,...

          页式非连续分配方式。其出发点打破了程序装入的整体性和存储分配的连续性允许进程逻辑地址空间不必分布在内存的一段连续空间中,而是可以分布在若干非连续的内存块中。即内存分配时,以页为单位,并按用户进程包含的页数多少进行分配,逻辑上相邻的页面在内存中不一定相邻,即用户进程不一定要分配连续的物理内存块。每一个进程就需要页表对其进行管理。当进程要访问某个逻辑地址中的数据时,启动地址变换机制。地址变换机制首先将逻辑地址分为两部分:页号P和页内偏移量W,用页号P与页表长度寄存器的内容进行比较。

          如果P大于等于页表长度,则表示访问地址超过了进程地址空间。

                 系统发出一个地址越界中断。

          如果访问地址没有越界,则根据页表起始地址寄存器的内容得到页表在内存的首地址。

                 根据页号P在也表中查找对应的物理块号。若页表中无此页号。

                       则产生一个地址越界中断。

                 若页表中有此页号,则可得到对应的物理块号。

    转换成可以访问的内存物理地址:物理地址=物理块号*页面长度+页内偏移。

          系统提供了一对硬件寄存器来支持页表的查找和保护机制:页表始地址寄存器、页表长度寄存器。对程序的逻辑地址空间进行分页是由操作系统自动完成的。分页管理方式从系统管理角度出发,以页面为单位来分配内存,由于每个页面较小,就很好的解决了内存碎片的问题,提高了内存空间利用率。分页机制需要硬件支持,没有根除碎片,页面太少,即页长,是由机器的地质结构决定的,即硬件决定了操作系统中页面的大小,现代操作系统都采用页面置换技术来从逻辑上扩展内存,但页长太小也降低了页面换入换出的效率。

          地址线从16位增长到32位后,单级页表机制显然不实用,随后出现了二级页表和多级页表机制。Linux中,版本2.6.11.1以后使用了4级页表机制。

    展开全文
  • 变量的内存分配情况

    2017-02-08 10:55:00
    首先,来了解一下C语言的变量是如何在内存分布的。C语言有全局变量(Global)、本地变量(Local)、静态变量(Static)和寄存器变量(Register)。每种变量都有不同的分配方式。先来看下面这代码: 编译后的...
  • 一, 启动流程计算机体系结构启动时的计算机内存和磁盘分布图CS:IP = 0XF000:FFF0 (CS:代码段寄存器; IP:指令指针寄存器)系统处于实模式 (刚刚通电情况下)PC = 16 * CS + IP 20位地址空间:1MB (可用)BIOS固件...
  • 一, 启动流程计算机体系结构启动时的计算机内存和磁盘分布图CS:IP = 0XF000:FFF0 (CS:代码段寄存器; IP:指令指针寄存器)系统处于实模式 (刚刚通电情况下)PC = 16 * CS + IP 20位地址空间:1MB (可用)BIOS固件...
  • 页式管理很好的解决了进程代码离散分布内存的问题,页式分配将程序中的逻辑地址分为两部分,低0~12位为页内地址高13~31位为页数。因此,内存中最大页面数为1M。为了维护页面信息,要建立一张页面表,里面包含了页号...
  • 启动时的计算机内存和磁盘分布图 CS:IP = 0XF000:FFF0 (CS:代码段寄存器; IP:指令指针寄存器) 系统处于实模式 (刚刚通电情况下) PC = 16 * CS + IP 20位地址空间:1MB (可用) BIOS固件提供功能: 基本输入输出...
  • STM中FLASHRAM的关系

    2020-05-29 19:57:36
    一、STM32中的内存分布情况 要搞清楚stm32 FLASH、RAM的使用情况,就需要理清楚stm32的存储结构。 在stm32中,flash,SRAM寄存器和输入输出端口被组织在同一个线性地址空间内 C语言上分为栈、堆、bss、data、code...
  • 线程基础

    2020-12-07 00:08:39
    (1)静态内存分布(程序)动态内存分布(进程,线程); (2)内存分布 |stack| 栈 :局部变量,运行时才有| |heap|堆:程序员自己分配内存,运行时才有| | .bss | 未初始化的数据:未初始化或者初始化为0的...
  • c++函数堆栈调用

    2019-07-27 17:20:28
    由一个.cpp/.c文件到达一个可执行文件分为以下几部分 预编译阶段:进行宏替换,删除注释,展开头文件,添加行号等 ...c++内存分布 c++函数调用参数传递(编译器为vs2013) 执行一个简单的函数 #inc...
  • 函数栈解析

    2015-08-06 11:09:25
    一个程序中内存分布如下图:   代码:保存程序文本,可读可执行不可写,指令寄存器EIP指向代码中将要执行的代码行首地址 数据:保存已初始化的全局变量静态变量,可读可写不可执行 BBS:未初始化的全局变量...
  • 进程

    2015-04-24 22:24:10
    进程:代码当前活动:程序计数器的值处理器寄存器的内容进程堆栈:临时数据,函数参数,返回地址局部变量 数据:全局变量 堆:进程运行期间动态分布内存 栈--堆--数据--文本 程序是被动实体 ...
  • 视频4: 主要内容:讲栈与栈相关的两个寄存器。... 还要注意,原来栈顶指向ffee,入栈后栈顶指向ffeb 内存中三个如上图分布。 如果栈满再入栈,那就把数据的内容覆盖了。 栈空出栈同样危
  • 1.1 数组字符串 2 1.1.1 一维数组的倒置 2 范例1-1 一维数组的倒置 2 ∷相关函数:fun函数 1.1.2 一维数组应用 3 范例1-2 一维数组应用 3 1.1.3 一维数组的高级应用 5 范例1-3 一维数组的高级应用 5 1.1.4...
  • Linux多任务相关的TR寄存器和TSS任务状态数据结构 中断异常 Linux引导初始化--代码搬移过程 Bootsect代码详解 Bootsect代码详解续 bootsect里的设备号问题  bootsect代码解析之system...
  • 8.3.1 用户程序的内存分布 8.3.2 reloc机制 8.3.3 flat可执行文件格式 8.3.4 执行文件加载流程 小结 习题 第9章 文件系统 9.1 文件系统结构 9.2 文件系统类型 9.2.1 romfs文件系统 9.2.2 ...
  • 实验三 移植U-Boot-1.3.1 实验 【实验目的】 了解 U-Boot-1.3.1 的代码结构,掌握其移植方法。 【实验环境】 1、Ubuntu 7.0.4发行版 2、u-boot-1.3.1 3、FS2410平台 4、交叉编译器 arm-softfloat-linux-gnu-...
  • C语言通用范例开发金典.part2.rar

    热门讨论 2012-08-31 14:18:18
    1.1 数组字符串 2 1.1.1 一维数组的倒置 2 范例1-1 一维数组的倒置 2 ∷相关函数:fun函数 1.1.2 一维数组应用 3 范例1-2 一维数组应用 3 1.1.3 一维数组的高级应用 5 范例1-3 一维数组的高级应用 5 1.1.4...
  • C 开发金典

    2013-06-20 16:20:03
    1.1 数组字符串 2 1.1.1 一维数组的倒置 2 范例1-1 一维数组的倒置 2 ∷相关函数:fun函数 1.1.2 一维数组应用 3 范例1-2 一维数组应用 3 1.1.3 一维数组的高级应用 5 范例1-3 一维数组的高级应用 5 1.1.4...
  • 测试代码格式并进行规则检査一一检査伪造指针、违反对象访问权限或试图改变对象类型 的非法代码。 注意-—所有源于网络的类文件都要经过字节码校验器 字节码校验器对程序代码进冇四遍校验,这可以保证代码符合JⅧM...
  • 因此在仿真程序调试时,关心的不再是某些语句执行时单片机寄存器和存储 器内容的改变,而是从工程的角度直接看程序运行电路工作的过程结果。 对于这样的仿真实验,从某种意义上讲,是弥补了实验工程应用间...

空空如也

空空如也

1 2
收藏数 24
精华内容 9
关键字:

内存分布和段寄存器