[전공 CS]/[컴퓨터 구조론][운영체제]

[운영체제] 03 프로세스와 프로세스 관리

우당탕탕 개발 일지 2025. 10. 20. 20:29
728x90

안녕하세요. 우당탕탕 개발일지 입니다. 수업 시간에 배운 내용 위주로 정리해서 올린 글입니다. 

 

시험대비 

1. 프로세스와 프로그램 개념
2. 자식이 먼저 종료된 경우 : 좀비 /부모가 먼저 종료된 경우 : 고아 (종료 과정까지 알아두기) →생명 주기 시험에 나옴. 
4. PCB와 VMS
5. 다시 ready로 가는 3가지 경우 

ch 3-1 프러세스 개요 

 

프로세스와 프로그램 

- 프로세스 : 프로그램이 메모리에 적재되어 *실행 중인 상태.  

- 프로그램 : 저장매체 안에 있는 상태 / 실행 파일의 형태 

 

* 실행 중 : cpu에 의해 현재 실행되고 있거나 준비 상태로 기다리거나 입출력 등올 인해 중단되어 cpu로 부터 실행을 대기 하는 모든 상태를 말함. 

 

프로세스 특징 

  • 프로세스들 마다 독립적인 메모리 공간을 가짐.→다른 프로세스는 접근 못함. 
  • 실행 - 대기 -잠자기 - 대기 - 실행- 종료  (프로세스 생명주기)
  • 프로세스의 모든 정보는 커널에 의해서 관리 
  • 자원 : 코드,데이터, 스택, 힙 공간 
  • 프로세스마다 고유 번호 (=PID)를 할당.

 

 프로그램의  다중 인스턴스⭐️

: 프로그램 실행 시 마다 생성되는 독립된 프로세스들.

 

 

프로세스 관리 내용 : (모두 커널에 의해 이뤄짐) 

-프로세스 생성

-실행

-일시 중단 & 재개 

-정보 관리 

-프로세스 통신 

-프로세스 동기화

-프로세스 컨텍스트 스위칭

 

 

 

프로세스 주소 공간 

 = 사용자 공간 + 커널 공간

= 프로세스가 실행 중에 접근할 수 있도록 허용된 주소의 최대 범위

 

CPU 주소 공간 : 컴퓨터 내에 cpu가 접근 가능한 전체 메모리 공간. 크기는 cpu 주소 선의 갯수에 따라 결정. 

 

 

→코드와 데이터 영역에 적재되고 나면 실행 중에는 크기가 변하지 않음. 

 

  • 사용자 공간 = 코드,데이터, 힙, 스택  
  • 커널 공간 =사용자 공간→ 시스템 호출  → 커널 코드 실행 

 

특징 

  • 각 프로세서 마다 독립된 사용자 공간을 제공함.  
  • 시스템 전체에 하나의 커널 주소 공간이 있음. 
  • 모든 프로세스는 커널 주소 공간을 공유함. 

 

 

Q 언제 사용자 공간/커널 공간을 사용할까? 예시 

#include <stdio.h>
int main() {
    printf("Hello\n");
    return 0;
}

1) printf() : 사용자 공간 

2) 화면에 쓰기위해서는 하드웨어 접근 필요 !  시스템 콜 

3) write() : 커널 공간 

 

 

 

Q 사용자 공간내 영역을 어떻게 사용할까? 예시

위에 예시에서 스택의 구조 .

→ 위에 예시에서 헷갈릴 수 있는게 d,c는 f()프레임의 공간이고 p와 b는 main() 프레임 공간이다. 

 

 

process +vms (=virtual memory space)


⭐️ 중요! ) 프로세스 주소 공간은 
가상공간(=논리 공간) | 프로세스가 사용하는 주소 = 가상주소

  • 논리적 : 0번지부터 시작해서 연속적인 메모리에 형성 ⭐️중요!)  가상 공간은 항상 0번째부터 시작. 
  • 물리적(실제) : 코드, 데이터, 힙, 스택에 흩어져서(=분산되서 ) 저장됨 

→ 가상주소 공간과 물리 주소 공간을 연결하는 매핑 테이블을 이용함.


ch 3-2 커널 프로세스 관리 

 

프로세스 테이블 : 시스템의 모든 프로세스들을 관리하기 위한 표. 

→ 구현 방식은 운영체제마자 다름. / 시스템(=컴퓨터 환경)에 1개씩 있음. 

 

프로세스 제어 블록 (PCE) : 프로세스에 관한 정보를 저장하는 구조체 

→ 프로세스당 하나씩 존재

 프로세스가 생성될때 만들어지고 종료되면 종료 

커널에 의해 관리 

 

운영체제가 관리해야하는 핵심 : 프로세스 테이블과 PCB의 위치

