通过 /proc/[pid]/ 查看进程状态

本文介绍了如何通过/proc/PID/status文件获取Linux系统中进程的状态信息,包括进程占用的虚拟内存、物理内存等关键指标,并提供了一个示例程序用于自动化获取这些信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考资料

[/proc官方手册] http://man7.org/linux/man-pages/man5/proc.5.html
[解读/proc/$PID/status中各种参数] https://my.oschina.net/aiguozhe/blog/125477
[查看Linux & Android中内存占用方法] http://blog.csdn.net/myarrow/article/details/7703296
[Measuring Memory Usage] http://locklessinc.com/articles/memory_usage/
[Linux下的/proc/[pid]目录下的文件分析] https://github.com/NanXiao/gnu-linux-proc-pid-intro
[Linux中查看进程占用内存的情况] http://hutaow.com/blog/2014/08/28/display-process-memory-in-linux/
[内存 /proc/meminfo 之谜]http://linuxperf.com/?p=142


/proc/[pid]/ 目录

在这篇文章中 http://man7.org/linux/man-pages/man5/proc.5.html 详细讲解了/proc 目录下各个文件的意义以及系统调用的方法。
当我们查看进程的时候 #ps –e#ps可以列举出当前进程ID以及名称。
ps 命令的使用可以参考:http://man7.org/linux/man-pages/man1/ps.1.html
其中PID为1的固定为init进程,2的为kthreadd进程。前面的几个进程基本上都是固定的。在 /proc目录下会有与PID一致的目录名,目录里面的信息就包含该进程的所有信息。对 /proc/[pid]/目录的解释如下:

这里写图片描述

比如说 PID=1的init进程的信息存放在 /proc/1/ 的目录如下。
这里写图片描述


/proc/[pid]/status

这里写图片描述

/proc/[pid]/stat/proc/[pid]/statm的内容以用户可以直观分析的格式打印出来。这里只列举了其中的几个参数的意义:

* Name: Command run by this process.

* State: Current state of the process.  One of "R (running)",

"S (sleeping)", "D (disk sleep)", "T (stopped)", "T (tracing
stop)", "Z (zombie)", or "X (dead)".

* Pid: Thread ID (see gettid(2)).

* VmPeak: Peak virtual memory size.

* VmSize: Virtual memory size.

* VmLck: Locked memory size (see mlock(3)).

* VmPin: Pinned memory size (since Linux 3.2).  These are
pages that can't be moved because something needs to
directly access physical memory.

* VmHWM: Peak resident set size ("high water mark").

* VmRSS: Resident set size.  Note that the value here is the
sum of RssAnon, RssFile, and RssShmem.

* RssAnon: Size of resident anonymous memory.  (since Linux
4.5).

* RssFile: Size of resident file mappings.  (since Linux 4.5).

* RssShmem: Size of resident shared memory (includes System V
shared memory, mappings from tmpfs(5), and shared anonymous
mappings).  (since Linux 4.5).

* VmData, VmStk, VmExe: Size of data, stack, and text
segments.

* VmLib: Shared library code size.

* VmPTE: Page table entries size (since Linux 2.6.10).

* VmPMD: Size of second-level page tables (since Linux 4.0).

* VmSwap: Swapped-out virtual memory size by anonymous private
pages; shmem swap usage is not included (since Linux
2.6.34).

* Threads: Number of threads in process containing this
thread.
  • VmPeak: 表示进程所占用最大虚拟内存大小
  • VmSize: 表示进程当前虚拟内存大小
  • VmLck: 表示被锁定的内存大小
  • VmHWM: 表示进程所占用物理内存的峰值
  • VmRSS: 表示进程当前占用物理内存的大小(与procrank中的RSS)
  • VmData: 表示进程数据段的大小
  • VmStk: 表示进程堆栈段的大小
  • VmExe: 表示进程代码的大小
  • VmLib: 表示进程所使用共享库的大小
  • VmPTE: 表示进程页表项的大小

如果我们只想要获取其中某几个参数的值,可以使用命令:
#cat /proc/1/status | grep -E 'VmSize|VmRSS'

有这么一个需求:要统计各个进程中占用的物理内存的大小,我总不能一条条去执行cat命令吧,于是我们可以写一个应用程序循环读取每个进程的某个状态值。于是就有了下面的 rdstatus 应用程序


cat /proc/[pid]/stat

stat这个文件包含了很多信息,总共有52个参数。
这里写图片描述

这些内容在内核代码中:<kernel_dir>/fs/proc/array.c中实现。


rdstatus 应用程序

rdstatus.c 只能用来获取 /proc/[pid]/status中的内容。通过读取 /proc目录下的所有文件,然后提取其中[pid]的目录,获取到 /proc/[pid]/status的路径,再组合成 cmd命令传递给system()执行。

#if 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>

