이번엔 wait와 쉘에서의 프로세스 관리에 대해서 설명해 볼 것이다.
앞서 본 내용에서는 자식 프로세스가 종료되는 경우, 커널은 프로세스를 시스템에서 직접 제거하지 않고, 부모 프로세스가 제거하도록 둔다.
만약 부모가 정상적으로 자식프로세스를 처리하지 못하면 init 프로세스가 청소하도록 한다.
wait
int wait( int *child_status) 형태이다.
현재 프로세스를 자신의 자식프로세스들 중 하나가 종료될 때까지 정지(기다리)하도록 하는 함수이다.
여기서 child_status는 자식프로세스의 종료이유를 알기 위해 파라미터로 전송한다.(이 주소 줄테니 여기다가 종료이유 적고 리턴해)
리턴 후 커널은 자식프로세스를 제거한다.
이 방식으로 부모는 자식프로세스를 명시적으로 삭제한다.

이 코드르 보자
여기서 fork로 자식 프로세스를 만든 후, 자식프로세스는 HC와 BYE를 호출 후 종료, 부모프로세스는 자식프로세스의 종료를 기다린 후, CT를 출력한다.
이를 그래프로 그려보면 다음과 같다.

또 다른 예시를 보자.
이번에 나올 예시는 두가지인데, 차이점을 잘 구분할 수 있어야한다.

위 코드에서는 이전과 다르게 포크를 n번한다 그럼 자식 프로세스도 n개가 생긴다.
자식프로세스는 그냥 만들어지자마자 죽는다.(ㅠ)
부모 프로세스는 이렇게 만든 n개의 자식들을 처리해야한다.
이를 위해서 부모는 만든 자식 수 만큼 for문을 통해 wait를 한다.
wait의 리턴값은 자식프로세스의 pid이고, child status에는 자식 프로세스의 종료 이유가 들어있다.
WITEXITED는 비정상종료 여부를 확인하기 위한 메크로이다. 정상일 경우 참을 리턴한다.
WEXITSTATUS는 child_status 안의 값으로 상태 정보를 리턴한다.
중요한건 이 방식을 사용하면, 자식들이 fork 된 순서와 상관없이, 부모는 먼저 종료된 자식 순서로 처리한다.
하지만

여기 코드를 보면, fork()를 할 때 리턴 값인 자식의 pid를 배열에 순서대로 저장한 후, waitpid를 통해 자식프로세스를 기다릴 때 특정 pid를 기다린다.
이렇게 하면 자식 프로세스를 만든 순서대로 처리가 가능하다.

로그를 보면 위와 같다. 위 코드에서 자식 프로세스가 exit할 때 상태 코드를 순서대로 넣어 종료했었다.
wait를 사용하였을 때에는 숫자가 뒤죽박죽이지만, waitpid를 사용했을 때에는 순서대로 출력되는 것을 볼 수 있다.
프로세스 sleep
unsigned int sleep(unsigned int secs)는 자기 자신을 secs 초만큼 정지시킨다.
정상적으로 깨어날 때는 0을 리턴하고, 그 외의 경우에는 잔여시간을 초로 리턴한다.
int pause(void)는 호출하는 프로세스를 시그널을 받을 때까지 잠재운다.
execve
int execve(char *filename, char *argv[], char *envp[])
이 함수는 실행파일 filename를 현재 프로세스의 환경변수를 이용하여 argv로 현재의 code, data, stack을 덮어씌운다.
현재 프로세스에서 다른 프로그램을 실행한다는 뜻이다.
execve는 호출되고, 에러가 없는 한 리턴되지 않는다.

위 그림은 리눅스 프로세스 체계이다.
fork는 부모의 복제인 자식프로세스에서 동일한 프로그램을 실행한다.
반면, exec는 현재 프로세스에서 새로운 프로그램을 실행하는 것이다. 즉, execve는 기존 프로세스와 동일한 pid를 가진다.
이 exec는 보통 부모프로세스에서 실행시키는 것이 목적이 아니라, 자식 프로세스 생성 -> 그 자식프로세스에서 실행할 프로그램 지정을 목적으로 한다.
쉘 프로그램 구현
쉘은 사용자의 명령어를 처리해주는 프로그램이다.

