Computer Science/시스템 프로그래밍

시스템 프로그래밍 chapter 03.

만능 엔터테이너 2024. 10. 8. 16:09
728x90
반응형
SMALL

Process in UNIX

 

  • 프로세스를 생성하는 방법을 배우기
  • fork와 exec 함수에 대해 실험하기
  • 프로세스 상속의 의미를 탐구하기
  • wait 함수를 사용하여 프로세스 정리를 수행하기
  • UNIX 프로세스 모델 이해하기

Process (프로세스)

- 프로세스는 대부분의 운영 체제 모델에서 기본적인 실행단위이고 실행 중인 프로그램을 의미
- CPU는 실행 준비된 프로세스들 사이에서 공유되며, 한 번에 하나의 프로세스만 실행되고 CPU는 프로세스 간 context switch (문맥 전환)을 통해 빠르게 전환되며 시스템 호출(system call)을 사용하여 운영체제와 통신함
- 프로세스언제든지 장치 인터럽트 및 시스템 호출에 의해 중단가능하며 프로세스 제어 블록(PCB) 또는 프로세스 설명자 라는 데이터 구조로 표현

Context Switch(문맥 교환)

- context switch는 현재 실행 중인 프로세스의 상태를 저장하고, 다음에 실행할 프로세스의 상태를 로드하는 과정 즉, 하나의 프로세스 상태를 저장하고 다른 프로세스의 상태로 전환하는 작업
현재 실행 중인 프로세스의 정보를 충분히 저장해야 나중에 중단된 시점부터 아무 일도 없었던 것처럼 다시 실행가능하며 이는 프로세스 제어 블록(PCB)의 중요한 구성 요소를 결정함
- Context Switch인터럽트에 의해 시작

  • 소프트웨어 인터럽트 (시스템 호출)
  • 장치 인터럽트
  • 타이머 인터럽트 (시간 할당량이 만료될 때)

Context Switch Step

  1. CPU가 인터럽트를 감지합니다.
  2. CPU가 특권 모드로 인터럽트 핸들러를 시작합니다.
  3. 인터럽트 핸들러가 중단된 프로세스의 상태를 저장합니다.
  4. 인터럽트 핸들러가 인터럽트 코드를 실행합니다.
  5. 인터럽트 핸들러가 CPU 스케줄러를 호출하여 다음 프로세스를 준비합니다.
  6. CPU 스케줄러가 다음 프로세스의 상태를 로드합니다.
  7. CPU가 사용자 모드로 돌아가 새로운 프로세스를 실행합니다.

이 과정을 통해 현재 실행 중인 프로세스에서 새로운 프로세스로 매끄럽게 전환이 이루어집니다.

Process Indentification (프로세스 ID)

pid_t getpid(void) - 프로세스 ID를 가져옴, pid_t getppid(void) - 부모 프로세스 ID를 가져옴, pid_t부호 없는 정수형 타입 / 부모 프로세스가 종료되면, 자식 프로세스는 시스템 프로세스에 의해 연결
- 시스템 관리자는 사용자의 계정 생성시, 고유한 사용자 ID와 그룹 ID를 할당하고 이를 통해 프로세스에 권한이 부여

  • gid_t getegid(void), uid_t geteuid(void) : 유효 사용자그룹 ID를 가져옴 (프로세스가 실행되는 사용자 - 유효 사용자)
  • gid_t getgid(void), uid_t getuid(void) : 실제 사용자그룹 ID를 가져옴 (프로세스를 실행한 사용자 - 실제 사용자)

=> real user와 effective user가 보통 동일하였는데, password command로 사용자의 암호를 변경할 때, shadow파일 변경하는 경우

Process state

 
Context switch (문맥 전환)실행 중인 프로세스를 중단하고, 다른 프로세스로 전환하는 작업을 의미
Process context (프로세스 문맥)문맥 전환 후 프로세스의 재시작을 위한 환경 정보를 포함한 데이터를 의미

