Linux下进程信息的深入分析

其实在认真阅读了这篇名为“计算内存使用”的文章之后,还是处于半迷糊状态。这位作者就说Linux下面没有特别好的显示内存占用的工具,虽然有top和free,但都说得不清楚,就跟巫毒教的魔术似的。
比如top这个工具,它会显示3种数据,作者分别解释如下:

VIRT:virtual memory usage。Virtual这个词很神,一般解释是:virtual adj.虚的, 实质的, [物]有效的, 事实上的。到底是虚的还是实的?让Google给Define之后,将就明白一点,就是这东西还是非物质的,但是有效果的,不发生在真实世界的,发生在软件世界的等等。这个内存使用就是一个应用占有的地址空间,只是要应用程序要求的,就全算在这里,而不管它真的用了没有。写程序怕出错,又不在乎占用的时候,多开点内存也是很正常的。
RES:resident memory usage。常驻内存。这个值就是该应用程序真的使用的内存,但还有两个小问题,一是有些东西可能放在交换盘上了(SWAP),二是有些内存可能是共享的。
SHR:shared memory。共享内存。就是说这一块内存空间有可能也被其他应用程序使用着;而Virt - Shr似乎就是这个程序所要求的并且没有共享的内存空间。
DATA:数据占用的内存。如果top没有显示,按f键可以显示出来。这一块是真正的该程序要求的数据空间,是真正在运行中要使用的。

所以DATA的含义比较确定,甚至可以用程序读取的数据量计算出来;SHR是一个潜在的可能会被共享的数字,如果只开一个程序,也没有别人共同使用它;VIRT里面的可能性更多,比如它可能计算了被许多X的库所共享的内存;RES应该是比较准确的,但不含有交换出去的空间;但基本可以说RES是程序当前使用的内存量。



这里我们主要介绍进程的状态,进程的状态可以通过/proc/PID/status来查看,也可以通过/proc/PID/stat来查看.
如果说到工具大家用的最多的ps也可以看到进程的信息.这里我们通过/proc/PID/status来分析进程的信息.
在2.6.18之后的内核,多了capibilty/cpusets等信息.
 
查看进程状态信息如下:
more status 
Name:   rsyslogd
State:  S (sleeping)
Tgid:   987
Pid:    987
PPid:   1
TracerPid:      0
Uid:    0       0       0       0
Gid:    0       0       0       0
Utrace: 0
FDSize: 32
Groups:
VmPeak:    36528 kB
VmSize:    36528 kB
VmLck:         0 kB
VmHWM:      1432 kB
VmRSS:      1420 kB
VmData:    33980 kB
VmStk:        88 kB
VmExe:       320 kB
VmLib:      2044 kB
VmPTE:        56 kB
VmSwap:        0 kB
Threads:        3
SigQ:   1/7954
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000001001206
SigCgt: 0000000180014c21
CapInh: 0000000000000000
CapPrm: ffffffffffffffff
CapEff: ffffffffffffffff
CapBnd: ffffffffffffffff
Cpus_allowed:   3
Cpus_allowed_list:      0-1
Mems_allowed:   1
Mems_allowed_list:      0
voluntary_ctxt_switches:        1
nonvoluntary_ctxt_switches:     0
 
 
 
Name:   rsyslogd
解释:进程名
 
 
 
State:  S (sleeping)
解释:进程的状态我们前文已经做了很详细的分析,各进程的状态代表的意义如下:
R (running)", "S (sleeping)", "D (disk sleep)", "T (stopped)", "T(tracing stop)", "Z (zombie)", or "X (dead)"
 
 
 
Tgid:   987
解释:Tgid是线程组的ID,一个线程一定属于一个线程组(进程组).
 
 
 
Pid:    987
解释:这个是进程的ID,更准确的说应该是线程的ID.
例如:
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root       987     1   987  0    3 00:18 ?        00:00:00 /sbin/rsyslogd -c 4
root       987     1   989  0    3 00:18 ?        00:00:00 /sbin/rsyslogd -c 4
root       987     1   990  0    3 00:18 ?        00:00:00 /sbin/rsyslogd -c 4
注:
/proc/pid/status中的Pid就是ps命令的LWP列输出,PID一列其实是进程组,而LWP是轻量级进程,也就是线程,因为所有的进程必须一个线程,那就是它自己.
 
 
 
PPid:   1
解释:当前进程的父进程
 
 
 
