Unix C (三)

存管理
程序是存在文件中的(硬盘),一个运行的程序是需要加载到内存中的,加载到内存中的程序叫进程。
    STL --> 内存是自动分配和回收
     |
    C++ --> new/delete,会调用malloc和free
     |
   C语言 --> malloc/free
     |
   Unix/Linux系统调用 -> brk/sbrk
     |
   内存映射  -> mmap
     |                         (用户层)
   ------------------------------------
    kmalloc/vmalloc            (内核层)
     |
    get_free_page()
 
虚拟内存技术:
  在32位的Unix/Linux中,用虚拟内存技术管理内存。
  每个进程都有0-4G的虚拟地址空间,虚拟地址必须映射到物理内存后才能使用,否则引发段错误。所谓的内存分配其实就是映射物理内存。程序员所接触到只能是虚拟内存,无法直接接触物理内存。
  0-3G是用户层使用的,叫用户空间,3G-4G是内核使用的,叫内核空间。用户程序只能直接访问用户空间,无法直接访问内核空间,但可以通过Unix/Linux系统提供的系统调用(一系列的函数)进入内核空间。内存管理的最小单位是一个内存页,大小4096(4k)。
   
 进程内存的分区:
  代码区:程序代码(函数)放入代码区,只读区
  全局区:保存全局变量/static变量
  BSS段: 保存未初始化的全局变量
   注:全局区和bss段 在main执行之前都会分配内存,但bss段会自动清0。
  栈区:保存局部变量(非malloc分配),包括函数的参数,内存分配和回收自动进行
  堆区:也叫自由区,内存管理是程序员执行。new/malloc分配的内存。
  注:在代码区附近有一个只读变量区,一般和代码区合并。
   
  段错误的原因:
   1 虚拟内存没有映射物理内存就使用
   2 操作某些没有权限的内存区域(修改只读区)
   3 释放内存时缺少必要的附加信息
  malloc的特点:
   1 申请小内存时(不足33个内存页),默认映射33个内存页。用完后再申请不一再给33个内存页。
   2 申请大内存时,映射稍多一点的内存页。
   3 申请内存时,会额外多分配一点内存,用于记录相关信息。
   注:malloc不一定映射新的物理内存,free也不一定真正释放物理内存。

   进程之间同样的虚拟地址对应 不同的物理内存。


存管理API
    包含头文件#include <unistd.h>
    1、函数void *sbrk(intptr_t increment/*内存增量,单位字节*/);它返回上一次调用sbrk/brk函数后的末尾指针,失败返回-1;
        increment取值:
            如果内存增量为0,则返回当前位置指针;内存增量可正可负,如果为正数,指针向后移动,增加内存空间;如果为负数,指针向前移动,并释放内存空间。
        函数工作原理:内部维护一个指针,指向当前堆内存最后一个字节的下一个位置。sbrk函数根据增量参数调整该指针的位置,同时返回该指针被调整之前的位置。若发现页耗尽或空闲,则会自动追加或解除页映射。
    2、函数int brk(void *end_data_segment/*内存块尾地址*/);成功返回0,失败返回-1。
        函数工作原理:内部维护一个指针,指向当前堆内存最后一个字节的下一个位置。brk函数根据指针参数设置该指针的位置。若发现页耗尽或空闲,则自动追加或解除页映射。

 
  brk/sbrk 函数是系统提供分配内存/回收内存的。
  一般用sbrk分配内存,用brk回收。
  void* sbrk(int size)
  size == 0 取当前位置
  size >0 分配内存,并返回之前的位置
  size <0 释放内存
   
 

  brk/sbrk 底层需要维护一个位置


实例:

(1)malloc函数演示:

#include <stdio.h>
#include <stdlib.h>

int main(void){
    int *p1 = (int *)malloc(sizeof(int));
    int *p2 = (int *)malloc(sizeof(int));

    printf("%p\n",p1);        //0x8491008
    printf("%p\n",p2);        //0x8491018
    //malloc分配内存时会有多余的空间存储一些额外的信息,在使用free释放时会使用.如果该信息被破坏,将导致free失败

    printf("每页%d个字节\n",getpagesize());      //每页4096个字节

    free(p2);
//    p1[3] = 0;   //破坏存储的额外信息
    free(p1);
    p1 = NULL;
    p2 = NULL;

    return 0;
}

(2)malloc函数的实现:

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>

#include <string.h>


