How to enable SIGINT signal for system() call in a pthread in C?

我的梦境 提交于 2021-02-08 11:55:25

问题


The below manual on system() says it blocks SIGINT and SIGQUIT signal for any binary program run through system() call. https://man7.org/linux/man-pages/man3/system.3.html#:~:text=The%20system()%20library%20function,the%20command%20has%20been%20completed.

Psedo Code:

thread_1()
{
...
system("binary application");

}


main() {
...
pid = pthread_create(thread_1);
pthread_cancel(pid);

}

pthread_cancel issues SIGINT to thread 1 which kill the thread 1, but not the binary application.

How to make the "binary application" receive the SIGINT signal?


回答1:


The man page also says:

(These signals will be handled according to their defaults inside the child process that executes command.)

So to answer your question "How to make the "binary application" receive the SIGINT signal?"; it's ok, it will anyway. The blocking happens in the thread that calls the command, not the command process.

EDIT:

To answer @Hanu's comment below, use the wait() set of system calls: you can get the pid of the command inside the system() call from there, and you can safely close your child thread or take action depending on the result of wait(). But I don't know what resources you would need to clean if the process has terminated: Linux will free all the resources associated with the process called by system: there is a distinction between how the OS cleans pthreads when they finish and process resources - see this SO answer .




回答2:


The below manual on system() says it blocks SIGINT and SIGQUIT signal for any binary program run through system() call.

No, it doesn't. It says this:

During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored, in the process that calls system(). (These signals will be handled according to their defaults inside the child process that executes command.)

(Emphasis added.) It is the process that calls system() whose signal-handling is affected, not the (separate) process in which the command runs. Moreover, this is purposeful, and you should not lightly attempt to interfere with it.

pthread_cancel issues SIGINT to thread 1

Doubtful. POSIX does not document how thread cancellation is implemented, but sending SIGINT is an unlikely choice, as its default behavior is to terminate the process. The Linux manual for pthread_cancel() does say that it is implemented via signals, but it also says that the first realtime signal is used if realtime signals are available, and otherwise SIGUSR2 is used. Neither of those is documented as being blocked while system() is running.

which kill the thread 1, but not the binary application.

Yes, forcible termination of the thread that calls system() while that function is running would not be expected to kill the separate process in which the specified command is executing. That has nothing to do with signals being blocked. If you want to terminate the process in which the command is running before it completes then you're probably looking for the kill() function. For that purpose, you'll need to find the PID of the child you want to kill, which might turn out to require considerable effort and trouble.

Overall, if you don't like the semantics of system() then you are probably better off rolling your own version, based on fork() and exec*(), so that you can have the level of control you want.




回答3:


Instead of using system(), you fork() a child process, and execl("/bin/sh", "-c", "system-command-goes-here", (char *)0); in that child process.

When you call fork(), it returns twice: once in the parent with a positive value – the process identifier, "pid", of the child process; and once in the child with a zero value.

To send the child process an INT signal, just use kill(pid, SIGINT);.

You can use pthread_cleanup_push(kill_int, (intptr_t)pid) in the thread to kill the child process if the thread exits (is canceled or killed), with

static void kill_int(void *pidptr)
{
    const pid_t  pid = (intptr_t)pidptr;
    pid_t        p;

    if (pid > 1)
        kill(pid, SIGINT);
}

Here are some Public Domain helper functions you might find useful. run.h:

/* SPDX-License-Identifier: CC0-1.0 */

#ifndef   RUN_H
#define   RUN_H
#include <unistd.h>
#include <sys/types.h>

/* Execute command.  First parameter is the binary to execute (path or name),
   and the second parameter is the argument array.  First element in the
   argument array is command name, and the last element must be (char *)0.
   Returns the child process ID if successful, -1 with errno set if error.
*/
pid_t run(const char *, const char *[]);

/* Execute shell command.  The parameter is the shell command,
   otherwise this behaves like run().
*/
pid_t run_sh(const char *);

/* Check if child process has exited.
   Returns the PID of the child if it has returned,
   with the status (use WIFEXITED(), WEXITSTATUS(), WIFSIGNALED(), WTERMSIG())
   stored at the location specified by the int pointer, if not NULL.
   Returns 0 if the child hasn't exited yet, or
   -1 if an error occurred (with errno set).

   try_reap() tries to reap a specific child,
   try_reap_any() checks if any child processes have exited, and
   try_reap_group() checks if a child belonging to a process group has exited.
*/
pid_t try_reap(pid_t, int *);
pid_t try_reap_any(int *);
pid_t try_reap_group(pid_t, int *);