TracerPid:      0
解释:跟踪当前进程的进程ID,如果是0,表示没有跟踪.
例如:
用strace跟踪top程序
strace top
 
查看top进程
ps -axjf
PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 2491  2500  2500  2491 pts/2     2500 S+       0   0:00          \_ strace top
 2500  2501  2500  2491 pts/2     2500 S+       0   0:00              \_ top
 
查看top进程的TracerPid位
cat /proc/2501/stat
stat    statm   status  
test1:/proc/2431# cat /proc/2501/status 
Name:   top
State:  S (sleeping)
Tgid:   2501
Pid:    2501
PPid:   2500
TracerPid:      2500
 
 
 
Uid:    0       0       0       0
Gid:    0       0       0       0
解释:
第一列数字(RUID):实际用户ID,指的是进程执行者是谁.
第二列数字(EUID):有效用户ID,指进程执行时对文件的访问权限.
第三列数字(SUID):保存设置用户ID,作为effective user ID的副本,在执行exec调用时后能重新恢复原来的effectiv user ID.
第四列数字(FSUID):目前进程的文件系统的用户识别码.一般情况下,文件系统的用户识别码(fsuid)与有效的用户识别码(euid)是相同的.
这里重点说明RUID和EUID,我们用test用户启动top,如下:
终端1)
su - test
top
 
查看该进程的EUID和RUID,如下:
终端2)
cat /proc/`pgrep top|grep -v grep`/status
前面略
Uid:    1002    1002    1002    1002
Gid:    1003    1003    1003    1003
后面略
 
注:这里我们看到进程的RUID和EUID都变成了1002.
 
我们将程序top加上setuid权限,如下:
chmod +s /usr/bin/top
 
重新运行top程序,并查看它的RUID和EUID,如下:
cat /proc/`pgrep top|grep -v grep`/status
前面略
Uid:    1002    0       0       0
Gid:    1003    0       0       0
后面略
 
注:我们看到RUID还是1002,说明程序是由test用户(UID=1002)启动的,而程序设定了setuid,那么在程序运行时是用程序的owner权限来运行程序,而不是启动的用户权限.
由于top的owner是root,那么它的EUID是0.
 
 
 
FDSize: 32
解释:
FDSize是当前分配的文件描述符,这个值不是当前进程使用文件描述符的上限.
我们看到这里是32,但实际并没有分配32个文件,如下:
ls -l /proc/`pgrep rsyslogd|grep -v grep`/fd   
total 0
lrwx------ 1 root root 64 2011-04-20 20:03 0 -> socket:[5741]
l-wx------ 1 root root 64 2011-04-20 20:03 1 -> /var/log/auth.log
l-wx------ 1 root root 64 2011-04-20 20:03 10 -> /var/log/mail.err
l-wx------ 1 root root 64 2011-04-20 20:03 11 -> /var/log/news/news.crit
l-wx------ 1 root root 64 2011-04-20 20:03 12 -> /var/log/news/news.err
l-wx------ 1 root root 64 2011-04-20 20:03 13 -> /var/log/news/news.notice
l-wx------ 1 root root 64 2011-04-20 20:03 14 -> /var/log/debug
l-wx------ 1 root root 64 2011-04-20 20:03 15 -> /var/log/messages
lrwx------ 1 root root 64 2011-04-20 20:03 16 -> /dev/xconsole
lr-x------ 1 root root 64 2011-04-20 20:03 17 -> /proc/kmsg
l-wx------ 1 root root 64 2011-04-20 20:03 2 -> /var/log/syslog
l-wx------ 1 root root 64 2011-04-20 20:03 3 -> /var/log/daemon.log
l-wx------ 1 root root 64 2011-04-20 20:03 4 -> /var/log/kern.log
l-wx------ 1 root root 64 2011-04-20 20:03 5 -> /var/log/lpr.log
l-wx------ 1 root root 64 2011-04-20 20:03 6 -> /var/log/mail.log
l-wx------ 1 root root 64 2011-04-20 20:03 7 -> /var/log/user.log
l-wx------ 1 root root 64 2011-04-20 20:03 8 -> /var/log/mail.info
l-wx------ 1 root root 64 2011-04-20 20:03 9 -> /var/log/mail.warn
我们看到这里只用到了18个文件描述符.而如果超过32个文件描述符,将以32进行递增,如果是64位系统,将以64进行递增.
FDSize这个值不会减少,如果我们程序打开了300个文件,并不会因为关闭文件,而减少FDSize这个值.
 
 
 
