운영체제

Chap 2. 프로세스

앞발로코딩 2023. 10. 11. 15:25

CPU 가상화 (Virtualization)
=> 어떻게 많은 수의 가상 CPU를 제공할수있을

 
프로세스의 실행
프로세스의 실행은 아래와 같이 이루어진다
Fetch -> Decode -> Execute -> Update

그렇다면 여러개의 프로세스를 동작하려면 다음과 같이 동작되어야한다.
=> 하지만 실제 CPU는 하나이므로 가상의 CPU가 필요함

실제로는 이런식으로 하나의 CPU 가 하나씩 프로세스의 일부를 돌아가면서 실행하면 프로세스 입장에서는 각 프로세스마다 CPU가 같이 동작한다고 생각하게할수있음

프로세스
: 실행되고 있는 프로그램의 인스턴스
=>인스턴스란 객체를 대명사로 만들었다고 생각하면 된다.
     =>자바 기준, 클래스 -> 프로그램(정적), 객체 -> 프로세스 (동적)
- 가장 작은 단위의 보안
- 고유의 프로세스 아이디로 구분(Process id, PID)
- 프로세스가 포함하는 것
: cpu 정보(레지스터) => 프로그램 카운터, 스택 포인터
: OS 자원 => 주소 공간, open files
: 그외 정보 => PID, state, owner

프로그램에서 프로세스
메모리
- Text는 프로그램 코드의 집합을 포함한다

- Stack은 함수들의 인자(parameter)와 반환 주소와 지역 변수(local variable)을 포함함

=> 함수는 스택에만 저장된다

- Data는 전역 변수(global variable)과 static 변수를 포함함

- Heap은 메모리 영역을 할당함

=> malloc을 통해 메모리 동적 할당

레지스터

- 프로그램 카운터

- 스택 포인터

- 프레임 포인터

 

프로세스 생성

1. 프로그램 코드를 메모리로 로드,  프로세스에 주소 공간을 할당

- 프로그램은 실행가능한 형태(executable format)로 디스크에 저장된다

- OS는 로딩 과정을 게으르게 수행함

=> 프로그램 실행 중 필요할때만 코드나 데이터를 로드한다는 뜻

 

 

2. 프로그램의 런타임 스택을 할당한다

 - 지역 변수, 함수 인자, 반환 주소에 대해 스택을 사용한다

- 인자들(main() 문의 argc와 argv )을 사용하여 스택을 초기화

 

3. 프로그램의  힙 생성

- 명시적으로 요청된 동적 할당 데이터에 사용됨

- 프로그램은 malloc()을 호출하여 해당 공간을 요청하여 할당, free() 함수를 사용하여 동적할당된 메모리 해제

 

4. OS 여러 초기화를 수행

- input/output (I/O) 설정

=> 각각의 프로세스들은 default로 3개의 open file descriptors가 있음

(파일 디스크립터(File Descriptor)란 리눅스 혹은 유닉스 계열의 시스템에서 프로세스(process)가 파일(file)을 다룰 때 사용하는 개념으로, 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값)

=> 표준 입력, 출력, 에러

 

5. entry point 에서 program을 실행한다(주로 main()문에서)

- OS는 CPU의 제어권을 새로 생성된 프로세스로 제어권을 넘긴다.

 

프로세스의 상태

: 프로세스 상태는 3가지가 있음

- Running

: 프로세서에서 실행되고있는 프로세스

- Ready

: 프로세스는 실행 준비가 되었지만, 여러 이유로 인해 OS가 아직 실행하지않은 프로세스

- Blocked

: 프로세스가 디스크에 대한 I/O 요청을 했을때, 이 요청이 형식에 맞지 않을 경우 blocked상태가 된다.

=> I/O가 없으면 blocked 상태는 존재하지않음

=> 디스크가 아닌, 메모리에 올려두는 것

CPU만(디스크 I/O 없음)

CPU와 디스크 I/O 

프로세스 생성

 

fork() 함수

- 부모 프로세스를 복사해서 새로운 프로세스를 만드는 함수

=> 부모 프로세스는 대부분의 리소스와 권한(열린 파일, UID 등) 대부분의 리소스와 권한을 자식프로세스에게 상속
=> 자식은 부모의 주소 공간도 복제한다

- 부모는 자식이 완료될 때까지 기다리거나(wait()를 사용하여) 병렬적인 처리로 continue한다
- Shell 이나 GUI는 내부적으로 이 시스템 호출을 사용

 

++ init 프로세스(가장 처음 시작되는 프로세스, 이 부분은 미리 설정된 코드로 실행된다)

 

exec() 함수

- 현재 프로세스 이미지를 새 프로그램으로 바꿈

=> 명령이나 실행파일을 실행함

- Windows 에서 프로세스를 만든다는것

=> CreateProcess() = fork() + exec()

 

Process Termination (프로세스 종료)

- 정상 종료 (자발적임)

 

- 오류로 인한 종료 (자발적임)

 

- Fatal error (비자발적임)

=> Segmentation fault (배열에서 4개를 할당했지만, 값이 5개가 들어왔을때 같은 경우 발생, 잘못된 메모리 접근(illegal memory access))

=> Protection fault

=> 할당된 리소스 초과 (Exceed allocated resources)

 

- 다른 프로세스에 의해 종료(killed) (비자발적임)

=> 신호를 수신

 

- 좀비 프로세스 : 종료되었지만 제거되지않았음

 