// 内存控制块
typedef struct mem_control_block {
    bool                      free; // 自由标志
    struct mem_control_block* prev; // 前块指针
    size_t                    size; // 本块大小
}    MCB;
// 桟顶指针
MCB* g_top = NULL;
// 分配内存
void* mymalloc (size_t size) {
    MCB* mcb;
    for (mcb = g_top; mcb; mcb = mcb->prev)
        if (mcb->free && mcb->size >= size)
            break;
    if (! mcb) {
        mcb = sbrk (sizeof (MCB) + size);
        if (mcb == (void*)-1)
            return NULL;
        mcb->prev = g_top;
        mcb->size = size;
        g_top = mcb;
    }
    mcb->free = false;
    return mcb + 1;
}
// 释放内存
void myfree (void* ptr) {
    if (! ptr)
        return;
    MCB* mcb = (MCB*)ptr - 1;
    mcb->free = true;
    for (mcb = g_top; mcb->prev; mcb=mcb->prev)
        if (! mcb->free)
            break;
    if (mcb->free) {
        g_top = mcb->prev;
        brk (mcb);
    }
    else if (mcb != g_top) {
        g_top = mcb;
        brk ((void*)mcb + sizeof (MCB) + mcb->size);
    }
}

typedef struct student{
    int id;
    char name[30];
    char gender[10];
    int age;
}Student;

int main(void){
    Student* stu = (Student*)mymalloc(sizeof(Student));
    printf("Please input the id of student:");
    scanf("%d",&(stu->id));
    printf("Please input the name of student:");
    scanf("%s",stu->name);
    printf("Please input the gender of student:");
    scanf("%s",stu->gender);
    printf("PLease input the age of student:");
    scanf("%d",&(stu->age));

    printf("ID:%d, Name:%s, Gender:%s, Age:%d.\n",stu->id,stu->name,stu->gender,stu->age);

    myfree(stu);

    return 0;
}


 
 #include <sys/mman.h>
void* mmap (
  void* start,            // 映射区首地址,NULL系统自动选择
  size_t length,       // 映射区长度(字节),按页取整
  int prot,                  // 映射权限
  int flags,                 // 映射标志
  int fd,                      // 文件描述符
  off_t offset             // 文件偏移量
);
成功返回映射区首地址,
失败返回MAP_FAILED(-1)。
prot取值:
            PROT_EXEC - 映射区可执行
            PROT_READ - 映射区可读
            PROT_WRITE - 映射区可写
            PROT_NONE - 映射区不可访问
flags取值:
            MAP_FIXED - 若在start上无法创建映射, 返回失败,无此标志则自动调整
            MAP_SHARED - 对映射区的写操作直接反映到文件中
            MAP_PRIVATE - 对映射区的写操作只反映到内存中,不进文件
            MAP_ANONYMOUS - 内存映射,将虚拟地址映射到物理内存中
            MAP_DENYWRITE - 拒绝其它对文件的写入

            MAP_LOCKED - 锁定映射区域


int munmap (
  void* start, // 映射区首地址
  size_t length // 映射区长度(字节)按页取整
);

成功返回0,失败返回-1。


实例:

#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


struct Emp{
  int id;
  char name[20];
  double sal;
};


int main(){
  int fd=open("emp.dat",O_RDWR|O_CREAT|O_TRUNC,0666);
  if(fd==-1) perror("open"),exit(-1);
  ftruncate(fd,sizeof(struct Emp)*3);//指定文件大小
  void* p = mmap(0,sizeof(struct Emp)*3,
    PROT_READ|PROT_WRITE,
    MAP_SHARED,//写入文件
//MAP_PRIVATE,//不会把内容写入文件,只有本进程使用
    fd,0);
  struct Emp* pe = p;
  pe[0].id = 100;
  strcpy(pe[0].name,"liubei");
  pe[0].sal = 200000;
  pe[1].id = 200;
  strcpy(pe[1].name,"guanyu");
  pe[1].sal = 150000;
  pe[2].id = 300;
  strcpy(pe[2].name,"zhangfei");
  pe[2].sal = 120000;

  munmap(p,sizeof(struct Emp)*3);
  close(fd);

  return 0;
}


几个动态内存分配函数区别:

mmap/munmap:底层不维护任何东西,只是返回一个建立映射的虚拟地址。
brk/sbrk:底层维护一个指针,记录堆尾。
malloc/free:底层维护一个双向链表,存储内存块的控制信息。 

你可能感兴趣的:(unix,C语言,malloc,内存管理,内存分配)