[참고]

  • new (새로 생성된 상태): 프로세스가 생성된 상태입니다.
  • ready (준비 상태): 실행될 준비가 된 상태로, CPU에 의해 선택될 수 있습니다.
  • running (실행 상태): 프로세스가 실제로 CPU에서 실행 중인 상태입니다.
  • blocked (차단 상태): 입출력 작업이 완료될 때까지 기다리는 상태입니다.
  • done (종료 상태): 프로세스가 정상 또는 비정상적으로 종료된 상태입니다.

Process Hierachy (프로세스 계층)

- Parent and Child Processes (부모 프로세스와 자식 프로세스): 프로세스 A (부모)가 프로세스 B (자식)를 생성
- Root process (루트 프로세스): 모든 프로세스의 조상으로, 시스템 부팅 시 실행되는 init 프로그램의 실행입니다.
- Shell Example (쉘 예시): 쉘은 입력에 응답하여 새로운 프로세스를 생성합니다. (여기서 ""은 명령어 해석기를 의미하며, 사용자가 명령어를 입력할 때 새로운 프로세스를 생성하는 역할을 합니다.)

쉘 프로세의 작동 과정

 

System Function for process (프로세스를 위한 시스템 함수)

  • fork: 호출한 프로세스를 복제하여 새로운 프로세스를 생성합니다.
  • exec family: 호출한 프로세스가 다른 프로그램을 실행하게 합니다.
  • exit: 호출한 프로세스를 종료시킵니다.
  • wait family: 자식 프로세스가 종료될 때까지 대기합니다.

fork() 함수

- 프로세스 생성 : 새로운 프로세스를 생성, 이 프로세스는 호출한 프로세스의 복사본
- 부모 & 자식 프로세스 : 호출한 프로세스를 부모, 새롭게 생성된 프로세스를 자식, 자식 프로세스부모 프로세스의 코드를 복사하여 가지고 있음
반환값 : 자식 프로세스는 0이 반환되고 부모 프로세스에서 자식 프로세스의 ID가 반환되며 부모 프로세스에는 자식 프로세스의 ID가 반환됨, 에러 발생 시, 부모 프로세스에 음수 값이 반환
 

Process ID:

  • 각 프로세스는 고유하게 할당된 정수형 ID를 가짐

프로세스 생성(fork), return값은 경우에 따라 3가지로 다르게 나타남

 

fork() 함수의 특징

- 부모 프로세스의 메모리 이미지(코드)복사하여 새로운 프로세스를 생성
- 자식 프로세스부모의 환경 및 권한과 같은 속성을 상속 받고 자식 프로세스는 또한 부모의 리소스 중 일부를 그대로 상속 받아 사용함 ex) 열린 파일 및 장치를 상속, 자식은 부모의 환경과 리소스 일부를 물려 받음
- fork() 함수호출된 프로세스의 정확한 복사본을 만들고 PID(프로세스 ID)PPID(부모 프로세스 ID)만 다르게 나타나며 모든 데이터는 지연 복사
- 2개의 프로세스는 같은 코드를 실행하며 실행의 동일한 지점에서 실행됩니다. fork() 후 반환값은 다릅니다.

  • 부모 프로세스자식의 PID를 반환받음
  • 자식 프로세스 0을 반환받음

Parent Attributes and Resources (부모 프로세스의 속성과 리소스)

- 모든 속성리소스자식에게 상속되지 않습니다.

  • 프로세스 ID는 상속되지 않고 자식은 새로운 프로세스 ID를 가짐
  • CPU 사용 시간자식 프로세스가 0으로 초기화
  • 락과 알람은 상속되지 않음, 부모 및 자식이  설정한 알림이 상호 통지되지 않음
  • 대기 중인 신호도 상속되지 않고 부모가 대기 중인 신호를 가져도 자식은 새로운 상태에서 시작됨, 각 프로세스는 시그널 마스크가 존재하여 설정된 신호를 막음(부모가 막은 시그널은 자식이랑 상관없는 정보)
  • 자식 프로세스는 부모 프로세스와 별도로 다른 프로세스와 프로세서 시간을 놓고 경쟁을 함
  • 사용자가 타임 쉐어링 시스템에서 여러 프로세스를 생성하여 더 많은 cpu 시간을 확보할 수 있음