Groups: 0
解释:
这里的groups表示启动这个进程的用户所在的组.
我们当前的用户test,现在在两个组(1000,2000)里面,如下:
id
uid=1002(test) gid=1002(nagcmd) groups=1000(chenkuo),1002(nagcmd)
 
用test用户启动top程序,并查看它的groups,如下:
终端1
top
 
终端2
cat /proc/`pgrep top|grep -v grep`/status
截取信息如下:
Groups: 1000 1002 
 
 
 
 
VmPeak:    36528 kB
解释:这里的VmPeak代表当前进程运行过程中占用内存的峰值.
我们用下面的程序申请内存,然后释放内存,最后通pause()函数中止程序的运行,程序源码如下:
 
#include
#include
#include
#include
 
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);
        }
 
        printf("allocated %d mb\n", mb);
        free(ptr);
        pause();
        return 0;
}
 
gcc callmem.c -o callmem
 
./callmem 10
allocated 10 mb
 
终端2
我们打开status文件,查看VmPeak值,如下:
cat /proc/`pgrep callmem|grep -v grep`/status
Name:   callmem
State:  S (sleeping)
Tgid:   2930
Pid:    2930
PPid:   2831
TracerPid:      0
Uid:    1002    1002    1002    1002
Gid:    1002    1002    1002    1002
FDSize: 256
Groups: 1000 1002 
VmPeak:    11852 kB
VmSize:     1608 kB
VmLck:         0 kB
VmHWM:       396 kB
VmRSS:       396 kB
VmData:       28 kB
VmStk:        84 kB
VmExe:         4 kB
VmLib:      1468 kB
VmPTE:        12 kB
下面略
注:我们看到程序申请了10240kb(10MB)的内存,VmPeak的值为11852kb,为什么不是10MB呢,因为除了我们申请的内存外,程序还会为加载动态链接库而占用内存.
 
 
 
VmSize:    36528 kB
解释:VmSize代表进程现在正在占用的内存
这个值与pmap pid的值基本一致,如果略有不同,可能是内存裂缝所造成的.
 
 
 
VmLck:         0 kB
解释:VmLck代表进程已经锁住的物理内存的大小.锁住的物理内存不能交换到硬盘.
我们用下面的程序进行测试,如下:
#include
#include
 
int main(int argc, char* argv[])
{
        char array[2048];
 
        if (mlock((const void *)array, sizeof(array)) == -1) {
                perror("mlock: ");
                return -1;
        }
 
        printf("success to lock stack mem at: %p, len=%zd\n",
                        array, sizeof(array));
 
        sleep(60);
        if (munlock((const void *)array, sizeof(array)) == -1) {
                perror("munlock: ");
                return -1;
        }
 
        printf("success to unlock stack mem at: %p, len=%zd\n",
                        array, sizeof(array));
 
        return 0;
}
 
编译后运行:
gcc memlock.c -o memlock
 
我们这里将2048个字节的数组地址空间锁定到了物理内存中.
接下来我们看下Vmlck值的变化,如下:
cat /proc/`pgrep memlock|grep -v grep`/status
Name:   memlock
State:  S (sleeping)
Tgid:   3249
Pid:    3249
PPid:   3139
TracerPid:      0
Uid:    0       0       0       0
Gid:    0       0       0       0
FDSize: 256
Groups: 0 
VmPeak:     1624 kB
VmSize:     1608 kB
VmLck:         4 kB
VmHWM:       356 kB
VmRSS:       356 kB
VmData:       28 kB
VmStk:        84 kB
VmExe:         4 kB
VmLib:      1468 kB
VmPTE:        16 kB
 
我们看到Vmlck的值为4Kb,这是因为分配的最少单位是4KB,以后每次递增都是4KB的整数倍.
 
 
 
 
VmHWM:      1432 kB
VmRSS:      1420 kB
解释:
VmHWM是程序得到分配到物理内存的峰值.
VmRSS是程序现在使用的物理内存.
我们用下面的程序进行测试,如下:
#include
#include
#include
#include
 
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 callmem.c -o test
注意这个程序在每页都修改一个字节的数据,导致系统必须为它分配占用物理内存.
 
首先我们查看当前的内存,如下:
free -m
             total       used       free     shared    buffers     cached
