시그널에 대한 더 자세한 내용과 그 외의 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

sigaction을 왜 사용할까?

이전 UNIX의 signal함수보다 더 정교한 작업이 가능한 함수입니다. 그 정교한 작업이라는 게 어떤 것들이 있을까요? 

  • 정교한 작업1 (sigaction 코드 예제 - 자식 프로세스의 현재 상태 확인)

예를 들어서 자식이 종료하면 자식 프로세스에서 SIGCHLD 신호를 자식 프로세스에서 발생이 됩니다. 그러면 부모프로세스는 자식이 종료하였는지를 알 수 있죠. 그런데 많은 분들은 자식이 종료(EXIT)할때만 SIGCHLD를 발생시키는 것으로 알고 있습니다. 그런데요. 자식 프로세스는  자신이 정지(STOP)되었을 때, 혹은 재개(CONTINUED)되었을 때, 하물며 다른 프로세스에 의해 죽었을때 (KILL) 역시 부모 프로세스에 SIGCHLD를 보내게 됩니다. 우리가 signal 콜만 이용했을 경우 이러한 차이점을 부모 프로세스가 알아서 세세하게 제어할 수가 없습니다. 그런데 sigaction을 그런 차이들을 알아내어 컨트롤이 가능합니다. 이에 대한 예제 코드는 sigaction에 대해서 설명한 이후에 등장합니다.

  • 정교한 작업2 (sigaction 코드 예제  - 시그널 함수 구현)

뿐만 아니라 read 시스템 콜이 발생하여 사용자로부터 입력을 기다리고 있는 도중에, 시그널이 발생했다고 가정해보세요. 이럴 경우 시그널 핸들러 수행 이후에 1)read를 다시 호출해서 사용자 입력을 받을까요? 아니면 그냥 read는 넘어가고 다음 코드부터 수행할까요? 이러한 제어는 어떻게 해야하는 건가요? 이렇게 재개를 할지, 말지도 sigaction을 통해서 정할 수 있습니다. 물론 재개할지 말지 정하는 sigaction 사용 예제 코드는 밑에 있습니다.  

그전에 이 함수를 알기 위해서는 어느정도 시그널에 대한 기본지식이 있어야합니다. 시그널 집합, 시그널 차단 등의 개념이 나오기 때문인데요. 아래의 포스팅을 통해서 개념을 잡고 오시면 될것 같네요.

https://reakwon.tistory.com/46

 

[리눅스] 시그널 (SIGNAL)1 시그널 다루기(시그널 핸들러)

시그널 의미전달에 사용되는 대표적인 방법은 메세지와 신호입니다. 메세지는 여러가지 의미를 갖을 수 있지만 복잡한 대신 , 신호는 1:1로 의미가 대응되기 때문에 간단합니다. 컴퓨터에서 신

reakwon.tistory.com

https://reakwon.tistory.com/53

 

[리눅스] 시그널 (SIGNAL) 2 시그널 함수 sigprocmask, sigfillset, sigemptyset, sigaddset, sigdelset

시그널 관련 함수 지난 시간에는 간단하게 시그널 기본적인 설명과 어떻게 핸들링하는지에 대해서 알아보았죠? 시그널 개념과 시그널 핸들러는 지난 포스팅을 참고하시기 바랍니다. https://reakwo

reakwon.tistory.com

 

https://reakwon.tistory.com/54

 

[리눅스] 시그널(Signal) 3 sigpending, sigismember, sigsuspend