장점:
- 자식 프로세스는 fork를 통해 부모의 데이터를 상속받고 부모로부터 추가적인 통신이 필요 없이 자식 프로세스가 정보를 얻을 수 있음
단점:
- 동일한 코드 파일만 사용해야 하며, 다른 코드를 실행하려면 fork만으로는 불가능
 

코드 설명:

  • 변수 x는 0으로 초기화됩니다.
  • fork() 함수 호출 후, 부모와 자식 프로세스가 생성됩니다.
  • 자식 프로세스는 fork 이후 실행되며, 자식에서 x 값은 1로 변경됩니다.
  • printf를 통해 각 프로세스의 PIDx 값이 출력됩니다.
  •  

질문:

  • x의 값은 무엇인가요? x = 1 (자식 프로세스에서 값이 1로 변경됨)
  • 부모 프로세스와 자식 프로세스 중 누가 먼저 실행되나요? 답을 알 수 없지만, 대부분 부모 프로세스가 먼저 실행됩니다. Context switch가 발생하며, 자식 프로세스가 뒤이어 실행됩니다.

위의 예제 문제를 도식화한 이미지

 

Run Different Code( 다른 코드를 실행)

이전에는 부모와 자식 프로세스가 같은 명령을 실행하였지만 fork() 함수의 반환 값을 사용하여 부모와 자식 프로세스가 서로 다른 명령을 실행할 수 있음
fork() 함수는 자식 프로세스에 0을 반환하고 부모 프로세스에는 자식 프로세스의 PID를 반환

부모와 자식이 서로 다른 코드를 실행하는 상황 예시 / 부모 - 파란색, 자식 - 초록색

자식 프로세스일 경우 3111, ID = 3110 / 부모 프로세스일 경우 3110, ID = 3110

부모가 fork를 하고 자식이 그 다음 fork로 하는 형식 구조

 


- Example 4 작동, n 개의 프로세스 체인을 생성
하는 예시 설명

  1. 각 반복에서 부모 프로세스는 childpid가 0이 아닌 값을 받고 루프를 종료합니다.
  2. 자식 프로세스는 childpid 값이 0이고 다음 루프 반복에서는 부모가 됨
  3. 에러 발생 시, fork는 -1을 반환하고 프로세스는 루프를 종료

fork() 호출의 조건을 변경했을 때 발생하는 상황을 다룹니다.

  • 원래 조건 (childpid = fork()) <= 0을 (childpid = fork()) == -1로 바꿨을 경우:
    • 에러가 발생하면 루프에서 빠져나오게 됩니다.
  • 모든 프로세스는 fork() 실패가 발생하지 않는 한 루프에 계속 남아 있게 됩니다.
  • 루프의 각 반복은 프로세스 수를 2배로 증가시킵니다.

sleep function 

- ps 명령어로 생성된 프로세스를 보려면 return 전에 sleep(30); 문장을 추가하여 "각 프로세스가 종료되기 전 30초 동안 멈추어 ps명령어로 확인 가능!"


wait() 함수

부모 프로세스는 자식 프로세스가 할당된 작업을 끝냈는지 확인할 필요가 있습니다.
- wait 함수는 자식 프로세스의 상태가 준비되거나 호출한 프로세스가 신호를 받을 때까지 실행을 중지
- wait 함수부모 프로세스에게 다음을 수행하도록 합니다.

  • 자식 프로세스 종료될 때까지 기다림
  • 자식 프로세스종료 될 때 반환된 값을 받음
  • 자식 프로세스로부터 상태 정보를 수신

wait & waitpid 함수

  • 헤더 파일 포함: #include <sys/wait.h>
  • 함수 프로토타입:
    • pid_t wait(int *stat_loc);
    • pid_t waitpid(pid_t pid, int *stat_loc, int options);
  • wait 함수종료된 자식 프로세스의 PID를 호출자에게 반환합니다.
    • 자식 프로세스의 종료 상태도 반환할 수 있습니다 (예: stat_loc 변수에 저장)
    • 자식 프로세스가 시그널로 종료되었는지, 어떤 시그널로 종료되었는지 등의 정보도 포함
    • 오류가 발생하면 -1을 반환하고, errno 변수를 설정하여 오류 내용을 저장

