c - 为什么 setitimer 和 dup2 在 execvp 之后为子进程工作?

标签 c dup2 setitimer

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

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

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

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

问题 1 为什么在父进程中设置带有 ITIME_PROF 的 setitimer 工作? child 的 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 真的可以工作,让 child 的输入/输出被重定向?

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;
}

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

先谢谢大家

最佳答案

问题一

Why doesn't the setitimer with ITIME_PROF work when it's set in the parent process ? The CPU / System calls for the child are not collected by it ?

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

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

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

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

问题2

Why does this WORK!? Doesn't the execvp overwrite all the functions ( timeout_sigprof, main and any other)? And couldn't someone potentially catch the signal in the child program and supersede the original function ?

事实并非如此,或者至少不是您所想的那样。正如您所说,您在父进程中安装的信号处理程序被 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

这证实了我上面所说的。我没有看到字符串“The alarm SIGPROF is here !”打印出来,我确实在 parent 身上看到一个迹象表明 child 被信号 27 杀死,这是 SIGPROF。

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

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

问题3

Why does the dup2 placed here actually work and let's the child's input / output to be redirected ?

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

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

dup2(outFileId, 1);

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

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

close 的手册页说:

If fd is the last file descriptor referring to the underlying open file description (see open(2)), the resources associated with the open file description are freed

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

关于c - 为什么 setitimer 和 dup2 在 execvp 之后为子进程工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24885081/

相关文章:

c - setitimer()调用是否被 child 继承

c - gcc 函数属性会影响函数指针吗?

c - 具有多个命令的 Shell 和作业

c - 需要实现一个pipe,dup2函数来执行 "ls | tr a-z A-Z > file.txt"

c++ - 玩具 shell 管道不正确

c - 了解 struct itimerval 字段 tv_usec

c - 在两点之间用新位替换旧位

c - C中是否有bool的格式说明符?

c - ESP32变量的多重定义