리눅스 프로그래밍 - 10주차

Lellow_Mellow·2022년 11월 6일
1
post-thumbnail

🔔 학교 강의를 바탕으로 개인적인 공부를 위해 정리한 글입니다. 혹여나 틀린 부분이 있다면 지적해주시면 감사드리겠습니다.

System Call : sigaction

#include <signal.h>

int sigaction(int signo, const struct sigaction * act, struct sigaction *oact);

sigaction을 이용하여 signal에 대한 action을 수정할 수 있다. act에는 새로 등록할 handler에 대한 정보가 담겨있으며, oact에 기존 action을 저장한다.

Argument

  • signo : signal number
  • act : signal에 대한 action
  • oact : 기존 action

Return

  • 성공 시 0 return
  • error 발생 시 -1 return

위에서 actoact의 구조체는 아래와 같다.

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

sigaction

  • sa_handler : int를 인자로 받는 function의 포인터로, handler에 해당함.
    - SIG_IGN이 오면 해당 signal을 무시, SIG_DFL가 오면 기존 action으로 복원함.
  • sa_mask : signal handler가 종료될때까지 block될 signal의 집합.
    - 해당 signal들은 무시되는 것이 아니라 blocking됨.
  • sa_flag : signal을 handling할때의 option에 해당함.
    - SA_RESTART : interrupt되지 않고 다시 시작
    - SA_SIGINFO : sa_sigaction으로 handler를 지정

sa_sigaction

  • SA_SIGINFO flag가 설정되었을 경우에 대체로 사용하는 signal handler
  • sa_handler와 둘 중에 하나만 선택되어 사용되며, 인수로 전달되는 info는 해당 signal에 대한 추가 정보를 담고 있음.

Example : Catching SIGINT

#include <signal.h>

void catchint(int signo) {
	printf(“\nCATCHINT: signo=%d\n”, signo);
	printf(“CATCHINT: returning\n\n”);
}