- waitpid는 더 일반적인 함수로 아래의 기능을 제공합니다.

  • 특정 자식 또는 특정 프로세스 그룹의 자식 프로세스기다릴 수 있고, 옵션을 사용하여 비차단(non-blocking) 상태로 만들 수 있습니다. (예, WNOHANG 옵션)
  • 정지된 자식 프로세스의 상태를 알 수 있고, 오류가 발생하면 -1을 반환 및 errno를 설정합니다. 만약, WNOHANG 옵션을 설정한 상태에서 waitpid 함수를 호출하면, 부모 프로세스는 자식 프로세스가 끝날 때까지 기다리지 않고 바로 반환됩니다. 이때 반환값이 0인 경우는, 자식 프로세스 중에 아직 종료되지 않은 프로세스가 있다는 것을 의미합니다. 하지만 그 자식 프로세스의 종료 상태는 아직 알 수 없음

차이점 정리:

  • wait: 모든 자식 프로세스를 기다림. 자식이 종료될 때까지 부모는 일시정지 상태.
  • waitpid: 특정 자식을 골라 기다릴 수 있으며, 옵션을 통해 기다리지 않고 바로 확인할 수도 있음.

stat_loc

- wait 또는 waitpid 함수는 자식 프로세스가 종료된 후 부모 프로세스가 그 상태를 확인할 수 있도록 돕는 함수, 여기서 stat_loc 인수는 자식 프로세스의 종료 상태를 저장하는 정수형 변수의 포인터를 말하며 이를 통해 부모는 자식의 종료 상태를 확인 할 수 있음
- stat_loc 인수는 NULL이 아니면, wait 또는 waitpid 함수가 자식의 종료 상태를 stat_loc에 저장
- 자식 프로세스는 exit, _exit, _Exit 함수나 main 함수에서 return을 호출하여 종료 상태를 반환 / 이때, 부모 프로세스는 자식의 상태 값 중 8비트만 접근이 가능
 
POSIX는 자식의 상태를 확인하기 위한 6개의 매크로를 제공합니다:

  • WIFEXITED: 자식이 정상적으로 종료되었을 때 반환값이 0이 아닙니다.
  • WEXITSTATUS: 자식이 정상적으로 종료되었을 때 반환된 하위 8비트 값을 확인합니다.
  • WIFSIGNALED: 자식이 잡히지 않은 신호로 인해 종료되었을 때 0이 아닌 값을 반환합니다.
  • WTERMSIG: 자식 프로세스가 어떤 신호로 인해 종료되었는지를 반환합니다.
  • WIFSTOPPED: 자식이 현재 정지 상태라면 0이 아닌 값을 반환합니다.
  • WSTOPSIG: 자식을 정지시킨 신호 번호를 반환합니다.

fork 함수와 wait 함수를 동시에 사용하는 것이 일반적, 부모 프로세스 100 자식 프로세스 105번 ID를 가짐

 
 

Waiting for all children

  • while(r_wait(NULL) > 0):
    이 코드는 r_wait() 함수가 반환하는 값이 0보다 큰 동안 반복문을 실행합니다. 즉, 모든 자식 프로세스들이 종료될 때까지 계속해서 기다린다는 의미입니다.
  • r_wait() 함수:
    wait() 함수가 신호에 의해 인터럽트되었을 때, 이를 다시 재시작하도록 설계되었습니다.

exec() 함수

- fork() 함수 : 호출 프로세스의 복사본을 생성 (새로운 프로세스 생성됨)
exec() 함수 : 새로운 코드를 로드 및 새로운 프로세스를 생성하지 않고, 현재 프로세스의 코드를 새로운 코드로 교체함
 
