벽에 부딪히면 돌아오는 공을 받아쳐서 블록을 깨는 게임을 본 적이 있을 것이다. 최초의 아케이드 비디오게임 중 하나인 PONG 이 이런 방법으로 탁구와 같은 게임을 만들었는데, 우리는 이것을 Space Programming이라 합니다.
curses library: 텍스트 기반의 UI를 위한 터미널 제어 라이브러리
터미널 스크린: 문자를 표시하기 위한 그리드 형태의 셀로 구성되며, 왼쪽 최상단에서 시작
curse.h
| Function | Description |
|---|---|
initscr() | curse 라이브러리 및 tty 초기화 |
endwin() | curse 모드 종료 |
refresh() | 화면 업데이트(갱신): refresh()를 호출하여 변경 사항을 반영 |
move(row, col) | 커서를 특정 위치(row, col)로 이동 |
addstr(s) | 해당 문자열(s)을 현재 위치에 출력 |
addch(c) | 하나의 문자(c)를 현재 위치에 출력 |
clear() | 화면을 지움 |
standout() | standout 모드 on(화면 reverse) |
standend() | standout 모드 off |
위에서는 출력하는 방법을 알아봤다면, 이번엔 이미지의 애니메이션 효과 및 이미지 이동에 대한 내용입니다.
#include <stdio.h>
#include <unistd.h>
#include <curses.h>
#define LEFTEDGE 10
#define RIGHTEDGE 30
#define ROW 10
int main() {
char *message = "Hello";
char *blank = " "; // message 길이와 같음
int direction = 1;
int position = LEFTEDGE;
initscr();
clear();
while (1){
move(ROW, position); // (10, 30)으로 이동
addstr(message);
move(LINES - 1, COLS - 1); // cursor 위치 이동
refresh();
sleep(1);
move(ROW, position);
addstr(blank); // 텍스트 지우기
position += direction; // 1칸 이동
if (position <= LEFTEDGE)
direction = 1; // 왼쪽 벽에 닿으면 방향 바꾸기
if (position <= RIGHTEDGE)
direction = -1; // 오른쪽 벽에 닿으면 방향 바꾸기
}
return 0;
}프로그램에 초 단위 delay 추가
sleep(sec) 함수
sleep() 동작 과정
signal(SIGAARM, handler) 호출
alarm(num_seconds) 호출
pause() 호출