Mem:           503         18        484          0          0          5
-/+ buffers/cache:         12        490
Swap:         7632          7       7624
 
我们看到当前有490MB的空闲物理内存.
运行callmem分配450MB的物理内存,如下:
./test 450&
[1] 2402
allocated 450 mb
 
我们查看进程的VmHWM和VmRSS,如下:
cat /proc/`pgrep test`/status
VmHWM:    461208 kB
VmRSS:    461208 kB
我们看到此时VmHWM和VmRSS是一样的,表示占用了460MB左右的物理内存(因为它会用到动态链接库等).
 
下面我们查看当前的内存使用情况,如下:
free -m
             total       used       free     shared    buffers     cached
Mem:           503        470         33          0          0          6
-/+ buffers/cache:        463         40
Swap:         7632          7       7625
 
我们看到还有40MB空闲物理内存.
我们下面再申请100MB的内存,此时系统会通过物理内存和SWAP的置换操作,把第1次运行的test进程所占用的物理内存置换到SWAP,把空出来的物理内存分配给第2次运行的程序,如下:
mv test test1
./test1 100&
[1] 2419
allocated 100 mb
 
再次查看test进程所占用的物理内存,如下:
cat /proc/`pgrep test`/status
VmHWM:    461208 kB
VmRSS:    386704 kB
 
最后我们看到VmHWM没有变化,因为它表示的是该进程所占用物理内存的峰值,不会因为把内存置换到SWAP,而做改变.
而VmRSS则由461208KB变成了386704KB,说明它占用的物理内存因为置换所以减少.
 
 
 
VmData:    33980 kB
VmStk:        88 kB
VmExe:       320 kB
VmLib:      2044 kB
 
解释:
VmData:表示进程数据段的大小.
VmStk:表示进程堆栈段的大小.
VmExe:表示进程代码的大小.
VmLib:表示进程所使用LIB库的大小.
 
关于代码段,堆栈段,数据段:
代码段可以为机器中运行同一程序的数个进程共享
堆栈段存放的是子程序(函数)的返回地址、子程序的参数及程序的局部变量
数据段则存放程序的全局变量、常数以及动态数据分配的数据空间(比如用malloc函数申请的内存)
与代码段不同,如果系统中同时运行多个相同的程序,它们不能使用同一堆栈段和数据段.
 
注意:
堆栈段代表的是程序中的堆区(stack),堆区一般是编译器自动分配释放的.
我们用malloc申请的内存,它占用的其实是栈区(heap),栈区一般是程序员自已分配释放的,而栈区在这里属于数据段,所以我们看到上面测试程序通过调用malloc函数后,VmData一值有了很大的变化.
 
 
 
VmPTE:        56 kB
VmSwap:        0 kB
 
VmPTE:        56 kB
解释:
占用的页表的大小.
 
VmSwap: 0 kB
解释:
进程占用Swap的大小.
 
 
 
Threads:        3
解释:
表示当前进程组有3个线程.
 
 
 
 
SigQ:   1/7954
解释:
表示当前待处理信号的个数,我们用下面和程序进行测试,如下:
#include
#include
#include
#include
#include
 
volatile int done = 0;
 
void handler (int sig)
{
  const char *str = "handled...\n";
  write (1, str, strlen(str));
  done = 1;
}
 
void child(void)
{
  int i;
  for (i = 0; i < 3; i++){
    kill(getppid(), SIGRTMIN);
    printf("child - BANG!\n");
  }
  exit (0);
}
 
int main (int argc, char *argv[])
{
  signal (SIGRTMIN, handler);
  sigset_t newset, oldset;
  
  sigfillset(&newset);
  sigprocmask(SIG_BLOCK, &newset, &oldset);
  
  pid_t pid = fork();
  if (pid == 0)
  child();
  
  printf("parent sleeping \n");
  
  int r = sleep(30);
  
  printf("woke up! r=%d\n", r);
  
  sigprocmask(SIG_SETMASK, &oldset, NULL);
  
  while (!done){
  };
  
  printf("exiting\n");
  exit(0);
}
 
编译:
gcc sig.c -o sig
本程序会发达三次信号,此后进入sleep,我们可以在这期间来查看待处理信号的个数,如下:
./sig
parent sleeping 
child - BANG!
child - BANG!
child - BANG!
woke up! r=0
handled...
handled...
handled...
exiting
 