프로세스 상속 (Process Hierarchy)

부모-자식 관계

- 프로세스는 다른 프로세스를 생성할수있음

- 유닉스에서는 계층 구조를 "프로세스 그룹"이라고 부름

- Windows에는 프로세스 계층 구조 개념이 없음

 

프로세스 목록 찾아보는 법:
- Unix의 ps 명령
- Windows의 작업 관리자(taskgr)

 

$ cat file1 | wc

|는 파이프라고 한다. 파이프는  두 프로세스 사이에서 한 방향으로 통신할 수 있도록 지원(in & out)

wc는 word counter

#include <sys/types.h>
#include <unistd.h>
int main()
{
	int pid;
    // fork() 함수는 리턴값이 2개다, 이전 프로세스(1), 새로운 프로세스 (0)
	if ((pid = fork()) == 0)
		/* child */
		printf (“Child of %d is %d\n”, getppid(), getpid());
	else
		/* parent */
		printf (“I am %d. My child is %d\n”, getpid(), pid);
}

 

리눅스 프로세스

 

프로세스의 구현

PCB (Process Control Block, Process Descriptor)

: 프로세스에 대한 모든 정보를 포함한다

- CPU 레지스터
- PID, PPID, 프로세스 그룹, 우선순위, 프로세스 상태, 신호
- CPU 스케줄링 정보
- 메모리 관리 정보
- 계정 정보
- 파일 관리 정보
- I/O 상태 정보
- 자격 증명

 

- Process Descriptor는 보통 PCB를 말하는 것이나, Linux에서는 task_struct( 3248 bytes as of Linux 3.2.0 )

 

xv6 kernel Proc Structure

// the registers xv6 will save and restore
// to stop and subsequently restart a process
struct context {
	int eip; // Index pointer register
	int esp; // Stack pointer register
	int ebx; // Called the base register
	int ecx; // Called the counter register
	int edx; // Called the data register
	int esi; // Source index register
	int edi; // Destination index register
	int ebp; // Stack base pointer register
};

// the different states a process can be in
enum proc_state { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };

// the information xv6 tracks about each process
// including its register context and state
struct proc {
	char *mem; // Start of process memory
	uint sz; // Size of process memory
	char *kstack; // Bottom of kernel stack
					// for this process
	enum proc_state state; // Process state
	int pid; // Process ID
	struct proc *parent; // Parent process
	void *chan; // If non-zero, sleeping on chan
	int killed; // If non-zero, have been killed
	struct file *ofile[NOFILE]; // Open files
	struct inode *cwd; // Current directory
	struct context context; // Switch here to run process
	struct trapframe *tf; // Trap frame for the
						// current interrupt
};

 

Context Switch in Linux

Context Switch : CPU가 어떤 하나의 프로세스를 실행하고 있는 상태에서 인터럽트 요청에 의해 다음 우선 순위의 프로세스가 실행되어야 할 때 기존의 프로세스의 상태 또는 레지스터 값(Context)을 저장하고 CPU가 다음 프로세스를 수행하도록 새로운 프로세스의 상태 또는 레지스터 값(Context)를 교체하는 작업

 

프로세스 상태 큐 ( Process State Queues )
OS는 시스템의 모든 프로세스 상태를 나타내는 시스템의 모든 프로세스 상태를 나타냄 (큐베이스로 프로세스 관리)
- 준비 큐(또는 실행 큐) (ready queue)
- 대기 큐: 각 이벤트 유형(장치, 타이머, 메시지, ...) (wait queue)

=> block I/O

 

각 PCB는 현재 상태에 따라 상태 대기열에 대기

- 프로세스 상태가 변경되면 PCB는 여러 대기열에서 변경됨

 

fork() 생성

- 새 PCB를 생성하고 초기화
- 새 주소 공간을 생성하고 초기화
- 부모 주소 공간의 전체 복사본으로 주소 공간을 초기화
- 내용으로 주소 공간을 초기화
- 커널 리소스가 부모가 사용하는 리소스를 가리키도록(point) 초기화
- 리소스를 가리키도록(point) 커널 리소스를 초기화 (예: open files).
- PCB를 ready queue에 배치합니다.
- 자식 프로세스의 PID를 부모 프로세스에 반환하고 자식 프로세스에는 0을 반환함

C언어에서의 프로세스 생성 구현

fork() => call by value (포인터가 아님) => 참조하지않으므로 클론 후 각자 다른 프로세스(?)

 

do_fork() <kernel/fork.c>

- 새 PID 할당

- copy_process()

   -  dup_task_struct()을 호출하여 새로운 커널 스택(new kernel stack), 스레드_정보(thread_info), 태스크_구조체(task_struct)를 생성

   - 리소스 복사: copy_files(), copy_fs(), copy_mm(), ...

   - 커널 스택을 초기화하기 위한 copy_thread()

   - sched_fork()를 사용하여 새 작업을 스케줄링할 준비

 

wake_up_new_task()

- 하위 작업 예약(하위 작업 먼저 실행)

 

exec()

- 현재 프로세스를 중지
- 프로세스 주소 공간에 프로그램 "prog"를 로드

- 새 프로그램에 대한 하드웨어 컨텍스트( hardware context )와 "args"를 초기화

- PCB를 ready queue에 위치시킴

- exec()는 새로운 프로세스를 만들지않음

- exec()는  어떤 값도 return하지 않으며, 단순히 그 명령어를 수행