精华内容
下载资源
问答
  • Linux虚拟地址空间

    2021-05-14 01:08:26
    Linux虚拟内存管理(glibc)在使用mysql作为DB开发的兑换券系统中,随着分区表的不断创建,发现mysqld出现了疑似“内存泄露”现象...Linux的虚拟内存管理有几个关键概念:1、每个进程都有独立的虚拟地址空间,进程访问...

    Linux虚拟内存管理(glibc)

    在使用mysql作为DB开发的兑换券系统中,随着分区表的不断创建,发现mysqld出现了疑似“内存泄露”现象,但通过 valgrind

    等工具检测后,并没发现类似的问题(最终原因是由于glibc的内存碎片造成)。

    因此,需要深入学习 Linux

    的虚拟内存管理方面的内容来解释这个现象;Linux

    的虚拟内存管理有几个关键概念:

    1、每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址; 2、虚拟地址可通过每个进程上的页表(在每个进程的内核虚拟地址空间)与物理地址进行映射,获得真正物理地址; 3、如果虚拟地址对应物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。 基于以上认识,进行了如下分析:一、Linux

    虚拟地址空间如何分布?

    Linux 使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别为: 1、只读段:该部分空间只能读,不可写;(包括:代码段、rodata

    段(C常量字符串和#define定义的常量) )

    2、数据段:保存全局变量、静态变量的空间; 3、堆 :就是平时所说的动态内存,

    malloc/new 大部分都来源于此。其中堆顶的位置可通过函数 brk 和 sbrk

    进行动态调整。 4、文件映射区域

    :如动态库、共享内存等映射物理空间的内存,一般是mmap

    函数所分配的虚拟地址空间。 5、栈:用于维护函数调用的上下文空间,一般为 8M ,可通过 ulimit –s

    查看。 6、内核虚拟空间:用户代码不可见的内存区域,由内核管理(页表就存放在内核虚拟空间)。

    下图是 32

    位系统典型的虚拟地址空间分布(来自《深入理解计算机系统》)。

    a4c26d1e5885305701be709a3d33442f.png

    32 位系统有4G 的地址空间::

    其中

    0x08048000~0xbfffffff 是用户空间,0xc0000000~0xffffffff

    是内核空间,包括内核代码和数据、与进程相关的数据结构(如页表、内核栈)等。另外,%esp 执行栈顶,往低地址方向变化;brk/sbrk

    函数控制堆顶_edata往高地址方向变化。

    64位系统结果怎样呢? 64 位系统是否拥有 2^64

    的地址空间吗? 事实上, 64 位系统的虚拟地址空间划分发生了改变: 1、地址空间大小不是2^32,也不是2^64,而一般是2^48。因为并不需要

    2^64

    这么大的寻址空间,过大空间只会导致资源的浪费。64位Linux一般使用48位来表示虚拟地址空间,40位表示物理地址,

    这可通过 /proc/cpuinfo 来查看 address sizes : 40 bits

    physical, 48 bits virtual 2、其中,0x0000000000000000~0x00007fffffffffff 表示用户空间,

    0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF 表示内核空间,共提供 256TB(2^48)

    的寻址空间。

    这两个区间的特点是,第 47 位与 48~63 位相同,若这些位为 0

    表示用户空间,否则表示内核空间。 3、用户空间由低地址到高地址仍然是只读段、数据段、堆、文件映射区域和栈;

    三、如何查看堆内内存的碎片情况 ?

    glibc 提供了以下结构和接口来查看堆内内存和 mmap

    的使用情况。 struct mallinfo { int

    arena; int ordblks; int

    smblks; int

    hblks; int

    hblkhd; int

    usmblks; int fsmblks; int uordblks;

    int fordblks;

    int keepcost;

    };

    struct

    mallinfo mallinfo();

    void malloc_stats();

    可通过以下例子来验证mallinfo和malloc_stats输出结果。

    #include  #include  #include  #include  #include  #include

    size_t heap_malloc_total,

    heap_free_total,mmap_total, mmap_count;

    void print_info() { struct

    mallinfo mi = mallinfo(); printf("count

    by

    itself:\n"); printf("\theap_malloc_total=%lu heap_free_total=%lu

    heap_in_use=%lu\n\tmmap_total=%lu

    mmap_count=%lu\n", heap_malloc_total*1024, heap_free_total*1024,

    heap_malloc_total*1024-heap_free_total*1024,

    mmap_total*1024,

    mmap_count); printf("count by

    mallinfo:\n"); printf("\theap_malloc_total=%lu

    heap_free_total=%lu heap_in_use=%lu\n\tmmap_total=%lu

    mmap_count=%lu\n", mi.arena, mi.fordblks, mi.uordblks, mi.hblkhd, mi.hblks); printf("from

    malloc_stats:\n"); malloc_stats(); }

    #define ARRAY_SIZE 200 int main(int argc, char** argv) { char**

    ptr_arr[ARRAY_SIZE]; int

    i; for(

    i = 0; i < ARRAY_SIZE; i++) { ptr_arr[i] = malloc(i * 1024); if

    ( i <

    128) //glibc默认128k以上使用mmap

    {

    heap_malloc_total += i; }

    else {

    mmap_total

    += i; mmap_count++; } } print_info();

    for( i = 0;

    i < ARRAY_SIZE; i++) { if ( i % 2 == 0) continue; free(ptr_arr[i]);

    if ( i < 128) {

    heap_free_total += i; }

    else { mmap_total -= i; mmap_count--; } } printf("\nafter

    free\n"); print_info();

    return

    1; }

    该例子第一个循环为指针数组每个成员分配索引位置 (KB)

    大小的内存块,并通过 128 为分界分别对 heap 和 mmap 内存分配情况进行计数;

    第二个循环是 free

    索引下标为奇数的项,同时更新计数情况。通过程序的计数与mallinfo/malloc_stats 接口得到结果进行对比,并通过

    print_info打印到终端。

    下面是一个执行结果: count by itself: heap_malloc_total=8323072 heap_free_total=0

    heap_in_use=8323072 mmap_total=12054528 mmap_count=72 count by mallinfo: heap_malloc_total=8327168 heap_free_total=2032

    heap_in_use=8325136 mmap_total=12238848 mmap_count=72

    from malloc_stats: Arena 0: system

    bytes = 8327168 in use

    bytes = 8325136 Total (incl. mmap): system

    bytes = 20566016 in use

    bytes = 20563984 max mmap regions

    = 72 max mmap bytes = 12238848

    after

    free

    count by itself: heap_malloc_total=8323072 heap_free_total=4194304

    heap_in_use=4128768 mmap_total=6008832 mmap_count=36

    count by mallinfo: heap_malloc_total=8327168 heap_free_total=4197360

    heap_in_use=4129808 mmap_total=6119424 mmap_count=36

    from malloc_stats: Arena 0: system

    bytes = 8327168 in use

    bytes = 4129808 Total (incl. mmap): system

    bytes = 14446592 in use

    bytes = 10249232 max mmap regions

    = 72 max mmap bytes = 12238848

    由上可知,程序统计和mallinfo 得到的信息基本吻合,其中 heap_free_total

    表示堆内已释放的内存碎片总和。 如果想知道堆内究竟有多少碎片,可通过 mallinfo 结构中的

    fsmblks 、smblks 、ordblks 值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是 0~80 字节,80~512

    字节,512~128k。如果 fsmblks 、 smblks

    的值过大,那碎片问题可能比较严重了。 不过, mallinfo

    结构有一个很致命的问题,就是其成员定义全部都是 int ,在 64 位环境中,其结构中的

    uordblks/fordblks/arena/usmblks

    很容易就会导致溢出,应该是历史遗留问题,使用时要注意!

    四、既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap

    来分配,munmap直接释放呢? 既然堆内碎片不能直接释放,导致疑似“内存泄露”问题,为什么

    malloc 不全部使用 mmap 来实现呢(mmap分配的内存可以会通过 munmap 进行 free

    ,实现真正释放)?而是仅仅对于大于 128k 的大块内存才使用 mmap ?

    其实,进程向 OS 申请和释放地址空间的接口 sbrk/mmap/munmap

    都是系统调用,频繁调用系统调用都比较消耗系统资源的。并且, mmap 申请的内存被 munmap

    后,重新申请会产生更多的缺页中断。例如使用 mmap 分配 1M 空间,第一次调用产生了大量缺页中断 (1M/4K 次 )

    ,当munmap 后再次分配 1M 空间,会再次产生大量缺页中断。缺页中断是内核行为,会导致内核态CPU消耗较大。另外,如果使用

    mmap 分配小内存,会导致地址空间的分片更多,内核的管理负担更大。

    同时堆是一个连续空间,并且堆内碎片由于没有归还

    OS ,如果可重用碎片,再次访问该内存很可能不需产生任何系统调用和缺页中断,这将大大降低 CPU

    的消耗。 因此, glibc 的 malloc 实现中,充分考虑了 sbrk 和 mmap

    行为上的差异及优缺点,默认分配大块内存 (128k) 才使用 mmap 获得地址空间,也可通过

    mallopt(M_MMAP_THRESHOLD, ) 来修改这个临界值。

    五、如何查看进程的缺页中断信息?

    可通过以下命令查看缺页中断信息 ps -o majflt,minflt -C  ps -o majflt,minflt -p  其中:: majflt 代表 major fault ,指大错误;

    minflt

    代表 minor fault ,指小错误。

    这两个数值表示一个进程自启动以来所发生的缺页中断的次数。

    其中 majflt 与 minflt 的不同是::

    majflt 表示需要读写磁盘,可能是内存对应页面在磁盘中需要load

    到物理内存中,也可能是此时物理内存不足,需要淘汰部分物理页面至磁盘中。

    六、除了 glibc 的

    malloc/free ,还有其他第三方实现吗?

    其实,很多人开始诟病 glibc 内存管理的实现,特别是高并发性能低下和内存碎片化问题都比较严重,因此,陆续出现一些第三方工具来替换

    glibc 的实现,最著名的当属 google 的tcmalloc和facebook 的jemalloc

    。 网上有很多资源,可以自己查(只用使用第三方库,代码不用修改,就可以使用第三方库中的malloc)。

    展开全文
  • Linux内核中采用了通用的四...本文通过一个内核模块程序模拟内核中虚拟地址转换为物理地址的过程,有关分页机制的原理可以参见这里的文章。1static void get_pgtable_macro(void)2{3printk("PAGE_OFFSET = 0x%lx\n",...

    Linux内核中采用了通用的四级分页模型,这种模型不仅适合32位系统也适合64位系统。分页单元是MMU(内存管理单元)中的一部分,它将线性地址转换为物理地址。本文通过一个内核模块程序模拟内核中虚拟地址转换为物理地址的过程,有关分页机制的原理可以参见这里的文章。

    1

    static void get_pgtable_macro(void)

    2

    {

    3

    printk("PAGE_OFFSET = 0x%lx\n", PAGE_OFFSET);

    4

    printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);

    5

    printk("PUD_SHIFT = %d\n", PUD_SHIFT);

    6

    printk("PMD_SHIFT = %d\n", PMD_SHIFT);

    7

    printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);

    8

    9

    printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);

    10

    printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);

    11

    printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);

    12

    printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);

    13

    14

    printk("PAGE_MASK = 0x%lx\n", PAGE_MASK);

    15

    }

    16

    17

    static unsignedlong vaddr2paddr(unsignedlong vaddr)

    18

    {

    19

    pgd_t *pgd;

    20

    pud_t *pud;

    21

    pmd_t *pmd;

    22

    pte_t *pte;

    23

    unsignedlong paddr = 0;

    24

    unsignedlong page_addr = 0;

    25

    unsignedlong page_offset = 0;

    26

    27

    pgd = pgd_offset(current->mm, vaddr);

    28

    printk("pgd_val = 0x%lx\n", pgd_val(*pgd));

    29

    printk("pgd_index = %lu\n", pgd_index(vaddr));

    30

    if (pgd_none(*pgd)) {

    31

    printk("not mapped in pgd\n");

    32

    return -1;

    33

    }

    34

    35

    pud = pud_offset(pgd, vaddr);

    36

    printk("pud_val = 0x%lx\n", pud_val(*pud));

    37

    if (pud_none(*pud)) {

    38

    printk("not mapped in pud\n");

    39

    return -1;

    40

    }

    41

    42

    pmd = pmd_offset(pud, vaddr);

    43

    printk("pmd_val = 0x%lx\n", pmd_val(*pmd));

    44

    printk("pmd_index = %lu\n", pmd_index(vaddr));

    45

    if (pmd_none(*pmd)) {

    46

    printk("not mapped in pmd\n");

    47

    return -1;

    48

    }

    49

    50

    pte = pte_offset_kernel(pmd, vaddr);

    51

    printk("pte_val = 0x%lx\n", pte_val(*pte));

    52

    printk("pte_index = %lu\n", pte_index(vaddr));

    53

    if (pte_none(*pte)) {

    54

    printk("not mapped in pte\n");

    55

    return -1;

    56

    }

    57

    58

    //页框物理地址机制 | 偏移量

    59

    page_addr = pte_val(*pte) & PAGE_MASK;

    60

    page_offset = vaddr & ~PAGE_MASK;

    61

    paddr = page_addr | page_offset;

    62

    printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);

    63

    printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);

    64

    65

    return paddr;

    66

    }

    67

    68

    static int __init v2p_init(void)

    69

    {

    70

    unsignedlong vaddr = 0;

    71

    72

    printk("vaddr to paddr module is running..\n");

    73

    get_pgtable_macro();

    74

    printk("\n");

    75

    76

    vaddr = (unsignedlong)vmalloc(1000 *sizeof(char));

    77

    if (vaddr == 0) {

    78

    printk("vmalloc failed..\n");

    79

    return 0;

    80

    }

    81

    printk("vmalloc_vaddr=0x%lx\n", vaddr);

    82

    vaddr2paddr(vaddr);

    83

    84

    printk("\n\n");

    85

    vaddr = __get_free_page(GFP_KERNEL);

    86

    if (vaddr == 0) {

    87

    printk("__get_free_page failed..\n");

    88

    return 0;

    89

    }

    90

    printk("get_page_vaddr=0x%lx\n", vaddr);

    91

    vaddr2paddr(vaddr);

    92

    93

    return 0;

    94

    }

    95

    96

    static void __exit v2p_exit(void)

    97

    {

    98

    printk("vaddr to paddr module is leaving..\n");

    99

    vfree((void *)vaddr);

    100

    free_page(vaddr);

    101

    }

    整个程序的结构如下:

    1.get_pgtable_macro()打印当前系统分页机制中的一些宏。

    2.通过vmalloc()在内核空间中分配内存,调用vaddr2paddr()将虚拟地址转化成物理地址。

    3.通过__get_free_pages()在内核空间中分配页框,调用vaddr2paddr()将虚拟地址转化成物理地址。

    4.分别通过vfree()和free_page()释放申请的内存空间。

    vaddr2paddr()的执行过程如下:

    1.通过pgd_offset计算页全局目录项的线性地址pgd,传入的参数为内存描述符mm和线性地址vaddr。接着打印pgd所指的页全局目录项。

    2.通过pud_offset计算页上级目录项的线性地址pud,传入的参数为页全局目录项的线性地址pgd和线性地址vaddr。接着打印pud所指的页上级目录项。

    3.通过pmd_offset计算页中间目录项的线性地址pmd,传入的参数为页上级目录项的线性地址pud和线性地址vaddr。接着打印pmd所指的页中间目录项。

    4.通过pte_offset_kernel计算页表项的线性地址pte,传入的参数为页中间目录项的线性地址pmd和线性地址vaddr。接着打印pte所指的页表项。

    5.pte_val(*pte)先取出页表项,与PAGE_MASK相与的结果是得到要访问页的物理地址;vaddr&~PAGE_MASK用来得到线性地址offset字段;两者或运算得到最终的物理地址。

    展开全文
  • 虚拟地址与物理地址

    2021-04-11 20:31:10
    文章目录虚拟地址与物理地址/proc/pid/page_mapmknodwaitWIFEXITEDWEXITSTATUSwaitpid返回值和错误举例监视进程makefile实例理解setsidumaskpmap命令系统总内存的统计四、对调试内存泄露类问题的一些启示 虚拟地址与...

    虚拟地址与物理地址

    本文旨在通过虚拟地址计算物理地址,理解环境实际运行机制以及写时复制技术

    /proc/pid/page_map

    Linux文件目录中的/proc记录着当前进程的信息,称其为虚拟文件系统。在/proc下有一个链接目录名为self,这意味着哪一个进程打开了它,self中存储的信息就是所链接进程的。self中有一个名为pagemap的文件,专门用来记录所链接进程的物理页号信息。这样通过/proc/pid/pagemap文件,允许一个用户态的进程查看到每个虚拟页映射到的物理页
    
    /proc/pid/pagemap中的每一项都包含了一个64位的值,这个值内容如下所示。每一项的映射方式不同于真正的虚拟地址映射,其文件中遵循独立的对应关系,即虚拟地址相对于0x0经过的页面数是对应项在文件中的偏移量
    
    这两段话目前还是比较难理解
    - /proc/pid/pagemap.  This file lets a userspace process find out which
      physical frame each virtual page is mapped to.  It contains one 64-bit
      value for each virtual page, containing the following data (from
      fs/proc/task_mmu.c, above pagemap_read):
      - Bits 0-54  page frame number (PFN) if present//present为1时,bit0-54表示物理页号
      - Bits 0-4   swap type if swapped
      - Bits 5-54  swap offset if swapped
      - Bit  55    pte is soft-dirty (see Documentation/vm/soft-dirty.txt)
      - Bit  56    page exclusively mapped (since 4.2)
      - Bits 57-60 zero
      - Bit  61    page is file-page or shared-anon (since 3.5)
      - Bit  62    page swapped
      - Bit  63    page present//如果为1,表示当前物理页在内存中;为0,表示当前物理页不在内存中
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        在计算物理地址时,只需要找到虚拟地址的对应项,再通过对应项中的bit63判断此物理页是否在内存中,若在内存中则对应项中的物理页号加上偏移地址,就能得到物理地址
    
    通过程序获取物理地址并验证写时拷贝技术
    

    计算过程

    1、首先获取一个变量的地址;(逻辑地址:段内偏移量)
    2、获取系统设定的页面大小getpagesize()
    3、计算虚拟地址相对于0x0经过的页面数
    unsigned long v_pageIndex = vaddr/pageSize;
    4、计算在/proc/pid/page_map文件中的偏移
    unsigned long page_offset=v_pageIndex*sizeof(uint64_t);
    5、计算虚拟地址的页内偏移量
    unsigned long page_offset = vaddr%pageSize;
    6、以只读方式打开/proc/pid/page_map
    7、将游标移动到相应位置,即对应项的起始地址
    lseek(fd,v_offset,SEEK_SET)
    8、取8字节对应项
    read(fd,&item,sizeof(uint64_t);
    9、计算物理页号
    uint64_t phy_pageIndex = (((uint64_t)1<<55)-1)&item;
    10、计算物理地址
    *paddr = (pht_pageIndex*pageSize)+page_offset;
    
    
    #include <stdio.h>
    
    
    #include <stdlib.h>
    
    
    #include <sys/types.h>
    
    
    #include <unistd.h>
    
    
    #include <sys/stat.h>
    
    
    #include <fcntl.h>
    
    
    #include <stdint.h>
    
    
    //    计算虚拟地址对应的地址,传入虚拟地址vaddr,通过paddr传出物理地址
    void mem_addr(unsigned long vaddr, unsigned long *paddr)
    {
        int pageSize = getpagesize();调用此函数获取系统设定的页面大小
    
            unsigned long v_pageIndex = vaddr / pageSize;//计算此虚拟地址相对于0x0的经过的页面数
            unsigned long v_offset = v_pageIndex * sizeof(uint64_t);//计算在/proc/pid/page_map文件中的偏移量
            unsigned long page_offset = vaddr % pageSize;//计算虚拟地址在页面中的偏移量
            uint64_t item = 0;//存储对应项的值
    
            int fd = open("/proc/self/pagemap", O_RDONLY);//。。以只读方式打开/proc/pid/page_map
            if(fd == -1)//判断是否打开失败
            {
                printf("open /proc/self/pagemap error\n");//
                return;//
            }
    
        if(lseek(fd, v_offset, SEEK_SET) == -1)//将游标移动到相应位置,即对应项的起始地址且判断是否移动失败
        {
            printf("sleek error\n");//
            return;// 
        }
    
        if(read(fd, &item, sizeof(uint64_t)) != sizeof(uint64_t))//读取对应项的值,并存入item中,且判断读取数据位数是否正确
        {
            printf("read item error\n");//
            return;//
        }
    
        if((((uint64_t)1 << 63) & item) == 0)//判断present是否为0
        {
            printf("page present is 0\n");//
            return ;//
        }
    
        uint64_t phy_pageIndex = (((uint64_t)1 << 55) - 1) & item;//计算物理页号,即取item的bit0-54
            printf("phypageIndex:%d\n pageSize:%d\n pageoffset:%d\n",phy_pageIndex,pageSize,page_offset);
    
            *paddr = (phy_pageIndex * pageSize) + page_offset;//再加上页内偏移量就得到了物理地址
    }
    
    int a = 100;//全局常量
    
    int main()
    {
        int b = 100;//局部变量
            static c = 100;//局部静态变量
            const int d = 100;//局部常量
            char *str = "Hello World!";//
    
        unsigned long phy = 0;//物理地址
    
            char *p = (char*)malloc(100);//动态内存
    
            int pid = fork();//创建子进程
            if(pid == 0)
            {
                sleep(10);
                a=98;
                printf("子进程中修改动态内存\n");
                p[0] = '1';//子进程中修改动态内存
                    mem_addr((unsigned long)&a, &phy);//
                printf("pid = %d, virtual addr = %x , physical addr = %x\n", getpid(), &a, phy);//
            }
            else
            { 
                mem_addr((unsigned long)&a, &phy);//
                printf("pid = %d, virtual addr = %x , physical addr = %x\n", getpid(), &a, phy);//
            }
    
        sleep(100);//
        free(p);//
        waitpid();//
        printf("父进程安全退出\n");
        return 0;//
    }
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-33787By5-1618144263365)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618122377036.png)]

    测试效果没有达到预期

    解决方法

    with a gap in time
    

    访问物理内存

    mknod /dev/dram c 85 0
    ./fileview /dev/dram
    
    

    mknod

    手动创建设备文件
    mknod DEVNAME {b|c} MAJOR MINOR
     1,DEVNAME是要创建的设备文件名,如果想将设备文件放在一个特定的文件夹下,就需要先用mkdir在dev目录下新建一个目录;
           2, b和c 分别表示块设备和字符设备:
                      b表示系统从块设备中读取数据的时候,直接从内存的buffer中读取数据,而不经过磁盘;
                      c表示字符设备文件与设备传送数据的时候是以字符的形式传送,一次传送一个字符,比如打印机、终端都是以字符的形式传送数据;
           3,MAJOR和MINOR分别表示主设备号和次设备号:
                 为了管理设备,系统为每个设备分配一个编号,一个设备号由主设备号和次设备号组成。主设备号标示某一种类的设备,次设备号用来区分同一类型的设备。linux操作系统中为设备文件编号分配了32位无符号整数,其中前12位是主设备号,后20位为次设备号,所以在向系统申请设备文件时主设备号不好超过4095,次设备号不好超过2^20 -1。
    

    wait

    通过分析各个回收方法理解进程之间的关系

    父进程一旦调用了wait就立即阻塞自己,由wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,
    如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
     wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.
    
      如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID 1)继承,当子进程终止时,init进程捕获这个状态.
    
      参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样:
    
    pid = wait(NULL);
    
    如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
    
      如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中, 这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息 被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:
    ————————————————
    版权声明:本文为CSDN博主「DNFK」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/wyhh_0101/article/details/83933308
    

    WIFEXITED

    这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非0值
    请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数–指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)
    

    WEXITSTATUS

     WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
    ————————————————
    版权声明:本文为CSDN博主「DNFK」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/wyhh_0101/article/details/83933308
    

    waitpid

    从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:
    pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
      pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
      pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
      pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
    ————————————————
    版权声明:本文为CSDN博主「DNFK」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/wyhh_0101/article/details/83933308
    

    返回值和错误

    waitpid的返回值比wait稍微复杂一些,一共有3种情况:

          1、当正常返回的时候,waitpid返回收集到的子进程的进程ID;
    
          2、如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
    
          3、如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
    

    当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
    ————————————————
    版权声明:本文为CSDN博主「DNFK」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/wyhh_0101/article/details/83933308

    举例监视进程

    目的:监视守护进程

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/wait.h>
    #include <sys/stat.h>
    void Daemon(void)
    {
        pid_t pid = -1;
        int ret = 01;
        int fd = -1;
        int status = 0;
        pid = fork();
        if(-1 == pid)
        {
            perror("fork");
            exit(1);
        }
        else if(pid >0)
        {
            //父进程退出
            exit(1);
        }
        //创建会话
        ret = setsid();
        //当进程是会话的领头进程时setsid调用失败并返回(-1)
        //调用setsid函数的进程成为新的会话的领头进程,并与其父进程的绘画组和进程组脱离
        //由于会话对控制终端的独占性,进程同时与控制终端脱离
        if(-1 == ret)
        {
            perror("setsid");
            exit(1);
        }
        //设置权限掩码
        //
        umask(0);
        //改变当前进程工作目录
        //printf("当前目录:%s\n",get_current_dir_name());
        char buf[1024];
        memset(buf,0x00,sizeof(buf));
        printf("当前进程目录:%s\n",getwd(buf));
        //重定向文件描述符
        //守护进程起循环
        while(1)
        {
            pid = fork();
            if(-1 == pid)
            {
                perror("fork");
                exit(1);
            }
            else if(pid > 0)
            {
                //父进程监视异常
                //如果异常会回收资源
                wait(&status);
                //但不会继续向下执行
                //并发的子进程会跳出执行下一次循环使资源重新分配
                if(WIFEXITED(status))
                {
                    //子进程正常终止,父进程也退出
                    //正常结束
                    exit(0);
                }
            }
            else if(pid == 0)
            {
                //父进程阻塞
                //子进程跳出循环去执行任务
               break; 
            }
            else 
            {
                perror("fork");
                exit(1);
            }
        }
    }
    int main(int argc,char **argv)
    {
        if((2 == argc) &&(0 == strcmp("daemon",argv[1])))
        {
            Daemon(); 
        }
        else
        {
            printf("请输入两个参数\n");
            exit(1);
        }
        while(1)
        {
            sleep(2);
            printf("hello world\n");
        }
        return 0;
    }
    
    

    测试效果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sNBi9oyy-1618144263368)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618131481453.png)]

    未开启守护进程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-94mV0k7d-1618144263370)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618131516675.png)]

    开启守护进程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0xBw0dj5-1618144263372)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618132328198.png)]

    pid查看

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssNituaB-1618144263374)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618132361088.png)]

    关闭终端

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gGOLo3C8-1618144263375)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618132432921.png)]

    将后台任务恢复到前台

    查看后台运行任务

    杀掉子进程
    kill -9 11049
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8B4RPYa-1618144263376)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618133073342.png)]

    描述执行情况

    守护方法中增加一个循环
    开一个进程负责阻塞wait
    通过WIFEXITED(status)
    监视是否正常退出,
    如果返回真,则正常退出
    如果返回假,则子进程异常结束(比如接收一个kill 信号),再次执行循环fork一个子进程去执行任务
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w8G4qGKR-1618144263377)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618133440221.png)]

    杀掉父进程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uLq2mt5v-1618144263377)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618133491720.png)]

    makefile实例

    
    #获取所有的源文件
    SRCS:=$(wildcard *.c*)
    #获取所有的目标文件
    OBJS:=$(patsubst %.c*, %.o, $(SRCS))
    
    #指定编译器
    CC:= g++
    #指定目标
    TARGET:=write
    
    $(TARGET):$(OBJS)
    	$(CC) $^ -L/u01/app/oracle/product/11.2.0/db_1/lib -I/u01/app/oracle/product/11.2.0/db_1/rdbms/public -o $@ -lprotobuf -locci -lclntsh -lssl -lcrypto -lpthread
    
    %.o:%.c*
    	$(CC) -c -std=c++11 $< -o $@ -lprotobuf -locci -lclntsh 
    
    .PHONY:clean
    clean:
    	rm -rf $(OBJS) $(TARGET)
    
    
    

    理解setsid

    #include <sys/file.h>  
    #include <stdio.h>  
    #include <unistd.h>  
    #include <stdlib.h>  
    #include <sys/file.h>  
                                              
    int main (int argc, char ** argv)  
    {  
         if ( fork() > 0 ) {  
              printf ( "parent begin\n" ) ;  
              sleep(10);  
                
            printf ( "parent exit\n" ) ;  
            exit ( 0 ) ;  
         }  
           
         printf ( "child begin\n" ) ;  
         sleep(100);  
           
         printf ( "child exit\n" ) ;  
         return 0 ;  
    }  
    ————————————————
    版权声明:本文为CSDN博主「sweetfather」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/sweetfather/article/details/79457261
    

    测试结果

    错误

    c:23:2: error: stray ‘\302’ in program
    分析
    od -c *.c
    这个错误一般是源代码中含有一些隐藏的非ascii字符。你把东西copy到文本编辑器中,再copy回来试试。
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UozOfyi2-1618144263378)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618129119457.png)]

    打开另一个终端

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRga0GSo-1618144263379)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618129158583.png)]

    关闭运行test的窗口,父子进程都不在

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DgMq7af4-1618144263379)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618129226187.png)]

    子进程开始调用setsid

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wVahipfs-1618144263380)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618129559733.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pl9ZqLOb-1618144263381)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618129633493.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-okjnclhg-1618144263381)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618129664830.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fnLP5dXl-1618144263382)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618129689980.png)]

    testSid分为两个进程组

    父进程退出,子进程继续存在

    说明:setsid后子进程不受终端影响,终端退出,不影响子进程

    umask

    linux中的 umask 函数主要用于:在创建新文件或目录时 屏蔽掉新文件或目录不应有的访问允许权限。文件的访问允许权限共有9种,分别是:r w x r w x r w x(它们分别代表:用户读 用户写 用户执行 组读 组写 组执行 其它读 其它写 其它执行)。
    其实这个函数的作用,就是设置允许当前进程创建文件或者目录最大可操作的权限,比如这里设置为0,它的意思就是0取反再创建文件时权限相与,也就是:(~0) & mode 等于八进制的值0777 & mode了,这样就是给后面的代码调用函数mkdir给出最大的权限,避免了创建目录或文件的权限不确定性。
    ————————————————
    版权声明:本文为CSDN博主「sweetfather」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/sweetfather/article/details/79457261
    

    pmap命令

    举例

    进程1

    pmap -d 1
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z2aPUZHq-1618144263383)(C:\Users\11066\AppData\Roaming\Typora\typora-user-images\1618142968796.png)]

    最后一行的值
    mapped 表示该进程映射的虚拟地址空间大小,也就是该进程预先分配的虚拟内存大小,即ps出的vsz
    writeable/private  表示进程所占用的私有地址空间大小,也就是该进程实际使用的内存大小      
    shared 表示进程和其他进程共享的内存大小
    
    扩展格式
    pmap -x 1
    循环显示pid的设备格式的最后一行
    间隔2秒
    while true;
    pmap -d pid|tail -1;sleep 2;done
    
    

    分析cat maps

    第一行:从r-xp可知其权限为只读、可执行,该段内存地址对应于执行文件的代码段,程序的代码段需加载到内存中才可以执行。由于其只读,不会被修改,所以在整个系统内共享。
    第二行:从rw-p可知其权限为可读写,不可执行,该段内存地址对应于执行文件的数据段,存放执行文件所用到的全局变量、静态变量。
    第三行:从rwxp可知其权限是可读写,可执行,地址空间向上增长,而且不对应文件,是堆段,进程使用malloc申请的内存放在堆段。每个进程只有一个堆段,不论是主进程,还是不同的线程申请的内存,都反映到到进程的堆段。堆段向上增长,最大可以增长到1GB的位置,即0x40000000,如果大于1GB,glibc将采用mmap的方式,为堆申请一块内存。
    第四行:是程序连接的共享库的内存地址。
    第五行:是以mmap方式映射的虚拟地址空间。
    第六、七行:是线程的栈区地址段,每个线程的栈大小都是16K。
    第八行:是进程的栈区。关于栈段,每个线程都有一个,如果进程中有多个线程,则包含多个栈段。
    

    系统总内存的统计

    四、对调试内存泄露类问题的一些启示

     当进程申请内存时,实际上是glibc中内置的内存管理器接收了该请求,随着进程申请内存的增加,内存管理器会通过系统调用陷入内核,从而为进程分配更多的内存。
     针对堆段的管理,内核提供了两个系统调用brk和mmap,brk用于更改堆顶地址,而mmap则为进程分配一块虚拟地址空间。
    
    当进程向glibc申请内存时,如果申请内存的数量大于一个阀值的时候,glibc会采用mmap为进程分配一块虚拟地址空间,而不是采用brk来扩展堆顶的指针。缺省情况下,此阀值是128K,可以通过函数来修改此值。
    修改阈值:
    #include <malloc.h>
    intmallopt(int param,int value);
    
    如果在实际的调试过程中,怀疑某处发生了内存泄露,可以查看该进程的maps表,看进程的堆段或者mmap段的虚拟地址空间是否持续增加,如果是,说明很可能发生了内存泄露,如果mmap段虚拟地址空间持续增加,还可以看到各个段的虚拟地址空间的大小,从而可以确定是申请了多大的内存,对调试内存泄露类问题可以起到很好的定位作用。
    
    

    程的栈区。关于栈段,每个线程都有一个,如果进程中有多个线程,则包含多个栈段。

    
    ### 系统总内存的统计
    
    ### 四、对调试内存泄露类问题的一些启示
    
    

    当进程申请内存时,实际上是glibc中内置的内存管理器接收了该请求,随着进程申请内存的增加,内存管理器会通过系统调用陷入内核,从而为进程分配更多的内存。
    针对堆段的管理,内核提供了两个系统调用brk和mmap,brk用于更改堆顶地址,而mmap则为进程分配一块虚拟地址空间。

    当进程向glibc申请内存时,如果申请内存的数量大于一个阀值的时候,glibc会采用mmap为进程分配一块虚拟地址空间,而不是采用brk来扩展堆顶的指针。缺省情况下,此阀值是128K,可以通过函数来修改此值。
    修改阈值:
    #include <malloc.h>
    intmallopt(int param,int value);

    
    

    如果在实际的调试过程中,怀疑某处发生了内存泄露,可以查看该进程的maps表,看进程的堆段或者mmap段的虚拟地址空间是否持续增加,如果是,说明很可能发生了内存泄露,如果mmap段虚拟地址空间持续增加,还可以看到各个段的虚拟地址空间的大小,从而可以确定是申请了多大的内存,对调试内存泄露类问题可以起到很好的定位作用。

    
    
    展开全文
  • 原文:http://my.oschina.net/u/1770090/blog/2633261 虚拟地址空间概述Linux进程虚拟地址空间是linux内存管理一个重要的部分,...各进程的用户态虚拟地址空间起始于地址0,延伸到TASK_SIZE -1的位置,其上是内核的...

    原文:

    http://my.oschina.net/u/1770090/blog/263326

    1 虚拟地址空间概述

    Linux进程虚拟地址空间是linux内存管理一个重要的部分,我们知道,在IA-32系统上地址空间的范围可达2的32次幂=4G,总的地址空间通常按3:1的比例划分,用户态占用了3G,内核占用了1G。

    各进程的用户态虚拟地址空间起始于地址0,延伸到TASK_SIZE -1的位置,其上是内核的地址空间。

    无论当前的那个用户进程处于运行状态,虚拟地址空间的内核部分总是相同的。如下图所示:

    0818b9ca8b590ca3270a3433284dd417.png

    2 用户态地址空间

    虚拟地址空间有不同的段组成,分布方式是特定于体系结构的,但所有方法都有下列共同成分。

    在IA-32架构中,Linux传统内存空间布局如下:

    --芯片寄存器的虚拟地址也应该在内核地址1GB范围内吧,是不是还应该在0XF8000000之上?(我注释)

    --内核有stack,用户也有stack

    0818b9ca8b590ca3270a3433284dd417.png

    2.1 各段的说明

    TEXT:代码段,映射程序的二进制代码, 该区域为私有区域;在代码段中,也会包含一些只读的常数变量,比如字符串常量等。

    一般来说,IA-32体系结构中进程空间的代码段从0x08048000开始,这与最低可用地址(0x00000000)有128M的间距,按照linux内核架构一书介绍是为了捕获NULL指针(具体不详)。该值是在编译阶段就已经设定好的,其他体系结构也有类似的缺口,比如mips(), ppc() x86-64使用的是0x000000000400000(只4MB了)。

    DATA:数据段,映射程序中已经初始化的全局变量。

    BSS段:存放程序中未初始化的全局变量,在ELF文件中,该区域的变量仅仅是个符号,并不占用文件空间,但程序加载后,会占用内存空间,并初始化为0。

    HEAP:运行时的堆,在程序运行中使用malloc申请的内存区域。

    该区域的起始地址受start_brk影响,和BSS段之间会有一个随机值的空洞;该区域的内存增长方式是由低地址向高地址增长。

    MMAP:共享库及匿名文件的映射区域;该区域中会包含共享库的代码段和数据段。其中代码段为共享的,但数据段为私有的,写时拷贝。

    该区域起始地址也会有一个随机值,该区域的增长方式是从高地址向低地址增长(Linux经典布局中不同,稍后讨论)

    STACK:用户进程栈;

    该区域起始地址也存在一个随机值,通过PF_RANDOMIZE来设置。栈的增长方向是由高地址向低地址增长,并且若设置了RLIMIT_STACK即规定了栈的大小。

    最后,是命令行参数和环境变量。

    2.2 布局说明

    Linux经典内存布局和新布局不同

    0818b9ca8b590ca3270a3433284dd417.png

    从以上图对比可以发现,不同之处在于MMAP区域的增长方向,新布局导致了栈空间的固定,而堆区域和MMAP区域公用一个空间,这在很大程度上增长了堆区域的大小。

    那么为什么这么做呢?使用经典布局在32位计算机上,TASK_UNMMAPPED_BASE在IA-32中只有0x40000000(固定了害死人呢),也就是说对空间只有1G空间可用(包括malloc),堆超过了可用空间,继续增长会进入MMAP区域。在2.6.7内核开始,为IA-32引入了新的虚拟地址空间的布局(经典布局仍然可用)。

    布局的选择,如果用户通过/proc/sys/kernel/legacy_va_layout给出明确指示,或者要执行的二进制文件为unix变体编译需要旧的布局,或者栈可以无限制增长,则系统就会选择旧的布局。

    如果选择新的布局时,内存增长是有高地址向低地址,那MMAP的起始地址从什么地方开始的?可以使用栈的最大长度来计算栈最低的可能位置,作为MMAP的基址。但内核也会确保栈至少跨越128M空间。如果指定了栈的界限非常大,那内核也会至少保留一部分地址空间不会被栈占据。通常要求地址空间的随机化,上述位置会再减去一个偏移量,最大为1M。

    64位系统上堆虚拟地址空间总是使用经典布局。

    2.3 随机化问题

    在以上的描述中提到堆/栈/MMAP的随机化问题,那么为什么需要随机化呢?如果所有的进程都按照设定的默认值,攻击者很容易就知道栈和库函数的地址。

    3 内核态地址空间

    3.1 内核在物理内存的布局

    Linux的内核在初始化时会加载到内存区的固定位置(在此我们不讨论可重定位内核的情况),而内核所占用的内存区域的布局是固定的,如图所示:

    0818b9ca8b590ca3270a3433284dd417.png

    内存的第一个页不使用,通常保留给BIOS使用,接下来的640K原则上是可用的,但也不会加载内核代码,因为系统用于各种ROM(BIOS和显卡ROM)的映射。再之后的空间也是闲置的,原因是内核需要被放到连续的内存中;所以从0x100000(1MB)作为内核代码的起始地址。

    3.2 各段的说明

    内核占据的内存也分为几个段:

    _text和_etext是代码段的起始和终止位置

    _etext到_edata之间是数据段,保存了大部分的内核变量

    _edata到_end之间,是初始化为0的所有静态全局变量的BSS段。

    以上的信息可以通过cat /proc/iomem来查看,不同cpu体系有不同的情况。

    3.3 由物理地址到虚拟地址的转换

    IA-32架构可以访问4G的地址空间,通常会将线性地址空间划分为3:1的两个部分:用户态使用3G,内核态使用1G,即内核空间从0xC0000000开始,每个虚拟地址x都对应于物理地址x-0xC0000000。这样的设计可以加快内核空间的寻址速度(简单的减法操作)。在进程切换的过程中,只有用户态对应的页表被切换,高地址空间会公用内核页表。

    3.4 高端内存的情况

    IA-32架构的这种设计也存在这样的一个问题:既然内核只能处理1G的空间(实际上内核直接处理的空间还不足1G),那么物理内存大于1G,剩下的内存将如何处理呢?这种情况下内核无法直接映射全部的物理内存,这样就会用到高端内存区域的概念了(ZONE_HIGHMEM)。具体的内存分配如下图:

    0818b9ca8b590ca3270a3433284dd417.png

    由以上的图可以看到,内核区域的映射从_PAGE_OFFSE(0xC0000000)开始,即3G的位置开始映射到4G的区域,开始的一段用于直接映射,后而的128M为vmalloc使用的空间,再之后会有永久映射和固定映射区。所以事实上,物理内存能够直接映射到内核的空间=1G-vmalloc-永久映射-固定映射,真正的空间大约只剩下850M,也就是说,物理内存中只有850M是可以映射到内核空间的,对于超过的部分来说,作为高端内存使用。

    3.5 疑问一

    这里会存在一个疑问:如果内存只能处理896M的空间,那么如果内存很大比如(3G),那剩下的内存怎么办?对于这个问题,我们需要注意的是,1.这里讲述的内存是内核直接映射的物理内存,对于高地址的内存内核还有另外一套机制来管理;2.用户空间对物理内存的访问不是通过直接映射,还有另外一套机制。

    3.6 疑问二

    还有一个疑问:既然内核直接映射了将近1G的内存,那么如果我们有3G的物理内存,是不是只有2G多一点的内存可使用了呢?这个说法是错误的,上图所描述的只是内核在线性地址空间的分布情况,物理内存分配的过程中(用户态进程申请的内存,vmalloc部分的内存等),更倾向于先分配高地址的内存,在高地址内存耗尽的情况下,才会使用低850M的内存。

    4 虚拟地址空间的访问权限

    0818b9ca8b590ca3270a3433284dd417.png

    1.用户空间禁止访问内核空间。

    2.用户态通过系统调用切换到内核空间后,该进程处于内核态上下文,可以访问用户空间的内存。

    3.中断抢占进程后,处于中断上下文时,不能访问用户空间。

    4.内核线程不能访问用户空间。

    第一篇就先写到这里,可能也会有理解不太准确的地方,后续也会据此继续更新,希望使该篇文章保持准确性。

    展开全文
  • 虚拟服务器的ip地址

    2021-08-08 04:02:02
    虚拟服务器的ip地址 内容精选换一换虚拟IP地址用于为网卡提供第二个IP地址,同时支持与多个弹性云服务器的网卡绑定,从而实现多个弹性云服务器之间的高可用性。登录管理控制台。选择“计算 > 弹性云服务器”。在...
  • 服务器虚拟网络配置地址 内容精选换一换设置“网络”:在下拉列表中选择可用的虚拟私有云、子网,并设置私有IP地址的分配方式。弹性云服务器网络使用虚拟私有云(VPC)提供的网络,包括子网、安全组等。您可以选择使用...
  • 虚拟地址(也叫线性地址)--->物理地址。linux所有段基地址都为0,所以可以看做linux并没有采用段式的内存管理,linux的地址关系:虚拟地址--->物理地址因为,linux会为每个进程分配独立的进程地址空间(大小为3...
  • 两台服务器对外虚拟地址 内容精选换一换硬件要求如表1所示。操作系统要求如表2所示。如图1所示,用3台TaiShan 200服务器(型号2280)、两台RH2288V5组成K8s混部集群,另外使用3台TaiShan 200服务器(型号2280)组成Ceph...
  • 这里,我们讲解一下Linux是如何将虚拟地址转换成物理地址的 一、地址转换 在进程中,我们不直接对物理地址进行操作,CPU在运行时,指定的地址要经过MMU转换后才能访问到真正的物理内存。 地址转换的过程分为两部分...
  • linux DMA 物理地址虚拟地址的映射方法 最近在研究linux DMA的使用,做了很多的测试验证,也踩了很多坑,因为日常工作原因 ,我对linux kernel 的研究确实不是很多,也是工作原因,最近花时间在研究linux DMA,说...
  • 在 Linux 开发过程中,申请内存后,某些时候需要用物理地址传给其他外设进行写入或者读取操作,同时考虑到防止被操作系统 sawp,导致实际的物理地址发生变化,从而在操作对应的虚拟地址时无法正常运行等。...
  • 文章目录前言一、简单理解地址空间二、虚拟地址现象解释三、三个问题搞懂地址空间1. 什么是地址空间?2. 为什么要有地址空间?3. 地址空间是如何工作的?四、一些补充 前言 在之前的学习中,我们只学习了图中的下...
  • 用户进程通常情况下,只能访问用户空间的虚拟地址,不能访问到内核空间。每个进程的用户空间都是完全独立、互不相干的,用户进程各自有不同的页表。而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。...
  • 虚拟服务器地址大全

    2021-08-12 01:31:49
    虚拟服务器地址大全 内容精选换一换设置“网络”:在下拉列表中选择可用的虚拟私有云、子网,并设置私有IP地址的分配方式。弹性云服务器网络使用虚拟私有云(VPC)提供的网络,包括子网、安全组等。您可以选择使用已有...
  • 程序地址空间:进程的虚拟地址空间 1.通过代码演示两个进程中变量地址相同,但是数据不同---进程中访问的地址都是虚拟地址 2.虚拟地址空间:操作系统向进程通过mm_struct结构体描述的一个虚假的,线性的地址空间...
  • 服务器 虚拟化 ip地址规划 内容精选换一换ECS的网卡绑定虚拟IP地址后,该虚拟IP地址无法ping通。以下排查思路根据原因的出现概率进行排序,建议您从高频率原因往低频率原因排查,从而帮助您快速找到问题的原因。如果...
  • 二、linux进程地址空间由前面可得知,进程有4G的寻址空间,其中第一部分为“用户空间”,用来映射其整个进程空间(0x0000 0000-0xBFFF FFFF)即3G字节的虚拟地址;第二部分为“系统空间”,用来映射(0xC000 0000-0...
  • 采用分区式存储管理的系统,在储存分配过程中产生的、不能供用户作业使用的主存里的小分区称成“内存碎片”。... 线性地址:也叫虚拟地址,是一个32位无符号整数,由逻辑地址经过段式内存管理单元转换而来。
  • 虚拟服务器里怎样绑定ip地址 内容精选换一换在高可用部署场景下,ASCS主备节点通过共享盘实现数据同步。本章节指导用户将ASCS主节点的数据盘绑定给ASCS备节点并为ASCS主备节点绑定浮动IP。已在SAP ASCS主备节点之间...
  • 进程虚拟地址空间分段
  • 修改虚拟ip地址

    2021-08-08 01:27:02
    修改虚拟ip地址 内容精选换一换MRS使用Manager对集群进行监控、配置和管理,用户可以在MRS控制台页面打开Manager管理页面,使用创建集群时设置的admin账号和密码登录Manager。若用户创建集群时暂未绑定弹性公网IP,...
  • 程序地址空间我们先看一下一般的程序地址空间(假设是32位下的操作系统4G内存): 接下来我们看一段有意思的代码:#include#include#includeint gval=100;//定义一个全局变量int main(){pid_t pid=fork();//创建子进程...
  • 分析64位Linux系统虚拟地址转物理地址的原理。 将全程记录虚拟地址换物理地址的全过程。 2. 概念介绍 2.1 段概念的简单回顾 在进程中我们不直接对物理内存进行操作,CPU在运行时需要通过mmu转换后才可以真正的访问...
  • 如何向虚拟服务器分配端口和 IP 地址10/25/2013本文内容上一次修改主题: 2005-05-18为某个协议创建虚拟服务器时,可以为该服务器选择使用默认分配的端口和 IP(Internet 协议)地址。下表显示了与各协议相关联的默认...
  • 因此虚拟专用网中的各个主机所分配到的地址应该是本机构可自由分配的专用地址,而不是需要申请的、在因特网上使用的公有地址。     例如同一机构的两个部门分别位于上海和北京,这两个部门的专用网络之间的通信...
  • linux还存在虚拟内存

    2021-05-11 14:30:25
    Linux 的虚拟内存管理有几个关键概念:每个进程有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址虚拟地址可通过每个进程上页表与物理地址进行映射,获得真正物理地址如果虚拟地址对应物理地址不在物理...
  • 关于malloc申请内存

    2021-05-17 07:05:09
    虚拟地址(也叫线性地址)--->物理地址。linux所有段基地址都为0,所以可以看做linux并没有采用段式的内存管理,linux的地址关系:虚拟地址--->物理地址因为,linux会为每个进程分配独立的进程地址空间(大小为3...
  • 虚拟地址空间(二)

    2021-06-05 23:24:44
    在我最开始写博客的时候,第一篇就介绍了虚拟地址空间,但当时受限于自己的水平,并且很多内容当时还没有介绍,所以当时写的比较简单,这篇文章算是还愿了吧。 /proc/{pid}/maps linux的根目录下有很多目录,比如/...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 99,425
精华内容 39,770
关键字:

如何申请虚拟地址