cat /proc/`pgrep sig`/status
SigQ:   4/4294967295
 
我们发送了三次信号,这里为什么是4呢,因为我们用了fork派生了子进程,子进程结束后会发送SIGCHLD信号.所以这里有4个信号待处理.
 
 
 
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000001001206
SigCgt: 0000000180014c21
解释:
SigPnd:屏蔽位,存储了该线程的待处理信号,等同于线程的PENDING信号.
ShnPnd:屏蔽位,存储了该线程组的待处理信号.等同于进程组的PENDING信号.
SigBlk:存放被阻塞的信号,等同于BLOCKED信号.
SigIgn:存放被忽略的信号,等同于IGNORED信号.
SigCgt:存放捕获的信号,等同于CAUGHT信号.
 
 
 
CapInh: 0000000000000000
CapPrm: ffffffffffffffff
CapEff: ffffffffffffffff
CapBnd: ffffffffffffffff
解释:
CapEff:当一个进程要进行某个特权操作时,操作系统会检查cap_effective的对应位是否有效,而不再是检查进程的有效UID是否为0.
CapPrm:表示进程能够使用的能力,在cap_permitted中可以包含cap_effective中没有的能力,这些能力是被进程自己临时放弃的,也可以说cap_effective是cap_permitted的一个子集.
CapInh:表示能够被当前进程执行的程序继承的能力.
CapBnd:是系统的边界能力,我们无法改变它.
 
 
 
Cpus_allowed:   3
Cpus_allowed_list:      0-1
解释:
Cpus_allowed:3指出该进程可以使用CPU的亲和性掩码,因为我们指定为两块CPU,所以这里就是3,如果该进程指定为4个CPU(如果有话),这里就是F(1111).
Cpus_allowed_list:0-1指出该进程可以使用CPU的列表,这里是0-1.
Mems_allowed:   1
Mems_allowed_list:      0
内存同CPU一样,进程rsyslogd只是使用了结点0的内存资源.
 
我们这里调整该进程到CPU0,如下:
taskset -p 1 987
pid 987's current affinity mask: 3
pid 987's new affinity mask: 1
 
cat /proc/987/status
Cpus_allowed:   1
Cpus_allowed_list:      0
Mems_allowed:   1
Mems_allowed_list:      0
注:我们看到Cpus_allowed/Cpus_allowed_list较之前有了变化.Cpus_allowed由3变成了1.表明我们只会用CPU0.
 
 
 
 
voluntary_ctxt_switches:        1
nonvoluntary_ctxt_switches:     0
 
voluntary_ctxt_switches表示进程主动切换的次数.
nonvoluntary_ctxt_switches表示进程被动切换的次数.
 
首先查看一下当前进程,如下:
echo $$
1544
 
执行如下命令:
while ((1)); do echo 1; sleep 1; done
 
查看该进程的主动切换与被动切换,如下:
cat status
voluntary_ctxt_switches:        949
nonvoluntary_ctxt_switches:     55
我们看到主动切换和被动切换有了明显的变化.


http://www.uplinux.com/shizi/wenxian/3630.html 点击打开链接




2013-04-09 14:49 wushuan  chinaunix.net  字号: T |  T
一键收藏,随时查看,分享好友!

Linux在内存使用上的原则是:如果内存充足,不用白不用,尽量使用内存来缓存一些文件,从而加快进程的运行速度,而当内存不足时,会通过相应的内存回收策略收回cache内存,供进程使用。本文通过对proc下进程相关的文件进行分析,精确评估系统消耗内存的大小,还可以对内存泄露类问题的解决提供一种定位手段。

AD:51CTO学院:IT精品课程在线看!

在产品的开发中,通过对当前系统消耗内存总量的统计,可以对产品所需内存总量进行精确的评估,从而选择合适的内存芯片与大小,降低产品的成本。在遇到内存泄露类问题时,经常会对此束手无策,本文通过对proc下进程相关的文件进行分析,精确评估系统消耗内存的大小,还可以对内存泄露类问题的解决提供一种定位手段。

Linux在内存使用上的原则是:如果内存充足,不用白不用,尽量使用内存来缓存一些文件,从而加快进程的运行速度,而当内存不足时,会通过相应的内存回收策略收回cache内存,供进程使用。

一、系统总内存的分析

