私有内存与共享内存实验
0 结论先行
本地内存
- TOP的VIRT严格对应申请大小,一般就是申请多少就给多少虚拟内存地址范围。
- 64位系统内核占据128T地址范围:0xFFFF FFFF FFFF FFFF – 0x0000 7FFF FFFF FFFF
- 64位系统进程占据128T地址范围:0x0000 7FFF FFFF FFFF – 0x0000 0000 0000 0000(从高到低:栈、映射、堆、BSS、数据、代码、保留)TOP的RES即实际用页表映射到物理内存的大小,使用多少映射多少,按需满足。从实验来看最小映射单位为4KB(用1个字节也映射4KB)。
- malloc申请的内存在pmap来看属于匿名内存anon。
- 线程泄露特征:大量8MB块无人回收、VIRT超级大。
共享内存
- 无论是共享内存还是本地内存,申请内存后都会在VIRT上直接提现(只是给出使用范围,并没有真正申请物理内存)
- TOP的SHR也是实际使用内存的含义,SHR申请的是共享内存。
- SHR体现的是映射到物理内存上的大小。可以和其他进程的SHR重叠。
1 进程本地内存
申请连续内存块
代码语言:javascript复制#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
// gcc -g malloc.c -o malloc
void fun_sig(int sig) {
printf("recv sig %d, exit...n",sig);
exit(0);
}
int main() {
int i;
size_t s;
char *ptr;
signal(SIGINT, fun_sig);
s = 100 * 1024 * 1024;
ptr = malloc(s);
for (i = 0; i < 100 * 1024 * 1024; i ) {
//if (i % (1024*1024) == 0)
*(ptr i) = 0xFF;
}
printf("malloc done.n");
while(1);
return 0;
}
下表为TOP与pmap的观测结果:
MALLOC SIZE | VIRT | RES | SHR | Address Kbytes RSS Dirty Mode Mapping (KB) |
---|---|---|---|---|
1MB|全用 | 10.2m | 1.5m | 0.4m | 00007fae3c165000 1028 1028 1028 rw— anon |
10MB|全用 | 19.2m | 10.5m | 0.4m | 00007faa7b378000 10244 10244 10244 rw— anon |
100MB|全用 | 109.2m | 100.5m | 0.4m | 00007f36eb3e4000 102404 102404 102404 rw— anon |
100MB|用1MB | 109.2m | 1.5m | 0.4m | 00007eff30578000 102404 1028 1028 rw— anon |
100MB|用50MB | 109.2m | 50.4m | 0.4m | 00007f996e3fb000 102404 51204 51204 rw— anon |
100MB|用100字节|每1M用1字节 | 109.2m | 0.9m | 0.4m | 00007f5473728000 102404 400 400 rw— anon |
100MB|用0字节 | 109.2m | 0.4m | 0.3m | 00007f1f7b2e7000 102404 4 4 rw— anon |
结论
- VIRT严格对应申请大小,一般就是申请多少就给多少虚拟内存地址范围。
- 64位系统内核占据128T地址范围:0xFFFF FFFF FFFF FFFF – 0x0000 7FFF FFFF FFFF
- 64位系统进程占据128T地址范围:0x0000 7FFF FFFF FFFF – 0x0000 0000 0000 0000(从高到低:栈、映射、堆、BSS、数据、代码、保留)
- RES即实际用页表映射到物理内存的大小,使用多少映射多少,按需满足。从实验来看最小映射单位为4KB(用1个字节也映射4KB)。
- malloc申请的内存在pmap来看属于匿名内存anon。
2 pthread线程内存
这里针对线程无join、detach回收的场景做测试:
代码语言:javascript复制#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
// gcc -g -lpthread ph.c -o ph
void* worker(void* arg) {
// printf("c");
// char *xx = malloc(1000);
// *(xx) = 0x12;
// *(xx 51) = 0x12;
// free(xx);
return NULL;
}
void fun_sig(int sig) {
printf("recv sig %d, exit...n",sig);
exit(0);
}
int main() {
int i;
signal(SIGINT, fun_sig);
for (i = 0; i < 100000; i ) {
pthread_t t;
int ret;
ret = pthread_create(&t, NULL, worker, NULL);
if (ret != 0) {
printf("pthread_create error, ret=%d, i=%d.n",ret, i);
while(i);
}
// pthread_join(t, NULL);
}
printf("all thread created.n");
while(i);
return 0;
}
输出
代码语言:javascript复制pthread_create error, ret=11, i=32745.
在创建到32745个线程时,pthread框架报告没有资源创建新线程了,这个是框架自己对于内存使用的显示。
现在的TOP情况
代码语言:javascript复制 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
26706 mingjie 20 0 256.0g 274.6m 0.6m R 100.0 0.9 0:16.87 ./ph
pmap情况
代码语言:javascript复制$ pmap -x 22359
00007f3cf591c000 8192 8 8 rw--- [ anon ]
00007f3cf611c000 4 0 0 ----- [ anon ]
00007f3cf611d000 8192 8 8 rw--- [ anon ]
00007f3cf691d000 4 0 0 ----- [ anon ]
00007f3cf691e000 8192 8 8 rw--- [ anon ]
00007f3cf711e000 4 0 0 ----- [ anon ]
...
...
$ pmap -x 22359 | grep 8192 | wc -l
32746
结论
- VIRT:pthread共创建了32745个线程,每个线程pthread申请8MB本地内存,共
32746 * 8MB = 255.8GB
,和VIRT基本持平。 - RES:内存全被框架占用,一个线程占用8KB左右,
32745 * 8KB = 255MB
,和RES基本持平。 - pmap中存在大量8MB匿名内存块(malloc出来的),线程泄露的特征。
线程泄露特征:大量8MB块无人回收、VIRT超级大。
3 mmap匿名继承内存
《Linux内存映射函数mmap与匿名内存块》
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char *addr;
size_t s = 100 * 1024 * 1024;
addr = mmap(NULL, s, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// *addr = 1;
switch (fork())
{
case -1:
fprintf(stderr, "fork() failedn");
exit(EXIT_FAILURE);
case 0:
printf("Child started, value = %dn", *addr);
for (int i = 0; i < s; i )
{
*(addr i) = 0xFE;
}
printf("Child started, value = %dn", *addr);
while(1);
exit(EXIT_SUCCESS);
default:
while(*(addr s/2) != -2);
int kk;
for (int i = 0; i < s / 2; i )
{
kk = *(addr i);
}
while(kk);
if (wait(NULL) == -1)
{
fprintf(stderr, "wait() failedn");
exit(EXIT_FAILURE);
}
printf("In parent, value = %dn", *addr);
if (munmap(addr, sizeof(int)) == -1)
{
fprintf(stderr, "munmap()() failedn");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
}
3.1 场景一:父进程申请|子进程继承|都未使用
父VIRT | 父RES | 父SHR | 子VIRT | 子RES | 子SHR |
---|---|---|---|---|---|
111788 | 436 | 332 | 111792 | 108 | 0 |
父进程pmap
代码语言:javascript复制Address Kbytes RSS Dirty Mode Mapping
00007f9a61f8e000 102400 0 0 rw-s- zero (deleted)
子进程pmap
代码语言:javascript复制Address Kbytes RSS Dirty Mode Mapping
00007f9a61f8e000 102400 4 0 rw-s- zero (deleted)
父子进程的映射地址是相同的。
3.2 场景二:父进程申请|子进程继承|子进程写满
父VIRT | 父RES | 父SHR | 子VIRT | 子RES | 子SHR |
---|---|---|---|---|---|
111788 | 440 | 332 | 111792 | 102508 | 102392 |
父进程pmap
代码语言:javascript复制Address Kbytes RSS Dirty Mode Mapping
00007f12d530c000 102400 0 0 rw-s- zero (deleted)
子进程pmap
代码语言:javascript复制Address Kbytes RSS Dirty Mode Mapping
00007f12d530c000 102400 102400 102400 rw-s- zero (deleted)
子进程写入了100MB体现在在SHR中。
3.3 场景三:父进程申请|子进程继承|子进程写满父进程读一半
父VIRT | 父RES | 父SHR | 子VIRT | 子RES | 子SHR |
---|---|---|---|---|---|
111788 | 51636 | 51528 | 111792 | 102508 | 102396 |
父进程pmap
代码语言:javascript复制Address Kbytes RSS Dirty Mode Mapping
00007f236a1db000 102400 51204 51200 rw-s- zero (deleted)
子进程pmap
代码语言:javascript复制Address Kbytes RSS Dirty Mode Mapping
00007f236a1db000 102400 102400 102400 rw-s- zero (deleted)
父进程读取了50MB体现在SHR中。