/* Wait until a specific child exits.
   Returns the child PID with status set if not NULL,
   or -1 if an error occurs.
*/
pid_t reap(pid_t, int *);

/* Wait until all child processes have exited.
   If non-NULL, the callback is called for each reaped child.
   If the callback returns nonzero, the function returns immediately
   without waiting for other children.
   Returns 0 if success, callback return value if it returns nonzero,
   or -1 with errno set if an error occurs.
*/
pid_t reap_all(int (*report)(pid_t, int));
pid_t reap_group(pid_t, int (*report)(pid_t, int));

#endif /* RUN_H */

Implementation, run.c:

/* SPDX-License-Identifier: CC0-1.0 */

#define _POSIX_C_SOURCE  200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

#ifndef  RUN_FAILURE_EXIT_STATUS
#define  RUN_FAILURE_EXIT_STATUS  69
#endif

static inline int has_slash(const char *cmd)
{
    while (*cmd)
        if (*(cmd++) == '/')
            return 1;
    return 0;
}

pid_t run(const char *cmd, const char *args[])
{
    int    ctrl[2] = { -1, -1 };
    int    cause;
    pid_t  child, p;

    /* Sanity checks. */
    if (!cmd || !*cmd || !args) {
        errno = EINVAL;
        return -1;
    }

    /* Create a close-on-exec control pipe. */
    if (pipe2(ctrl, O_CLOEXEC) == -1) {
        /* Failed; errno already set. */
        return -1;
    }

    /* Fork the child process. */
    child = fork();
    if (child == (pid_t)-1) {
        /* Failed; errno set. */
        cause = errno;
        close(ctrl[0]);
        close(ctrl[1]);
        errno = cause;
        return -1;
    } else
    if (!child) {
        
        /* This is the child process. */

        /* Close parent end of control pipe. */
        close(ctrl[0]);

        /* Try and execute the command. */
        if (has_slash(cmd))
            execv(cmd, (char *const *)args);
        else
            execvp(cmd, (char *const *)args);

        /* Failed. Try and report cause to parent. */
        cause = errno;
        {
            const char       *ptr = (const char *)(&cause);
            const char *const end = (const char *)(&cause) + sizeof cause;
            ssize_t           n;

            while (ptr < end) {
                n = write(ctrl[1], ptr, (size_t)(end - ptr));
                if (n > 0) {
                    ptr += n;
                } else
                if (n != -1 || errno != EINTR)
                    break;
            }
        }

        exit(RUN_FAILURE_EXIT_STATUS);
    }

    /* This is the parent process. */

    /* Close child end of control pipe. */
    close(ctrl[1]);

    /* Try reading from the control pipe. */
    {
        char       *ptr = (char *)(&cause) + sizeof cause;
        char *const end = (char *)(&cause) + sizeof cause;
        int         err = 0;
        ssize_t     n;

        while (ptr < end) {
            n = read(ctrl[0], ptr, (size_t)(end - ptr));
            if (n > 0) {
                ptr += n;
            } else
            if (!n) {
                break;
            } else
            if (n != -1) {
                err = EIO;
                break;
            } else
            if (errno != EINTR) {
                err = errno;
                break;
            }
        }

        /* If we failed, and didn't get a full cause,
           use the error from the read. */
        if (err && ptr != end)
            cause = err;

    }

    /* Close parent end of the control pipe. */
    close(ctrl[0]);

    /* If we failed, reap the child and exit. */
    if (cause) {
        do {
            p = waitpid(child, NULL, 0);
        } while (p == -1 && errno == EINTR);
        errno = cause;
        return -1;
    }

    /* Everything looks okay! */
    return child;
}

pid_t run_shell(const char *command)
{
    const char *args[4] = { "sh", "-c", command, (char *)0 };
    return run("/bin/sh", args);
}

pid_t try_reap(const pid_t pid, int *status)
{
    int   temp_status;
    pid_t p;

    if (pid <= 1) {
        errno = EINVAL;
        return -1;
    }

    do {
        p = waitpid(pid, &temp_status, WNOHANG);
    } while (p == -1 && errno == EINTR);
    if (status && p > 0)
        *status = temp_status;
    return p;
}

