前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >真正看懂TOP的进程内存(VIRT、RES、SHR)

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

作者头像
mingjie
发布2023-12-20 09:21:19
1.2K0
发布2023-12-20 09:21:19
举报

?

私有内存与共享内存实验

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() failed\n");
		exit(EXIT_FAILURE);

	case 0:
		printf("Child started, value = %d\n", *addr);
		for (int i = 0; i < s; i++)
		{
			*(addr + i) = 0xFE;
		}

		printf("Child started, value = %d\n", *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() failed\n");
			exit(EXIT_FAILURE);
		}

		printf("In parent, value = %d\n", *addr);
		if (munmap(addr, sizeof(int)) == -1)
		{
			fprintf(stderr, "munmap()() failed\n");
			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中。

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 结论先行
  • 1 进程本地内存
  • 2 pthread线程内存
  • 3 mmap匿名继承内存
    • 3.1 场景一:父进程申请|子进程继承|都未使用
      • 3.2 场景二:父进程申请|子进程继承|子进程写满
        • 3.3 场景三:父进程申请|子进程继承|子进程写满父进程读一半
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    http://www.vxiaotou.com