즉, fork로 프로세스를 복제한 후 exec 함수를 호출하게 되면 자식 프로세스는 부모와 동일한 코드를 실행하지 않고 새로운 코드를 시작부분부터 실행하게 됨

fork함수가 실행되면 fork한 반화된 코드 다음부터 작업을 수행, exec 함수를 실행하게 되면 새로운 코드가 로드되면서 처음부터 작업을 수행

쉘 프로세스에 사용자가 명령어를 입력 -> 쉘 프로세스는 cat 명령어를 수행하기 위해 fork를 하여 쉘 복제본을 생성함, 그리고 그 복제본에서 cat 명령을 수행하고 사용자가 입력한 file1을 제공
 
모든 exec 함수군은 궁극적으로 execve를 호출하는 동일한 작업을 수행함, 단지 exec 함수들 사이의 차이점은 각각의 다른 인수를 다룸

exec 함수군의 궁극적인 작동 과정

exec 함수를 호출하면, 호출된 프로세스는 새로운 프로세스로 교체되고 fork 함수처럼 부모 프로세스의 복사본을 가지지 않음
- exec 함수fork 함수와 달리 부모 프로세스로 돌아가지 않음 / exec 함수는 새로운 프로그램으로 현재 프로세스를 완전히 대체하며, 부모 프로세스로 돌아가지 않는 특성을 가집니다.
 

Characteristics of exec (exec 함수의 특성)

- 프로그램 텍스트, 변수, 스택, 힙 덮어쓰기가 됨 : exec 함수가 호출되면 기존 프로세스의 이러한 부분들이 새로운 프로그램 코드로 덮어쓰여 집니다.
- 교체된 프로세스는 환경 변수를 상속 받음(단, execle 또는 execev 함수는 제외) : 기본적으로 exec 함수는 환경 변수를 유지하지만 특정함수는 새로운 환경 변수를 지정할 수 있음

크게 execl 패밀리와 execv 패밀리로 나눌 수 있음

execl : execl,execlp,execle    / execv : execv, execvp, execve


execl

execl(const char *path, const char *arg0, ..., const char *argn, (char*) 0);
- execl 함수는 알려진 파일알려진 인자있을 때 유용
- 마지막 매개변수는 항상 0이어야 하고(NULL 종료자 역할을 함) 
- path실행할 파일의 이름을 가리키고 절대 경로 및 현재 디렉터리 기준으로 상대 경로일 수 있음
- arg0(실행 파일명)은 path와 같은 문자열을 나타내며 arg1 ... argn명령에 대한 인자들을 가리킴, 0(또는 NULL)은 가변적인 인자 목록의 끝을 표시

execlp

int execlp(const char* file, const char *arg0, ..., const char *argn, (char*) 0);
- execlp 함수는 파일 경로인자로 받아 실행하는 함수로 만약, 첫 번째 인자인 file에 /(슬래시)가 포함되면 execl 함수처럼 파일을 경로명으로 취급하고 /(슬래시, 경로 없이 ONLY 파일명)가 포함되어 있지 않으면 PATH 환경 변수 사용하여 실행 파일을 찾음

execle

int execle(const char *path, const char *arg0, ..., const char *argn, (char*) 0, char *const envp[]);
- envp[]새로운 프로세스의 환경을 설정하는 배열

예시로, char *env[] 배열에 "USER=user1""PATH=/usr/bin:/bin:/opt/bin"과 같은 환경 변수들이 포함되어 있으며, 이 배열은 새로운 프로세스의 환경을 구성합니다.

빨간색 부분은 자식 프로세스가 실행하는 부분, 부모 프로세스는 빨간색 부부을 거너뛰고 아래의 if문을 실행

에러 부분만약 에러가 날 시 에러 핸들링을 처리하기 위해 작성됨

execv

int execv(const char *path, char *const argv[]);
- execv는 명령줄 인자 형식이 미리 고정되지 않은 상황에 유용하고 런타임인자 배열을 구성하여 실행파일을 실행 가능
- execv는 경로(실행 파일의 경로)인자 배열(명령줄 인자를 담은 배열) 2개의 매개변수를 받음