→커널 영역,커널 코드만 액세스 가능. 

 

 

상태정보는  PCB에 저장. 

프로세스 마다 프로세스 id가 있음.

 

if 프로세스 == 사람

→ PCB는 주민등록증

  PID는 주민등록번호 

 

 

PCB에는 이런 정보들이 들어 있어요

  • PID (프로세스 ID)
  • 상태 (Running / Ready / Waiting / Zombie 등)
  • 부모 PID (PPID)
  • 사용한 자원, CPU 시간
  • 종료 코드(exit code)

 

 

⭐️⭐️⭐️ 프로세스 생명주기와 상태 변이 (생명주기 그리는 거 시험문제 )_ 연습문제랑 엮어서 보기 

 

 

 

<상태정보>

* 대기큐 와 준비큐 

-대기큐 : 입출력이나 외부 이벤트를 기다림  [상태: Waiting 또는 Blocked]

-준비큐 : cpu를 기다림 [상태: ready]

 

**종료코드 : PCB에 위치함 

상태 전이도



1) new 

-프로세스가 생성된 상태 

- PCB에 NEW상태로 등록 

 

new → ready : 실행 준비를 마치면

 

2) ready

-스케줄링을 기다리는 대기 상태.

-프로세스 준비 큐에서 대기(준비큐는 대기 장소이고 어떤 순서가 우선순위가 될진 OS가 정함)

 

ready →  running : 스케줄링이 되면 

 

3) running

-프로세스 CPU에 의해 실행되는 상태.

 

running  → ready : 시분할 시스템에서 할당한 만큼의 시간(=타임 슬라이드)이 지나면 다시 ready의 준비큐 삽입 상태로 변경. 

 

running  → blocked : 프로세스가 입출력을 시핸하면 커널이 프로세스를 blocked로 만들고 대기 큐 삽입

 

 

4)Blocked=Wait 

-프로세스가 자원을 요청하거나 입출력을 요청하고 완료를 기다리는 상태

 

blocked →ready  : 입출력이 완료되면 다시 ready의 준비큐에 삽입 

 

 

 

 

5) Terminated/Zombie = 자식 종료 & 부모 종료 X

-프로세스의 불완전 종료 상태. 

자식 프로세스가 남긴 종료 코드(PCB에 있음)를 부모 프로세스가 읽어가지 않아 완전히 종료되지 않은 상태

 

 

6) Terminated/Out

-자식 프로세스가 종료 하면서 남긴 종료코드를 부모 프로세스가 읽어 완전히 종료된 상태.

-프로세스 테이블의 항목과 PCB가 전부 제거된 상태. 

 


 

 다시 ready로 가는 방법 3가지 ⭐⭐⭐

1. running → waiting → ready (I/0) : 입출력 완료시 인터럽트 발생

2. running → ready (TTS)  : 시간 초과로 cpu 반납

3. running  ready (preemption) : 선접 스케줄링에 의해 cpu 뺏김. 

 

 

 

오늘날 운영체제의 실행단위 : 스레드 

  • 프로세스 스테줄링은 사라지고 스레드 스케줄링함. 
  • ready 상대에서 스레드 중 실행시킬 스레드 선책함. 
  • 프로세스  : 스레드(thread)에게 공유 자원을 제공하는 컨테이너 역할로 변경됨. 

 

 


Ch 3-3  프로세스 계층 구조 

 

  1. #0 프로세스 =조상 프로세스 (부팅시 실행되는 최초의 프로세스) = 시스템 유휴 프로세스 #0 (=swapped/sched) 커널이 만든  | #1 (=init/systemd) 사용자 레벨 최상위 
  2. 부모 프로세스  : 여러개의 자식 프로세스를 가질 수 있음 | wait() 시스템 호출을  통해 반드시  자식 프로세스의 종료 코드를 읽어야 함.
  3. 자식 프로세스 : 부모 프로세스에 의해 생성 (프로세스 생성은 시스템 호출로)

#0 , #1프로세스를 제외한 모든 프로세스는 부모 프로세스를 가짐.

 

 

 

 

 


fork() : 시스템 콜 함수 , 자식 프로세스의 생성.

 

exec(): 덮어 쓸때 사용 = 프로세스의 오버레이에서 사용.

→현재 프로세스의 종료를 알리는 시스템 호출 & 커널 코드 실행. 

 

wait() : 부모가 자식 프로세스의 종료를 기다리고 확인. (종료대기).

※ 부모가 죽었으면 좀비 프로세스이고 다른 부모 프로세스로 입양시켜줘여함

 

exit() : 프로세스 종료 과정에서 사용.

 

이 정보도 pcb에 저장되어 있음.

 

 

 

