真正看懂TOP的进程内存(VIRT、RES、SHR)

2023-12-20 09:21:19 浏览数 (1)

私有内存与共享内存实验

0 结论先行

本地内存

  • TOP的VIRT严格对应申请大小,一般就是申请多少就给多少虚拟内存地址范围。
代码语言:txt复制
- 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中。

0 人点赞