好久没更新博客了,最近被虚拟内存给搞糊涂了,翻书 google差不多半个月吧,争取搞出点头绪来,但是基础知识不多说,大家看书看看什么是虚拟空间吧,这篇文章重在是一个进程所占虚拟空间的分析
先看top
top - 18:34:57 up 4 days, 4:20, 4 users, load average: 0.00, 0.00, 0.00
Tasks: 114 total, 1 running, 113 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.2%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 4051424k total, 3612920k used, 438504k free, 200040k buffers
Swap: 8385920k total, 0k used, 8385920k free, 3114324k cached
在这里重点介绍的是Swap,当物理内存空间不够用的时候,进程的虚拟空间的内容会放到硬盘上的swap分区中,
[root@web-dev-146 ~]# cat /proc/swaps
Filename Type Size Used Priority
/dev/sda3 partition 8385920 0 -2
很明显分了8g的大小也就是硬盘分区来存放进程虚拟空间内容数据
对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区(swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。
一会通过实例我们来分析swap cache的作用
swap cached中的大小我们通过
free来看
total used free shared buffers cached
Mem: 4051424 3613052 438372 0 200072 3114368
-/+ buffers/cache: 298612 3752812
Swap: 8385920 0 8385920
说明读取文件占用了这么大的物理空间,用于缓存已经打开的文件
倒没必要释放操作系统会根据需要多少内存就从cache释放多少内存,所以不必操心,但是通过我们的例子你可以看到swap cache是不断增长的那是因为我们用到了文件映射,并且去读文件的内容
可以通过echo 3 > /proc/sys/vm/drop_caches 手动释放
写了一个c程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
int main()
{
int fd;
int i;
if ( (fd = open("/home/mongodb/shard200/fs_media_recommend.8", O_RDWR|O_CREAT, S_IRWXU)) < 0){
printf("open file wrong!");
return 0;
}
struct stat file_stat;
if ( fstat( fd, &file_stat) < 0 )
{
printf(" fstat wrong");
return 0;
}
int *start_fp;
if( ( start_fp = (int *)mmap(NULL, 2146435072, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 )) == MAP_FAILED)
{
printf("mmap wrong");
return 0;
}
for(i=0;i<10000000;i++){
printf("%d\n", start_fp[i]);
}
/*msync( start_fp, file_stat.st_size, MS_ASYNC);
if ( munmap( start_fp, file_stat.st_size ) < 0 )
{
printf("munmap wrong");
return 0;
}*/
sleep(100000);
}
其中我们用到了mmap文件映射,故意sleep这么久我们可以分析下进程的虚拟地址的信息
top - 18:43:07 up 4 days, 4:28, 4 users, load average: 0.15, 0.05, 0.02
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu1 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 4051424k total, 3612804k used, 438620k free, 200052k buffers
Swap: 8385920k total, 0k used, 8385920k free, 3114332k cached
Cumulative time On
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ SWAP CODE DATA COMMAND
16955 root 16 0 2050m 25m 25m R 0.0 0.6 0:25.84 2.0g 4 120 test9
当运行改程序,会发现进程的res在不断增大,但是VIRT和SWAP以及上面的Swap没有丝毫变化
大家可以试着去掉我从映射读取内容那块代码,可以见到一上来VIRT和SWAP就已经确定了大小,该大小是在进程加载后分配了虚拟空间,当然并不是真的分了2g这么大的空间,因为虚拟空间对应的Swap并没有发生变化,说明虚拟空间内容没有写到磁盘中,其实我们看下进程虚拟空间的分析
[root@web-dev-146 ~]# ps -ef | grep test9
root 16968 9017 19 18:47 pts/2 00:00:01 ./test9
root 16970 13123 0 18:47 pts/1 00:00:00 grep test9
[root@web-dev-146 ~]# cat /proc/16968/maps
00400000-00401000 r-xp 00000000 08:02 6305715 /root/test9
00600000-00601000 rw-p 00000000 08:02 6305715 /root/test9
3c5a400000-3c5a41c000 r-xp 00000000 08:02 1405202 /lib64/ld-2.5.so
3c5a61b000-3c5a61c000 r--p 0001b000 08:02 1405202 /lib64/ld-2.5.so
3c5a61c000-3c5a61d000 rw-p 0001c000 08:02 1405202 /lib64/ld-2.5.so
3c5a800000-3c5a94d000 r-xp 00000000 08:02 1405203 /lib64/libc-2.5.so
3c5a94d000-3c5ab4d000 ---p 0014d000 08:02 1405203 /lib64/libc-2.5.so
3c5ab4d000-3c5ab51000 r--p 0014d000 08:02 1405203 /lib64/libc-2.5.so
3c5ab51000-3c5ab52000 rw-p 00151000 08:02 1405203 /lib64/libc-2.5.so
3c5ab52000-3c5ab57000 rw-p 3c5ab52000 00:00 0
2ad9f31db000-2ad9f31dc000 rw-p 2ad9f31db000 00:00 0
2ad9f31ed000-2ad9f31ef000 rw-p 2ad9f31ed000 00:00 0
2ad9f31ef000-2ada730ef000 rw-s 00000000 08:05 60686353 /home/mongodb/shard200/fs_media_recommend.8
2ada730ef000-2ada730f0000 rw-p 2ada730ef000 00:00 0
7fffd6e4f000-7fffd6e64000 rw-p 7ffffffea000 00:00 0 [stack]
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0 [vdso]
这就是该进程对应的虚拟地址空间,可以很清楚的看到虚拟空间对应了我代码里面文件的映射关系,大小是多少呢
16968: ./test9
0000000000400000 4K r-x-- /root/test9
0000000000600000 4K rw--- /root/test9
0000003c5a400000 112K r-x-- /lib64/ld-2.5.so
0000003c5a61b000 4K r---- /lib64/ld-2.5.so
0000003c5a61c000 4K rw--- /lib64/ld-2.5.so
0000003c5a800000 1332K r-x-- /lib64/libc-2.5.so
0000003c5a94d000 2048K ----- /lib64/libc-2.5.so
0000003c5ab4d000 16K r---- /lib64/libc-2.5.so
0000003c5ab51000 4K rw--- /lib64/libc-2.5.so
0000003c5ab52000 20K rw--- [ anon ]
00002ad9f31db000 4K rw--- [ anon ]
00002ad9f31ed000 8K rw--- [ anon ]
00002ad9f31ef000 2096128K rw-s- /home/mongodb/shard200/fs_media_recommend.8
00002ada730ef000 4K rw--- [ anon ]
00007fffd6e4f000 84K rw--- [ stack ]
ffffffffff600000 8192K ----- [ anon ]
total 2107968K
很清楚吧,大小就是我代码里面映射的大小,大家可以通过一下更深刻了解一下虚拟空间的信息
[root@web-dev-146 ~]# cat /proc/16968/status
Name: test9
State: S (sleeping)
SleepAVG: 98%
Tgid: 16968
Pid: 16968
PPid: 9017
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 256
Groups: 0 1 2 3 4 6 10
VmPeak: 2099776 kB
VmSize: 2099776 kB
VmLck: 0 kB
VmHWM: 39440 kB
VmRSS: 39440 kB
VmData: 36 kB
VmStk: 84 kB
VmExe: 4 kB
VmLib: 1444 kB
VmPTE: 116 kB
StaBrk: 1ede2000 kB
Brk: 1ede2000 kB
StaStk: 7fffd6e63850 kB
Threads: 1
SigQ: 1/36864
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000000000000
CapInh: 0000000000000000
CapPrm: 00000000fffffeff
CapEff: 00000000fffffeff
Cpus_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,0000000f
Mems_allowed: 00000000,00000001
我们可以看到虚拟空间是2099776kB大小,这和我们top该进程对应的数据是一致的,
上面的例子为了说明virt虚拟内存大小并不是其实占用了物理内存后者磁盘空间的大小,这个大小的确定是由于进程在物理内存中放了一份映射表,没分配一个进程总是又这么一个映射数据结构即
加载关于进程虚拟地址空间的页面时,一系列的vm_area_struct将自动生成,每一个vm_area_struct描述进程的一部分,如执行代码、数据等。Linux虚拟存储管理支持了多数标准的虚拟内存操作,如读取、关闭、共享、缺页等。一旦vm_area_struct结构生成,就可以通过该结构中的指向vm_operation_struct的指针进行虚拟内存操作了。
如图3所示,为虚存管理数据结构之间的关系。
图 3 虚拟存储管理的数据结构关系
这个结构引用一下网上的,这个结构是不可避免的是真正占用内存空间的当然不会太大,它只记录虚拟空间分配内容中的头和尾的信息,所以占用虚拟空间大小一算就知道了刚才我们提到过VIRT放到SWAP分区的所以对于该进程刚开始初始化的时候我们发现VIRT和SWAP空间大小一致,都是虚拟的,
但是当我们读取映射文件的内容时,这时候通过页面中断,我们映射文件的内容会加载到真正的物理内存中,就是我们上面做的实验,我们会发现物理内存RES 在不断的增加,但是由于有一些内容已经导入到了物理内存中,所以操作系统会对SWAP磁盘空间进行释放,看下面
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ SWAP CODE DATA COMMAND
9193 root 15 0 3974m 3.0g 640 S 0.0 19.0 28:50.33 933m 328 3.9g redis-server
这个时候我们发现VIRT = SWAP+RES的大小,很容易理解,既然你物理内存已经存放了数据,当然我要空出那些没必要的冗余空间用来作别的
先到这我会把想到的继续补充进去