프로세스 종료 

  • PCB에 종료 코드(종료 상태) 저장 
  • PCB에 프로세스 상태를 Terminated라고 표시 
  • 프로세스에 할당된 모든 메모리 반환 __※ 단  PCB와 프로세스 테이블은 제거 되지 않음. (부모 프로세스가 자식의 종료 상태를 읽어야 하기 때문. )

 

좀비 상태 

  • 종료 하였지만 부모가 종료 코드를 읽지 않은 상태 프로세스
  • 프로세스는 죽었지만 PCB는 남아있는 상태. 

 

좀비 프로세스 제거 방법

 

방법 1) 쉘에서 부모 프로세스에게 SIGCHLD 신호 보내기

(부모가 신호를 받으면 wait()호출 → 자식의 종료 상태를 읽고 PCB 제거)

 

방법 2) 부모 프로세스에서 강제 종료.

방법1 로 종료된 상태.

 

 

 

고아프로세스 

: 부모가 먼저 종료한 자식 프로세스 

커널 역할 자식(고아)를  init(#1 최상위 프로세스)에 입양시킴
쉘의 예외적 행동 모든 자식 프로세스를 강제 종료 하기도 함. 
결론 고아 프로세스는 결국 init이 관리하므로 좀비가 되지 않음

 

 

여러 종류의 프로세스 ( 간단하게만) 

  • 백그라운드 프로세스 : 터미널과 상호 작용하지 않고 돌아가는 작업. 
  • 포그라운드 프로세스 : 실행되는 동안 터미널 사용자의 입력을 독접하는 프로세스. 

 

→ 운영체제  스케줄링  : I/O 집중  > CPU 집중

  • cpu 집중 프로세스  :대부분의 시간을 계산 중심의 일을 하느라 보내는 프로세스 . 
  • I/O 집중 프로세스  : 입출력 작업을 하느라 대부분의 시간을 보내는 프로세스 .  

Ch 3-4 프로세스 제어 

 

1. 프로세스 생성 :  fork() , CreateProcess()

: 시스템 호출을 통해서만 프로세스 생성 ⭐

 

 

<프로세스 생성 과정 > :  PID → PCB → 메모리 → 준비 큐

1 PID 번호 할당 고유한 식별자 부여
2 PCB 생성 프로세스 제어 정보 저장 구조체 생성
3 프로세스 테이블 슬롯 확보 전체 목록에 새 항목 등록 
4 PCB 연결 프로세스 테이블과 PCB 연결
5 메모리 공간 확보 코드, 데이터, 힙, 스택 영역 준비
6 코드/데이터 로드 실행파일을 RAM에 복사
7 PCB 정보 기록 상태, PC, 메모리 주소 등 설정
8 Ready 큐 등록 실행 대기 상태로 스케줄러에게 전달

 

 

 

< 프로세스 생성되는 경우 5가지 >

 

(1)  시스템 부팅 과정에서 필요한 프로세스 생성 

컴퓨터가 켜질 때 자동으로 생기는 프로세스들.ex ) 커널이 로드되고 PID 1 (init 또는 systemd)가 만들어짐.

 

(2) 사용자 로그인 후 , 사용자와 대화를 위한  프로세스 생성 

사용자가 로그인 하면 나오는 프롬프트 창 (bash,zsh같은 쉘) 생성 

user login login 프로세스  bash 실행 

 

(3) 새로운 프로세스를 생성하도록 하는 사용자 명령

사용자가 쉘에 명령을 입력 했을때 쉘 내부적으로 fork() + exec()를 호출해서 새 프로세스를 만듦.

 

 

(4) 배치 작업 실행시 

→예약 작업, 자동 실행 작업. 

 

 

(5) 사용자 응용프로그램이 시스템 호출로 새 프로세스 생성. 

→ ex ) 어떤 프로그램 안에서 직접 새 프로세스를 만드는 경우. 

 

 


 

fork리턴값 잘보기

⚠️위에 그림에서 헷갈릴 수 있지만 pid라는 변수가 0인거지 실제 프로세스 id가 0이라는 건 아님.

Q 자식프로세스  pid는 얼마일까요? 

 

 

fork()

: 현재 프로세스를 복사하여 자식 프로세스 생성. 

 

int pid = fork();

  • 부모와 동일한 모양이지만 독립된 주소 공간 소유
  • fork() 리턴 값 : 부모 (자식 PID) | 자식 ( 0 ) 