可以从proc目录下的meminfo文件了解到当前系统内存的使用情况汇总,其中可用的物理内存=memfree+buffers+cached,当memfree不够时,内核会通过回写机制(pdflush线程)把cached和buffered内存回写到后备存储器,从而释放相关内存供进程使用,或者通过手动方式显式释放cache内存:

echo 3 > /proc/sys/vm/drop_caches

下图是海思平台下当前系统内存的总体使用情况,其中可以看到,系统消耗掉了29M的内存,下面继续分析这些内存都是被谁消耗掉了。

# cat /proc/meminfo

 MemTotal:        68956 kB

MemFree:         18632 kB

Buffers:          4096 kB

Cached:          17260 kB

SwapCached:          0 kB

Active:          21304 kB

Inactive:        19248 kB

SwapTotal:           0 kB

SwapFree:            0 kB

Dirty:               0 kB

Writeback:           0 kB

AnonPages:       19216 kB

Mapped:           2472 kB

Slab:             6900 kB

SReclaimable:      924 kB

SUnreclaim:       5976 kB

PageTables:        460 kB

NFS_Unstable:        0 kB

Bounce:              0 kB

CommitLimit:     62060 kB

Committed_AS:    28864 kB

VmallocTotal:   442368 kB

VmallocUsed:     46984 kB

VmallocChunk:   393212 kB

  二、进程使用内存的统计

在32位操作系统中,每个进程拥有4G的虚拟内存空间,其中0~3GB是每个进程的私有用户空间,这个空间对系统中其他进程是不可见的。3~4GB是linux内核空间,由系统所有的进程以及内核所共享的。通过访问/proc/{pid}/下相关文件,可以了解每个线程虚拟内存空间的使用情况,从而了解每个线程所消耗内存的多少。

由于我们的产品都是使用多线程方式实现的,多个线程共享一个进程的用户态虚拟地址空间,虚拟地址空间包含若干区域,主要有如下几个区域:

1、当前执行文件的代码段,该代码段称为text段。

2、执行文件的数据段,主要存储执行文件用到的全局变量,静态变量。

3、存储全局变量和动态产生的数据的堆。

4、用于保存局部变量和实现函数调用的栈。

5、采用mmap方式映射到虚拟地址空间中的内存段

所以只需要查看任意一个线程的用户态虚拟地址空间分配即可知道属于同一进程的所有线程占用总内存的大小。可以通过查看/proc/{pid}/maps文件来获取相关的虚拟地址空间内容,下文摘列部分典型的内容:

# cat /proc/568/maps

 00008000-0036a000 r-xp 00000000 00:0e 236        /home/hik/hicore

00372000-003a5000 rw-p 00362000 00:0e 236        /home/hik/hicore

003a5000-00e28000 rwxp 003a5000 00:00 0          [heap]

40000000-40005000 r-xp 00000000 01:00 94         /lib/ld-uClibc.so.0

416db000-41770000 rw-s c2005000 00:0f 68         /dev/mem

b51fc000-b5200000 rwxp b51fc000 00:00 0

…….

be1fc000-be200000 rwxp be1fc000 00:00 0

be93b000-be950000 rwxp befeb000 00:00 0          [stack]

第一行:从r-xp可知其权限为只读、可执行,该段内存地址对应于执行文件的代码段,程序的代码段需加载到内存中才可以执行。由于其只读,不会被修改,所以在整个系统内共享。

第二行:从rw-p可知其权限为可读写,不可执行,该段内存地址对应于执行文件的数据段,存放执行文件所用到的全局变量、静态变量。

第三行:从rwxp可知其权限是可读写,可执行,地址空间向上增长,而且不对应文件,是堆段,进程使用malloc申请的内存放在堆段。每个进程只有一个堆段,不论是主进程,还是不同的线程申请的内存,都反映到到进程的堆段。堆段向上增长,最大可以增长到1GB的位置,即0x40000000,如果大于1GB,glibc将采用mmap的方式,为堆申请一块内存。

第四行:是程序连接的共享库的内存地址。

第五行:是以mmap方式映射的虚拟地址空间。

第六、七行:是线程的栈区地址段,每个线程的栈大小都是16K。

第八行:是进程的栈区。关于栈段,每个线程都有一个,如果进程中有多个线程,则包含多个栈段。

三、当前系统总内存的统计

1、进程占用的总内存可以通过上述maps表计算出来。