int main() {
	static struct sigaction act;
    
	act.sa_handler = catchint;
	sigfillset(&(act.sa_mask));	// 모든 signal block
	sigaction(SIGINT, &act, NULL);
    
	printf(“sleep call #1\n”);
	sleep(1);
	printf(“sleep call #2\n”);
	sleep(1);
	printf(“sleep call #3\n”);
	sleep(1);
	printf(“sleep call #4\n”);
	sleep(1);
    
	printf(“Exiting\n”);
	return 0;
}

해당 코드는 SIGINT에 대하여 handler를 sigaction을 이용해 설정해주고, signal이 들어오는 것을 기다리기 위해 총 4번 sleep한다. 실행 중간데 ^C 가 입력되어 SIGINT signal이 발생하면 catchint가 이를 handling하여 해당 signo를 출력하게 된다.

Example : Restoring a Previous Action

#include <signal.h>

static struct sigaction act, oact;

sigaction(SIGTERM, NULL, &oact);

act.sa_handler = SIG_IGN;
sigaction(SIGTERM, &act, NULL);

...

sigaction(SIGTERM, &oact, NULL);

위 코드는 sigaction을 이용해 handler를 수정한 이후, 기존 action으로 복원해준다. act 대신 NULL을 이용하여 oact에 기존 action을 저장하고, 다시 act를 이용하여 handler를 변경한다. 특정 코드 실행 이후, 다시 oact를 이용하여 기존 action으로 handler를 지정하는 것을 볼 수 있다.

Example : A Graceful Exit

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

void g_exit(int s) {
	unlink(“tempfile”);
	fprintf(stderr, “Interrupted – exiting\n”);
	exit(1);
}

SIGINT와 같은 interrupt가 발생하여 file이 닫히지 않은 경우를 위해서 위와 같이 signal handler를 사용하여 닫히지 않은 상태로 유지되지 않도록 처리할 수 있다.

Structure : siginfo_t

struct siginfo_t {
	int si_signo;	/* signal number */
	int si_errno;	/* if nonzero, errno value from <errno.h> */
	int si_code;	/* additional info (depends on signal) */
	pid_t si_pid;	/* sending process ID */
	uid_t si_uid;	/* sending process real user ID */
	void *si_addr;	/* address that caused the fault */
	int si_status;	/* exit value or signal number */
	long si_band;	/* band number for SIGPOLL */
...
};

alternate signal handler, 다시 말해 sa_sigaction에서의 siginfo_terror number, signal을 보낸 process의 pid와 real user id, error가 발생한 code 주소 등 위와 같은 정보를 담고 있다. 다시 말해 signal이 왜 발생했는지에 대한 정보를 담고 있는 것이다.

Example : sa_sigaction

#include <signal.h>
void sig_handler(int sig, siginfo_t *siginfo, void* param2) {
	printf([Parent:%d]: receive a signal from child %d\n”, getpid(), siginfo->si_pid);
}

int main() {
	pid_t pid;
	static struct sigaction act;
	act.sa_sigaction = sig_handler;
	act.sa_flags = SA_SIGINFO;
	sigfillset(&act.sa_mask);
	sigaction(SIGUSR1, &act, 0);
    
	int i = 0;
	while(pid=fork()) {
		printf([Parent:%d]: create child %d\n\n”, getpid(), pid);
		if(i++==3) break;
	}
	if (pid>0)
		getchar();
	else
		kill(getppid(), SIGUSR1);
	return 0;
}

위 코드는 sa_sigaction에 대한 예제이며, sig_handler의 인자로 siginfo를 전달받아 이에 대한 pid를 출력하는 것을 볼 수 있다.

⭐ 출력 관련해서는 이후에 공부 이후 추가 예정

Signals and System Call

signal은 보통 system call이 끝나기 전까지 block된다. 하지만 slow system call일 경우에는 system call이 interrupt되고, -1을 return한다. 이때, errno = EINTR이다.

Slow system calls

  • pipe, terminal or network device
  • pause, wait
  • interprocess communication function

device의 경우 언제 끝날지 예측이 쉽지 않기 때문에 slow에 해당된다. (그냥 file에 대한 I/O는 이에 해당되지 않는다.) process간의 통신 function의 경우도 마찬가지다.

system call이 중단됨에 따라 error return을 명시적으로 해주어야 할 필요가 있다. 만일 device에 대한 I/O의 진행 중에 interrupt가 발생해 system call이 interrupt 되었다면, 어느 시점부터 I/O가 잘못된 것인지 판단이 불가능하다.

if ((write(tfd, buf, size) < 0) {
	if (errno == EINTR) {
		warn(“Write interrupted”);
	}
}

따라서 아래와 같이 수정하여 다시 처음부터 처리할 필요가 있다.

again:
if ((write(tfd, buf, size) < 0) {
	if (errno == EINTR) {
		warn(“Write interrupted”);
		goto again;
	}
}

이와는 다른 방법으로 sigaction을 이용하여 SA_RESTART flag를 설정해주는 것이다. 해당 flag가 설정되면, interrupt되는 것이 아니라 다시 시작되기 때문에 errno도 설정되지 않고 다시 시작하게 된다.

More about Signals

process에도 signal mask가 존재하며, sigactionact에서의 mask가 process의 signal mask에 포함되어 작동된다. handling된 signal 역시 signal mask에 포함되게 되며, handler가 종료되면 기존 mask 값으로 재설정된다.

Function : sigsetjmp, siglongjmp

#include <setjmp.h>

int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);

sigsetjmp는 특정 순간을 타임스탬프처럼 지정하며, siglongjmp는 지정된 위치로 다시 돌아가는 역할을 한다.

Argument

  • sigjmp_buf env : sigsetjmp는 여기에 정보를 저장하며, siglongjmp는 이를 이용해 해당 위치로 이동
  • int savemask : process의 signal mask를 같이 저장할지에 대한 여부를 결정
  • int val : sigsetjmp의 return value

Return

  • sigsetjmp : 직접적으로 call되었을 경우에는 0을 return, siglongjmp를 통해 call되었을 경우에는 val을 return

Example : sigsetjmp, siglongjmp

#include <sys/types.h>
#include <signal.h>
#include <setjmp.h>
#include <stdio.h>
sigjmp_buf position;

// 출력 이후 jump
void goback(void) {
	fprintf(stderr, “\nInterrupted\n”);
	siglongjmp(position, 1); /* Go back to the position */
}

int main() {
	static struct sigaction act;
	...
    // 처음 호출될 경우 실행
	if (sigsetjmp(position, 1) == 0) { 
		act.sa_handler = goback;
		sigaction(SIGINT, &act, NULL);
	}
    // jump 이후 실행
	domenu();
	...
}

위 코드는 sigsetjmpsiglongjmp의 활용 예제이다. 여기서 save masktrue로 설정해주었는데, 이를 설정해주지 않으면 siglongjmp에 의해 goback 함수가 끝까지 실행되지 않아 해당 signal은 block된 상태가 지속되게 된다. 이를 1로 설정해주면 이러한 경우에도 mask를 원래대로 복원해주기 때문에 설정하였다.

6.3 Signal Blocking

Signal Blocking

중요한 코드를 실행하는 과정에서 interrupt가 되지 않도록, 즉 signal에 의해 blocking되지 않도록 하는 것이 가능하다. 이는 해당 작업을 완료할때까지 신호를 처리하지 않으며, signal을 무시하는 것은 아니다.

System Call : sigprocmask

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

sigprocmask는 해당 process로 전달된 signal들이 block되도록 한다.

Argument

  • int how : sigprocmask를 어떻게 수행할지 결정
    - SIG_BLOCK : 기존에 있는 set에 추가
    - SIG_UNBLOCK : 기존에 있는 set에서 제거
    - SIG_SETMASK : 해당 set으로 지정
  • const sigset_t *set : 전달할 signal mask
  • const sigset_t *oset : 현재 signal mask를 저장

Return

  • 성공 시 0 return
  • error 발생 시 -1 return

Example : sigprocmask

#include <signal.h>

int main () {
	sigset_t set1, set2;
    
    // set1은 전체
	sigfillset(&set1);
    
    // set2는 2개 제외 전체
	sigfillset(&set2);
	sigdelset(&set2, SIGINT);
	sigdelset(&set2, SIGQUIT);
    
	// set1로 대체 = 전부 block
	sigprocmask(SIG_SETMASK, &set1, NULL);
    
	// set2를 제외 = 2개만 block
	sigprocmask(SIG_UNBLOCK, &set2, NULL);
    
	// 전체를 unblock
	sigprocmask(SIG_UNBLOCK, &set1, NULL);
}

위 코드는 sigprocmask에 대한 각 how의 활용 예제이다.

6.4 Sending Signals

process는 자기 자신에게, 다른 process에게 혹은 process group에게 signal을 보낼 수 있다.

System Call : kill

#include <signal.h>

int kill(pid_t pid, int signo);

kill은 process나 process group에게 signal을 보낸다. sender process는 signal을 받을 receiver process의 pid를 알 필요가 있으며, 이는 주로 child와 parent와 같이 밀접하게 관련된 process 사이에서 활용한다.

또한 sender의 real or effective uid가 receiver의 effective uid와 동일해야 signal을 보낼 수 있다. super user는 누구에게나 signal을 보낼 수 있다.

Return

  • 성공 시 return 0
  • error 발생 시 return -1

Arguments

  • pid : pid에 따라 보낼 대상이 달라짐
    - > 0 : process ID가 pid인 process에게
    - == 0 : sender의 process group ID와 동일한 process group ID를 가진 모든 process에게
    - < 0 : pid의 절댓값과 동일한 process group ID를 가지는 모든 process에게
    - == -1 : sender가 signal을 보낼 권한이 있는 모든 process에게
  • signo : 보낼 signal의 signal number

Example : kill

/* synchro */
#include <unistd.h>
#include <signal.h>

int ntimes = 0;
void p_action (int), c_action (int);

int main() {
	pid_t pid, ppid;
	static struct sigaction pact, cact;

	pact.sa_handler = p_action;
	sigaction(SIGUSR1, &pact, NULL);

	switch (pid = fork()) {
		case -1:
			perror (“synchro”);
			return 1;
		case 0:
			cact.sa_handler = c_action;
			sigaction (SIGUSR1, &cact, NULL);

			ppid = getppid();
			for (;;) {
				sleep(1);
				kill(ppid,SIGUSR1);
				pause();
			}
		default:
			for (;;) {
				pause();
				sleep(1);
				kill(pid,SIGUSR1);
			}
	}
	return 0;
}

void p_action (int sig) {
	printf (“Parent caught signal #%d\n”, ++ntimes);
}

void c_action (int sig) {
	printf (“Child caught signal #%d\n”, ++ntimes);
}

위 코드는 kill을 활용하는 예제이며, p_actionc_action은 handler로 몇 번째인지를 출력하는 역할을 한다. parent와 child가 서로 signal을 주고받는 것을 반복하는 코드이며, handler는 fork시 자식에게 유전된다는 것을 유의해야 한다.

System Call : raise

#include <signal.h>

int raise(int sig);

raise는 자기 자신에게 signal을 보낸다.

Return

  • 성공 시 retur 0
  • error 발생 시 return -1

System Call : alarm

#include <unistd.h>

unsigned int alarm(unsigned int secs);

alarm은 말 그대로 특정 시간 이후에 SIGALRM signal을 스스로에게 보내는 역할을 한다. 기본 동작은 process 종료이며, alarm은 하나만 존재한다. 즉, 다시 alarm을 call하면 처음에 call한 alarm을 대신하게 된다. alarm(0)은 만료되지 않은 alarm을 종료시킨다.

Return

  • 이전에 설정된 alarm의 초를 return
  • 이전 alarm이 없는 경우 return 0

System Call : pause

#include <unistd.h>

int pause(void);

pause는 signal이 전달될 때까지 process를 sleep하도록 한다.

Return

  • signal이 catch되고, catch function이 return된 경우에만 return
  • return -1과 함께 errno = EINTR

signal에 대한 action이 process 종료일 경우에는 pause는 return하지 않으며, signal을 catch하는 function을 실행하는 것이라면 해당 function이 return한 이후에 pause가 return된다.

profile
festina lenta

0개의 댓글