이것은 한낱 대학생이 교수의 수업을 듣고 작성한 개인저장용 복습 문서입니다.
그렇지만, 물론 지적과 수정은 환영합니다.
Basic signal concept
- 시그널은 소프트웨어적 통지수단.
- 어떤 일이 일어났을때 그를 알려주는 방법 중 하나.
- 생성되었지만 아직 전달되지 않은 신호는 pending 되었다라고 한다.
- signal이 signal handler를 실행시키면서 프로세스가 해당 signal을 포착한다.
- sigaction을 조정하여 signal handler를 호출하거나 SIG_DFL로 기본기능수행, SIG_IGN로 신호를 무시할 수있다.
- signalmask를 통하여 해당 시그널을 차단할 수 있다.
모든 시그널은 SIG로 시작하는 symbolic name이 있다.
- signal.h에 정의되어있음.
kill command를 통해 시그널 발생 가능
- Ex: kill –s USR1(signal이름) 3423(process id)
- Ex: kill -9(signal number) 3423
- kill -l => 가능한 signal목록 출력
kill –s signal_name pid ..
Kill –l [exit_status]
Kill [-signal_name] pid..
Kill [-signal_number] pid..
Generating signals
#include <signal.h>
int kill(pid_t pid, int sig);
- pid : 타겟 프로세스의 ID, 0으로 주면 caller process의 그룹 프로세스 전체 타겟, -1을 주면 전체 프로세스 타겟, -1보다 작은 값주면 해당 값의 절댓값 프로세스의 그룹 전체 타겟
- 성공시 0리턴, 실패시 -1리턴.
int raise(int sig)
– 나에게 시그널 보내기
unsigned alarm(unsigned seconds)
– 나에게 알람시그널 발생, 0을 주면 미리 설정되었던 알람 취소
Signal sets
#include <signal.h>
int sigaddset(sigset_t* set, int signo);
int sigdelset(sigset_t* set, int signo);
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigismember(const sigset_t* set, int signo);
Signal set
- add, del: set에 시그널 추가 / 삭제 (성공시 0, 실패시 -1)
- empty : 빈 시그널 세트 초기화 (성공시 0, 실패시 -1)
- fill : 모든 시그널이 채워진 세트 초기화 (성공시 0, 실패시 -1)
- member : 특정 시그널이 있는지 확인 (있으면 1, 없으면 0)
Signal masks
일련의 signal_set을 signal mask에 추가하거나 제거한다.
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
-how : SIG_BLOCK(추가), SIG_UNBLOCK(제거), SIG_SETMASK(초기화 후 추가)
-set : 조정에 사용할 signal_set
-oset : 아웃풋 파라미터로서 변경전 signal mask의 상태를 저장 (보통 signal mask 수정 후 원래 상태로 되돌리는 용도로 사용)
-cf. *restrict : c에서 사용하는 키워드, 현재 과정에서는 무시해도 좋음.
-성공시 0, 실패시 -1 리턴
-single thread에서만 사용해야함.
-SIGSTOP, SIGKILL은 추가해도 pending되지 않는다.
SIGINT 추가하기
1
2
3
4
5
6
|
sigset_t newsigset;
if((sigemptyset(&newsigset) == -1 ||
(sigaddset(&newsigset, SIGINT) == -1))
perror(“Failed to initialize the signal set”);
else if( sigprocmask(SIG_BLOCK, &newsigset, NULL) == -1)
perror(“Failed to block SIGINT”);
|
sigpromask 만들었다가 다시 제거하기
1
2
3
4
5
6
7
8
9
|
sigset_t blockmask;
sigset_t oldmask;
… //add signals to blockmask
if( sigprocmask(SIG_SETMASK, &blockmask, &oldmask) == -1)
return -1;
…
if( sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1)
return -1;
…
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
|
Catching and ignoring signals
#include <signal.h>
int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);
- sigaction은 해당 신호에 대한 signal handler를 변경할 수 있는 함수이다.
- sigaction함수는 sigaction 구조체를 파라미터로 가진다.
- sig : signal 넘버
- act : signal에 지정된 signal handler함수
- oact : 원래 지정되어 있던 signal handler 저장
- 성공시 0, 실패시 -1 리턴
struct sigaction{
void (*sa_handler)(int); /* SIG_DFL, SIG_IGN or pointer to function */
sigset_t sa_mask; /* additional signals to be blocked during execution of handler*/
int sa_flags; /* special flags and options */
void (*sa_sigaction) (int, siginfo_t *, void *); /* realtime handler */
}
- sa_handler 와 sa_sigaction 함수에 수행할 함수를 등록한다.
- sa_flag를 지정하여 무슨 함수를 사용할지 선택하기도 한다.
- SIG_DFL: signal에 대한 default signal handler
- SIG_IGN: signal을 무시하는 signal handler
struct sigaction newact;
newact.sa_handler = mysighand;
newact.sa_flags = 0;
if(( sigemptyset(&newact.sa_mask) == -1) || ( sigaction(SIGINT, &newact, NULL) == -1))
perror(“Failed to install SIGINT signal handler”);
- 구조체를 선언하여 필드값을 수정한다음, sigaction함수를 호출하는 구조이다.
struct sigaction act;
if(sigaction(SIGINT, NULL, &act) == -1)
perror(“Failed to get old handler for SIGINT”);
else if( act.sa_handler == SIG_DFL ){
act.sa_handler = SIG_IGN;
if( sigaction(SIGINT, &act, NULL) == -1)
perror(“Failed to ignore SIGINT”);
- act에 현재값 불러와서 해당것이 DFL이면, IGN으로 바꾸고 해당 act를 다시 등록한다.
program 8.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
static volatile sig_atomic_t doneflag = 0;
/* ARGSUSED */
static void setdoneflag(int signo) {
doneflag = 1;
}
int main (void) {
struct sigaction act;
int count = 0;
double sum = 0;
double x;
act.sa_handler = setdoneflag; /* set up signal handler */
act.sa_flags = 0;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGINT, &act, NULL) == -1)) {
perror("Failed to set SIGINT handler");
return 1;
}
while (!doneflag) {
x = (rand() + 0.5)/(RAND_MAX + 1.0);
sum += sin(x);
count++;
printf("Count is %d and average is %f\n", count, sum/count);
}
printf("Program terminating ...\n");
if (count == 0)
printf("No values calculated yet\n");
else
printf("Count is %d and average is %f\n", count, sum/count);
return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
|
- 사용자가 ctrl + c 를 하여 SIGINT를 보내기 전까지 계산을 계속 수행
Waiting for signals
호출시 잠들어 있다가 시그널이 도착하면 깨어나도록 설정할 수 있다. (pause(), sigsuspend(), sigwait())
Pause function
#include <unistd.h>
int pause(void);
- 아무 시그널이 오면 깨어나고, 언제나 -1을 리턴한다.
- signal handler이 있는 시그널일 경우 해당 작업을 수행한 뒤 리턴된다.
static volatile sig_atomic_t sigreceived = 0;
while( sigreceived == 0 )
pause();
- signal_handler가 sigreceived를 1로 바꾸어 활용한다.
- pause 전에 시그널이 catch될 경우 제대로 수행이 안되거나 영원히 pause에서 빠져나오지 못할 수 있다.
- signal_mask를 활용해 해당 signal을 block했다가 pause를 할 때 다시 unblock하는 방법 제안
- 하지만 이 방법 역시 그 사이에 signal이 오면 같은 문제가 발생한다
- 새로운 방법 필요
Sigsuspend function
#include <unistd.h>
int sigsuspend(const sigset_t* sigmask);
- 자체적으로 signal_mask를 가져, 해당 signal을 block하고 있다가 pause와 동시에 unblock할 수 있도록 한다.
- mask를 바꿔주는 방식으로 구동
sigfillset(&sigmost);
sigdelset(&sigmost, signum);
sigsuspend(&sigmost);
- 시그널을 다넣고, 시그널에서 하나를 제거한다음, 해당 signal_mask를 suspend에게 준다.
- pause가 되는 순간 해당하는 signal_mask를 적용했다가 pause가 끝나면(리턴되면) 풀어준다.
- 즉, sigsuspend가 되는 동안
- 항상 -1을 리턴한다.
static volatile sig_atomic_t sigreceived = 0;
sigset_t maskall, maskmost, maskold;
int signum = SIGUSR1;
sigfillset(&maskall);
sigfillset(&maskmost);
sigdelset(&maskmost, signum);
sigprocmask(SIG_SETMASK, &maskall, &maskold);
if(sigreceived == 0)
sigsuspend(&maskmost);
sigprocmask(SIG_SETMASK, &maskold, NULL);
- USR1만 열고 잠든다.
- while문 필요없음에 주의
- 다른 시그널을 막아버린다는 것이 단점
- cf. pending된 시그널은 사라지는 것이 아니라 대기하고 있다가 받아진다.
static volatile sig_atmoic_t sigreceived = 0;
sigset_t maskblocked, maskold, maskunblocked;
int signum = SIGUSR1;
sigprocmask(SIG_SETMASK, NULL, &maskblocked);
sigprocmask(SIG_SETMASK, NULL, &maskunblocked);
sigaddset(&maskblocked, signum);
sigdelset(&maskunblocked, signum);
sigprocmask(SIG_BLOCK, &maskblocked, &maskold);
while(sigreceived == 0)
sigsuspend(&maskunblocked);
sigprocmask(SIG_SETMASK, &maskold, NULL);
- 현재 sig_set의 상태를 두개 받아온다.
- 그리고 한군데에는 USR1을 넣고 한군데에는 빼준다.
- 우선 USR을 포함한 signal_make를 설정한다.
- 그 후 del된 것을 sigsuspend처리하여 열어준다.
- 마지막으로는 signal_mask의 상태를 다시 돌려준다.
Sigwait function
#include <signal.h>
int sigwait(const sigset_t *restrict sigmask, int *restrict signo);
- sigmask에 들어가 있는 어떤 signal이든, 기다렸다가 오면 pending처리한다. 그리고 pending된 signal들이 저장된 pending signals에서 해당 정보를 삭제한다.
- 그리고 output파라미터인 signo에 해당 정보가 저장된다. (결과적으로 signal이 전달되는 것은 아니다.)
- 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다.
- sigsuspend와는 다르게 signal_mask를 건들지도 않고, signal_handler를 호출하지도 않는다.
- 그저 그 밑의 구문이 이어서 실행된다.
PROGRAM 8.11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
int signalcount = 0;
int signo;
int signum = SIGUSR1;
sigset_t sigset;
if ((sigemptyset(&sigset) == -1) ||
(sigaddset(&sigset, signum) == -1) ||
(sigprocmask(SIG_BLOCK, &sigset, NULL) == -1))
perror("Failed to block signals before sigwait");
fprintf(stderr, "This process has ID %ld\n", (long)getpid());
for ( ; ; ) {
if (sigwait(&sigset, &signo) == -1) {
perror("Failed to wait using sigwait");
return 1;
}
signalcount++;
fprintf(stderr, "Number of signals so far: %d\n", signalcount);
}
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
|
- SIGUSR1에 해당하는 signal이 오면 시그널 횟수를 1증하여 보여준다.
Errors and Async-signal safety
- POSIX 함수가 signal에 의해 interrupt되는 함수라면, r_read / r_write 등의 함수처룸 해당 signal을 다뤄 정상적으로 돌아갈 수 있어야 한다.
- signal handler를 사용할때, async_singal safe한 함수만 사용해야 한다. (리스트 존재)
- printf는 async_signal safe하지 않은 반면, write는 async_signal safe하다.
- errno 변수를 사용할 경우, errno은 쉽게 덮여지니 saveerrno = errno; 등의 기법으로 원래 errno을 저장해놓는 방법도 존재한다.
- 애매할때는 그냥 프로그램코드 내에서 재시작 라이브러리를 사용
- signal handler에 사용된 능이 async_singal safe한지 확인
- 외부 변수를 변경하는 signal handler와 변수에 액세스하는 다른 프로그램 코드 간의 잠재적 상호 작용을 분석
- 적절한 경우 errno를 저장하고 다시 복원 (saveerrno)
에러 발생 시, flag변수 등을 활용해 코드를 조정할 수도 있지만 불편 따라서
밑의 함수활용.
Sigsetjmp and siglongjmp
#include <setjmp.h>
void siglongjmp(sigjmp_buf env, int val); // 뛰기
int sigsetjmp(sigjmp_buf env, int savemask); // 뛸 지점 세팅
- env : 나중에 뛰어야 할 지점 가르킨다.
- val : sigsetjmp에 의해 세트된 지점으로 갔을 때의 리턴값
- savemask : 0이 아니라면, env buffer에 현재의 signal_mask 상태가 저장된다. (0이면 초기화)
- return values : 직접 불렸을 경우엔 0 리턴, siglongjmp에 의해 호출되면 해당 val값 리턴
- 같은 sigjmp_buf값을 가진 sigsetjmp로 돌아간다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t jumpok = 0;
/* ARGSUSED */
static void chandler(int signo) {
if (jumpok == 0) return;
siglongjmp(jmpbuf, 1);
}
int main(void) {
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = chandler;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGINT, &act, NULL) == -1)) {
perror("Failed to set up SIGINT handler");
return 1;
}
/* stuff goes here */
fprintf(stderr, "This is process %ld\n", (long)getpid());
if (sigsetjmp(jmpbuf, 1))
fprintf(stderr, "Returned to main loop due to ^c\n");
jumpok = 1;
for ( ; ; )
; /* main loop goes here */
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
|
- signal handler 로서 chandler를 설정
- jumpok : 일종의 flag로서, 뛸지점이 정해지지 않았을때 signal handler가 작동되는 것을 방지
- 반복문을 돌다가 signal이 발생하면 signal handler가 호출된다.
Programming with asynchronous I/O
Asynchronous I/O
- 몇몇 application는 프로그램 실행과 동시에 I/O 작업을 asynchronous하게 처리할 수 있음.
- 아래 4가지 기능을 활용하여 이를 정의
- aio_read(), aio_write(), aio_return(), aio_error()
#include <aio.h>
int aio_read(struct aiocb* aiocbp);
- 프로세스가 읽기 대기상태에 들어감.
- aiocbp를 읽는다. → aio_nbytes만큼 aiocbp-aio_fildes와 연관된 파일에서 aiocbp에서 구분한 buffer에 -> aio_buf
- 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다.
#include <aio.h>
int aio_write(struct_aiocb* aiocbp);
- 프로세스가 쓰기 대기상태에 들어감.
- 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다.
Struct aiocb structure
- int aio_fildes; : volatile void *aio_buf; / size_t aio_nbytes;
- off_t aio_offset; : I/O의 시작점을 정해준다.
- int aio_reqprio; : 요청에 대한 우선순위를 낮춘다.
- struct sigevent aio_sigevent; : 프로세스에 어떻게 완료를 알릴지 결정, aio_sigevent.sigev_notify가 SIGEV_NONE이면 OS가 신호를 생성하지 않음, SIGEV_SIGNAL 인 경우 OS는 aio_sigevent.sigev_signo에 지정된 신호를 생성.
- int aio_lio_opcode; : 여러 I/O 신호를 보내는데에 활용
#include <aio.h>
ssize_t aio_return(struct aiocb* aiocbp);
- 준비된 I/O 명령의 상태를 리턴.
- Return values : 명령이 완료되었을 때 읽거나 쓴 byte 수를 검색합니다.
#include <aio.h>
int aio_error(const struct aiocb* aiocbp);
- asynchronous I/O 명령의 과정을 모니터링한다.
- 성공적일 경우 0을 리턴하고, 계속 실행 중이면 EINPROGRESS를, 실패하면 error code를 리턴한다.
aio_suspend
#include <aio.h>
int aio_suspend(const struct aiocb * const list[], int nent, const struct timespec* timeout);
- timeout : NULL이면 하나의 I/O 작업만 완료되어도 반환, NULL이 아니면 지정된 시간 종료후 반환
- 즉 이를 활용하면 aio_error가 EINPROGRESS하지 않게된다.
- aiocb 구조체 안에 시그널 번호를 셋팅한다.
- nent는 aiocb 구조체의 개수이다.
- 성공하면 0, 실패하면 -1 리턴과 동시에 errno을 재설정한다.
aio_cancel
#include <aio.h>
int aio_cancel(int fildes, struct aiocb* aiocbp);
- 하나 혹은 그 이상의 asynchronous I/O 요청을 취소한다.
- aiocbp : 내가 요청했던 취소하려는 aiocb 구조체
- fildes : 한꺼번에 취소가 가능하도록 한다. aiocbp를 NULL로 줄경우 해당 filedes의 모든 작업이 취소된다.
- 성공시 AIO_CANCELED 리턴, 실패시 AIO_NOTCANCELED 리턴, 이미 완료된 작업에 대해 요청할 경우 AIO_ALLDONE 리턴, error발생시 -1 리턴
'💻 CS > 시스템프로그래밍' 카테고리의 다른 글
[시스템프로그래밍] POSIX Threads (0) | 2019.12.10 |
---|---|
[시스템프로그래밍] Times and Timers (0) | 2019.12.07 |
[시스템프로그래밍] UNIX Special Files (0) | 2019.12.01 |
[시스템프로그래밍] Files and Directories (0) | 2019.11.26 |
[시스템프로그래밍] UNIX I/O - 2 (0) | 2019.11.07 |