2、当系统运行起来以后,会把应用层相关的文件挂载到tmpfs文件系统下,海思系统下这部分大概有13M左右,这部分内存是以cache方式统计出来的,但是这部分内存cache无法通过回收策略或者显式的调用释放掉。

3、根文件系统ramdisk占用的内存。

4、当前系统保留内存的大小,可以通过查看/proc/sys/vm/min_free_kbytes来获取或者修改此内存的大小。

5、当然,当系统运行起来后,还应该留有一定的内存用于在硬盘读写时做cache或者网络负荷比较高时分配skb等,一般需要30M以上。

四、对调试内存泄露类问题的一些启示

当进程申请内存时,实际上是glibc中内置的内存管理器接收了该请求,随着进程申请内存的增加,内存管理器会通过系统调用陷入内核,从而为进程分配更多的内存。

针对堆段的管理,内核提供了两个系统调用brk和mmap,brk用于更改堆顶地址,而mmap则为进程分配一块虚拟地址空间。

当进程向glibc申请内存时,如果申请内存的数量大于一个阀值的时候,glibc会采用mmap为进程分配一块虚拟地址空间,而不是采用brk来扩展堆顶的指针。缺省情况下,此阀值是128K,可以通过函数来修改此值。

#include 

Int mallopt(int param, int value)

Param的取值分别为M_MMAP_THRESHOLD、M_MMAP_MAX。

Value的取值是以字节为单位的。

M_MMAP_THRESHOLD是glibc中申请大块内存阀值,大于该阀值的内存申请,内存管理器将使用mmap系统调用申请内存,如果小于该阀值的内存申请,内存管理器使用brk系统调用扩展堆顶指针。

M_MMAP_MAX是该进程中最多使用mmap分配地址段的数量。

如果在实际的调试过程中,怀疑某处发生了内存泄露,可以查看该进程的maps表,看进程的堆段或者mmap段的虚拟地址空间是否持续增加,如果是,说明很可能发生了内存泄露,如果mmap段虚拟地址空间持续增加,还可以看到各个段的虚拟地址空间的大小,从而可以确定是申请了多大的内存,对调试内存泄露类问题可以起到很好的定位作用。


群里讨论出mysql的问题,因为mysql是一个连接建立一个线程的,这就涉及到mysql可以建立多少个线程。
无论是windwos 还是linux ,每个线程都有自己独立的stack,每个stack 都占用一定的空间。
windwos 默认的是1M,这个在exe中可以看到,也可以编译时指定。linux 默认使用pthread.h中的PTHREAD_STACK_SIZE,这和glibc的编译有关系,
这样说明linux 可以使用更多的线程。
int __pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
{
#ifdef FLOATING_STACKS
  /* We have to check against the maximum allowed stack size.  This is no
     problem if the manager is already started and we determined it.  If
     this hasn't happened, we have to find the limit outself.  */
  if (__pthread_max_stacksize == 0)
    __pthread_init_max_stacksize ();

  if (stacksize > __pthread_max_stacksize)
    return EINVAL;
#else
  /* We have a fixed size limit.  */
  if (stacksize > STACK_SIZE)
    return EINVAL;
#endif

  /* We don't accept value smaller than PTHREAD_STACK_MIN.  */
  if (stacksize < PTHREAD_STACK_MIN)
    return EINVAL;

  attr->__stacksize = stacksize;
  return 0;
}


对于java ,Solaris 默认  256k for 32-bit intel, 512K for 32-bit sparc, 1M for 64-bit sparc
           linux 可以使用-Xss,默认使用ulimit -s的值。  
先来讲说线程内存相关的东西,主要有下面几条:
进程中的所有的线程共享相同的地址空间。
任何声明为static/extern的变量或者堆变量可以被进程内所有的线程读写。
一个线程真正拥有的唯一私有储存是处理器寄存器。
线程栈可以通过暴露栈地址的方式与其它线程进行共享。
下面的网上的代码
     有大数据量处理的应用中,有时我们有必要在栈空间分配一个大的内存块或者要分配很多小的内存块,但是线程的栈空间的最大值在线程创建的时候就已经定下来了,如果栈的大小超过个了个值,系统将访问未授权的内存块,毫无疑问,再来的肯定是一个段错误。可是没办法,你还是不得不分配这些内存,于是你开会为分配一个整数值而动用malloc这种超级耗时的操作。当然,在你的需求可以评估的情况下,你的需求还是可以通过修改线程的栈空间的大小来改变的。

