sigaction() 및 SA_RESTART를 올바르게 사용하는 방법은 무엇입니까?

sigaction() 및 SA_RESTART를 올바르게 사용하는 방법은 무엇입니까?

나는 키보드 입력과 키보드 생성 신호를 모두 기다리고 다른 I/O(파이프 및 파일 포함)를 활성 상태로 유지할 수 있는 C 코드를 개발하고 싶습니다.

플래그를 증가시키고 현재 디버깅을 제공하는 최소한의 신호 처리기가 있습니다.

void Handler (int signo)

{
    char msg[80];

    ss->nTstp++;
    sprintf (msg, "\n%s .. Called Handler %d sig %d ..\n",
        TS(), ss->nTstp, signo);
    write (STDERR_FILENO, msg, strlen (msg));
}

다음과 같이 핸들러를 설정했습니다.

sigemptyset (& ss->sa.sa_mask);
sigaddset (& ss->sa.sa_mask, SIGTSTP);
ss->sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
ss->sa.sa_handler = Handler;
rc = sigaction (SIGTSTP, & ss->sa, NULL);

내 딜레마는 신호가 직접 승인되지만 터미널 입력이 자동으로 다시 시작된다는 것입니다. Enter를 누를 때까지 핸들러 플래그에 대한 작업을 수행할 수 없습니다.

SA_RESTART를 설정하지 않으면 터미널 입력에서 EINTR을 받지만 다른 모든 파일 설명자에서도 EINTR을 예상해야 합니다(그리고 다시 시작 가능한 전송을 작성해야 합니다).

특정 파일 설명자에서 SA_RESTART를 비활성화하는 방법이나 다른 fd가 EINTR을 얻지 못하도록 보장하는 방법이 있습니까?

답변1

귀하의 요구 사항을 이해한다면 (1) 키보드 입력, (2) 신호 및 (3) 잠재적으로 다른 이벤트 소스를 처리할 수 있기를 원할 것입니다. 이것이 맞다면 이것이 당신이 추구하는 일의 시작일 수 있습니다.

#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static int pipe_fds[2];

static void handler(const int signo)
{
    #define msg "received SIGTSTP\n"
    write(pipe_fds[1], msg, strlen(msg));
    #undef msg
}

int main(void)
{
    if (pipe(pipe_fds) < 0) {
        perror("pipe");
    }

    struct sigaction sa = {
        .sa_handler = handler,
        .sa_flags = SA_RESTART | SA_NOCLDSTOP,
    };

    if (sigemptyset(&sa.sa_mask) < 0) {
        perror("sigemptyset");
        return 1;
    }

    if (sigaction(SIGTSTP, &sa, NULL) < 0) {
        perror("sigaction");
        return 1;
    }

    struct pollfd fds[] = {
        {
            .fd = STDIN_FILENO,
            .events = POLL_IN,
        },
        {
            .fd = pipe_fds[0],
            .events = POLL_IN,
        },
    };

    const int num_fds = 2;
    char buffer[1024] = {};

    for (;;) {
        const int ret = poll(fds, num_fds, -1);
        if (ret < 0) {
            if (errno == EINTR) {
                // Receiving SIGTSTP can cause poll() to return
                // -1 with errno = EINTR.  Ignore that.
                continue;
            }
            perror("poll");
            return 1;
        }

        for (int i = 0; i < num_fds; ++i) {
            if (fds[i].revents & POLL_IN) {
                const int count = read(fds[i].fd, buffer, sizeof(buffer) - 1);

                buffer[count - 1] = '\0';
                printf("Read '%s' from file descriptor %d\n", buffer, fds[i].fd);
            }
        }
    }

    return 0;
}

main()함수는 먼저 파이프를 생성합니다. 프로그램은 파이프를 사용하여 신호 처리 함수(신호 컨텍스트에서)와 기본 프로그램 간의 통신을 수행합니다. 다음으로 SIGTSTP위에서 설명한 대로 signal 에 대한 신호 처리기를 설정합니다 .

struct pollfd그런 다음 이라는 배열을 만듭니다 fds. 이 배열의 각 항목은 프로그램이 활동을 모니터링하는 데 관심이 있는 파일 설명자에 해당합니다. 배열의 첫 번째 항목은 표준 입력에 대한 파일 설명자입니다. 두 번째는 위 파이프의 읽기 끝입니다. 다른 이벤트 소스(파일 설명자와 관련된 이벤트)를 처리하기 위해 이 예제를 확장하려는 경우 여기에서 수행할 수 있습니다. fds적절한 파일 설명자를 사용하여 배열에 추가 요소를 추가하기만 하면 됩니다.

그런 다음 이벤트 루프에 들어가는 데 사용됩니다 poll. timeout 후에는 -1( poll1) 등록된 파일 설명자 중 하나에서 활성화되거나 (2) 신호가 이를 중단할 때까지 차단됩니다(예: received SIGTSTP). 따라서 프로그램은 의 반환 값을 확인하고 poll, 0보다 작으면 오류를 명시적으로 확인하고 무시합니다 EINTR(시스템 호출에 의해 중단됨).

poll()활동으로 인해 반환된 경우 revents관련 필드가 struct pollfd그에 따라 표시됩니다. 그런 다음 연관된 파일 설명자에서 읽고 메시지를 인쇄합니다.

실행 예시는 다음과 같습니다.

$ ./a.out
Hello!
Read 'Hello!' from file descriptor 0
How are you?
Read 'How are you?' from file descriptor 0
^ZRead 'received SIGTSTP' from file descriptor 3
Good
Read 'Good' from file descriptor 0
^C
$

예제 실행에서는 키보드 Hello!에서 읽습니다. How are you?두 경우 모두 프로그램은 내가 입력한 내용을 읽고 응답을 인쇄하여 응답합니다. 다음으로 파이프에서 읽고 응답을 인쇄하는 SIGTSTP신호를 생성합니다 . received SIGTSTP다음으로 Good키보드의 응답을 읽고 인쇄합니다. 결국 프로그램을 중단 Ctrl-C해서 프로그램이 종료되었습니다.

read사용 가능한 바이트 수를 더 적게 반환할 수도 있습니다 . 단순화를 위해 해당 조건을 확인하지 않았습니다. 처리하려는 이벤트 소스에 따라 이 작업을 수행해야 할 수도 있습니다.

관련 정보