매 10.5초 마다 공이 빠르게 움직이는 경우
Taxi 요금 계산
Interval Timer
각 프로세스는 3개의 타이머를 가짐
위의 그림은 하나의 프로그램이 실행을 시작한 후 30초 후에 종료되는 경우를 나타냄예시
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
void count_down(int signum){
static int num = 10;
printf("%d..", num--);
fflush(stdout);
if (num < 0) {
printf("DONE!\n");
exit(EXIT_SUCCESS);
}
}
int set_ticker(int n_msecs){
struct itimerval new_timeset;
long n_secs, n_usecs;
n_secs = (long)(n_msecs / 1000); // 초기 타이머 값 0초
n_usecs = (long)(n_msecs % 1000) * 1000L; // 반복 타이머 값 0.5초
new_timeset.it_value.tv_sec = n_secs;
new_timeset.it_value.tv_usec = n_usecs; // 시작 타이머
new_timeset.it_interval.tv_sec = n_secs;
new_timeset.it_interval.tv_usec = n_usecs; // 인터벌 타이머
return setitimer(ITIMER_REAL, &new_timeset, NULL); // 타이머가 만료 될 때 마다 SIGALRM 시그널 발생
}
int main(){
signal(SIGALRM, count_down); // SIGALRM 시그널이 발생할 때 마다 countdown
if (set_ticker(1000) == -1) {
perror(NULL);
exit(EXIT_FAILURE);
}
else {
while (1)
pause();
}
return 0;
}
signal() 함수를 사용하면 하나의 시그널을 처리하는 것은 효과적이지만, 여러 signal이 도착했을 때는 문제가 될 수 있다.
실제로 signal을 연속적으로 발생 시키고, 그에 따른 handler가 실행되고 있는 중에는 signal이 무시됨.
위와 같은 signal 함수의 문제로 인해 이를 대체할 함수가 있는데, 그것이 sigaction이다.
usage: sigaction( int signum, const struct sigaction *action, struct sigaction *prevaction )
sigaction은 함수명도, 구조체명도 다 sigaction이다.
struct sigaction
{
/* use only one of these two */
void (*sa_handler)(int); /* Old style:SIG_DFL, SIG_IGN, or function */
void (*sa_sigaction)(int, siginfo_t *, void *); /* New style handler */
sigset_t sa_mask; /* signals to block while handling */
int sa_flags; /* enable various behaviors */
}
sa_sigaction을 사용하려면 sa_flags 멤버 변수에 SA_SIGINFO bit 값을 1로 설정하면 된다.
sa_flags: handler의 동작 방식을 제어
| sa_flags 값 | Meaning |
|---|---|
| SA_RESETHAND | signal 발생 handler 호출 후 signal을 SIG_DFL로 설정 |
| SA_NODEFER | 해당 signal을 처리하는 중에 같은 signal이 발생하면 kernel이 blocking 하지 못하게 설정 |
| SA_RESTART | signal handler에 의해 중지된 시스템 호출을 handler 처리 이후 자동 재시작 |
| SA_SIGINFO | 1이면 sa_sigaction, 0이면 sa_handler 수행 |
sa_mask
siginfo_t
이제 위의 내용을 다 사용한 예제 하나를 보자.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pwd.h>
#define INPUTLEN 100
char *uid_to_name(uid_t uid){
struct passwd *pw_ptr;
static char numstr[10];
if ((pw_ptr = getpwuid(uid)) == NULL){
sprintf(numstr, "%d", uid);
return numstr;
}
else
return pw_ptr->pw_name;
}
void int_handler(int sig, siginfo_t *siginfo, void *context){
printf("Error value %d, Signal code %d\n", siginfo->si_errno, siginfo->si_code);
printf("Sending UID %-8s\n", uid_to_name(siginfo->si_code));
printf("Called with signal %d\n", sig);
sleep(sig);
printf("done handling signal %d\n", sig);
}
int main(){
struct sigaction new_handler;
sigset_t blocked;
char x[INPUTLEN];
new_handler.sa_sigaction = int_handler;
new_handler.sa_flags = SA_RESETHAND | SA_RESTART | SA_SIGINFO;
sigemptyset(&blocked);
sigaddset(&blocked, SIGQUIT);
new_handler.sa_mask = blocked;
if (sigaction(SIGINT, &new_handler, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
} else {
while (1) {
fgets(x, INPUTLEN, stdin);
printf("input: %s", x);
}
}
}
위의 코드를 빌드하고 ^C ^C, ^C ^\ 를 하면 각각 아래와 같은 결과가 출력된다.

잘 보면 SIGINT 신호 발생 후에 int_handler를 수행하는 중에는 다른 signal은 blocking 됨을 볼 수 있다.
sigaddset(&blocked, SIGQUIT);
new_handler.sa_mask = blocked;
이 코드가 blocking 하는 역할을 하고,
new_handler.sa_flags = SA_RESETHAND | SA_RESTART | SA_SIGINFO;
이 코드가 blocking 됐던 signal을 재시작하게 해준다.
sa_mask: signal handler에서 signal들을 blocking
sigprocmask: process에서 signal들을 blocking
예제를 보면 쉽게 이해할 수 있다.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(void){
sigset_t mask, prev;
sigemptyset(&mask);
sigaddset(&mask, SIGINT); //mask에 SIGINT 추가
sigprocmask(SIG_BLOCK, &mask, &prev); // SIGINT blocking
printf("SIGINT blocked.\n");
for (int count = 3; count > 0; count--){
printf("count %d\n", count);
sleep(1);
} // 이 반복문이 실행되는 동안은 SIGINT는 무시됨
printf("SIGINT unblocked.\n");
sigprocmask(SIG_SETMASK, &prev, NULL); // SIGINT unblocking(만약 SIGINT를 발생시켰었다면 여기서 종료
sleep(5);
printf("End\n");
return 0; // 발생시킨 적이 없다면 여기서 종료
}
하나의 프로세스는 다른 동일한 user ID를 갖는 프로세스에게 signal을 전달할 수 있음
kill() 함수를 사용해서 다른 프로세스 종료 가능
간단한 예제를 보자.
killer.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(){
int sig;
int id;
while (1){
printf("Enter PID: ");
scanf("%d", &id);
printf("Enter signal: "); //SIGKILL: 9
scanf("%d", &sig);
printf("Send signal %d to %d\n", sig, id);
kill(id, sig);
}
return 0;
}
killed_proc.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(){
int count = 0;
while (1)
{
printf("PID: %i\n", getpid());
printf("Seconds in process: %i\n", count);
sleep(2);
count = +2;
}
return 0;
}

killed_proc을 실행 시킨 후 killer를 실행하면 올바른 PID를 입력하고 signal에 9를 입력하면 killed_proc이 종료되는 것을 볼 수 있다.