evecvp & execve

int execvp(const char *file, char *const argv[])
- execlp 함수와 기능이 동일, 중간의 차이점!
 
int execve(const char *file, char *const argv[], char *const envp[]);
- execle 함수와 기능이 동일

작동하는 방식

- argv 배열3개의 토큰을 가리키는 포인터 (execcmd, ls, -l)
- execvp에 전달되는 인수 배열&argv[1]에서 시작하여 ls와 -l을 가리키는 포인터로 구성
execvp의 두 번째 인수로 전달되는 인수 배열의 크기는 얼마인가요? ( execcmd ls -l *.c )

답변:

  • 현재 디렉터리에 있는 .c 파일의 개수에 따라 달라집니다.
  • 셸은 execcmd로 명령을 전달하기 전에 *.c를 확장합니다.

쉽게 말해서, execvp 함수명령어와 그 인수를 배열 형태로 전달받는데, *.c와 같은 와일드카드 패턴은 실제로는 셸에서 확장되어 처리됩니다.


exit() 함수

exit:  Terminate execution (exit: 실행 종료)

- exit 함수는 실행을 어느 시점에서든 종료시킬 수 있습니다. 예) 치명적 오류를 발견했을 시 더 이상 프로그램을 계속 실행할 필요가 없음
상태 정보를 반환합니다. 예) 정수 값, 짧은 유언

보통 에러가 발생하였을 경우에 함수를 return 하지 않고 exit 함수로 함수를 종료시키는 역할을 수행함

atexit: clean-up on exit (atexit: 종료 시 정리 작업)

- 정리 작업 함수를 등록(리턴 타입이 void), 등록된 함수(스택 내부에 등록)는 프로세스가 종료될 때 자동으로 호출됨
- 최대 32개의 함수를 등록할 수 있음

프로그램이 끝날 때, error를 만날 경우 exit에서 프로그램이 종료되기 전 atexit함수에 등록되어 있던 cleanup함수를 실행하고 종료시킴


Backgruond Processes and Daemons (백그라운드 프로세스와 데몬)

Interrupt character (인터럽트 문자)

- 쉘은 명령어 해석기로 명령어 입력을 요청하고, 표준 입력에서 명령어를 읽고 명령어 실행을 위해 자식 프로세스를 생성하여 자식 프로세스가 끝날 때까지 기다림
- 표준 입력과 출력이 터미널 장치로부터 오는 경우, 사용자는 실행 중인 명령어를 인터럽트 문자입력하여 종료 가능
많은 시스템은 기본적으로 Ctrl C인터럽트 문자로 사용

{Ctrl C 예시}

- cd / etc(루트 및 etc 디렉터리에서)  , ls -l (ls -l을 입력하고 출력되는 많은 양이 될때 ctrl c를 누르면 작업이 중단됨)
이 예시는 터미널에서 명령어 실행 중에 Ctrl-C를 사용하여 명령어를 중단할 수 있다는 것을 보여줍니다.

Background Process (백그라운드 프로세스)

- 쉘에서 명령어 끝에 &를 붙이면 해당 명령은 백그라운에서 실행됨 즉, 해당 명령어가 완료될 때까지 기다리지 않고 바로 다음 명령을 받을 수 있음
- Ctrl C로 종료되지 않음Ctrl C를 누르면 일반적으로 실행 중인 포그라운드 프로세스를 종료할 수 있지만 백그라운드에서 실행 중인 프로세스Ctrl C로 종료되지 않기에 이 경우, 해당 프로세스는 계속 실행
예시) ls -l& 명령을 실행한 후 Ctrl C를 입력하여도 백그라운드 프로세스는 종료되지 않고 계속 실행됨

Daemon (데몬)

일반적으로 무기한 실행되는 백그라운드 프로세스를 말하며 UNIX 운영체제정기적인 작업을 수행하기 위해 많은 데몬 프로세스에 의존

  • ls -l & 명령과 유사한 방식으로 ls -l을 백그라운드에서 실행합니다.
728x90
반응형
LIST