→ 부모와 지식이 완전히 똑같은 코드를 실행하고 차이가 딱 이거 하나임. 

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
        pid_t pid;
        int i, sum=0;

        pid = fork(); // 자식프로세스 생성
		 if(pid > 0) { // 부모 프로세스에 의해 실행되는 코드
 				  printf("부모프로세스: fork()의 리턴 값 = 자식프로세스 pid = %d\n", pid);  //(1)⭐
                printf("부모프로세스: pid = %d\n", getpid()); //(2)⭐
                wait(NULL); // 자식 프로세스가 종료할 때까지 대기
                printf("부모프로세스종료\n"); //(6)⭐
                return 0;		
		 }
        else if(pid == 0) { // 자식 프로세스에 의해 실행되는 코드
				printf("자식프로세스: fork()의 리턴 값 pid = %d\n", pid); //(3)⭐
                printf("자식프로세스: pid = %d, 부모프로세스 pid = %d\n", getpid(), getppid()); // (4)⭐
				 for (i=1; i<=100; i++) 
                        sum += i;
                printf("자식프로세스: sum = %d\n", sum); // (5)⭐
                return 0;	
		}
		else { // fork() 오류
                printf("fork 오류");
                return 0;
        }
}

 

 

위에 코드 실행 결과  / 순서 예상 해보기 

더보기

$ gcc -o forkex forkex.c

$ ./forkex

부모프로세스: fork()의 리턴 값 = 자식프로세스 pid = 29138

부모프로세스: pid = 29137

자식프로세스: fork()의 리턴 값 pid = 0

자식프로세스: pid = 29138, 부모프로세스 pid = 29137

자식프로세스: sum = 5050

부모프로세스종료

 


 

2. 프로세스 오버레이 : exec()

: 현재 실행중인 프로세스의 주소 공간에 새로운 응용 프로그램을 적재하여 실행시키는 기법 

 

  • 새로 프로세스를 생성하는 과정은 없다. 
  • 프로세스의 코드, 데이터, 힙, 스택에 새로운 응용 프로그램이 적재됨. 
  • fork()에 의해 생성된 자식 프로세스는 생성후 바로 exec()가 실행되는 경우가 많음. 
  • 시스템 호출  : execlp(), execv(), execvp() 

 

fork()    exec()

  • fork() : 기존 프로세스를 복제해서 자식 프로세스 생성 
  • exec() :  자식 프로세스의 기존 코드 (메모리)를 새 프로그램으로 완전히 덮어쓰기 

 

exec()
위에 예시

 

pid 변화 살펴 보기

 


3. 자식 프로세스 종료 대기  : wait() 와 프로세스 종료 : exit()

 

● 프로세스 종료 (동일한 동작)

- exit() 시스템 호출 

- C프로그램의 main()에서 리턴.

 

 

  종료코드 :

= 부모 프로세스에 전달하는 값. (1~255) 

=프로세스가 종료한 상태나 이유를 부모에게 전달하기 위한것. 

 

(동일한 동작)

- main()의 리턴 값; return 종료 코드 

- exit( 종료 코드 )

 

만약에 return -1 또는 exit(-1)을 하게 되면 0xff로 255 전달됨. 

 

 

 

< exit() 시스템 호출로 프로세스 종료 과정 >

1. 자원 반환  프로세스의 모든 자원 해제.  코드,데이터, 스택. 힙 등의 모든 메모리 자원을 반환. 
2. PCB 갱신  프로세스 상태 변경 및 종료 코드 저장. terminated로 변경 PCB에 종료 코드 저장
3.  자식 프로세스 처리  고아 프로세스 입양 부모가 먼저 종료된 경우, init() 에게 입양됨. 
4. 부모에게 신호 전송 SIGCHLD 신호 발생.  자식이 종료될때 알려줌.  " 나 죽음"

 

 

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;
        }
}

 

 

child.c

#include <stdio.h>

int main() {
        printf("I am  a child\n");
        return 100;
}

 

 

실행 순서 잘 생각해보기 

더보기

$ gcc -o child child.c

$ gcc -o waitex waitex.c

$  ./waitex

부모프로세스: 자식의 종료를 기다림

I am a child

부모프로세스: child의 종료 코드=100

$


4. 좀비 프로세스 

: 자식이 죽었는데 부모가 wait()으로 종료 코드를 읽지 않으면 커널은 PCB를 지우지 않고 남겨둠. 이 상태가 좀비 프로세스.

  1. 정상 종료_ main() 함수 종료나 exit() 호출
  2. 강제 종료 _다른 프로세스에 의해(kill)

 

프로세스 종료시 )

 

모든 자원이 반환되어야 함.

-PCB는 프로세스 테이블에서 제거 되지 않음. [부모 프로세스가 자식의 종료 상태를 확인 할 수 있게 남겨두는 것임]

-프로세스 상태 : terminated

⭐ 종료 코드를 읽은 뒤에서 커널에서 해당 PCB가 완전히 삭제됨. 

 

 

 

좀비 상태는   프로세스 테이블에서 확인 가능. (S가 Z로 표기)