fork + execl获取子进程资源使用信息

OJ如何获取子进程执行信息(内存,执行时间)?

Posted by bbkgl on May 18, 2020

直道相思了无益

未妨惆怅是清狂

问题是什么?

如果要命令行运行某个程序,那么如何知道这个进程对资源的使用量呢?

从我的知识角度来说,第一反就是通过特殊的文件系统,比如读取/proc/<pid>/目录下的某个文件,比如/proc/[pid]/stat就会列出进程使用的CPU信息和内存信息,包括峰值和实时的值。

但问题就是进程退出后,/proc/[pid]/stat这个文件就不见了,除非能有某种方法让进程退出前停一下。。。但是好像不太科学。。。

还有一种办法,就是利用系统调用wait3/wait4

fork + execl + wait3/4

fork函数

fork就不多说了,感觉无论是例子,还是理论还是其他,各个博客都讲的很多,也没什么好补充的。这里就不再多bb了。

execl

exec是一个家族,包括了好几个类似功能的函数,但效果都是一样的,通过命令行执行某个可执行文件,并让这个新生进程顶掉当前进程,且进程ID不变。

这里我比较好奇实现的原理,所以大概地查阅资料,了解一下。

#include <unistd.h>

int execl( const char *pathname, const char *arg0, ... /* (char *)0 */ );

int execv( const char *pathname, char *const argv[] );

int execle( const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */ );

int execve( const char *pathname, char *const argv[], char *const envp[] );

int execlp( const char *filename, const char *arg0, ... /* (char *)0 */ );

int execvp( const char *filename, char *const argv[] );

6个函数返回值:若出错则返回-1,若成功则不返回值

从资料来看,原调用进程的内容除了进程号外,其他全部被新的进程替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行的脚本文件。

新的进程会把原进程的代码段、数据段和内存堆栈全部进行替换,从而实现“顶替”。。。具体实现原理我也不知道是什么,系统调用就是这样,一脸懵逼。。。

这里我使用的是execl函数:

int execl( const char *pathname, const char *arg0, ... /* (char *)0 */ );
  • 第一个参数是可执行文件路径
  • 第二个参数是可执行文件的名字
  • 第三个参数是参数列表。。。没参数就传nullptr/NULL就行了

感觉问题不大。

wait3/wait4

这里我直接贴man文档的说明,然后我稍微翻译一下比较好,网上资料也不是很多。

#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>

pid_t wait3(int *status, int options,
            struct rusage *rusage);

pid_t wait4(pid_t pid, int *status, int options,
            struct rusage *rusage);

wait3和wait4除了能够等待子进程结束,给子进程收尸之外,还能通过最后的参数,返回一些资源使用信息。

二者的区别在于,wait3等的是所有的子进程,而wait4可以指定等待某个子进程。。。这个和waitpid区别不大。

如果rusage不为空的话,对应的结构体就会被填充子进程的资源使用信息,这个的话可以见getrusage

获取命令行执行的子进程资源使用信息

使用上面三个系统调用进行组合就能做到。

  1. 首先fork出子进程
  2. 然后用exec顶掉原来的子进程,替换成命令行执行的子进程
  3. 最后用wait3/4进行收尸

下面给出示例。。。。

首先是内存测试程序:

#include <cstdio>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <cstring>

long wait4stop(pid_t pid) {
    struct rusage usage;
    int status = 99;
    do {
        if (wait4(pid, &status, 0, &usage) == -1 || WIFEXITED(status) || WIFSIGNALED(status))
            return usage.ru_maxrss;
    } while(!WIFSTOPPED(status));
    return 1;
}

int main() {
    // 修改成自己的可执行文件路径
    std::string exefile = "/home/bbkgl/vimcode/test/build/testo";
    pid_t chpid = -666;
    if ((chpid = fork()) == 0) {
        execl(exefile.c_str(), "testo", nullptr);  // testo是可执行文件名字,注意修改!
    }
    long mem_size = wait4stop(chpid);
    fprintf(stdout, "Memory use %.2lf MB.", 1.0 * mem_size / 1024);
    return 0;
}

被测试程序:

#include <cstdio>
#include <iostream>
#include <unistd.h>
#include <random>
#include <cstring>

using namespace std;

constexpr int ARRAY_NUM = 50 * 1024 * 1024;
constexpr int MEM_SIZE = 4 * ARRAY_NUM;

int main() {
    int *array = new int[ARRAY_NUM];
    std::memset(array, 0,  MEM_SIZE);
    delete[] array;
    return 0;
}

然后是结果:

20200518233604.png