· 목차
- 프로세스(Process)의 구조
- PID, PT, PT entry, PCB
- Process Creation
- Process Termination
- Process States
- Context Switch
안녕하세요, 이번에는 Process를 배우고 다음 시간에 Threads에 대해서 배워보도록 하겠습니다.
우선 Process에 대해서 알아보기 이전에 코어에 대해서 알아야 합니다.
CPU 내부의 다이라고 불리는 블록 내부에 위치하는 코어는 CPU에 요구되는 연산을 수행하는 실제 연산 장치입니다.
여기서 프로세스는 실행 중인 프로그램으로 시스템에서 독립적인 메모리 공간을 가집니다.
프로세스가 실행되면 운영 체제는 그 프로세스를 CPU 코어에 할당합니다.
스레드는 프로세스 내에서 실제로 실행되는 단위입니다.
한마디로, 프로세스는 실행을 위해 적어도 하나의 스레드를 가져야 합니다.
여러 스레드가 있는 경우 운영체제는 이 스레드들을 여러 코어에 분산시켜 동시에 실행할 수 있습니다.
이제 스레드는 뒤에서 더 자세히 살펴보고, 프로세스에 대해서 알아보기 전에 운영체제에서
여러 작업을 동시에 처리하는 개념으로 Multiprogramming과 Multiprocessing이 있습니다.
Multiprogramming은 하나의 CPU에서 여러 프로그램(프로세스)가 실행되는 것을 의미하며,
CPU가 여러 프로그램 중 하나를 선택하여 실행하고, 하나의 프로그램이 실행 중일 때 나머지 프로그램은 대기하게 됩니다.
Multiprogramming은 프로그램이 동시에 실행되는 것 처럼 보이지만 실제로는 각 프로그램이
번갈아 가며 CPU를 사용하는 비동기 작업 방식입니다.
Multiprocess는 여러 CPU가 동시에 여러 프로그램(프로세스)을 처리하는 것을 의미합니다.
해당 방식은 둘 이상의 CPU가 물리적으로 존재하기 때문에 실제로 여러 프로그램이 실제로 동시에 실행될 수 있습니다.
그리고 프로세스가 CPU나 코어에서 동시에 실행되기 때문에 실제로 동시성(Parallelism)이 발생합니다.
위의 Multiprogramming과 Multiprocessing은 아래의 그림을 참고하시면 됩니다.
프로세스(Process)의 구조
하지만 단순히 실행 중인 프로그램 이상의 복잡한 구조를 프로세스는 갖고 있습니다.
프로세스가 실행될 때, 운영 체제는 프로세스가 독립적이고 안전하게 실행될 수 있도록 여러 리소스를 할당하고 관리합니다.
1. 주소공간(Address Space)
주소 공간은 프로세스가 사용할 수 있는 메모리 영역입니다. 이 공간은 보호되는 가상 메모리로, 각 프로세스는 다른 프로세스와
독립적으로 자신의 메모리 공간을 가지고 있기 때문에 각 프로세스는 서로 다른 프로세스의 메모리를 침범함거나 손상시키는
것을 방지할 수 있습니다.
위에서 말한 가상 메모리(Virtual Memory)는 실제 물리 메모리(RAM)와 상관없이 각 프로세스는 운영 체제가 제공하는
가상 메모리를 사용합니다. 운영 체제가 실제 물리 메모리와 가상 메모리를 매핑해주어, 프로세스는 큰 메모리 공간을 사용하는
것처럼 동작할 수 있습니다.
2. 실행 중인 프로그램의 데이터
프로세스는 실해되는 동안 데이터도 필요합니다. 여기에는 프로그램에서 사용되는 전역 변수, 정적 변수, 힙 데이터 등이
포함됩니다. 전역 변수 및 정적 변수는 프로그램이 실행되면서 저장된 값들이 저장되는 메모리 공간입니다.
힙(Heap)은 프로그램 실행 중에 동적으로 할당되는 메모리입니다. 예를들어 프로세스가 동적 메모리 할당을 요청할 때,
이 데이터는 힙 영역에 저장됩니다.
3. 실행 스택과 스택 포인터
실행 스택은 함수 호출과 관련된 데이터를 저장하는 구조입니다. 함수가 호출될 때 마다 함수의 지역변수, 함수의 리턴 주소,
매개 변수 등이 스택에 저장되고, 함수가 종료되면 스택에서 제거됩니다.
PID, PT, PT entry, PCB
그렇다면 운영체제에서 프로세스를 어떻게 관리하는지에 대해서 알아보며, 주로 프로세스 식별, 테이블 구조, 프로세스 제어 블록 관련된 개념을 중심으로 다뤄보도록 하겠습니다.
1. 프로세스 식별자 (PID, Process ID)
- PID는 프로세스 식별자로, 운영체제에서 각 프로세스를 구별하는 고유한 번호 입니다.
- PID는 각 프로세스가 실행될 때 할당되며, 시스템 내에서 실행 중인 모든 프로세스를 구분할 수 있게 해줍니다.
- 여러 프로세스가 동시에 실행 중이라면, 각각은 다른 PID를 가지게 됩니다. 이 고유한 번호 덕분에 운영체제는 프로세스를 추적하고
관리할 수 있습니다.
2. 프로세스 테이블 (Process Table, PT)
- 운영체제는 모든 프로세스의 정보를 관리하기 위해 프로세스테이블이라는 data structure를 사용합니다.
- 프로세스 테이블은 시스템에 존재하는 모든 프로세스에 대한 정보를 가지고 있습니다.
- PID는 이 프로세스 테이블의 인덱스 또는 포인터 역할을 합니다. 즉, PID는 프로세스 테이블에서 해당 프로세스의 정보를 찾기 위한
키(key)라고 할 수 있습니다.
3. 프로세스 테이블의 항목 (PT Entry)
- 프로세스 테이블의 각 항목(entry)은 특정 프로세스에 대한 정보를 저장하고 있으며, 이 항목은 PCB라고도 합니다.
즉, PT entry = PCB라는 말이 여기에서 나온 것 입니다.
- PCB는 프로세스와 관련된 다양한 정보를 포함한 구조체(structure)로, 운영체제가 프로세스를 관리하는 데
필요한 정보를 담고 있습니다.
4. 프로세스 제어 블록(PCB, Process Control Block)
PCB는 프로세스의 상태와 운영에 필요한 데이터를 포함하는 data structure입니다. 이 안에는 아래의 정보들이 저장됩니다.
- 프로세스 상태: 프로세스가 현재 실행 중인지, 대기 중인지 등의 상태 정보를 저장
- 프로세스 ID(PID): 프로세스의 고유 식별자
- 레지스터 정보: 프로세스가 CPU에서 작업하던 중단된 시점의 레지스터 값
- 프로세스 메모리 정보: 프로세스가 사용하는 메모리 주소, 페이지 테이블 정보
- 프로세스 우선 순위: CPU 스케줄링에 사용하는 우선순위 값
- 입출력 상태 정보: 입출력 장치와의 연계 정보나 대기 큐(ready queue) 정보
Process Creation
fork()
우선 기본 전제로 Unix에서 프로세스를 생성하는 과정으로 전제하며, Unix에서는 프로세스를 생성하는 과정중에 fork() System call을
먼저 살펴보도록 하겠습니다. fork()는 부모 프로세스의 메모리 공간을 복사하여 새로운 자식 프로세스를 생성합니다.
1. fork()는 부모 프로세스와 동일한 자식 프로세스를 생성하는 것으로 자식 프로세스는 부모 프로세스의 복사본으로 만들어지며,
대부분의 환경과 자원이 동일합니다.
2. fork()를 호출하면 새로운 PCB가 생성되고 초기화됩니다. PCB는 새로 생성된 프로세스의 상태, 레지스터 값, 메모리 정보 등을
관리합니다.
3. 새로운 자식 프로세스는 부모 프로세스의 주소 공간을 복사하여 자신의 주소 공간으로 사용하게 됩니다. 즉, 부모 프로세스가 가지고 있던
메모리 구조와 데이터를 그대로 복사하여 자식 프로세스가 동일한 메모리 구조를 가지게 되는 것 입니다. 여기서 주의해야 할 것은 부모의
주소 공간을 사용하게 된다고 했는데, 같은 address space를 공유한다는 것이 아닌, 부모의 주소 공간의 내용을 복사해서 자신의 주소 공간을 초기화하게 되는 것 입니다. 즉, 부모의 주소 공간을 복사하여 독립적인 주소 공간에 할당하여 사용하게 되는 것 입니다.
4. 새로운 프로세스가 생성되면, 해당 프로세스의 PCB는 ready queue에 추가됩니다. ready queue는 실행 준비가 완료된 프로세스가
대기하는 곳으로, CPU가 사용할 수 있을 때 스케줄러가 준비 큐에서 프로세스를 선택하여 실행하게 됩니다.
5. fork() system call은 독특하게도 두 번 반환을 합니다. 부모 프로세스로 한번 반환되고, 이때 자식 프로세스의 PID가 부모에게
반환됩니다. 또한 자식 프로세스로 한번 반환되며, 이때 자식 프로세스에게는 0이 반환됩니다.
5번을 조금 풀어서 써보자면, system call이 실행되면 하나의 부모 프로세스가 새로운 자식 프로세스를 생성하게 되고, 부모와 자식
프로세스 모두 fork()에서 각각 반환을 받게 됩니다. 이를 통해 두 프로세스가 서로 구분될 수 있습니다.
부모 프로세스는 fork()가 호출된 후 자식 프로세스의 PID를 반환받게 됩니다. 즉, system call의 반환 결과 자식의 PID인 경우에
해당 프로세스는 부모 프로세스임을 알 수 있습니다. 또한 `만약에 반환값이 0이라면 자식 프로세스임을 알 수 있습니다.
만약에 C code로 fork()에 대한 예시를 살펴보면 아래와 같습니다.
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int global = 10; // 전역 변수
int main() {
int i = 20; // 지역 변수
pid_t pid; // 프로세스 ID
int status;
pid = fork(); // 새로운 프로세스 생성
if (pid == 0) {
// 자식 프로세스
global = global + 10; // 전역 변수 수정
i = i + 10; // 지역 변수 수정
printf("자식 프로세스 - global = %d; i = %d\n", global, i);
} else {
// 부모 프로세스
global = global + 100; // 전역 변수 수정
i = i + 100; // 지역 변수 수정
wait(&status); // 자식 프로세스 종료 대기
printf("부모 프로세스 - global = %d; i = %d\n", global, i);
}
return 0;
}
# 부모 프로세스가 먼저 실행되는 경우
부모 프로세스 - global = 110; i = 120
자식 프로세스 - global = 20; i = 30
# 자식 프로세스가 먼저 실행되는 경우
자식 프로세스 - global = 20; i = 30
부모 프로세스 - global = 110; i = 120
여기서 부모 프로세스가 먼저 실행되느냐, 자식 프로세스가 먼저 실행되느냐에 따라서 서로 다른 결과가 나오는 것을 보실 수 있습니다.
그 이유는 부모 프로세스와 자식 프로세스의 실행 순서가 언제나 동일하지 않기 때문입니다. UNIX 운영체제에서 fork()를 호출하면,
부모 프로세스와 자식 프로세스는 동시에 실행될 수 있으며, 스케줄러에 의해 어느 프로세스가 먼저 실행될지는 미리 알 수 없습니다.
즉, 두 프로세스가 어떤 순서로 실행될지는 운영체제의 스케줄러에 의해 결정되기 때문에 어떤 것이 먼저 실행되는지 알 수 없습니다.
exec()
이번에는 exec()에 대해서 배워보도록 하겠습니다. fork()의 경우에는 새로운 프로세스를 생성하지만, 그 프로세스는 부모 프로세스의
복사본입니다. 반면, exec()는 기존 프로세스를 종료하고 새로운 프로그램을 실행하는 system call입니다.
중요한 점은, exec()는 새로운 프로세스를 생성하지 않고, 기존 프로세스를 새로운 프로그램으로 대체한다는 것 입니다.
exec()함수의 예시는 int exec(char * prog, char ** argv)와 같습니다.
exec() 함수의 동작은 어떻게 될까요?
exec()는 현재 프로세스의 실행을 중단하고, 지정한 프로그램(prog)을 현재 프로세스의 주소 공간에 로드합니다.
새로운 프로그램이 메모리에 로드되면, 기존 프로그램의 코드와 데이터는 모두 사라지고,
새로운 프로그램이 해당 프로세스에서 실행됩니다.
프로그램의 인자는 argv배열을 통해 전달되고, 명령행 인자를 가르킵니다.
argc는 인자의 개수를 나타냅니다.
더 정확하게 말하자면, 아래와 같습니다.
argv : 명령행 인자 배열로, argv[0]은 실행할 프로그램 이름, argv[1]부터는 명령행 인자를 담습니다.
argc : 명령행 인자의 개수를 나타내며, argv 배열의 크기를 결정합니다.
exec의 예시는 아래와 같습니다.
- exec ls: 현재 셸이 종료되고 ls 명령이 실행됩니다. 더 이상 셸은 존재하지 않으며, 프로세스가 ls로 대체됩니다.
- exec csh: 현재 셸이 csh로 대체되어 새로운 셸이 실행됩니다.
Process Termination
우선 프로세스의 종료에는 여러가지 이유가 있습니다. 정상종료, 에러 종료, 치명적 오류 종료, 다른 프로세스에 의해 강제 종료가 있습니다.
1. 정상종료(Normal exit, voluntary)
프로그램이 모든 작업을 정상적으로 완료했을 때 자발적으로 종료됩니다. return 0 또는 exit(0)와 같은 명령어로 종료됩니다.
- exit()호출: ANSI C에서 제공하는 exit()함수는 모든 종료 핸들러를 실행(atexit()로 등록된 함수)하고,
표준 입출력 스트림을 닫습니다.
- _exit()호출: exit()에 의해 호출되며, Unix 고유의 종료 작업을 처리합니다. 표준 입출력 스트림을 닫지 않으며, 빠르게 종료합니다.
- 비정상 종료: 1. abort() 호출 → 강제로 SIGABRT 신호를 발생시켜 비정상적으로 프로그램을 종료합니다.
2. 특정 신호 수신 → 프로세스가 특정 신호를 받으면 강제 종료됩니다.
2. 에러 종료(Error exit, voluntary)
프로그램이 실행 도중 오류를 감지했을 때 자발적으로 종료됩니다. 예를 들어 파일을 찾을 수 없거나 잘못된 입력이 발생했을 때
exit(1)과 같은 명령어로 종료될 수 있습니다.
3. 치명적 오류 종료(Fatal error, involuntary)
프로그램 실행 도중 심각한 오류가 발생하여 운영체제가 강제 종료하는 경우입니다. 존재하지 않은 메모리 참조, 0으로 나누기 등과 같은
오류가 발생할 때 비 자발적으로 종료됩니다.
4. 다른 프로세스에 의해 강제 종료(kill, involuntary)
다른 프로세스가 강제로 해당 프로세스를 종료할 수 있습니다. 그 예로, kill 명령으로 다른 프로세스를 강제 종료할 수 있습니다.
시스템 관리자가 문제 있는 프로세스를 강제로 종료하는 경우와 같습니다.
프로세스가 어떤 방식으로 종료되든 커널은 파일 디스크립터를 닫고, 메모리 해체하는 등의 정리 작업을 수행하게 됩니다.
Process States
운영체제에서 각 프로세스는 실행 상태를 가지며, 이는 프로세스가 현재 무엇을 하고 있는지를 나타냅니다.
대표적인 상태는 다음과 같습니다.
1. Ready(준비상태)
프로세스는 CPU 할당을 기다리고 있는 상태로 실행 가능한 상태이지만, 다른 프로세스가 CPU를 사용하고 있어서 대기중입니다.
2. Running(실행상태)
프로세스가 CPU에서 실제로 실행중인 상태입니다. 하나의 CPU는 동시에 하나의 프로세스만 실행할 수 있습니다.
3. Waiting/Blocked(차단상태)
프로세스가 특정 이벤트(예: I/O 작업 완료)를 기다리는 중이며, 해당 이벤트가 발생하기 전까지 실행되지 않습니다.
Process state transition
- New: 새로운 프로세스가 생성된 상태입니다.
- Ready: 프로세스는 실행 준비가 되었으나, CPU가 할당되지 않은 상태입니다.
- Running: CPU가 할당되어 프로세스가 실제로 실행 중인 상태입니다.
- Waiting/blocked: 프로세스가 I/O 또는 특정 이벤트를 기다리고 있는 상태입니다.
- Terminated: 프로세스가 종료된 상태입니다.
Context Switch
Context switch는 CPU가 한 프로세스에서 다른 프로세스로 작업을 전환하는 과정을 말합니다.
이 과정은 운영체제가 멀티태스킹 환경을 지원하기 위해서 필수적인 기능입니다.
Context Switch의 실행 단계는 다음과 같습니다.
1. 현재 프로세스 상태 저장
현재 실행 중인 프로세스의 하드웨어 상태(레지스터, 프로그램 카운터, 스택 포인터 등)를 PCB에 저장합니다.
2. 프로세스 상태 복원
복원(돌아갈) 프로세스 상태가 저장된 PCB의 상태를 CPU에 복원하고 이어서 실행하게 됩니다.
Context Switch의 실행과정은 복잡하지 않습니다. 지금까지 운영체제에 대한 글을 쭉 읽으셨다면 이해가 가실거라고 생각합니다.
Context Switch는 멀티태스킹과 프로세스 스케줄링을 위해서 필수적인 역할을 합니다. CPU는 한 번에 하나의 프로세스만 실행할 수 있기 때문에, 여러 프로세스를 실행하는 것처럼 보이도록 하기 위해 Context Switch를 이용합니다.
그렇다면, 멀티태스킹과 프로세스 스케줄링을 지원하기 때문에 Context Switch는 무조건 좋은 영향을 끼칠까요?
이에 대한 대답은 "아닙니다."가 맞는 것 같습니다.
Context Switch는 운영체제가 프로세스 간의 하드웨어 상태를 저장하고 복구하는 데 시간이 걸리기 때문에 오버헤드가 발생합니다.
이 오버헤드는 CPU가 실제로 계산 작업을 하지 않고 상태를 저장하고 복구하는 데 사용되는 시간으로,
빈번하게 발생하게 된다면 시스템이 느려질 수 있다는 단점을 가지고 있습니다.
지금까지 Process에 대해서 배워봤습니다. 앞에서 말한 것과 같이 다음시간에는 Thread에 대해서 배워보도록 하겠습니다.
감사합니다.
'전자전기공학 > 운영체제' 카테고리의 다른 글
[Operating System/운영체제][Synchronization] (0) | 2024.10.14 |
---|---|
[Operating System/운영체제][Thread] (4) | 2024.10.08 |
[Operating System/운영체제][Trap, System Call, Fault, Interrupt] (0) | 2024.09.30 |
[Operating System/운영체제][운영체제와 하드웨어] (0) | 2024.09.15 |
[Operating System/운영체제][운영체제란 무엇인가?] (0) | 2024.09.14 |