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

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

와나진짜

,