- 프로세스는 운영체제가 만든 프로그램을 실행시키고 종료할때까지 관리하기 위해 만든 정보 체계임을 안다.
- 프로세스의 구성, 주소 공간, 생명 주기 등에 관해 충분히 이해한다.
- 커널이 어떻게 프로세스를 관리하는지 이해한다.
- 프로세스들 사이의 부모 자식 계층 구조를 이해한다.
- 프로세스의 제어에 대해 이해한다.
자식 프로세스의 생성 과정 -fork()시스템 호출을 통해
프로세스의 오버레이 -exec()시스템 호출을 통해
자식 프로세스의 종료 대기 -wait()시스템 호출을 통해
프로세스의 종료 과정 -exit()시스템 호출을 통해
좀비 프로세스
프로그램(program)
하드디스크 등의 저장 매체에 저장하며, 실행 파일의 형태
프로세스(process)
프로세스는 실행 단위이다.
프로그램이 메모리에 적재되어 실행 중인 상태
필요한 모든 자원을 할당받음
자원: 코드/데이터/스택/힙 공간
프로세스의 특징
- 운영체제는 프로그램을 메모리에 적재하고 프로세스로 다룸
- 운영체제는 프로세스에게 실행에 필요한 메모리 할당,
이곳에 코드와 데이터 적재- 프로세스들은 서로 독립적인 메모리 공간을 가짐
다른 프로세스의 영역에 접근 불허- 운영체제는 각 프로세스의 메모리 위치와 크기 정보를 관리
- 운영체제는 각 프로세스마다 고유한 번호(프로세스 , PID) 할당
- 프로세스에 관한 모든 정보는 커널에 의해 관리
- 프로세스는 실행-대기-잠자기-대기-실행-종료 등의 생명 주기를 가짐
- 프로세스 생성, 실행, 대기, 종료 등의 모든 관리는 커널에 의해 수행
프로세스는 상호 독립적인 메모리 공간에서 실행된다.
프로세스의 생성에서 종료까지, 관리는 모두 커널에 의해 이루어짐
커널 영역에 프로세스 테이블을 만들고, 프로세스들 목록 관리관리 내용
프로세스 생성, 실행, 일시 중단 및 재개, 정보 관리 프로세스 통신, 프로세스 동기화, 프로세스 중단, 프로세스 컨텍스트 스위칭
ps -ax: 리눅스에서 현재 실행 중인 프로세스 목록 보기 명령어
한 프로그램을 여러번 실행시키면?
프로그램 실행 시 마다 독립된 프로세스가 실행됨→이 프로세스들을 프로그램의 다중 인스턴스(multiple instance of a program)라고 부름
각 프로세스에게 독립된 메모리 공간 할당
각 프로세스를 별개의 프로세스로 취급
CPU 주소 공간(CPU address space)
CPU가 주소선을 통해 액세스할 수 있는 전체 메모리 공간
공간 크기
CPU 주소선의 수에 의해 결정32비트 CPU→32개의 주소선→개의 주소→바이트→4GB 공간
1번지의 저장 공간 크기는 1바이트
주소 공간은 0번지부터 시작CPU 주소 공간보다 큰 메모리
있다고 해도 액세스 불가능하며,
CPU가 설치된 메모리의 주소 영역을 넘어 액세스하면 시스템 오류가 발생
예)
32비트 CPU를 가진 컴퓨터(4GB까지 메모리 액세스 가능)에 2GB의 메모리가 설치되어 있을 때, 2GB를 넘어서 액세스하면 없는 메모리를 액세스 하므로 심각한 오류가 발생하게 된다.CPU 주소 공간보다 작은 메모리
액세스 가능
프로세스의 구성
코드(code) 영역
프로세스 코드가 적재되는 영역. 텍스트(text)영역으로도 불림
사용자가 작성한 함수의 모든 코드
사용자가 호출한 라이브러리 함수들의 코드데이터(data) 영역
프로세스의 전역 변수들과 정적 변수들이 적재되는 영역
전역 변수 공간, 정적 데이터 공간
사용자 프로그램과 라이브러리의 전역 변수 포함코드 영역과 데이터 영역의 크기는
프로그램 적재 시 할당되며, 크기도 정해진다.힙(heap) 영역
프로세스가 실행 중에 동적 할당받는 영역
malloc()등으로 할당받는 영역은 힙 영역에서 할당
힙 영역에서 아래 번지로 내려가면서 할당스택(stack) 영역
함수가 호출될 때, 지역변수, 매개변수, 함수로부터 돌아갈 주소 등이 저장되는 영역
매개변수들, 지역변수들, 함수 종료 후 돌아갈 주소 등
함수는 호출될 때, 스택 영역에서 위쪽으로 공간 할당
함수가return하면 할당된 공간 반환
함수 호출 외에 프로세스에서 필요시 사용 가능
프로세스의 주소 공간
프로세스가 실행 중에 접근할 수 있도록 허용된 주소의 최대 범위
프로세스 주소 공간은 논리 공간(가상 공간)
0번지에서 시작하여 연속적인 주소
프로세스 주소 공간의 크기
CPU가 액세스할 수 있는 전체 크기
예)
32비트 CPU의 경우 4GB(윈도우, 리눅스 모두 동일)
프로세스의 크기
적재된 코드 + 전역 변수 +
힙 영역에서 할당받은 동적 메모리 공간 +
스택 영역에 현재 저장된 데이터 크기프로세스 주소 공간의 크기 ≠ 프로세스의 현재 크기
힙은 아래로 향하며, 스택은 위로 향함
프로세스의 사용자 공간과 커널 공간
프로세스 주소 공간 = 사용자 공간 + 커널 공간
사용자 공간
프로세스의 코드, 데이터, 힙, 스택 영역이 순서대로 할당되는 공간
코드와 데이터 영역의 크기는 프로세스 적재 시 결정
힙은 데이터 영역 바로 다음부터 시작하고,
스택은 사용자 공간의 바닥에서 시작하여 거꾸로 자람힙 영역은 높은 번지로 자라고, 스택은 낮은 번지로 자람
예)
처음malloc(1000)으로 동적 할당받는 공간은 데이터 영역 바로 다음의 힙 시작 부분부터 할당
처음 함수가 호출될 때, 할당되는 스택 공간은
스택 영역 바닥부터 위로 할당
커널 공간
프로세스가 시스템 호출을 통해 이용하는 커널 공간
커널 코드/데이터/스택(커널 코드가 실행될 때)이 존재
프로세스의 코드와 데이터 영역은 실행 파일에 결정된 상태로 적재됨
→실행 중에 크기가 변하지 않음프로세스는 사용자 공간의 최대 범위까지 동적할당 받으면서 힙 영역과 스택 영역을 늘려갈 수 있음
→실행 중에 크기가 변할 수 있음
커널 공간의 의미
각 프로세스는 독립된 사용자 공간 소유, 커널 공간 공유
커널 공간
프로세스가 사용자 코드에서
시스템 호출을 통해 커널 코드를 실행할 때 커널 공간 사용
사용자 프로세스가 커널 코드를 실행함
"사용자 프로세스가 커널 모드에서 실행되고 있다" 라고 표현함커널 코드가 적재된 물리 메모리의 위치 역시
사용자 프로세스가 소유한 매핑 테이블 사용
사용자 영역과 커널 영역을 하나의 가상 주소 영역으로 다룬다는 의미
프로세스마다 각각 사용자 공간이 있다
시스템 전체에는 하나의 커널 공간이 있다
모든 프로세스는 커널 주소 공간을 공유한다
커널 코드는 모든 프로세서가 사용하는 공동의 코드인데 왜 할당받은 공간에서 1~2GB나 차지하는가?
커널 영역에서 사용되는 데이터 값들이 주로 위치하기 때문
IVR(ISR 주소 목록), 페이지 테이블, 각종 포인터들과 같은 주소값들은 상대적으로 크기가 작고, 할당받은 공간의 일부만을 차지함실제로 대부분을 차지하는 것들은 데이터들로, PCB(각 프로세스의 상태, 레지스터값, 스택 등), 디스크 캐시, 디바이스 드라이버 데이터, 파일 시스테 메타데이터 등이 커널 공간의 대부분을 차지함
프로세스의 주소 공간은 가상 공간
프로세스가 사용하는 주소는 가상 주소이다
프로세스에서 0번지는 가상 주소 0번지
물리 메모리의 0번지가 아님
가상 주소는 0번지부터 시작
프로세스 내의 코드/전역변수/malloc()에 의해return된 주소, 스택에 담긴 지역 변수의 주소는 모두 가상 주소
프로세스의 주소 공간(가상 주소 공간)은 사용자나 개발자가 보는 관점
사용자나 개발자는 프로그램이 0번지부터 시작하여 연속적인 메모리 공간에 형성되고, 최대 크기의 메모리가 설치되어 있다고 상상
실제 상황
설치된 물리 메모리의 크기는 주소 공간보다 작을 수 있고,
프로세스의 코드, 데이터, 힙, 스택은 물리 메모리에 흩어져 저장됨
연속적인 메모리 공간이 아님
커널 코드와 데이터는 여러 프로세스들이 공유함
프로세스 주소 공간은 각 프로세스마다 주어지는가?
그렇다. 프로세스마다 주소 공간은 별개이다.
프로세스 주소 공간은 충돌하는가?
그렇지 않다. 프로세스 주소 공간은 가상 공간이며, 가상 주소가 실제 주소로 매핑되므로 물리 메모리에서는 충돌하지 않는다.
탐구 3-1 프로세스의 구성 영역 그려보기
#include <stdio.h> #include <stdlib.h> int a=10; void f() { int c=30; printf("%d", c); } int main() { int b=20; int* p = (int*)malloc(100); f(); printf("%d", b); return -1; }
프로세스 테이블(Process Table)
- 시스템의 모든 프로세스를 관리하기 위한 표
- 시스템에 한 개만 있음
- 구현 방식은 운영체제마다 다름
프로세스 정보는 프로세스 제어 블록의 형태로 저장되며,
프로세스 테이블은 PCB의 배열
프로세스 제어 블록(Process Control Block, PCB)
- 프로세스에 관한 정보를 저장하는 구조체
- 프로세스당 하나씩 존재
- 프로세스가 생성될 때 만들어지고, 종료되면 삭제
- 커널에 의해 생성, 저장, 읽혀지는 등 관리
프로세스 테이블과 프로세스 제어 블록의 위치
커널 영역에 위치하며, 커널 코드(커널 모드)만이 액세스 가능
프로세스 번호(Process Identification Number, PID)
- 프로세스를 식별하는 고유한 번호로,
사용자나 응용프로그램, 운영체제 모두 PID로 프로세스를 식별한다.- PID는 0과 양의 정수로만 사용된다.
부모 프로세스 번호(Parent Process Identification Number, PPID)
- 프로세스는 프로세스에 의해 생성됨
- 이들 사이에는 부모-자식 관계가 형성됨
- 부팅할 때 만들어진 최상위 프로세스를 제외하고
모든 프로세스는 부모 프로세스를 가짐
프로세스 상태 정보(Process State)
- 프로세스는 생성된 후 종료될 때까지 실행하는 동안 여러 상태로 바뀜
- 운영체제마다 다르지만
생성 초기 상태(New)
현재 CPU에 의해 실행되고 있는 실행 상태(Running)
스케줄링을 기다리고 있는 준비 상태(Ready)
입출력을 요청한 후 입출력을 기다리는 상태(sleep)
시스템 호출을 실행하여 타이머 알람을 기다리거나
요청 자원이 사용 가능 상태가 되기를 기다리는 상태(Block)- 커널은 프로세스의 상태를 바꿀 때마다 PCB에 상태 정보를 저장함
프로세스 컨텍스트 정보(Process Context)
- 현재 프로세스의 실행 상태를 나타내는 PC, SP, 범용 레지스터 등
CPU에 들어 있는 레지스터들의 값
스케줄링 정보
프로세스의 우선순위(priority, nice)나 프로세스가 사용한 CPU 시간, 최근에 CPU를 할당받아 실행한 시간 등의 값이 저장됨
프로세스의 우선순위는 프로세스가 생성될 때 주어지며, 시스템 호출을 통해 변경 가능하다.
종료코드(exit code)
- 프로세스가 종료할 때,
종료 이유를 부모 프로세스에게 전달하기 위한 정수 값(0~255)
종료코드는exit(종료코드)시스템 호출의 매개변수 값이나
C/C++에서main()의return값- PCB에 남겨놓은 종료코드가 부모 프로세스에 의해 읽혀질 때까지 운영체제는 프로세스의 PCB를 제거하지 않고 프로세스 테이블 항목도 그대로 남겨둔다.
- 종료되었지만 부모가 종료코드를 읽어가지 않은 상태의 프로세스를
좀비(zombie) 프로세스라고 함
PCB와 프로세스 테이블 항목이 그대로 남아있기 때문에, 프로세스 목록을 출력하면 좀비 상태로 출력됨
프로세스의 오픈 파일 테이블(Per-process open file table)
프로세스가 실행 중에 열어 놓은 파일에 관한 정보들을 저장
프로세스가 열어 놓은 파일을 닫지 않고 종료해도, 커널이 오픈 파일 테이블에 기록된 파일을 모두 닫음
메모리 관리를 위한 정보들
대부분의 운영체제들은 가상 주소→물리 주소 변환을 위한 매핑 테이블을 메모리에 두고, PCB에는 매핑 테이블의 주소를 저장함
메핑 테이블은 매핑 방법에 따라
페이지 테이블(page table), 세그먼트 테이블(segment table) 등이 있음
프로세스 사이의 통신 정보들
프로세들은 서로의 영역에 접근할 수 없음
따라서 프로세스↔프로세스 통신은 커널이 지원하는
프로세스간 통신 방법(Inter Process Communication)을 이용함
PCB에는 핸들러 리스트,
다른 프로세스로부터 받은 신호 플래그, 메시지 등이 저장됨
회계 정보
프로세스의 CPU 사용 총 시간, 프로세스가 실행을 시작하여 경과한 총 시간, 프로세스의 제한 시간 등이 저장되며
사용자 컴퓨터의 사용료 계산, 성능 통계를 위해 사용됨
프로세스의 소유자 정보
프로세스를 생성한 사용자의 로그인 이름이나 사용자 ID(user id) 정보 등
기타
프로세스가 현재 사용 중인 입출력 장치의 리스트, 준비 상태에 있는 다른 PCB에 대한 링크
프로세스의 생명 주기
- 프로세스는 탄생에서 종료까지 여러 상태로 바뀌면서 실행
- 상태 정보는 PCB에 기록되고, 상태가 바뀔 때마다 갱신됨
New 상태
- 프로세스가 생성된 상태(
fork()등의 명령어로)- 메모리 할당 및 필요한 자원이 적재된 상태
- PCB에 New 상태로 등록
- 실행 준비를 마치면
Ready상태로 바뀜
Ready 상태
- 프로세스가 스케줄링을 기다리는 준비 상태
- 프로세스는 준비 큐에서 대기
스케줄링이 잘못되면 준비 큐에 오래 머무는
기아 프로세스(starving process)가 생기기도 함
프로세스가 Ready 상태가 되는 경우- 스케줄링되면
Running상태가 되고, CPU에 의해 실행됨- New 상태에서 준비 큐에 삽입될 때
- Running 상태에서 프로세스에게 할당된 CPU 시간이 경과되거나
스스로 다른 프로세스에게 CPU 사용을 양보할 때
다시 준비 큐에 삽입함- 입출력 장치나 저장 장치로부터 요청한 작업이 완료되었을 때
Running 상태
- 프로세스가 CPU에 의해 현재 실행되고 있는 상태
- 프로세스가 입출력을 시행하면,
커널은 프로세스를Block상태로 만들고 대기 큐에 삽입- 프로세스의 실행이 완료되면 커널은
Terminated/Zombie상태로 만듦- 컨텍스트 스위칭이 일어날 때
Running상태였던 프로세스의 컨텍스트를 PCB에 저장
Block/Wait 상태
- 프로세스가 자원을 요청하거나, 입출력을 요청하고 완료를 기다리는 상태
- 입출력이 왼료되면 프로세스는
Ready상태로 바뀌고 준비 큐에 삽입
Terminated/Zombie 상태
- 프로세스가 종료되면(
exit()),
커널은 프로세스가 차지하고 있던 메모리와
할당받았던 자원들을 모두 반환하고,
열어 놓은 파일들도 모두 닫는다.- 하지만 이 상태는 프로세스가 완전히 소멸된 상태가 아님
(불완전 종료 상태)
부모 프로세스가 종료코드를 읽기 전까지 유지됨,
이 상태의 프로세스를 좀비 프로세스라고 함
Terminated/Out 상태
- 부모 프로세스가 Zombie 상태인 자식 프로세스의 PCB에서 종료코드를 읽을 때, 프로세스는 시스템에서 완전히 사라짐
프로세스 스케줄링
다중프로그래밍 운영체제에서
실행 중인 여러 프로세스 중 CPU를 할당할 프로세스를 결정하는 과정
- 과거 운영체제에서 실행 단위는 프로세스였으며,
Ready상태의 프로세스 중에 실행시킬 프로세스를 선택
오늘날 운영체제는 스레드를 대상으로 스케줄링- 오늘날 프로세스 스케줄링은 없음
- 오늘날 운영체제에서 실행 단위는 스레드
Ready상태의 스레드 중 실행시킬 스레드 선택
오늘날의 프로세스는 스레드들에게 공유 자원을 제공하는 컨테이너 역할
프로세스 테이블과 PCB에서 액세스
사례)
리눅스 쉘 명령어로 프로세스 정보 보기
탐구 3-2: C 프로그램으로 프로세스와 부모 프로세스 번호 알아내기
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t pid, ppid; pid = getpid(); // 현재 프로세스의 PID 알아내기 ppid = getppid(); // 부모 프로세스의 PID 알아내기 printf("프로세스 PID = %d, 부모 프로세스 PID = %d\n", pid, ppid); }$ gcc -o pinfo pinfo.c $ ./pinfo 프로세스 PID = 31006, 부모 프로세스 PID = 30861 $
프로세스는 일반적으로 부모-자식 관계
윈도우에서는 상대적으로 느슨함
#0시스템 부팅 시 실행되는 최초의 프로세스, 조상 프로세스- 부모 프로세스는 여러 개의 자식 프로세스를 가질 수 있음
- 모든 프로세서는 부모 프로세서를 가짐(
#0프로세스 제외)
자식 프로세스의 생성- 모든 프로세스는 프로세스(부모)에 의해 생성
프로세스 생성은 시스템 호출을 통해서만 가능
fork(),clone()등의 커널 코드가 자식 프로세스 생성
에외): PID 0 1, 2 등의 몇몇 조상 프로세스는
시스템 호출이 아닌 수작업(hand-craft)으로 생성
리눅스 사례#0프로세스: swapper/idle 프로세스(hand-craft)#1프로세스: init 프로세스
부팅 후 생성되는 모든 사용자 프로세스의 조상#2프로세스: kthreadd 프로세스
커널 모드에서 커널 코드로만 실행되는 모든 커널 프로세스(thread)의 조상
#0프로세스
- 최고의 어른(조상) 프로세스
Unix의#0프로세스- swapper라고 불림, 부팅을 담당하고
#1프로세스 생성
리눅스의#0프로세스- idle 프로세스, 부팅 관계 없이 아무 일도 하지 않고 루프
- 우선 순위가 가장 낮은 프로세스로,
다른 프로세스가 있으면 실행될 일 없음- 실행 중인 프로세스가 1개도 없는 상태에 빠지지 않기 위해 만든 프로세스
- Unix 시절의 관례에 따라 swapper라고도 부름
Windows의#0프로세스: system idle process(시스템 유휴 프로세스)- 아무 일도 하지 않고 루프를 도는 단순 프로세스
CPU 시간의 89% 사용(CPU의 11%만 사용함).
사용자가 컴퓨터를 사용하지 않은 시간 동안 실행
유휴 프로세스는 커널 모드에서 돌아감.
리눅스와 Windows의#0프로세스는 유휴 프로세스임
fork()
자식 프로세스를 생성하는 시스템 호출
exit()
현재 프로세스의 종료를 커널에 알리는 시스템 호출
현재 프로세스의 종료를 처리하는 커널 코드 실행
Wait()
부모가 자식 프로세스를 종료할 때 까지 기다리는 시스템 호출
부모가 자식을 생성한 뒤 자식의 종료를 기다리는 경우
자식이 부모보다 먼저 종료한 경우
프로세스가 종료할 때
- PCB에 종료 코드(exit status) 저장
- PCB에 프로세스 상태를
Terminated라고 표시- 프로세스에 할당된 모든 메모리 반환
PCB와 프로세스 테이블의 항목은 반환되지 않음
부모 프로세스의 의무wait()시스템 호출을 통해 자식 프로세스의 종료 코드를 읽어야 함- 자식이 종료되면 부모에게
SIGCHLD신호가 전송됨.
부모가 이 신호를 받았을 때,
wait()시스템 호출을 하도록 작성되어 있지 않다면,
자식 프로세스는 계속 좀비 상태로 남아있음
좀비 프로세스(zombie process)- 종료하였지만, 부모가 종료코드를 읽지 않은 상태의 프로세스
- 프로세스 테이블에는 남아 있으므로, 프로세스 목록을 출력할 때 나타남
좀비 프로세스 제거 방법- 방법 1) 쉘에서 부모 프로세스에게
SIGCHLD신호 보내기
부모 프로세스의SIGCHLD핸들러가wait()를 호출하여 좀비 자식 제거- 방법 2) 부모 프로세스 강제 종료
좀비는init프로세스의 자식이 되고
init이wait()호출하여 좀비 프로세스 제거
고아 프로세스(Orphan Process)
부모가 먼저 종료한 자식 프로세스
부모 프로세스가 종료할 때
- 일반적으로
커널(exit() 시스템 호출 코드)은 자식 프로세스가 있는지 확인
커널은 자식 프로세스를init프로세스에게 입양- 운영체제에 따라, 혹은 쉘의 경우
모든 자식 프로세스를 강제 종료하기도 함
백그라운드 프로세스
- 터미널에서 실행되었지만,
터미널 사용자와의 대화가 없는 채 실행되는 프로세스- 사용자와 대화 없이 실행되는 프로세스
- 사용자 입력을 필요로 하지 않는 프로세스
- idle 상태로 잠을 자거나 디스크에 스왑된 상태의 프로세스
포그라운드 프로세스- 실행되는 동안 터미널 사용자의 입력을 독점하는 프로세스
CPU 집중 프로세스(CPU intensive process)
- 대부분의 시간을 계산 중심의 일(CPU 작업)을 하느라 보내는 프로세스
- 배열 곱, 인공지능 연산, 이미지 처리 등의 작업
- CPU 속도가 성능 좌우(CPU bound process)
I/O 집중 프로세스(I/O intensive process)- 입출력 작업을 하느라 대부분의 시간을 보내는 프로세스
- 네트워크 전송, 파일 입출력에 집중된 프로세스
- 파일 서버, 웹 서버
- 입출력 장치나 입출력 시스템의 속도가 성능 좌우(I/O bound process)
운영체제의 스케줄링 우선순위: I/O 집중 프로세스 > CPU 집중 프로세스- I/O가 작업하는 동안 다른 프로세스에게 CPU 할당 가능
컴퓨터 시스템에서 프로세스가 생성되는 5가지 경우
- 시스템 부팅 과정에서 필요한 프로세스 생성
- 사용자의 로그인 후 사용자와 대화를 위한 프로세스 생성(bash 등 쉘)
운영체제와 사용자 사이의 명령어 인터페이스- 새로운 프로세스를 생성하도록 하는 사용자의 명령(
vi a.c)- 배치 작업 실행 시(
at,batch명령)
batch 명령: 여러 명령어를 파일에 미리 작성해두고 한번에 실행하는 방식- 사용자 응용프로그램이 시스템 호출로 새 프로세스 생성
프로세스 생성- 프로세스가 프로세스를 생성
- 시스템 호출을 통해서만 프로세스 생성
커널만이 프로세스를 생성하는 작업 가능
리눅스:fork()시스템 호출
Windows:CreateProcess()등 시스템 호출
프로세스의 생성 과정- 새로운 PID 번호 할당
- PCB 구조체 생성
- 프로세스 테이블에서 새 항목 할당
- 새로 할당된 프로세스 테이블 항목에 PCB 연결
- 새로운 프로세스를 위한 메모리 공간 할당
프로세스의 데이터, 코드, 힙, 스택 영역
할당받은 메모리 공간에 프로세스의 코드와 데이터 적재- PCB에 프로세스 정보 기록
- PCB에 프로세스 상태를
Ready로 표시하고,
준비 큐에 넣어서 차후 스케줄되게 함
fork()시스템 호출
현재 프로세스를 복사하여 자식 프로세스 생성int pid = fork(); // 자식 프로세스 생성 // 부모 프로세스의 모든 환경, 메모리 PCB 등을 복사 // 부모와 동일한 모양이지만 , 독립된 주소 공간 소유리턴 값
부모 프로세스에게는 자식 프로세스의 PID 리턴
자식 프로세스에게는 0 리턴
아래 코드 참조1 pid_t pid; // pid 변수 선언 2 3 pid = fork(); // 자식 프로세스 생성 4 if(pid > 0) { 5 /* 이곳에 부모 프로세스가 계속 실행할 코드 작성 */ 6 } 7 else if(pid == 0) { 8 /* 이곳에 자식 프로세스가 실행할 코드 작성 */ 9 } 10 else { 11 /* fork() 오류를 처리하는 코드 작성 */ 12 }
프로세스 오버레이(process overlay)
- 현재 실행중인 프로세스의 주소 공간에
새로운 응용프로그램을 적재하여 실행시키는 기법- exec 패밀리 시스템 호출
execlp(),execv(),execvp()시스템 호출들
실행 파일을 적재하여 현재 프로세스의 메모리 공간에 단순히 덮어쓰고, 새로운 프로세스의 생성 과정을 거치지 않는다.- 프로세스의 PID 변경 없음
- 프로세스의 코드, 데이터, 힙, 스택에 새로운 응용프로그램이 적재됨
fork()에 의해 생성된 자식 프로세스는 생성 후 바로exec()을 실행하는 경우가 다반사임
탐구 3-6:
fork()로 자식 프로세스 만들고 execlp()로 "ls -l" 명령을 오버레이 해서 실행#include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main() { pid_t pid; pid = fork(); // 자식프로세스 생성 if (pid > 0) { // 부모 프로세스 코드 printf("부모프로세스: fork()의 리턴 값 = 자식프로세스 pid = %d\n", pid); printf("부모프로세스: 프로세스 pid = %d\n", getpid()); wait(NULL); // 자식프로세스가 종료할 때까지 대기 return 0; } else if (pid == 0) { // 자식 프로세스 코드 printf("자식프로세스: fork()의 리턴 값 pid = %d\n", pid); printf("자식프로세스: pid = %d, 부모프로세스 pid = %d\n", getpid(), getppid()); execlp("/bin/ls", "ls", "-l", NULL); // /bin/ls를 현재프로세스에 오버레이하여 실행 } else { // fork() 오류 printf("fork 오류"); return 0; } }실행 결과
$ gcc -o execex execex.c $ ./execex 부모프로세스: fork()의 리턴 값 = 자식프로세스 pid = 29566 부모프로세스: 프로세스 pid = 29565 자식프로세스: fork()의 리턴 값 pid = 0 자식프로세스: pid = 29566, 부모프로세스 pid = 29565 합계 32 -rwxrwxr-x 1 han00 han00 9016 4월 23 23:21 execex -rw-rw-r-- 1 han00 han00 883 4월 23 23:21 execex.c -rwxrwxr-x 1 han00 han00 9016 4월 23 16:20 forkex -rw-rw-r-- 1 han00 han00 969 4월 23 16:20 forkex.c $
프로세스 종료
exit()시스템 호출- C 프로그램의
main()에서 리턴
exit()시스템 호출이 진행됨
종료 코드- 부모 프로세스에게 전달하는 값
main()함수의 리턴 값;return 종료코드;
exit(종료코드)exit()시스템 호출로 프로세스 종료 과정- 1) 프로세스의 모든 자원 반환
코드, 데이터, 힙, 스택 등의 모든 메모리 자원 반환
열어 놓은 파일이나 소켓 등 닫음- 2) PCB에 프로세스 상태를
Terminated로 변경, PCB에 종료 코드 저장- 3) 자식 프로세스들이 있으면 이들을
init프로세스에게 입양- 4) 부모 프로세스에게
SIGCHLD신호 전송
부모가SIGCHLD신호 핸들러를 작성하고,
여기서wait()시스템 호출을 이용하여 자식의 종료 코드 읽기 실행
혹은 언젠가 부모가 자식의 죽음 처리. 그동안 자식은 좀비 상태에 있음
종료코드
- 프로세스가 종료한 상태나 이유를 부모에게 전달하기 위한 것
- POSIX 표준에서 0~255 사이의 1바이트 숫자
정상 종료는0
1~255: 개발자가 종료 이유를 임의로 정해 사용
종료 코드 사용 시 유의할 점main()이나exit()에서 255 이상의 값을 사용할 때 유의int main() { return 300; // return 44;와 같음 } void func() { exit(300); // exit(44)와 같음 }- -1을 리턴하는 경우
-1→0xff→255
탐구 3-7:
wait()로 자식 프로세스 종료 대기
child.c#include <stdio.h> int main() { printf("I am a child\n"); return 100; }waitex.c
#include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main() { pid_t pid; int status; pid = fork(); // 자식프로세스 생성 if (pid > 0) { // 부모 프로세스 코드 printf("부모프로세스: 자식의 종료를 기다림\n"); wait(&status); // 자식프로세스 종료 대기. status에 종료 코드 받음 printf("부모프로세스: child의 종료 코드=%d\n",WEXITSTATUS(status)); return 0; } else if (pid == 0) { // 자식 프로세스 코드 execlp("./child", "child", NULL); // child를 자식프로세스로 실행 } else { // fork() 오류 printf("fork 오류"); return 0; } }실행 결과
$ gcc -o child child.c $ gcc -o waitex waitex.c $ ./waitex 부모프로세스: 자식의 종료를 기다림 I am a child 부모프로세스: child의 종료 코드=100 $ 
프로세스 종료
- 두 종료
1) C 언어에서main()함수의 종료나exit()을 호출한 정상 종료
2) 다른 프로세스에 의해 강제 종료(kill)
프로세스가 종료되면- 차지하고 있던 메모리와 자원 모두 반환
- PCB는 프로세스 테이블에서 제거되지 않음
- 프로세스 상태:
Terminated
**부모 프로세스의wait()시스템 호출을 통해,
죽은 자식이 남긴 종료코드를 읽게 되면 자식 프로세스의 PCB가 완전히 제거
좀비 프로세스
종료할 때 리턴한 정보(main()함수의 리턴값, 종료 코드)를
부모 프로세스가 읽지 않았을 때,
죽었지만 PCB만 남아 완전히 제거되지 못한 상태
탐구 3-8: 좀비 프로세스 만들고 관찰하기
zombieex.c#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main() { pid_t pid, zompid; int status; pid = fork(); if(pid > 0) { // 부모 프로세스 코드 sleep(10); // 10초 동안 잠자기 zompid = wait(&status); // 자식프로세스 종료 대기 printf("부모프로세스: 자식 PID=%d, 종료 코드=%d\n",zompid, WEXITSTATUS(status)); return 0; } else if(pid == 0) { // 자식프로세스 코드 printf("자식프로세스: %d 종료합니다.\n", getpid()); exit(100); // 자식이 종료하여 좀비 프로세스가 됨. 종료 코드 100 전달 } else { // fork() 오류 printf("fork 오류"); return 0; } }실행 결과
<defunct>: 좀비 프로세스를 의미