【问题标题】:Why do setitimer and dup2 work for a child proccess after execvp?为什么 setitimer 和 dup2 在 execvp 之后为子进程工作?
【发布时间】:2014-09-13 03:18:41
【问题描述】:

首先让我说这里有很多问题。

我的论文的其中一项任务要求我编写一个程序来执行一个子程序,如果它的运行时间(不是 wall-time 而是 user+sys )超过一个特定值或者它的 RAM 消耗量是大于另一个指定值。

虽然我还没有弄清楚 RAM 部分。我用 setitmer 和 ITIMER_PROF 信号来消磨时间。 (因为 ITIMER_PROF 收集实际的 CPU 使用情况,而不是设置一个时间起点,然后计算 x 时间)

我使用 setitimer 的原因是因为我需要的精度低于秒精度。 (例如,在 1.75 秒(1750000 微秒)后终止进程。setrlimit 方法只有一秒。

问题1为什么带有ITIME_PROF的setitimer在父进程中设置时不起作用?孩子的 CPU / 系统调用不被它收集?

childPID = fork();

if (childPID == -1){
        printf( "Puff paff ... fork() did not work !\n" );
        exit(1);
}

// Child 
if(childPID == 0) {
    execvp(args[0], args);
    exit(1);
}
// Parent
else{
    // Using a ITIMER_PROF inside the parent program will not work!
    // The child may take 1 hour to execute and the parent will wait it out!
    // To fix this we need to use a ITIMER_REAL ( wall-time ) but that's not an accurate measurement 
    struct itimerval timer;
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 500000;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 500000;
    setitimer ( ITIMER_PROF, &timer, NULL);

    int status;
    waitpid(childPID,&status,0);
    if (WIFEXITED(status)) {
        fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", cPID, WEXITSTATUS(status) );
    }
}

问题 2 为什么会这样!? execvp 不会覆盖所有函数(timeout_sigprof、main 和任何其他函数)吗?难道就不能有人在子程序中捕捉到信号并取代原来的函数吗?

void timeout_sigprof( int signum ){
    fprintf(stderr, "The alarm SIGPROF is here !\nThe actual pid: %d\n", getpid());
    //TODO: Write output and say the child terminated with
    // ram or time limit exceeded
    exit(105); // Note the 105 !
}

childPID = fork();

if (childPID == -1){
        printf( "Puff paff ... fork() did not work !\n" );
        exit(1);
}

// Child 
if(childPID == 0) {
    // 
    struct sigaction sa;
    memset (&sa, 0, sizeof (sa));
    sa.sa_handler = &timeout_sigprof;
    sigaction (SIGPROF, &sa, NULL);

    struct itimerval timer;
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 250000;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 250000;
    setitimer ( ITIMER_PROF, &timer, NULL);

    execvp(args[0], args);
    exit(1);
}
// Parent process
else {
    // Waiting for the child
    int status;
    waitpid(childPID,&status,0);
    if (WIFEXITED(status)) {
        fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", cPID, WEXITSTATUS(status) );
    }
    exit(0);
}

问题3为什么放在这里的dup2居然会起作用,让我们对孩子的输入/输出进行重定向?

childPID = fork();

if (childPID == -1){
        printf( "Puff paff ... fork() did not work !\n" );
        exit(1);
}

// Child 
if(childPID == 0) {

    // Redirect all I/O to/from a file
    int outFileId = open("output", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);

    // Redirect the output for the CHILD program. Still don't know why it works.
    dup2(outFileId, 1)

    // No idea why these dup2's work ! As i close the file descriptors here ?!
    close(outFileId);

    execvp(args[0], args);
    exit(1);
}
// Parent process
else {
    // Waiting for the child
    int status;
    waitpid(childPID,&status,0);
    if (WIFEXITED(status)) {
        fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", cPID, WEXITSTATUS(status) );
    }
    exit(0);
}

这是我编写的代码,它仅在程序运行 X 时间 (x = 500ms) 后才会终止程序。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>

volatile pid_t childPID;

// This function should exist only in the parent! The child show not have it after a exec* acording to :
// The  exec()  family  of  functions  replaces  the current process image with a new process image.
void timeout_sigprof( int signum ){
    fprintf(stderr, "The alarm SIGPROF is here !\nThe actual pid: %d\n", getpid());
    //TODO: Write output and say the child terminated with a ram or time limit exceeded
    exit(105); // Note the 105 !
}

int main(int argc, char *argv[]) {
    int cstatus;
    pid_t cPID;

    char *args[2];
    args[0] = "/home/ddanailov/Projects/thesis/programs/prime/prime";
    args[1] = NULL; // Indicates the end of arguments.

    // Handle the SIGPROF signal in the function time_handler in both the child and 
    struct sigaction sa;
    memset (&sa, 0, sizeof (sa));
    sa.sa_handler = &timeout_sigprof;
    sigaction (SIGPROF, &sa, NULL);

    childPID = fork();

    if (childPID == -1){
            printf( "Puff paff ... fork() did not work !\n" );
            exit(1);
    }

    // Child 
    if(childPID == 0) {
        struct itimerval timer;
        timer.it_value.tv_sec = 0;
        timer.it_value.tv_usec = 250000;
        timer.it_interval.tv_sec = 0;
        timer.it_interval.tv_usec = 250000;
        setitimer ( ITIMER_PROF, &timer, NULL);

        // Redirect all I/O to/from a file
        int outFileId = open("output", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
        // int inFileId = open("input");

        // Redirect the output for the CHILD program. Still don't know why it works.
        //dup2(inFileId, 0);
        dup2(outFileId, 1);
        //dup2(outFileId, 2);

        // No idea why these dup2's work ! As i close the file descriptors here ?!
        close(outFileId);
        close(inFileId);

        execvp(args[0], args);
        exit(1);
    }
    // Parent process
    else {
        // Waiting for the child
        int status;
        waitpid(childPID,&status,0);
        if (WIFEXITED(status)) {
            fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", cPID, WEXITSTATUS(status) );
        }
        exit(0);
    }

    return 0;
}

任何帮助/解释将不胜感激!

提前谢谢大家,

【问题讨论】:

  • 你真的看到“SIGPROF 警报来了”吗?还是孩子默默退出?
  • 嘿@harmic,是的,我确实看到 SIGPROF 在这里,pID 等。您可以使用我粘贴的最后一个完整程序(在孩子体内有 setitimers 的那个)进行测试在 execvp 之前处理)而不是“/home/ddanailov/Projects/thesis/programs/prime/prime”,在此处添加一个需要 1 秒以上的程序。感谢您对此进行调查!
  • 顺便说一句,您标记此 C++ 的任何特殊原因?我这里只能看到C代码
  • 删除了 C++ 标签。谢谢。我不确定它为什么会在那里。我认为 SO 将其作为建议标签。

标签: c dup2 setitimer


【解决方案1】:

问题 1

为什么使用 ITIME_PROF 的 setitimer 不工作 在父进程中设置?孩子的 CPU / 系统调用是 不是它收集的?

不,他们不是。与 ITIME_PROF 相关的计时器仅在设置了计时器的进程正在执行时,或者当系统调用代表它执行时,而不是在子进程正在执行时递减。

这些信号通常由分析工具使用,这些工​​具包含在您链接到您尝试分析的程序的库中。

但是:您可能不需要将信号发送到父进程。如果您的目标是在程序超过允许的使用量后终止程序,那么让它接收 SIGPROF 并退出(如下面我对 Q2 的回答所示)。然后,在waitpid 返回后,您检测到程序已因 SIGPROF 退出,您可以通过调用timesgetrusage 找出子进程使用的实际时间量。

唯一的缺点是子程序可以通过在 SIGPROF 上设置自己的信号处理程序来破坏这个过程。

问题 2

为什么会这样!? execvp 不会覆盖所有 函数(timeout_sigprof、main 和其他)?而且不能有人 可能在子程序中捕获信号并取代 原函数?

它没有,或者至少不是你可能想的那样。正如您所说,您在父进程中安装的信号处理程序将替换为由 execvp 加载的新图像。

它似乎工作的原因是,如果新程序没有为 SIGPROF 设置信号处理程序,那么当该信号发送到进程时,它将终止。回想一下,发送到进程的任何信号,如果该进程尚未为​​其设置处理程序,或明确决定忽略该信号,都会导致该进程终止。

如果 execvp 正在执行的程序确实为 SIGPROF 设置了信号处理程序,那么它不会被终止。

更新

看到你的评论后,我想我最好试试你的程序。我在waitpid之后的if语句中添加了另一个分支,如下所示:

    waitpid(childPID,&status,0);
    if (WIFEXITED(status)) {
        fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", childPID, WEXITSTATUS(status) );
    } else if (WIFSIGNALED(status)) {
        fprintf(stderr, "Process pid=%d received signal %d\n",childPID,WTERMSIG(status));
    }

当我运行它时,我看到以下内容:

$ ./watcher
Process pid=1045 received signal 27

这验证了我上面所说的。我没有看到字符串“警报 SIGPROF 在这里!”打印出来了,我确实在父母身上看到了孩子被信号 27(即 SIGPROF)杀死的迹象。

我只能想到一种情况,在这种情况下您会看到信号处理程序执行,那就是如果计时器设置得太低以至于它在 execv 实际设法加载新图像之前触发。不过,这看起来不太可能。

另一种可能性是您无意中在目标程序中安装了相同的信号处理程序(复制粘贴错误?)。

问题 3

为什么放置在这里的 dup2 确实有效,让我们来看看 孩子的输入/输出被重定向?

我从代码中的 cmets 中假设您的意思是“即使我在 dup2 之后立即关闭了原始文件描述符,为什么它仍然有效?”

dup2将旧FD复制到新FD中,所以执行后:

dup2(outFileId, 1);

您有两个引用相同文件描述的文件描述符:一个包含在变量 outFileId 和 FD 1(即标准输出)中。另请注意,此操作将关闭原始标准输出。

文件描述符就像对底层文件描述数据结构的引用,它代表打开的文件。调用dup2后,有两个文件描述符指向同一个文件描述。

close 的手册页说:

如果 fd 是引用底层打开的最后一个文件描述符 文件描述(参见 open(2)),与打开相关的资源 文件描述被释放

所以它可以正常工作:你仍然有一个 FD 打开,FD 1 (stdout),新的子进程可以写入它。

【讨论】:

  • 嘿@harmic,您能否在回答中详细说明这些:Q1 您能否给我一个替代方法来准确收集子进程的性能?如果没有,没关系!我会继续寻找方法。 Q2 我回答了您关于“您真的看到警报 SIGPROF 在这里了吗?”的评论。所以是的,我愿意。这意味着它不会被 execvp 覆盖? Q3 我没有看到我在子进程中留下的最后一个打开的 FD 吗? dup2 本身?感谢您对此进行调查!
  • @hamic 谢谢! getrusage 不适合吸小的时间间隔...希望时间可以完成工作,但谢谢你!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-03
  • 1970-01-01
  • 1970-01-01
  • 2011-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多