pid_t try_reap_any(int *status)
{
    int   temp_status;
    pid_t p;

    do {
        p = waitpid(-1, &temp_status, WNOHANG);
    } while (p == -1 && errno == EINTR);
    if (status && p > 0)
        *status = temp_status;
    return p;
}

pid_t try_reap_group(pid_t pgid, int *status)
{
    int   temp_status;
    pid_t p;

    if (pgid <= 1) {
        errno = EINVAL;
        return -1;
    }

    do {
        p = waitpid(-1, &temp_status, WNOHANG);
    } while (p == -1 && errno == EINTR);
    if (status && p > 0)
        *status = temp_status;
    return p;
}

pid_t reap(const pid_t pid, int *status)
{
    int   temp_status;
    pid_t p;

    if (pid <= 1) {
        errno = EINVAL;
        return -1;
    }

    do {
        p = waitpid(pid, &temp_status, 0);
    } while (p == -1 && errno == EINTR);
    if (status && p > 0)
        *status = temp_status;
    return p;
}

int reap_all(int (*report)(pid_t pid, int status))
{
    int   status, retval;
    pid_t p;

    while (1) {
        p = waitpid(-1, &status, 0);
        if (p == -1) {
            if (errno == ECHILD)
                return 0;
            else
            if (errno != EINTR)
                return -1;
        } else
        if (p > 0 && report) {
            retval = report(p, status);
            if (retval)
                return retval;
        }
    }
}

int reap_group(pid_t pgid, int (*report)(pid_t pid, int status))
{
    int   status, retval;
    pid_t p;

    if (pgid <= 1) {
        errno = EINVAL;
        return -1;
    }

    while (1) {
        p = waitpid(-pgid, &status, 0);
        if (p == -1) {
            if (errno == ECHILD)
                return 0;
            else
            if (errno != EINTR)
                return -1;
        } else
        if (p > 0 && report) {
            retval = report(p, status);
            if (retval)
                return retval;
        }
    }
}

and here is an example of use, example.c, which runs the binary specified by command-line parameters:

/* SPDX-License-Identifier: CC0-1.0 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#include "run.h"

int main(int argc, char *argv[])
{
    pid_t  child, p;
    int    status;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        const char *argv0 = (argc > 0 && argv && argv[0]) ? argv[0] : "(this)";
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
        fprintf(stderr, "       %s COMMAND [ ARGS ... ]\n", argv0);
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    child = run(argv[1], (const char **)(argv + 1));
    if (child == -1) {
        fprintf(stderr, "%s: Cannot execute: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }
    fprintf(stderr, "%s: Started process %d.\n", argv[1], (int)child);

    p = reap(child, &status);
    if (p == -1) {
        fprintf(stderr, "%s: Cannot reap child: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    } else
    if (p != child) {
        fprintf(stderr, "%s: Internal bug: reaped the wrong child process (%d, expected %d).\n", argv[1], (int)p, (int)child);
        return EXIT_FAILURE;
    }

    if (WIFEXITED(status)) {
        if (WEXITSTATUS(status) == EXIT_SUCCESS) {
            fprintf(stderr, "%s: Exited successfully.\n", argv[1]);
            return EXIT_SUCCESS;
        } else {
            fprintf(stderr, "%s: Exited with status %d.\n", argv[1], WEXITSTATUS(status));
            return WEXITSTATUS(status);
        }
    } else
    if (WIFSIGNALED(status)) {
        fprintf(stderr, "%s: Died from signal %d.\n", argv[1], WTERMSIG(status));
        return EXIT_FAILURE;
    } else {
        fprintf(stderr, "%s: Child process vanished!\n", argv[1]);
        return EXIT_FAILURE;
    }
}

To tie all these together, Makefile:

CC      := gcc
CFLAGS  := -Wall -O2
LDFLAGS :=
PROGS   := example

all: $(PROGS)

clean:
    rm -f *.o $(PROGS)

%.o:%.c
    $(CC) $(CFLAGS) -c $^

example: run.o example.o
    $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@

Note that this forum eats Tabs, so you need to run sed -e 's|^ *|\t|' -i Makefile to fix the indentation. To compile, just run make. To run, run e.g

./example date

The parent process examines how and why the child process exited, and will report both process identifier (pid) and the exit status.



来源:https://stackoverflow.com/questions/62909860/how-to-enable-sigint-signal-for-system-call-in-a-pthread-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!