#define PROC_DIR_PATH "/proc"
#define DIR_MAX  256
#define DIR_NAME_MAX 40
#define CMD_NAME_MAX 200
#define RD_BUFFER_MAX 512

#if 0 // 介绍目录操作
http://songlee24.github.io/2014/09/20/linux-get-directory/

在头文件<dirent.h>中定义了两种主要的数据类型。

DIR:代表一个目录流的结构。

struct __dirstream
{
    void *__fd;              /* 'struct hurd_fd' pointer for descriptor.*/
    char *__data;            /* Directory block.  */
    int __entry_data;        /* Entry number `__data' corresponds to.*/
    char *__ptr;             /* Current pointer into the block.*/
    int __entry_ptr;         /* Entry number `__ptr' corresponds to.*/
    size_t __allocation;          /* Space allocated for the block.*/
    size_t __size;                /* Total valid data in the block.*/
    __libc_lock_define (, __lock) /* Mutex lock for this structure.*/
};

typedef struct __dirstream DIR;

struct dirent:包含一个文件或目录信息的结构体。

struct dirent
{
    long d_ino;                 /* inode number 索引节点号 */
    off_t d_off;                /* offset to this dirent 在目录文件中的偏移 */
    unsigned short d_reclen;    /* length of this d_name 文件名长 */
    unsigned char d_type;       /* the type of d_name 文件类型 */    
    char d_name [NAME_MAX+1];   /* file name 文件名,最长255字符 */
}

DIR* opendir(const char* dirname);
/* 打开一个目录:
        成功 - 返回指向DIR类型对象的指针。
        失败 - 返回NULL    */

int closedir(DIR *dirp);
/* 关闭目录流:
        成功 - 返回0
        失败 - 返回-1    */

struct dirent *readdir(DIR *dirp);
/* 读取目录流:
        成功 - 返回指向struct dirent对象的指针。
        失败 - 返回NULL(出错或流末尾)  */

int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
/* 读取目录流:用 dirp 当前位置的目录初始化entry,并让 result 指向 entry。
        成功 - 返回0
        失败 - 返回error number   */

void rewinddir(DIR *dirp);
/* 重置目录流的位置到开头 */

void seekdir(DIR *dirp, long int loc);
/* 设置目录流的位置,设置以后readdir()会读取到loc位置的目录。 */

long int telldir(DIR *dirp);
/* 返回目录流的当前位置 */

#endif

int translat(char c)
{
	if(c <= '9'&&c >= '0')
		return 1;
	else
		return -1;
}

// 判断字符串是否是数字
int isStringNum(char *str)
{
	int length = strlen(str);
	int i,n=0;
	int cnt=0;
	if(length == 0)
		return 0;
	
	for(i=0; i<length; i++) {
		cnt += translat(str[i]);
	}

	if(cnt == length)
		return 1;
	else
		return -1;
}

void ShowHelp(void)
{
	printf("show this help:\n");
	printf("  -h : show this help\n");
	printf("  -a : cat all pid all message\n");
	printf("  -c : cycle yes or not, 0 for not, 1 for yes\n");
	printf("  -n : process name\n");
	printf("  -p : pid number\n");
	printf("  -r : pid rang, pid_min ~ pid_max\n");
	printf("  -s : cat status, VmSize VmRSS VmPeak etc\n");
	printf("\n");
	
	// 命令控制只能采用以下的格式
	printf("for example:\n");
	printf("  ./rdstatus -h\n");		// flag = 1
	printf("  ./rdstatus -c 1 -a\n");	// flag = 2
	printf("  ./rdstatus -c 1 -n init -s VmSize\n");// flag = 3
	printf("  ./rdstatus -c 1 -p 1 -s VmSize\n");		// flag = 4
	printf("  ./rdstatus -c 1 -r 51 77 -s VmSize\n");	// flag = 5
	printf("\n");
	
	printf("argument number must 1 & 4 & 7 or more & 8 or more\n");
}

int GetRdFlag(int l_argc, char **l_argv)
{
	int flag = 1;

	if(l_argc == 2)
		flag = 1;
	else if(l_argc == 4)
		flag = 2;
	else if((l_argc >= 7) && (!strcmp(l_argv[3], "-n")))
		flag = 3;
	else if((l_argc >= 7) && (!strcmp(l_argv[3], "-p")))
		flag = 4;
	else if((l_argc >= 8) && (!strcmp(l_argv[3], "-r")))
		flag = 5;
	else
		flag = 1;

	return flag;
}


// 通过 process name 判断与当前 pid 是否匹配
int isProcessMatchPid(char *pid, char *name)
{
	char path[CMD_NAME_MAX];
	int ret = 0;
	int fd = 0;
	char rd_buf[RD_BUFFER_MAX];

	//printf("pid = %s, process_name = %s\n", pid, name);
	sprintf(path, "/proc/%s/status", pid);
	//printf("path = %s\n", path);

	fd = open(path, O_RDWR);
	if(fd < 0) {
		printf("open %s error\n", path);
		return -1;
	}
	ret = read(fd, rd_buf, 30);
	//printf("rd_buf = %s\n", rd_buf);
	if(strstr(rd_buf, name) != NULL){
		//printf("Match process name ok\n");
		close(fd);
		return 1;
	} else {
		//printf("Match process name failure\n");
		close(fd);
		return -1;
	}
	
}