下面的我们用pthread_attr_getstacksize和pthread_attr_setstacksize的方法来查看和设置线程的栈空间。
注意:
      下面的测试代码在我自己的机子上(ubuntu6.06,ubuntu6.10,redhat 9, gentoo)通过了测试,但是很奇怪的是在我同事的机子上,无论是改变环境,还是想其它方法都不能正常的运行 。在网上查了一下,很多人也存在同样的问题,至今不知道为何。

linux线程的实现方式决定了对进程的限制同样加在了线程身上:)所以,有问题,请参见

直接看代码吧,只有一个C文件(thread_attr.c)
#include
#include

 


void *thread_routine (void *arg)
{
    printf ("The thread is here\n");
 char p[1024*1024*15];
 int i=1024*1024*15;


 while(i--)
 {
  p[i] = 3; 
 }

 printf( "Get 15M Memory!!!\n" );
 
 
 char p2[ 1024 * 1020 + 256 ];
 memset( p2, 0, sizeof( char ) * ( 1024 * 1020 + 256 ) );
 printf( "Get More Memory!!!\n" );
    return NULL;
}

int main (int argc, char *argv[])
{
    pthread_t thread_id;
    pthread_attr_t thread_attr;
    size_t stack_size;
    int status;

    status = pthread_attr_init (&thread_attr);
    if (status != 0)
        printf ("Create attr");

    status = pthread_attr_setdetachstate (
        &thread_attr, PTHREAD_CREATE_DETACHED);

    if (status != 0)
        printf ( "Set detach");
 
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
   status = pthread_attr_getstacksize (&thread_attr, &stack_size);
    if (status != 0)
        printf ( "Get stack size");
    printf ("Default stack size is %u; minimum is %u\n",
        stack_size, PTHREAD_STACK_MIN);

    status = pthread_attr_setstacksize (
        &thread_attr, PTHREAD_STACK_MIN*1024);
    if (status != 0)
        printf ("Set stack size");
 
   status = pthread_attr_getstacksize (&thread_attr, &stack_size);
    if (status != 0)
        printf( "Get stack size");
    printf ("Default stack size is %u; minimum is %u\n",
        stack_size, PTHREAD_STACK_MIN);
#endif
 int i = 5;
 while(i--)
 {
  status = pthread_create (
   &thread_id, &thread_attr, thread_routine, NULL);
  if (status != 0)
   printf ("Create thread");
 }

    getchar();
    printf ("Main exiting\n");
    pthread_exit (NULL);
    return 0;
}


 

看看执行过程:

gcc -pthread -g -DDEBUG -lrt  -o thread_attr thread_attr.c
./thread_attr
Default stack size is 8388608; minimum is 16384         //默认的栈大小为8M
Default stack size is 16777216; minimum is 16384      //设置后的结果为16M
The thread is here
The thread is here
The thread is here
The thread is here
The thread is here
Get 15M Memory!!!
Get More Memory!!!
Get 15M Memory!!!
Get More Memory!!!
Get 15M Memory!!!
Get 15M Memory!!!
Get More Memory!!!
Get More Memory!!!
Get 15M Memory!!!
Get More Memory!!!

Main exiting



如果你要查看某个进程占用的内存区域,可以使用命令cat /proc//maps获得(pid是进程号,你可以运行上面我们给出的例子——./example &;pid便会打印到屏幕),你可以发现很多类似于下面的数字信息。

由于程序example使用了动态库,所以除了example本身使用的的内存区域外,还会包含那些动态库使用的内存区域(区域顺序是:代码段、数据段、bss段)。

我们下面只抽出和example有关的信息,除了前两行代表的代码段和数据段外,最后一行是进程使用的栈空间。

08048000-08049000 r-xp 00000000 03:03 439029 /home/mm/src/example 
08049000-0804a000 rw-p 00000000 03:03 439029 /home/mm/src/example
bfffe000 - c0000000 rwxp ffff000 00:00 0

每行数据格式如下:

(内存区域)开始-结束 访问权限 偏移 主设备号:次设备号 i节点 文件。

注意,你一定会发现进程空间只包含三个内存区域,似乎没有上面所提到的堆、bss等,其实并非如此,程序内存段和进程地址空间中的内存区域是种模糊对应,也就是说,堆、bss、数据段(初始化过的)都在进程空间中由数据段内存区域表示。


你可能感兴趣的:(linux,应用编程,status,进程,内存情况)