기본적으로 사용자 명령, 명령처리로 구성된다. while문으로 사용자가 종료할때까지 무한반복한다.
쉴에는 built-in 명령어와 utility program 명령어가 있다.
built in 명령어는 쉘에 내장되어있는 명령어이다. 따라서 쉘은 이 명령어를 받으면 그냥 바로 실행하면 된다.
반면 utility programm은 외부 파일로 존재하는 명령어이다. 따라서 쉘이 해당 위치에서 불러와 실행한다.
백그라운드 작업
유저는 보통 한번에 한개의 명령어를 실행한다.
하지만 어떤 프로그램은 실행시간이 길어서 유저가 해당 실행시간을 다 기다리기에는 비효율적인 일이 생긴다.
이를 위해 병렬로 처리되는 방식이 백그라운드 작업이다.
job
job이란, 쉘이 사용자의 한개의 명령줄을 실행해서 생성된 프로세스를 의미한다.
forground job는 0또는 1개, background는 0또는 여러개이다.(forground는 둘이상 존재할 수 없다.)
쉘은 각 job마다 별도의 그룹을 할당한다.
가끔 메인으로 실행되는 forground job을 일시정지시키고 싶을 때가 있다.
이를 위해서는 ctrl+Z 명령어를 입력하면 된다. 이때 fg를 입력한다면 정지된 현재 작업을 forground에서 실행한다는 의미이고, bg를 입력하면 backgroud에서 작업하겠다는 의미이다.
백그라운드 작업으로 명령어를 실행시키려면, 명령어 뒤에 &를 붙이면 된다.
이렇게 하면 해당 작업이 자식으로 생성되어 부모와 같이 수행되나, 백그라운드로 실행되므로, 키보드를 제어하지 않는다. (키보드 안먹음)

위 함수는 사용자가 입력한 문자열 명령문을 읽고 시행한다.
여기서 일단, parseLine을 통해서 사용자가 입력한 문자열의 뒤에 &가 있나 판단한다. (백그라운드 여부 판단)
그 다음 fork를 통해서 해당 명령어를 실행할 자식 프로세스를 만든다.(파일을 읽는다거나...)
그 다음 execve 함수를 활용해서 해당 자식프로세스에서 명령어를 실행하도록 한다.
이후 부모 프로세스는 입력된 명령어가 fg이면 방금 만든 자식 프로세스가 종료될떄까지 기다린다.
반면 bg실행이면, 자식 프로세스의 종료를 기다리지 않는다.(병렬처리)
이때 bg 실행 시에 발생할 수 있는 문제는 좀비프로세스 문제이다.
자식 프로세스가 끝나면 부모 프로세스가 이를 제거해야하는데, 부모 프로세스인 쉘은 거의 종료되지 않으므로 이를 처리할 수 없다. 이 결과로 커널 메모리 부족 현상이 발생할 수 있다.
이를 해결하기 위한 방법이 바로 시그널이다

출처
충남대 김형신교수님 시스템프로그래밍 강의자료
ESLAB | Home
eslab.cnu.ac.kr
'CSE > 시스템프로그래밍' 카테고리의 다른 글
| [시스템프로그래밍] 메모리2 (1) | 2025.12.14 |
|---|---|
| [시스템프로그래밍] 동적 메모리 할당 (1) | 2025.12.13 |
| [시스템 프로그래밍] 예외적인 제어흐름 - 프로세스 (1) | 2025.12.02 |
| [시스템프로그래밍] 실수의 표현 및 처리(1) (1) | 2025.10.21 |
| [시스템프로그래밍] 2장 정보의 표현 및 처리(정수의 표현) (1) | 2025.10.11 |