好久沒更新部落格了,最近被虛擬記憶體給搞糊塗了,翻書 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的大小,很容易理解,既然你實體記憶體已經存放了資料,當然我要空出那些沒必要的冗餘空間用來作別的
先到這我會把想到的繼續補充進去