一)缺页
当CPU请求一个不在RAM中的内存页时,会发生缺页,比如我们从内存读取/写入数据,而数据未在内存,此时都会发生缺页.
我们通过下面的程序对内存缺页情况进行测试,程序通过分配大块内存以供程序使用,该程序只访问一次内存就不再使用它,
它的做法是通过malloc分配内存,并在每页修改1个字节,然后进入睡眠状态.
注:Linux非常灵敏,它不提供任何物理存储给未被修改过的页,所以我们必须在一个已分配区域的每页中读出或写入至少1个字节,来消耗内存中的页.
测试程序hog.c如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int
main (int argc, char *argv[])
{
if (argc != 2)
exit (0);
size_t mb = strtoul(argv[1],NULL,0);
size_t nbytes = mb * 0x100000;
char *ptr = (char *) malloc(nbytes);
if (ptr == NULL){
perror("malloc");
exit (EXIT_FAILURE);
}
size_t i;
const size_t stride = sysconf(_SC_PAGE_SIZE);
for (i = 0;i < nbytes; i+= stride) {
ptr[i] = 0;
}
printf("allocated %d mb\n", mb);
pause();
return 0;
}
编译
gcc hog.c -o hog
查看当前的内存
free -m
total used free shared buffers cached
Mem: 503 206 296 0 30 140
-/+ buffers/cache: 36 467
Swap: 1027 0 1027
我们通过使用GNU time命令来查看缺页的次数
\time ./hog 100
allocated 100 mb
Command terminated by signal 2
0.00user 3.12system 0:04.52elapsed 69%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+25719minor)pagefaults 0swaps
注:
25719minor表示缺页25719次,每次4KB,正好是100MB的内存分配.
major表示主缺页,主缺页是要求输入/输出到磁盘的缺页.
minor表示次缺页,次缺页是任何其它的缺页.
二)置换
置换是指程序请求内存,而物理内存不足时,内核将被迫把缺页存储到置换分区,也就是说,它将最近最少使用的页面(LRU)置换到SWAP.
如果SWAP不足,或没有SWAP,就会发生内存分配失败.如下:
swapoff -a
./hog 500 &
[1] 2901
malloc: Cannot allocate memory
[1]+ Exit 1 ./hog 500
下面看看置换的发生,先查看当前的内存.
swapon -a
free -m
total used free shared buffers cached
Mem: 503 43 459 0 0 9
-/+ buffers/cache: 33 469
Swap: 1027 0 1027
现在有469MB的空闲空间,此时我们通过hog程序,请求500MB的内存空间,将会发生置换,如下
\time ./hog 500
allocated 500 mb(程序pause在这里,要用CTRL+C中断才能看到下面的信息,在这之间我们可以在另一个终端查看内存空间)
Command terminated by signal 2
0.02user 3.53system 0:07.19elapsed 49%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+128142minor)pagefaults 0swaps
在另一个终端用free查看内存如下:
free -m
total used free shared buffers cached
Mem: 503 497 6 0 0 3
-/+ buffers/cache: 493 9
Swap: 1027 53 974
这里我们看到产生了53MB的内存置换.
回到第一个终端,我们看到缺页的信息是:0major+128142minor
这0个主缺页是指当进程请求一个驻留在磁盘上的页时,才发生主缺页,在这种情况下,页面不存在,因此它们没有驻留在磁盘上,所以不被计入主缺页数.
虽然hog进程引起系统向磁盘写入页,但实际上它没有把那些页写入磁盘,实际写入是由kswapd完成的.kswapd内核线程负责把数据从内存移到磁盘的烦琐工作,
只有当拥有那些页的进程再次调用它们时才会产生一个主缺页.
三)top命令
top命令是周期性的更新.
下面介绍几个top命令的使用技巧.
1)切换显示命令名称和完整命令
输入c
2)根据驻留内存大小进行排序
输入M
3)根据CPU使用百分比大小进行排序
输入P
4)根据时间/累计时间进行排序
输入T
5)显示线程
输入H
6)将top的信息划分为4个屏幕,分别DEF,Job,Mem,Usr
输入Shift+A
1:Def - 12:50:16 up 1:14, 2 users, load average: 0.00, 0.01, 0.00
Tasks: 72 total, 2 running, 70 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 5.6%sy, 0.0%ni, 94.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 515600k total, 72684k used, 442916k free, 3372k buffers
Swap: 1052248k total, 23140k used, 1029108k free, 48128k cached
1 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
138 root 13 -5 0 0 0 S 0.0 0.0 0:04.23 [kswapd0]
2569 root 15 0 8216 664 436 R 5.6 0.1 0:02.69 sshd: root@pts/0,pts/1
2463 root 18 0 1924 160 140 S 0.0 0.0 0:02.32 hald-addon-storage: polling /dev/hdc
2405 root 15 0 25188 6180 1792 S 0.0 1.2 0:02.23 /usr/bin/python /usr/sbin/yum-updatesd
2439 haldaemo 18 0 5348 640 392 S 0.0 0.1 0:01.81 hald
1 root 15 0 2032 160 140 S 0.0 0.0 0:01.18 init [3]
3234 root 15 0 2164 1000 796 R 0.0 0.2 0:00.95 top
2422 avahi 15 0 3696 352 260 S 0.0 0.1 0:00.94 avahi-daemon: running [test1.local]
323 root 10 -5 0 0 0 S 0.0 0.0 0:00.75 [kjournald]
2 PID PPID TIME+ %CPU %MEM PR NI S VIRT SWAP RES UID COMMAND
3234 2721 0:00.95 0.0 0.2 15 0 R 2164 1164 1000 0 top
2721 2569 0:00.29 0.0 0.2 15 0 S 4620 3812 808 0 bash
2571 2569 0:00.45 0.0 0.1 16 0 S 4616 4020 596 0 bash
2569 2271 0:02.69 5.6 0.1 15 0 R 8216 7552 664 0 sshd
2539 1 0:00.01 0.0 0.0 24 0 S 1624 1524 100 0 mingetty
2516 1 0:00.01 0.0 0.0 24 0 S 1624 1524 100 0 mingetty
2515 1 0:00.01 0.0 0.0 24 0 S 1628 1528 100 0 mingetty
2514 1 0:00.00 0.0 0.0 18 0 S 1624 1524 100 0 mingetty
2511 1 0:00.00 0.0 0.0 18 0 S 1624 1524 100 0 mingetty
3 PID %MEM VIRT SWAP RES CODE DATA SHR nFLT nDRT S PR NI %CPU COMMAND
2405 1.2 25188 18m 6180 4 8688 1792 264 0 S 15 0 0.0 yum-updatesd
3234 0.2 2164 1164 1000 52 344 796 0 0 R 15 0 0.0 top
1866 0.2 9612 8672 940 4 1664 512 72 0 S 12 -3 0.0 python
2721 0.2 4620 3812 808 684 400 620 77 0 S 15 0 0.0 bash
2235 0.1 12716 11m 672 4 3656 376 75 0 S 15 0 0.0 python
2569 0.1 8216 7552 664 368 736 436 90 0 R 15 0 5.6 sshd
2439 0.1 5348 4708 640 260 1908 392 66 0 S 18 0 0.0 hald
2571 0.1 4616 4020 596 684 396 452 84 0 S 16 0 0.0 bash
1549 0.1 2280 1768 512 432 420 392 31 0 S 15 0 0.0 dhclient
4 PID PPID UID USER RUSER TTY TIME+ %CPU %MEM S COMMAND
2371 1 43 xfs xfs ? 0:00.07 0.0 0.0 S xfs
1941 1 32 rpc rpc ? 0:00.01 0.0 0.0 S portmap
2405 1 0 root root ? 0:02.23 0.0 1.2 S yum-updatesd
3234 2721 0 root root pts/1 0:00.95 0.0 0.2 R top
1866 1864 0 root root ? 0:00.30 0.0 0.2 S python
2721 2569 0 root root pts/1 0:00.29 0.0 0.2 S bash
2235 1 0 root root ? 0:00.24 0.0 0.1 S python
2569 2271 0 root root ? 0:02.69 5.6 0.1 R sshd
2571 2569 0 root root pts/0 0:00.45 0.0 0.1 S bash
可以自定义选择要定义的屏幕的显示字段,比如我们要Mem屏幕增加Time字段的显示,要进行如下操作.
首先输入w切换窗口,使当前的屏幕为Mem,再输入f,将看到如下的信息.
Current Fields: ANOPQRSTUVbcdefgjlmyzWHIKX for window 3:Mem
Toggle fields via field letter, type any other key to return
* A: PID = Process Id
* N: %MEM = Memory usage (RES)
* O: VIRT = Virtual Image (kb)
* P: SWAP = Swapped size (kb)
* Q: RES = Resident size (kb)
* R: CODE = Code size (kb)
* S: DATA = Data+Stack size (kb)
* T: SHR = Shared Mem size (kb)
* U: nFLT = Page Fault count
* V: nDRT = Dirty Pages count
b: PPID = Parent Process Pid
c: RUSER = Real user name
d: UID = User Id
e: USER = User Name
f: GROUP = Group Name
g: TTY = Controlling Tty
j: P = Last used cpu (SMP)
l: TIME = CPU Time
m: TIME+ = CPU Time, hundredths
y: WCHAN = Sleeping in Function
z: Flags = Task Flags <sched.h>
* W: S = Process Status
* H: PR = Priority
* I: NI = Nice value
* K: %CPU = CPU usage
* X: COMMAND = Command name/line
输入L,表示选择 l: TIME = CPU Time,选择后将在被选项前出现*号,如:* L: TIME = CPU Time,再输入Return,就可以在Mem屏看到time的信息了,如下:
3 PID %MEM VIRT SWAP RES CODE DATA SHR nFLT nDRT TIME S PR NI %CPU COMMAND
2405 1.2 25188 18m 6180 4 8688 1792 264 0 0:02 S 15 0 0.0 yum-updatesd
3234 0.2 2164 1164 1000 52 344 796 0 0 0:01 R 15 0 0.7 top
1866 0.2 9612 8672 940 4 1664 512 72 0 0:00 S 12 -3 0.0 python
2721 0.2 4620 3812 808 684 400 620 77 0 0:00 S 15 0 0.0 bash
2235 0.1 12716 11m 672 4 3656 376 75 0 0:00 S 15 0 0.0 python
2569 0.1 8216 7552 664 368 736 436 90 0 0:02 S 15 0 0.0 sshd
2439 0.1 5348 4708 640 260 1908 392 66 0 0:01 S 18 0 0.0 hald
2571 0.1 4616 4020 596 684 396 452 84 0 0:00 S 16 0 0.0 bash
1549 0.1 2280 1768 512 432 420 392 31 0 0:00 S 19 0 0.0 dhclient
同样,我们也可以以指定的列进行排序,例如想将Mem屏以VIRT(虚拟内存)进行排序,进行下面的操作:
输入O(大写),表示排序.
看到只有N: %MEM = Memory usage (RES)行有*号,说明现在选中的是内存的百分比.
我们输入O(大写),表示选中 o: VIRT = Virtual Image (kb)如下:
Current Sort Field: N for window 3:Mem
Select sort field via field letter, type any other key to return
a: PID = Process Id
b: PPID = Parent Process Pid
c: RUSER = Real user name
d: UID = User Id
e: USER = User Name
f: GROUP = Group Name
g: TTY = Controlling Tty
h: PR = Priority
i: NI = Nice value
j: P = Last used cpu (SMP)
k: %CPU = CPU usage
l: TIME = CPU Time
m: TIME+ = CPU Time, hundredths
N: %MEM = Memory usage (RES)
* O: VIRT = Virtual Image (kb)
p: SWAP = Swapped size (kb)
q: RES = Resident size (kb)
r: CODE = Code size (kb)
s: DATA = Data+Stack size (kb)
t: SHR = Shared Mem size (kb)
u: nFLT = Page Fault count
v: nDRT = Dirty Pages count
w: S = Process Status
x: COMMAND = Command name/line
y: WCHAN = Sleeping in Function
z: Flags = Task Flags <sched.h>
输入回车返回主屏后,就是以VIRT进行排序了,如下:
3 PID %MEM VIRT SWAP RES CODE DATA SHR nFLT nDRT TIME S PR NI %CPU COMMAND
2405 1.2 25188 18m 6180 4 8688 1792 264 0 0:02 S 15 0 0.0 yum-updatesd
2235 0.1 12716 11m 672 4 3656 376 75 0 0:00 S 15 0 0.0 python
2141 0.0 12692 12m 244 84 10m 188 1 0 0:00 S 25 0 0.0 pcscd
1864 0.1 12048 11m 300 92 10m 220 14 0 0:00 S 16 -3 0.0 auditd
2252 0.0 9644 9484 160 364 644 156 5 0 0:00 S 18 0 0.0 cupsd
1866 0.2 9612 8672 940 4 1664 512 72 0 0:00 S 12 -3 0.0 python
2190 0.1 9332 8868 464 196 7168 360 37 0 0:00 S 25 0 0.0 automount
2569 0.1 8216 7552 664 368 736 436 90 0 0:02 S 15 0 0.0 sshd
2439 0.1 5348 4708 640 260 1908 392 66 0 0:01 S 18 0 0.0 hald
四)内存缺页与置换的综合实例
为完成最后一个测试,我们把上个程序做了更改,加入了信号处理及时间输出,如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
void handler(int sig)
{
}
#define TIMESPEC2FLOAT(tv) ((double) (tv).tv_sec+(double) (tv).tv_nsec*1e-9)
int
main (int argc,char *argv[])
{
if (argc != 2)
exit(0);
signal(SIGUSR1, handler);
size_t mb = strtoul(argv[1], NULL, 0);
size_t nbytes = mb * 0x100000;
char *ptr = (char *) malloc (nbytes);
if (ptr == NULL){
perror("malloc");
exit(EXIT_FAILURE);
}
int val = 0;
const size_t stride = sysconf(_SC_PAGE_SIZE);
while(1){
int i;
struct timespec t1, t2;
clock_gettime(CLOCK_REALTIME, &t1);
for (i = 0;i<nbytes; i += stride){
ptr[i] = val;
}
val++;
clock_gettime(CLOCK_REALTIME, &t2);
printf("touched %d mb; in %.6f sec\n", mb,
TIMESPEC2FLOAT(t2) - TIMESPEC2FLOAT(t1));
pause();
}
return 0;
}
编译源程序,并做这个程序的两个软链接,如下:
gcc -O2 -o son-of-hog son-of-hog.c -lrt
ln -s son-of-hog hog-a
ln -s son-of-hog hog-b
查看当前的内存:
free -m
total used free shared buffers cached
Mem: 503 185 317 0 10 140
-/+ buffers/cache: 35 468
Swap: 1027 0 1027
注:此时我们看到有468MB的空闲内存,这里面有140MB的cached和10MB的buffers
清空置换空间(swap):
swapoff -a
swapon -a
free -m
total used free shared buffers cached
Mem: 503 50 452 0 1 16
-/+ buffers/cache: 33 470
Swap: 1027 0 1027
我们执行hog-a,以此占用300MB的内存空间,查看它分配内存的时间:
./hog-a 300 &
[1] 2592
touched 300 mb; in 2.784745 sec
注:
这里分配300MB的内存共用了2.7秒多.这里面有换页和回收cache的时间.
对这个进程使用一个SIGUSR1将再次唤醒并访问内存:
kill -USR1 %1
touched 300 mb; in 0.010827 sec
注:
这里只用了10ms.
我们这时执行hog-b,看看它会占用多少时间:
./hog-b 300 &
[2] 2601
touched 300 mb; in 4.432349 sec
注:
因为物理内存都被分配了,为给hog-b分配空间,系统做了置换处理,即将物理内存分配给hog-b,而将hog-a占用的内存置换到swap.
对hog-b发送SIGUSR1信号,再次唤醒程序,这时只用143ms
pkill -USR1 hog-b
touched 300 mb; in 0.143772 sec
我们用top命令来查看:
top -p $(pgrep hog-a) -p $(pgrep hog-b)
1:Def - 06:12:26 up 10 min, 1 user, load average: 0.01, 0.10, 0.10
Tasks: 2 total, 0 running, 2 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.0%sy, 0.0%ni, 99.7%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 515600k total, 509468k used, 6132k free, 956k buffers
Swap: 1052248k total, 162924k used, 889324k free, 11312k cached
1 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2601 root 18 0 301m 300m 428 S 0.0 59.7 0:03.32 ./hog-b 300
2592 root 18 0 301m 154m 428 S 0.0 30.7 0:02.76 ./hog-a 300
2 PID PPID TIME+ %CPU %MEM PR NI S VIRT SWAP RES UID COMMAND
2601 2553 0:03.32 0.0 59.7 18 0 S 301m 1112 300m 0 hog-b
2592 2553 0:02.76 0.0 30.7 18 0 S 301m 147m 154m 0 hog-a
3 PID %MEM VIRT SWAP RES CODE DATA SHR nFLT nDRT S PR NI %CPU COMMAND
2601 59.7 301m 1112 300m 4 300m 428 235 0 S 18 0 0.0 hog-b
2592 30.7 301m 147m 154m 4 300m 428 0 0 S 18 0 0.0 hog-a
4 PID PPID UID USER RUSER TTY TIME+ %CPU %MEM S COMMAND
2601 2553 0 root root pts/0 0:03.32 0.0 59.7 S hog-b
2592 2553 0 root root pts/0 0:02.76 0.0 30.7 S hog-a
这里做几个说明:
1)两个程序都用了300MB的虚拟内存.(VIRT 301m)
2)由于hog-a先运行,而后占用的内存数据被置换到swap,所以只占用了154MB的物理内存(RES 154MB),占用了147MB的SWAP空间(SWAP 147).
3)由于hog-b后运行,所以用抢占了hog-a的物理内存,此时它占用了300M的物理内存(RES 300MB),由于置换它产生了235次页错误(Page Fault count).