시그널에 대해서 이야기하는 3번째 시간이 되겠네요. 지난 번에는 시그널 개념과 시그널 관련함수까지 다루어 봤습니다. 시그널 개념과 시그널 핸들러와 시그널 관련 함수(sigfillset, sigemptyset, s

reakwon.tistory.com

 

sigaction

이 함수를 이용하면 어느 특정 신호에 관련된 동작을 조회할 수 있고 수정할 수 있습니다. sigaction의 원형은 이렇습니다.

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

 

사용하기 위해서는 signal.h 헤더 파일을 include시켜줘야합니다. 

▶ signum : signum은 시그널 번호를 의미합니다. signal함수의 처음 인자와 같습니다. 이런거 있잖아요. SIGINT, SIGQUIT 같은 시그널 번호말이죠. 단, 제어불가한 신호 번호는 SIGKILL과 SIGSTOP입니다. 

act : sigaction 구조체인 act 인자는 signum에 대해서 어떤 동작을 취할지에 대한 정보를 담고 있습니다. 즉, 시그널에 대한 동작을 수정하는 정보입니다.

oact : 역시 sigaction구조체인데, 이전에 설정된 동작에 대해서 돌려줍니다. 즉, 시그널에 대한 동작을 조회하는 정보입니다.

sigaction의 구조체를 한번 볼까요? 

struct sigaction {
   void     (*sa_handler)(int);
   void     (*sa_sigaction)(int, siginfo_t *, void *);
   sigset_t   sa_mask;
   int        sa_flags;
   void     (*sa_restorer)(void);
};

 

  •  sa_handler : 앞서 signum에 대한 동작을 나타내는 함수의 포인터입니다. 설정되지 않으면 기본동작을 의미하는 SIG_DFL입니다.
  •  sa_sigaction : sa_flags로 SA_SIGINFO를 사용할때 설정할 수 있습니다. 이런 경우에는 sa_handler가 사용되지 않고 이 sa_sigaction이 대신 사용됩니다. sa_sigaction에서는 신호 처리부(신호를 처리하는 함수)에 두가지 정보를 더 담아서 보냅니다. siginfo_t와 프로세스 문맥의 식별자가 그것입니다. 
    • 가장 처음 int는 시그널 번호입니다.
    • siginfo_t는 시그널에 대한 부가적인 정보를 담은 구조체입니다. 어떤 정보를 포함하는지는 이 구조체를 참고하면 됩니다. 시그널 정보에 많은 정보들이 들어가기 때문에 필드가 많으니 리눅스 메뉴얼을 참고하시기 바래요! 짧막하게 보면 아래와 같은 정보가 들어갈 수 있습니다.
    • 이후 void* 는 커널이 저장해둔 signal context의 정보를 담습니다.
siginfo_t {
   int      si_signo;     /* Signal number */
   int      si_errno;     /* An errno value */
   int      si_code;      /* Signal code */
   int      si_trapno;    /* Trap number that caused
                             hardware-generated signal
                             (unused on most architectures) */
   pid_t    si_pid;       /* Sending process ID */
   uid_t    si_uid;       /* Real user ID of sending process */
   int      si_status;    /* Exit value or signal */
   clock_t  si_utime;     /* User time consumed */
   clock_t  si_stime;     /* System time consumed */
…
}
  •  sa_mask : 차단할 신호의 집합입니다. sigprocmask를 통해서 특정 신호를 BLOCK 시킬지, 말지를 정합니다.
  •  sa_flags : 신호 옵션들입니다. 아래와 같은 옵션들이 존재합니다. 
SA_NOCLDSTOP signum이 SIGCHLD인 경우 자식 프로세스가 정지되었을때, notification을 받지 않습니다. 자식이 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 신호를 받아 정지되었을때 신호를 안받는다는 겁니다. 
SA_NOCLDWAIT signum이 SIGCHLD일때, 자식 프로세스가 종료되었을때 시스템이 좀비프로세스를 만들지 않게 합니다.
SA_NODEFER 신호가 잡혀서 신호 처리 함수가 실행되는 도중에 다시 같은 신호가 발생됐을때, 시스템이 자동으로 차단하지 않습니다.
SA_ONSTACK sigaltstack으로 대체 스택을 선언해두었다면 신호가 대안 스택의 프로세스에 전달됩니다.
SA_RESETHAND 신호 처리 함수에 진입할때 이 신호의 처리 방식을 SIG_DFL로 재설정하고 SA_SIGINFO 플래그를 지웁니다. 
SA_RESTART interrupt된 시스템 콜 호출이 자동으로 재시작됩니다. 아래 예에서 보겠습니다.
SA_RESTORER 어플리케이션에서 사용할 의도로 만들어진 flag가 아닙니다. sa_restorer와 관련된 옵션입니다.
SA_SIGINFO 신호 처리부에 추가적인 두가지 정보를 전달합니다. 이때 sa_sigaction함수 포인터를 설정해야합니다. 위의 sa_sigaction 인자에 대한 설명을 참고하세요.
  •  sa_restorer : 이 필드는 앱 사용 목적으로 만들어진 필드가 아닙니다. sigreturn과 관련된 필드라고 하네요. 넘어가겠습니다.

 

sigaction 코드 예제 - 자식 프로세스의 현재 상태 확인

시그널 핸들러를 이용해서 자식 프로세스가 종료하여 SIGCHLD를 발생했을 때 wait을 호출해서 자식 프로세스의 종료 상태를 알 수 있습니다. 그런데 자식 프로세스의 종료뿐만 아니라 정지, 재개 상태로 바뀌었을 때도 이러한 SIGCHLD를 발생시킨다고 했었습니다. 그렇다면 자식 프로세스가 종료할 경우에만 딱 wait할 수 있는 방법이 있을까요?

SA_SIGINFO 플래그와 siginfo_t의 si_code를 이용하면 됩니다.

//sigchld_info.c

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>

void action(int signo, siginfo_t *info, void* context){
        if(signo == SIGCHLD){
                printf("pid:%d, uid:%d\n", info->si_pid, info->si_uid);
                if(info->si_code == CLD_EXITED){
                        pid_t child_process = wait(NULL);
                        printf("[parent] child process(%d) exit\n", child_process);
                        exit(0);
                }
                if(info->si_code == CLD_KILLED){
                        pid_t child_process = wait(NULL);
                        printf("[parent] child process(%d) killed\n", child_process);
                        exit(1);
                }
                if(info->si_code == CLD_STOPPED)
                        printf("[parent] child process stopped\n");
                if(info->si_code == CLD_CONTINUED)
                        printf("[parent] child process continued\n");
        } 
}
int main(){
        pid_t pid;
        struct sigaction act;
        act.sa_flags = SA_SIGINFO;
        act.sa_sigaction = action;

        if((pid = fork()) < 0){
                printf("fork error \n");
                return 1;
        } else if(pid == 0){  //child process
                int count = 0;
                while(count < 30) {
                        printf("\t[child] count:%d\n", count++);
                        sleep(1);
                }
                exit(0);
        } else { //parent process 
                printf("[parent] child process : %d\n", pid);
                sigaction(SIGCHLD, &act, NULL);
                while(1) pause();
        }

}

sigaction 구조체에 SA_SIGINFO를 이용해서 sa_sigaction 핸들러를 활용합니다. SA_SIGINFO를 사용하게 되면 sa_sigaction을 사용할 수 있다고 위 설명해서 말씀드렸죠?

struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = action;

action 함수에서 siginfo_t의 si_code는 SIGCHLD 신호가 발생했을 때 부가적인 정보가 담겨져있습니다. 그래서 보다 정교한 제어를 하게 됩니다. 

void action(int signo, siginfo_t *info, void* context)

30초간 자식의 상태를 바꿀 수 있습니다. Kill 명령어를 통해서 정지, 재개를 해보세요. 그리고 kill -SIGKILL을 통해서 비정상 종료도 해보시면 자식 프로세스의 상태를 더 자세히 확인할 수 있습니다.

terminal1 terminal2
# ./a.out
[parent] child process : 236739
...
               [child] count:6
pid:236739, uid:0
[parent] child process stopped
pid:236739, uid:0
[parent] child process continued
                [child] count:7
                [child] count:8
...
                [child] count:29
pid:236739, uid:0
[parent] child process(236739) exit
# kill -SIGTSTP 236739
# kill -SIGCONT 236739

 

si_code의 값은 발생한 시그널에 따라서 다르게 설정이 됩니다. 아래의 표를 참고하시기 바랍니다.

 

sigaction 코드 예제  - 시그널 함수 구현

아래는 sigaction함수를 통해서 signal함수를 흉내낸 코드입니다. 

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

typedef void Sigfunc(int);

Sigfunc* my_signal(int signo, Sigfunc *func){
        struct sigaction act,oact;
        act.sa_handler = func;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;

        if(sigaction(signo, &act, &oact) < 0)
                return SIG_ERR;
        return oact.sa_handler;

}
void sig_int(int signo){
        printf("sig_int start\n");
}

int main(){
        Sigfunc* origin;
        //그전의 신호 처리 함수 포인터가 origin에 저장됨. 지금은 SIG_DFL
        origin = my_signal(SIGINT, sig_int);
        printf("main start\n");
        //신호가 발생할때까지 대기
        pause();
        printf("main end\n");
}
main start
^Csig_int start      <-- Ctrl+C 입력
main end

 

뭔가 되는것같긴한데 문제점이 있습니다. 아래와 같이 main함수를 변경해봅시다. 

int main(){
        int n;
        char buf[64];
        my_signal(SIGINT,sig_int);
        if((n=read(0,buf,64)) < 0){
                printf("read failed\n");
        }else{
                printf("%s\n",buf);
        }

}

 

그리고 실행시켜보면 읽기가 실패했습니다. read하는 도중에 Ctrl+C를 눌러서 신호를 발생시켰고, 그때문에 신호 처리함수가 호출이 되었습니다. 이때 read는 interrupt되었지만, 다시 복구 되지 않고 있는 현상이 문제입니다.

^Csig_int start    <-- Ctrl + C 입력
read failed

그래서 SA_RESTART 플래그를 넣어서 시스템 콜이 재시작되도록 설정합니다.

Sigfunc* my_signal(int signo, Sigfunc *func){
        //... 중략
        act.sa_flags = 0;
        act.sa_flags |= SA_RESTART;
        //... 중략
}

이후의 실행은 아래와 같습니다.

hello^Csig_int start    <-- Ctrl + C 입력
world
world

 

hello를 입력하는 와중에 Ctrl+C를 입력시켜서 SIGINT신호를 발생시켰습니다. 그래서 sig_int start라는 출력문이 수행이되었고, 신호 처리 함수가 끝난 후 다시 read를 하게 됩니다. world를 출력하고 엔터를 치면 world만 출력이 되네요.

다시 read를 호출했기 때문입니다. 

지금까지 sigaction에 대한 설명과 sigaction을 활용한 예제 2가지를 보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스에 대한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

오류 처리(Error Handling)

우리가 open으로 파일을 열때 -1의 값을 돌려받는다면 오류가 발생한 것입니다. 그런데 -1만 가지고는 우리는 왜 오류가 발생했는지 알 수가 없죠. 접근 권한 부족이라던가, 그런 파일이 존재하지 않는다던가 말입니다. open과 관련된 오류는 15가지 정도나 됩니다. 이런 원인을 알 수만 있다면 문제를 해결하는데 큰 도움이 되겠죠. 그래서 이번 포스팅에서는 오류의 원인을 어떻게 쉽게 알아 낼 수 있는지 관련된 내용입니다.

errno

errno는 일종의 오류의 코드가 담긴 변수입니다. 이 변수를 활용하기 위해서는 우리는 errno.h라는 헤더파일을 include시켜줘야합니다.

#include <errno.h>

 

errno.h 파일에는 errno와 errno에 설정될 수 있는 에러 코드가 담겨있습니다. 에러 코드는 상수로 담겨있으며 그 종류가 매우 많아서 여기에서는 담지 않겠습니다. 여러분들이 에러 코드에 대한 정보를 직접 확인해보시는 것을 추천해드리며 리눅스 매뉴얼 페이지에 존재합니다. 아래의 명령을 통해서 메뉴얼 페이지를 확인해보세요.

# man 3 errno

 

혹은 errno 명령을 사용하시면 됩니다. 위 명령은 moreutils라는 페키지에 존재하기 때문에 없으면 설치해줍니다. 

# apt install moreutils

 

errno -l 명령어 실행시 

# errno -l
EPERM 1 명령을 허용하지 않음
ENOENT 2 그런 파일이나 디렉터리가 없습니다
ESRCH 3 그런 프로세스가 없음
EINTR 4 중단된 시스템 콜
EIO 5 입력/출력 오류
ENXIO 6 그런 장치 혹은 주소가 없음
E2BIG 7 인수 명단이 너무 김
ENOEXEC 8 Exec 형식 오류

에러를 나타내는 상수는 모두 앞에 E가 붙는 점을 확인하세요.

 

strerror

#include <string.h>
char *strerror(int errnum);

errno 가지고는 어떤 오류인지 사람이 직접적으로 확인하기가 어렵습니다. 코드를 직접 확인하여서 에러 메시지를 출력해야합니다. 이런 번거로움없이 strerror에 인자로 errno를 전달하게 되면 알아서 오류 메시지를 반환해줍니다. 아래는 아주 간략한 예제입니다.

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

int main(){
        char *msg;
        errno = EPERM;
        msg = strerror(errno);
        printf("error :%s\n",msg);

}

 

출력을 보게 되면 EPERM에 대한 오류 내용을 볼 수 있습니다. 

error :Operation not permitted

 

perror

#include <stdio.h>
void perror(const char *s);

perror는 strerror과 비슷하게 오류의 내용을 문자열로 출력하여 줍니다. 이때 전달되는 문자열 s는 우리가 출력해주길 원하는 문자열이며, 오류 내용은 errno에 따라서 알아서 출력을 해줍니다.

아래는 예제 코드입니다. 

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

int main(){
        errno = EPERM;
        perror("error 발생");
}

출력을 보면 errno를 전달하지 않았음에도 함수 내부에서 errno를 확인하기 때문에 에러를 알 수 있습니다. 에러 내용은 위의 strerror의 메시지와 같은 것을 알 수 있죠? 내부적으로 strerror을 사용하는 것을 알 수 있습니다. 

error 발생: Operation not permitted

perror는 이렇게 errno를 직접 명시적으로 넣어주지 않아도 되기 때문에 매우 편리합니다.

 

open error 처리 코드

한 예로 open에 대한 error처리는 아래와 같이 수행이 됩니다. 오직 root만이 읽을 수 있는 파일이 있습니다. 

$ ls -l
-r-------- 1 root   root       0  4월 14 17:08 root_file

 

그리고 root 권한이 없는 일반 사용자로 아래의 코드를 짜서 실행해보면 열리지 않겠죠. 결국 open은 -1을 반환하게 됩니다. 그리고 왜 열지 못하는지는 perror를 통해 확인해볼 수 있습니다.

//err.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>

int main(){
        int fd = open("root_file", O_RDONLY);
        if(fd < 0){
                perror("file open error");
                exit(-1);
        }
        return 0;
}
$ gcc err.c
$ ./a.out
file open error: Permission denied

 

지금까지 오류를 쉽게 확인할 수 있는 내용이었습니다. 사실 error에 대해서 처리해주는 것이 여간 귀찮은 것이 아닌데 이러한 오류 처리 함수를 통해서 번거로움을 줄여보시기 바랍니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스에 대한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

alarm함수

alarm함수는 일정 초단위의 시간 후에 SIGALRM을 발생시키는 함수입니다. 이 시그널이 발생하게 되면 기본 동작은 프로세스 종료입니다. 

혹시 시그널에 대해서 모르시나요? 아래의 포스팅을 참고하며 보시기 바랍니다.

https://reakwon.tistory.com/46

 

[리눅스] 시그널 (SIGNAL)1 시그널 다루기(시그널 핸들러)

시그널 의미전달에 사용되는 대표적인 방법은 메세지와 신호입니다. 메세지는 여러가지 의미를 갖을 수 있지만 복잡한 대신 , 신호는 1:1로 의미가 대응되기 때문에 간단합니다. 신호등이 가장

reakwon.tistory.com

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

seconds : 신호가 발생할 때까지의 클록 초를 의미합니다. 이 시간이 경과가 되고다면 커널이 신호를 발생시키게 됩니다. 만약에 alarm에 0을 전달하게 되면 alarm함수는 알람 발생을 취소하게 됩니다.

반환 : 여기서 주의해야할 점은 하나의 프로세스가 작동시킬 수 있는 알람 시계는 오직 하나뿐이라는 점입니다. 이전에 프로세스가 등록해 놓은 알람이 있다면 alarm함수는 이전에 등록되어있던 알람의 남은 시간(초)를 반환합니다. 만일 알람이 지정되어있지 않은 새로운 알람을 등록하는 것이라면 반환 값은 0이 됩니다. 아래의 심플한 코드와 결과를 보고 어떻게 동작이 되는지 알 수 있습니다.

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main(){
        //첫번째 알람으로 ret = 0
        int ret = alarm(10);
        printf("처음 등록한 알람 - 남은 시간:%d\n", ret);
        //첫번째 알람 끝나기도 전에 두번째 알람 등록
        ret = alarm(5);
        printf("두번째 등록한 알람 - 남은 시간 :%d\n",ret);

}
처음 등록한 알람 - 남은 시간:0
두번째 등록한 알람 - 남은 시간 :10

 

그런데 위의 코드에서는 alarm이 울리기 전에 코드가 모두 종료가 되기때문에 알람이 실제 울리는지 안울리는지 알수가 없는 코드가 됩니다. 아래의 pause함수도 같이 사용합시다.

 

pause

#include <unistd.h>
int pause(void);

이 함수는 신호를 기다리는 함수입니다. 이 함수가 수행이 되면 프로세스는 신호가 발생될때까지 sleep상태에 빠지게 됩니다. 위에서 언급한 SIGCHLD 외에 다른 신호도 기다립니다. 

반환 : 시그널 핸들러가 처리부를 실행하고 나서 시그널 핸들러가 반환될 경우에 이 함수가 반환됩니다. 이때 pause함수는 errno를 EINTR로 설정하고 -1을 반환합니다. 

다시 말해서 signal함수에 전달되는 시그널을 처리하는 함수가 있죠? 그 함수부터 반환되고 난 이후에 pause가 끝난다는 이야기입니다. 아래의 코드를 보게되면 signal함수로 signal_handler하는 시그널 핸들러를 등록해줍니다. 이후 pause로 신호가 발생될때까지 기다리게 됩니다. 신호가 발생하게 되면 1. signal_handler의 함수가 수행하고, 2. pause()가 반환하면서 3. pause() 이후의 코드가 수행된다는 뜻입니다.

//signal handler 등록
if(signal(SIGALRM, signal_handler) == SIG_ERR) return seconds;
// ... 수행부 ..//
pause();

 

sleep함수 구현

지금까지 소개한 alarm과 pause함수로 간단한 sleep()함수를 구현해볼 수가 있습니다. sleep함수는 원래 unistd.h에 포함되어 있는 함수이고 사용자가 지정한 초수만큼 대기(sleep상태)가 됩니다. 

#include <unistd.h>
unsigned int sleep(unsigned int seconds);

구현

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
static void sig_alarm(int signo){
        printf("signo:%d\n",signo);
}

unsigned int my_sleep(unsigned int seconds){
        if(signal(SIGALRM, sig_alarm) == SIG_ERR)
                return seconds;
        int ret = alarm(seconds);
        //이미 alarm이 존재하면 존재하는 alarm 삭제 후 다시 등록
        if(ret < seconds){
                alarm(0);       //취소
                alarm(seconds); //새등록
        }
        pause();
        return alarm(0);
}
int main(){
        printf("before my_sleep\n");
        //3초간 sleep
        my_sleep(3);
        printf("after my_sleep\n");

}

 

 

이미 sleep 중인데, 다시 sleep이 호출되게 되면 구현의 편의를 위해서 기존 sleep을 취소하고 새로운 sleep으로 교체하도록 하였습니다. 사실 이전의 sleep까지 기다리고 난 이후에 새로운 sleep을 해주어야합니다. 

보완

여기서 pause 호출전에 alarm이 먼저 호출하게 되면 pause는 다른 신호가 잡히기 전까지는 영원히 대기하게 됩니다. 이를 보완하기 위해서 setjmp를 활용할수 있습니다.

setjmp를 잘 모르신다면 setjmp와 longjmp와 관련해서는 아래의 포스팅을 참고하시기 바랍니다.

https://reakwon.tistory.com/211

 

[리눅스] 단번에 이해하는 setjmp, longjmp를 활용하는 방법

비국소(nonlocal) 분기 setjmp나 longjmp는 이름에서도 알수 있듯이 jump하는 함수입니다. 실행부를 점프한다는 것입니다. 그전에 비국소(nonlocal)라는 단어를 설명할 필요가 있습니다. C언어에서 goto구문

reakwon.tistory.com

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
static jmp_buf buf;
static void sig_alarm(int signo){
        longjmp(buf,1);
}

unsigned int my_sleep(unsigned int seconds){
        if(signal(SIGALRM, sig_alarm) == SIG_ERR) return seconds;

        if(setjmp(buf) == 0){
        	    //pause호출 되기전 SIGALRM이 호출된다면? 
                int ret = alarm(seconds); 
                if(ret < seconds){
                        alarm(0);
                        alarm(seconds); 
                }
                pause();
        }
        return alarm(0);
}
int main(){
        printf("before my_sleep\n");
        //3초간 sleep
        my_sleep(3);
        printf("after my_sleep\n");

}

 

만약 SIGALRM이 pause 호출 전에 발생하게 된다면, sig_alarm이 호출이 되고 longjmp에 의해 setjmp쪽으로 이동이 되겠죠. setjmp의 반환값은 1이 되기 때문에 if(setjmp(buf) == 0)을 수행하지 않고 빠져나오게 됩니다. 그래서 my_sleep함수가 끝나게 되죠. 이렇게 pause가 무한히 신호를 기다리지 않게 되기 때문에 처음 my_sleep() 구현에 문제점을 해결할 수가 있습니다.

그럼에도 불구하고 여전히 다른 문제점들이 존재하기는 합니다. 이 함수를 호출하는 프로세스에서 signal함수를 통해서 다른 시그널 핸들러를 등록하게 되면 안좋은 결과가 생길 수 있습니다.

하지만 alarm과 pause를 어떻게 사용하는지에 대한 기본적인 개념을 알기 위한 코드이니, 문제점은 여러분들이 해결해보시기 바랍니다.

지금까지 alarm함수와 pause함수에 대해서 알아보았고 이것을 활용하는 sleep함수까지 살짝 맛보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,