[시스템 프로그래밍] 시그널
컴퓨터는 다수의 프로세스를 concurrent 하게 실행시킨다.
각각의 프로세스들이 OS에 의해 문맥이 전환되기 때문에 여러 가지 프로그램이 병행적으로 작동하는게 가능하고, 여기서 시그널이 사용된다.
시그널은 어떤 이벤트가 시스템에 발생했음을 프로세스에게 알려주는 메세지이다.
예외 상황과 인터럽트를 커널에서 추상화 한 개념이고, 커널을 통해 프로세스에게 전달된다.
각각의 시그널들은 정수 값의 아이디로 구분된다.
SIGSEGV : 잘못된 주소로 접근했을 때 발생한다.
SIGCHLD : 자식이 종료되면 부모에게 SIGCHLD가 보내진다.
커널은 목적지 프로세스의 컨텍스트 내부 상태를 갱신하는 방법으로 시그널을 목적지 프로세스에 보낸다.
무슨 소리인지 자세히 알아보자.
메모리에는 프로세스를 돌리기 위한 자료구조가 존재하고, 이 자료구조 내부에 프로세스에 사용되는 변수가 있다.
시그널이 발생하면 이 변수의 값을 설정하는 방식을 사용한다. (ex. 5번 시그널이 발생하면 5번 변수를 1로 설정)
커널이 시스템 이벤트를 감지하거나 다른 프로세스가 kill 시스템 콜을 호출해 커널이 목적지 프로세스로 시그널을 보낼 것을 요청했을 때 커널은 해당 프로세스에 시그널을 보낸다.
시그널을 받았을 때는 세 가지 반응 중 하나를 선택해서 반응한다.
1. 무시 : 수신한 시그널을 무시한다.
2. 종료 : 대상 프로세스를 종료시킨다. (디버깅 정보를 가질 수 있다)
3. 처리 : 시그널 핸들러라고 불리는 유저 레벨 함수를 실행해 시그널을 잡는다. (컴퓨터구조에서의 인터럽트 발생과 유사하다)
시그널 관련 용어에 대해 알아보자.
커널은 모든 프로세스의 컨텍스트에 변수를 만들어 프로세스를 관리한다.
그 중 pending과 blocked 비트벡터를 사용하고, pending 변수를 통해 프로세스에 어떤 시그널들이 pending 상태인지 확인하고, blocked 변수를 통해 프로세스에 어떤 시그널들이 blocked 돼 있는지 확인한다.
1. Pending
시그널이 전송됐지만, 아직 수신되지 않은 상태이다.
특정 타입의 시그널에 대해서는 최대 한 개의 대기 시그널이 존재한다.
a타입의 대기 시그널을 가진 프로세스에 또 a타입의 시그널이 발생하면 무시된다. (다른 타입은 pending 된다)
대기 시그널을 나타내기 위해 비트벡터를 사용한다.
a타입 시그널이 도착할 때 마다 pending 값의 a번째 비트를 1로 설정한다.
a타입 시그널을 수신할 때 마다 pending 값의 a번째 비트를 0으로 설정한다.
2. Block
프로세스는 특정 시그널의 수신을 block 할 수 있다.
여기에서도 예외는 있다. SIGKILL 시그널은 프로세스에서 블록 될 수 없다.
sigprocmask 함수를 사용해 blocked 변수의 값을 설정한다.
block 된 시그널이 발생하지 않는건 아니지만, signal을 block 할 수 있다.
각각의 프로세스는 하나의 프로세스 그룹에 속한다.
foreground에서 실행되는 그룹은 하나 뿐이고, background에서 실행되는 그룹은 여러 개가 있을 수 있다.
기본적으로 자식은 부모와 같은 그룹에 속하기 때문에 필요 시 그룹을 바꿔 줘야 한다.
getpgrp() : 프로세스의 프로세스 그룹을 반환한다.
setpgid() : 프로세스의 그룹을 변경한다.
kill 명령어를 통해 프로세스나 프로세스 그룹에게 임의의 시그널을 보낼 수 있다.
ex.) kill -9 24818 : 9번 시그널을 24818 프로세스에게 보냄 (9번은 SIGKILL)
그룹에게 시그널을 줄 때는 -를 붙인다.
ex.) kill -9 -24817
그룹 전체가 시그널의 영향을 받는다.
키보드로 Ctrl + C 을 누르면 SIGINT 시그널이 foreground 프로세스 그룹의 모든 프로세스에 전달된다. (키보드 입력은 foreground 프로세스와 연결돼있다)
시그널을 받을 때는 비트벡터를 활용한다.
pnb = pending & ~blocked 를 계산해 pnb 값이 0이라면 다음 명령어로 제어를 넘겨주고, 1이라면 프로세스가 시그널을 받아 처리한다.
각각의 시그널 타입은 사전에 정의된 기본 동작을 가지지만, signal() 함수를 사용해 기본 동작을 변경 할 수 있다.
단, SIGSTOP과 SIGKILL은 예외다. 기본 동작을 바꿀 수 없다.
프로세스에 시그널이 도착하면 뭘 하고 있든 잠시 멈추고 시그널 핸들러를 호출한다.
이후 시그널 핸들러가 종료되면 프로세스도 함께 리턴된다.
코드 분석 시 이 부분에 유의하자.
프로그램이 전역변수에 동시에 접근할 때 signal을 사용하는 경우 Race 현상이 발생할 수 있다.
fork - execve 로 자식은 새로운 프로그램을 실행하고 프로그램은 deletejob을 수행한다.
부모는 addjob을 수행하는데..
자식과 부모가 race condition에 놓여 부모보다 자식이 deletejob을 먼저 수행하게 되면 예외가 발생한다.
따라서 addjob이 무조건 deletejob보다 먼저 발생될 수 있도록 처리 해 줘야 한다.
race condition을 제거하는 방법으로 spin loop를 사용해 무한 루프를 돌게 하는 방법도 있지만, 이렇게 구현할 시 CPU 사이클이 낭비된다는 단점이 있다.
여러 가지 좋은 방법이 있다.
sigsuspend를 사용하면 race condition을 효과적으로 제거할 수 있다.
while(!pid) {
sigprocmask(SIG_SETMASK, &prev, &pprev);
pause();
sigprocmask(SIG_SETMASK, &pprev, NULL);
}
sigsuspend는 위의 작업을 원자 단위로 실행해 다른 작업이 위의 작업에 끼어들 수 없도록 한다.
'Computer Science > System Programming' 카테고리의 다른 글
[시스템 프로그래밍] Data Lab (0) | 2022.12.20 |
---|---|
[시스템 프로그래밍] 동적 메모리 (1) | 2022.11.30 |
[시스템 프로그래밍] 프로세스 (2) (1) | 2022.11.06 |
[시스템 프로그래밍] 프로세스 (1) (0) | 2022.11.06 |
[시스템 프로그래밍] 프로시저 (0) | 2022.10.28 |
댓글
이 글 공유하기
다른 글
-
[시스템 프로그래밍] Data Lab
[시스템 프로그래밍] Data Lab
2022.12.20 -
[시스템 프로그래밍] 동적 메모리
[시스템 프로그래밍] 동적 메모리
2022.11.30 -
[시스템 프로그래밍] 프로세스 (2)
[시스템 프로그래밍] 프로세스 (2)
2022.11.06 -
[시스템 프로그래밍] 프로세스 (1)
[시스템 프로그래밍] 프로세스 (1)
2022.11.06