void SetCmd(int l_argc, char **l_argv, char *path, char *cmd, char *name)
{
	int j = 0;
	strcpy(path, PROC_DIR_PATH);		// 调用 strcpy() 可重新定位到 status_path 的开头处
	strcat(path, "/");
	strcat(path, name);
	strcat(path, "/");
	strcat(path, "status");				// 只支持读取 /proc/[pid]/status 的内容

	// 确定执行的命令
	strcpy(cmd, "cat ");
	strcat(cmd, path);

	strcat(cmd, "| grep -E 'Name");		// 固定显示 Name 字段
	strcat(cmd, "|");
	for(j=6; j<l_argc; j++) {
		strcat(cmd, l_argv[j]);			// 获取 -s 之后的内容
		if(j != (l_argc-1))			// 最后不添加 '|'
			strcat(cmd, "|");
	}
	strcat(cmd, "'");
	//printf("cmd = %s\n", cmd);
}

int main(int argc, char **argv)
{
	DIR *dp;
	struct dirent *dirp;
	int dir_num = 0;						// /proc 目录下总的文件数
	int i = 0;
	int j = 0;
	int ret = 0;
	int pid_min = 0;						// pid 进程最大值
	int pid_max = 0;						// pid 进程最大值
	int pid = 0;
	int temp_pid = 0;
	int rd_flag = 0;						// 读取的方式标记
	int cycle_flag = 0;					// 循环读取标记
	char dir_name[DIR_MAX][DIR_NAME_MAX];	// 存放 /proc 目录下所有的文件名
	char temp_name[DIR_NAME_MAX];
	char process_name[DIR_NAME_MAX];		// 进程名称
	char status_path[DIR_NAME_MAX];			// 保存 /proc/[pid]/status 的路径
	char cmd[CMD_NAME_MAX];					// 执行的命令

	rd_flag = GetRdFlag(argc, argv);
	if(rd_flag <= 1) {
		ShowHelp();
		return 0;
	}
	//printf("rd_flag = %d\n", rd_flag);

	// 获取循环标记
	cycle_flag = atoi(argv[2]);
	//printf("cycle_flag = %d\n", cycle_flag);
	if(cycle_flag < 0 || cycle_flag > 1) {
		printf("cycle_flag error, please check it!\n");
		return -1;
	}
	
	switch(rd_flag) {
		case 3:
			strcpy(process_name, argv[4]);
			//printf("process = %s\n", process_name);
			break;
		case 4:
			pid = atoi(argv[4]);
			//printf("pid = %d\n", pid);
			break;
		case 5:
			pid_min = atoi(argv[4]);
			pid_max = atoi(argv[5]);
			//printf("pid_min = %d, pid_max = %d\n", pid_min, pid_max);
			if(pid_min > pid_max){
				printf("pid range error, please check it\n");
				return -1;
			}
			break;
		default:
			break;
	}

	if((dp = opendir(PROC_DIR_PATH)) == NULL)
		printf("Can't open %s\n", PROC_DIR_PATH);

	while((dirp = readdir(dp)) != NULL) {
		strcpy(&dir_name[dir_num++][0], dirp->d_name);
	}

	//printf("dir_num = %d\n", dir_num);

	do {
		for(i=0; i<dir_num; i++) {
			strcpy(temp_name, &dir_name[i][0]);
			ret = isStringNum(temp_name);
			if(ret == 1) {								// 获取 /proc/[pid] 成功
				temp_pid = atoi(temp_name);
				//printf("temp_pid = %d\n", temp_pid);
				if(rd_flag == 3) {
					ret = isProcessMatchPid(temp_name, argv[4]);
					if(ret > 0) {
						//printf("Process match pid ok\n");
						SetCmd(argc, argv, status_path, cmd, temp_name);
						system(cmd);
						printf("\n");
						break;
					}
				}
				else if(rd_flag == 4 && temp_pid == pid) {	// 通过 pid 来 cat 状态
					SetCmd(argc, argv, status_path, cmd, temp_name);
					system(cmd);
					printf("\n");
					break;
				}else if(rd_flag == 5 && (temp_pid >= pid_min && temp_pid <= pid_max)) {	// 通过 pid 范围 cat 状态
					SetCmd(argc, argv, status_path, cmd, temp_name);
					system(cmd);
					printf("\n");
				}
			}
		}
		sleep(1);
		printf("===================\n");
	}while(cycle_flag == 1);
	
	closedir(dp);
	return 0;
}
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值