Process Management
Process Management
Programs, Process Threads 의 정의
Programs
- 하드디스크에 적재된 실행 가능한 코드
- 컴파일 된 실행 가능한 코드.
- 디스크와 같은 저장 메체에 위치
Process
- 실행중인 프로그램.
- 바이너리 이미지, 메모리 적재가 되어있음
- 가상의 메모리 인스턴스, 커널 리소스, 스레드를 포함
Thread
- 프로세스 내부에서 활동중인 유닛 (실제로 작업을 수행하는 코드)
- 스레드는 프로그램(프로세스) 내에서 실행되는 독립적인 흐름으로, 멀티스레딩 환경에서 여러 스레드가 동시에 실행될 수 있다.
- 각 스레드는 자체 “가상화된 프로세서”를 가지고 있는데, 이는 스레드가 독립적으로 실행 상태를 관리하고, 자신만의 스택, 레지스터, 명령 포인터 등을 가지고 있음을 의미
- 이러한 특성 덕분에 스레드는 병렬 처리와 동시성을 가능
- 하나의 프로세스 내에 여러개의 스레드가 존재할 수 있다.
- 메모리의 가상화는 프로세스와 연관되어 있기 때문에, 모든 스레드는 동일한 메모리 주소 공간을 공유한다.
The Process ID
Definition
- PID 는 각 프로세스를 대표하는 고유 식별자이다.
- 운영 체제는 이 번호를 사용하여 프로세스를 관리하고, 사용자와 다른 시스템 프로세스는 이 번호를 사용하여 특정 프로세스를 참조한다.
- 한 시점에 각 PID 는 고유합니다. 즉, 시스템 상에서 동시에 두 개 이상의 프로세스가 동일한 PID 를 가질 수 없다.
- 프로세스가 종료되면, 그 프로세스의 PID 는 재사용될 수 있다.
The idle process
- 이는 시스템에서 항상 실행 중인 특별한 프로세스로, 다른 실행 가능한 프로세스가 없을 때 커널이 실행하는 프로세스
- 아이들 프로세스는 항상 PID 0을 가지고 있다.
- 이 프로세스는 시스템이 다른 작업을 수행할 것이 없을 때 실행되며, 일반적으로 시스템의 CPU 사용 시간을 최소화하는 데 사용한다.
The init process
- he First Process Executed by the Kernel: init 프로세스는 시스템이 부팅된 후 커널이 처음으로 실행하는 프로세스이다.
- 이는 시스템의 모든 다른 프로세스의 부모 프로세스로 간주되며, PID 1을 가진다.
- Kernel’s Decision on the init Process:
- 사용자가 커널에게 명시적으로 어떤 프로세스를 실행할 것인지 지시하지 않는 한(예: init 커널 명령줄 매개변수를 통해), 커널은 스스로 적절한 init 프로세스를 식별해야 한다.
Process ID Allocation
- 커널은 프로세스에게 PID 를 할당하며, 이전 프로세스가 죽더라도 순차적으로 번호를 지정한다.
- 커널은 최상단에서 감싸지 않는 한 프로세스 ID 값을 재사용하지 않는다. 즉, /proc/sys/kernel/pid_max 에 있는 값이 할당될 때까지 이전 값들은 재사용되지 않는다.
- i.e., 프로세스에 PID 를 순차적으로 할당하고, 최대 허용 PID 까지 도달하면 다시 처음부터 시작하되, 최대값에 도달하기 전까지는 이전에 사용했던 PID 를 재사용하지 않는다.
The Process Hierarchy
부모-자식 관계:
- 프로세스는 ‘부모’와 ‘자식’의 관계를 가진다.
- 모든 프로세스(초기 init 프로세스 제외)는 다른 프로세스로부터 생성되며, 이에 따라 부모-자식 관계가 형성된다.
- 이러한 관계는 부모 프로세스 ID(ppid)에 기록되며, 이는 자식 프로세스가 부모 프로세스를 식별할 수 있게 한다.
사용자와 그룹 소유권:
- 각 프로세스는 특정 사용자와 그룹에 속한다.
- 이 소유권은 프로세스가 시스템 리소스에 접근하는 권한을 제어하는 데 사용된다.
- 자식 프로세스는 부모 프로세스의 사용자와 그룹 소유권을 상속받는다.
프로세스 그룹:
- 프로세스는 프로세스 그룹의 일원이다.
- 프로세스 그룹은 사용자/그룹 개념과는 별개이다.
- 자식 프로세스는 일반적으로 부모와 동일한 프로세스 그룹에 속한다.
- 셸이 파이프라인을 생성하면, 파이프라인의 모든 명령은 동일한 프로세스 그룹에 속한다.
- 프로세스 그룹은 그룹 내의 모든 프로세스에게 신호를 보내거나 정보를 얻는 것을 용이하게 한다.
요약:
- 프로세스 계층 구조는 부모-자식 관계와 사용자/그룹 소유권, 프로세스의 개념을 포함한다.
- 부모 프로세스는 자식 프로세스를 생성하고, 자식 프로세스는 부모의 소유권과 그룹 소유권을 상속받는다.
- 프로세스 그룹은 관련 프로세스들을 묶어 신호 전송이나 정보 추출을 쉽게 만들어준다.
pid_t
- 프로세스 ID를 표현하는 데 사용되는 데이터 타입이다.
- pid_t 는 <sys/types.h> 헤더 파일에 정의된다.
- c 표준은 아니나, 리눅스에서는 일반적으로 int 타입이다.
- getpid() 를 사용해 실행중인 프로세스의 pid 를 얻을 수 있다.
- getppid() 를 사용해 실행중인 프로세스의 부모 pid 를 얻을 수 있다.
Running a New Process
Executing a Program
- 프로세스는 cpu 자원을 할당받고, i/o 상태이거나 유휴상태일 때 자원을 반납. 준비상태가 되면 다시 자원을 할당받아 프로세스 실행한다.
- 하나의 시스템 호출을 사용하여 바이너르 프로그램을 메모리에 로드하며, 주소 공간의 이전 내용을 대체하고 새 프로그램의 실행을 시작한다.
- 이를 새 프로그램을 실행한다고 하며, 이 기능은 exec 함수 계열로 제공된다.
Creating a New Process
- A near-duplication its parent process (거의 복제하는 것)
- 부모프로세스의 영역을 카피해서 프로세스를 생성하니 시간이 오래걸린다!
- 그래서, 자식프로세스는 부모 프로세스에 접근 권한을 부여하여 효율성 증대
- 메모리를 공유한다. 프로그램 카운터만 변경 (각자의 실행 위치를 나타내는 프로그램 카운터만 다름!)
- 그러다 실제로 복사할 때도 있다:
- 프로세스가 공유중인 메모리를 수정해야 할 때.
- Copy-On-Write (이하 설명)
- 새 프로세스를 생성하는 행위를 포킹(forking)이라고 하며, 이 기능은 fork() 시스템 호출로 제공
- fork(새 프로세스 생성) - exec(새 바이너리 로드)
Exec Family of Calls
execl
- execl 함수는 프로그램의 새로운 엔트리 포인트(시작점)로 점프하여, 호출이 성공하면 그 프로세스의 주소 공간에서 방금 실행된 코드는 더이상 존재하지 않는다.
- execl을 호출하면 현재 프로세스의 메모리 주소 공간에 새 프로그램의 코드와 데이터가 로드된다.
- 이 과정에서 원래 프로세스의 코드와 데이터는 완전히 사라지게 된다.
- 이로 인해, execl이 성공적으로 호출된 후에는 원래 프로그램의 어떠한 코드도 더 이상 실행되지 않는다.
- 대신 새로운 프로그램의 엔트리 포인트(시작점)에서 실행이 시작된다.
- i.e., execl 함수가 단순히 새 프로그램을 실행시키는 것이 아니라 현재 프로세스의 내용을 새 프로그램의 내용으로 완전히 교체한다는 것을 의미한다.
- 이 동작은 프로세스의 생성 비용을 절약하면서도 다른 프로그램을 실행할 수 있게 해준다.
- e.g., 쉘 프로그램에서 사용자가 새로운 명령을 입력하면, 쉘은 fork 시스템 호출을 사용하여 자식 프로세스를 생성한 다음,
- 이 자식 프로세스 내에서 execl 또는 관련 함수를 사용하여 사용자가 원하는 프로그램을 실행시킬 수 있다.
- 이렇게 되면, 쉘 자체는 계속 실행되면서 사용자의 명령을 기다리는 동안, 자식 프로세스는 새로운 프로그램으로 대체되어 그 프로그램을 실행하게 된다.
- 변화가 발생하는 것들:
- 주소 공간과 프로세스 이미지: execl 호출이 성공하면 현재 프로세스의 주소 공간은 새로운 프로그램의 주소 공간으로 완전히 대체된다.
- 이는 현재 실행 중인 프로그램의 코드, 데이터, 힙, 스택 등이 새 프로그램의 것들로 바뀐다는 것을 의미한다.
- 대기 중인 신호 및 포착된 신호: 실행 중인 프로세스에 대기 중인 신호나 프로세스가 이미 잡아낸 신호는 exec 호출 후 사라진다.
- 메모리 잠금: 현재 프로세스에서 설정한 메모리 잠금이 해제된다.
- 스레드 속성: 만약 현재 프로세스가 멀티스레드로 실행되고 있다면, 그 스레드와 관련된 모든 속성과 데이터가 사라진다.. 새로운 프로그램은 단일 스레드로 시작된다.
- 정적 변수: 현재 프로세스의 정적 변수는 모두 초기화되거나 사라진다.
- 메모리 주소 공간과 관련된 모든 것: 이는 메모리에 매핑된 파일, 공유 메모리 세그먼트, 힙, 스택 등 현재 프로세스의 메모리 구조와 관련된 모든 것을 포함한다.
- 사용자 공간에서의 존재: atexit()와 같은 C 라이브러리의 특정 기능은 프로세스의 사용자 공간에서만 존재하므로, 이들도 모두 초기화된다.
- 주소 공간과 프로세스 이미지: execl 호출이 성공하면 현재 프로세스의 주소 공간은 새로운 프로그램의 주소 공간으로 완전히 대체된다.
- 변화가 발생하지 않는 것들:
- pid, 부모의 pid, 우선순위, 소유 사용자 및 그룹: exec 호출이 성공하더라도, 이러한 프로세스의 기본 속성은 변경되지 않는다.
- i.e., 프로세스 ID나 부모 프로세스의 ID, 우선순위 등은 그대로 유지된다.
- 열린 파일의 상속: execl 호출 후에도 현재 프로세스에서 열린 파일은 그대로 유지된다.
- 새로운 프로그램이 그 파일을 계속 사용할 수 있도록 하기 위해서.
- 보안상 문제가 될 수 있기 때문에 주의가 필요하다.
- 특정 파일이나 리소스에 접근할 수 있는 권한을 가진 프로세스가 다른 프로그램으로 대체되면, 그 새 프로그램도 같은 권한을 가지게 된다.
execv
- 성공 시:
- 시스템 호출들은 반환하지 않는다.
- 호출이 성공하면 현재 프로세스의 메모리 이미지가 새로운 프로그램의 이미지로 완전히 대체되기 때문
- 실패 시:
- -1 반환
exec errno
- E2BIG: 전달된 인자나 환경 변수의 크기가 너무 크다
- EACCES: 지정된 파일에 실행 권한이 없다
- EFAULT: 잘못된 주소가 제공
- ENOENT: 지정된 파일이나 경로를 찾을 수 없다
- ENOMEM: 커널에서 필요한 메모리를 할당할 수 없다
- ENOTDIR: 경로의 일부분이 디렉터리가 아니다
- ELIBBAD: ELF 인터프리터가 잘못되었다
- ETXTBSY: 실행하려는 바이너리 파일이 현재 쓰여지고 있다
- EIO: I/O 오류가 발생했다
- EINVAL: 잘못된 exec 형식을 사용했다
execl vs. execv
- execl:
- execl 함수는 명시적으로 개별 인자를 전달한다. 인자의 수는 가변적
- e.g., execl(“/bin/ls”, “ls”, “-l”, NULL);
- 첫 번째 인자는 실행하려는 프로그램의 경로이다.
- 그 이후의 인자들은 프로그램에 전달할 인자들이다.
- 인자 리스트의 마지막에는 NULL을 포함해야 한다.
- execl 함수는 명시적으로 개별 인자를 전달한다. 인자의 수는 가변적
- execv:
- execv 함수는 인자를 문자열 포인터의 배열로 전달한다.
- 첫번째 인자는 실행하려는 프로그램의 경로이다.
- 두 번째 인자는 인자들의 배열이다. 배열의 마지막 요소는 NULL 포인터여야 한다.
The fork() system call
- 특징
- fork()는 현재 프로세스(부모 프로세스라고도 함)의 복사본(자식 프로세스)을 생성한다.
- 자식 프로세스는 부모 프로세스의 메모리 이미지, 환경 변수, 파일 디스크립터 등을 상속받는다.
- fork 반환값:
- 자식 프로세스에서: fork() 호출이 성공하면 0을 반환한다. 이를 통해 프로그래머는 현재 코드가 자식 프로세스에서 실행 중인지 확인할 수 있다.
- 부모 프로세스에서: fork()는 자식 프로세스의 프로세스 ID (pid)를 반환한다. 이를 통해 부모는 자식 프로세스를 추적하거나 제어할 수 있다.
child vs. parents process
- PID (Process ID): 부모와 자식 프로세스는 서로 다른 PID를 가진다.
- 메모리 공간: 두 프로세스는 독립적인 메모리 공간을 가진다.
- 부모 프로세스의 메모리 공간은 자식 프로세스에 복사되지만, 이후의 변경은 각 프로세스에서 독립적으로 이루어진다.
- 따라서 한 프로세스에서의 메모리 변경은 다른 프로세스에 영향을 주지 않는다.
- 파일 디스크립터: 자식 프로세스는 부모 프로세스의 파일 디스크립터를 상속받지만, 두 프로세스의 파일 디스크립터는 독립적이다.
- 하나의 프로세스에서 파일 디스크립터를 닫아도 다른 프로세스에는 영향을 주지 않는다.
- 프로세스 상태: 자식 프로세스는 부모 프로세스의 상태나 우선순위, 신호 마스크 등을 상속받는다. 그러나 이후의 변경은 각 프로세스에서 독립적으로 처리된다.
- 자원 사용 제한: 자식은 부모의 자원 사용 제한을 상속받는다.
- 이런 차이점에도 불구하고, fork() 호출 직후에는 자식 프로세스가 부모 프로세스의 거의 정확한 복사본으로 시작한다.
Orphan vs. Zombie (고아 vs. 좀비)
- 고아 프로세스 (Orphan Process):
- 부모 프로세스가 종료되고 자식 프로세스가 아직 실행 중인 경우, 그 자식 프로세스는 고아 프로세스가 됩니다.
- 고아 프로세스는 init 프로세스 (PID 1)에 의해 ‘입양’됩니다. 즉, init 프로세스가 새로운 부모 프로세스가 되어 자식 프로세스를 관리하게 됩니다.
- init 프로세스는 주기적으로 자식 프로세스의 상태를 확인하며, 종료된 자식 프로세스의 종료 상태를 회수하여 좀비 프로세스가 되지 않게 합니다.
- 좀비 프로세스 (Zombie Process):
- 프로세스가 종료되면 그 정보는 여전히 프로세스 테이블에 남아 있다.
- 이 때, 부모 프로세스가 자식 프로세스의 종료 상태(스켈레톤 정보)를 회수하지 않으면, 자식 프로세스는 종료되었지만 프로세스 테이블에서는 여전히 정보가 남아있게 되어 “좀비” 상태가 됩니다.
- 좀비 프로세스는 CPU 자원을 사용하지 않지만, 프로세스 테이블에는 여전히 항목으로 남아 있기 때문에 리소스를 소비합니다.
- 부모 프로세스가 wait() 또는 waitpid()와 같은 시스템 호출을 사용하여 자식 프로세스의 상태를 회수하면, 좀비 프로세스는 완전히 시스템에서 제거됩니다.
- 만약 부모 프로세스가 자식의 상태를 회수하지 않는다면, init 프로세스가 그 자식 프로세스를 ‘입양’하고 해당 상태를 회수하여 좀비 프로세스를 제거합니다.
- 요약:
- 고아 프로세스는 부모가 먼저 종료된 후에도 실행 중인 프로세스입니다.
- 좀비 프로세스는 종료되었지만 그 상태가 아직 부모 프로세스에 의해 회수되지 않아 시스템에 남아 있는 프로세스입니다.
vfork()
- 복사하는 과정이 너무 길어서
- fork() 는 복사. vfork() 는 복사 ㅌ
- 부모와 자식이 메모리를 공유
- 동일한 메모리 주소 공간을 공유
- to share parent’s resources
- 자원 사용이 줄어든다.
- 프로그램 카운터(pid)와 스택을 자식 프로세스에 대해 생성.
- 부모 프로세스를 복사하면 어차피 같으니!
- 프로그램 카운터만 생성해 전달
- 부모프로세스는 자식 프로세스가 실행 될 때, 모든 정보를 기록, 자식에게 양보 (자식이 먼저 실행 후 종료하면, 기록된 정보를 불러오기)
- vfork()가 호출된 후, 자식 프로세스가 exec() 시리즈 함수를 호출하거나 종료할 때까지 부모 프로세스는 일시 중지된다. 이렇게 함으로써 자식과 부모 사이의 race condition을 방지
- 공유되는 변수에 대해 rase condition 방지
- execv 를 사용하면?
- 자식 프로세스는 vfork() 후에 주로 exec() 시리즈 함수 중 하나를 호출하여 새 프로그램을 로드한다.
- 만약 자식 프로세스가 메모리를 수정하게 되면, 이러한 변경이 부모 프로세스에게도 영향을 미칠 수 있기 때문에,
- vfork()를 사용할 때는 주로 변경 작업 없이 바로 exec()를 호출하는 것이 일반적이다.
Copy-on-Write (CoW)
- fork 호출 시 문제:
- fork 가 호출될 때, 커널은 모든 내부 데이터 구조를 복사하고 프로세스의 페이지 테이블 항목을 복제한 다음 부모의 주소 공간을 자식의 새 주소 공간으로 페이지별로 복사한다.
- 이 알고리즘은 많은 리소스를 낭비한다.
- vfork()를 사용한 리소스 공유:
- CoW가 구현되기 전에 vfork()가 도입되었다.
- 부모 프로세스는 자식 프로세스가 종료되거나 execve를 호출할 때까지 차단. 이는 race condition을 피하기 위해서.
- 자식은 주소 공간의 메모리를 수정해서는 안 된다.
- The child must not modify any memory in the address space
- 오류가 발생하기 쉽다. 만약 자식 프로세스가 부모 프로세스의 차단 중에 종료되면 문제가 발생할 수 있다.
- Too buggy: Child process termination during the parent’s block
- Lazy optimization:
- vfork 에서, 자식 프로세스가 부모프로세스에 write 할 때,
- 자식 프로세스는 그제서야 데이터를 복사.
- 아주 게을러 (p임): lazy optimization
- 이 최적화는 페이지를 읽기 전용으로 표시하고 커널의 페이지 관련 데이터 구조에서 복사-쓰기(copy-on-write)로 표시하는 방식이다.
- 프로세스가 페이지를 수정하려고 시도하면 페이지 오류가 발생한다.
- 커널은 이 페이지 오류를 처리하기 위해 페이지의 복사본을 투명하게 만든다
- 이 시점에서 페이지의 복사-쓰기 속성이 지워지며, 더 이상 공유되지 않는다.
- 요약:
- Copy-on-Write는 프로세스 생성 시 메모리를 실제로 복사하는 대신 필요할 때까지 복사를 지연시키는 최적화 기법이다.
- 이로 인해 메모리 사용량과 성능이 크게 향상된다. vfork()는 CoW 이전의 해결책이었지만, CoW 기법의 도입으로 그 필요성이 크게 줄었다.
Terminating a Process (프로세스 종료)
프로세스가 종료되는 시점
- 정상적으로 종료
- 다른 프로세스에 의해 수동적으로 종료
- 운영체제에 의해 종료 (rap of kernel)
프로세스 종료: 표준 함수 사용
void exit (int status);
- 반환 값 없음
- 상태 매개 변수 (status parameter)
- 프로세스의 종료 상태를 나타내기 위해 사용
- 쉘에서도 확인 가능
프로세스 종료 단계
- exit() 또는 on_exit()로 등록된 함수 호출:
- 프로세스 종료 시 호출해야 할 함수들을 등록할 수 있습니다. 이 함수들은 등록된 역순으로 호출
- 모든 열려있는 표준 I/O 스트림 플러시:
- 열려 있는 모든 I/O 스트림에 남아있는 데이터를 디스크나 해당 장치로 전송하여 모든 출력 작업을 완료
- 열린 파일 및 소켓 닫기
- 동적 할당 메모리 해제
- 자식 프로세스에게 필요한 리소스 정리
- 프로세스 종료 상태 설정 및 종료
- 열려 있는 모든 I/O 스트림에 남아있는 데이터를 디스크나 해당 장치로 전송하여 모든 출력 작업을 완료
- tmpfile() 함수로 생성된 임시 파일 제거:
- 프로세스에 의해 생성된 임시 파일들을 제거한다.
- 추가 시스템 동작:
- 시스템 호출: exit() 함수는 프로세스를 종료시키기 위한 시스템 콜 호출
- 리소스 정리: 모든 리소스 정리(메모리, 파일,시스템 v 세마포 등)
- 프로세스 소멸 및 부모 알림: 리소스 정리 후 커널은 프로세스를 제거하고, 자식 프로세스의 종료를 부모에게 알림 (스켈레톤을 통해)
other wats to terminate
- 프로그램의 끝에 도달하여 종료됨. (정상 종료)
- SIGTERM 및 SIGKILL 신호를 받음. (다른 프로세스에 의해 일시정지, pulse)
- 커널의 경고로 종료됨. (강제; 권한 밖의 작업 요청과 같은)
atexit()
on_exit()
SIGCHLD
- 프로세스가 종료될 때, 커널은 부모 프로세스에게 SIGCHLD 신호를 보냄
- 신호는 비동기적으로 동작
Waiting for a Specific Process
If,
- 자식 프로세스가 종료할 때, 불완전하게 종료 될 경우 부모는 자식 프로세스의 상태 정보를 받아올 수 없다.
- 좀비 프로세스 상태
- 자식 프로세스가 종료되었으나 부모 프로세스가 그 상태를 아직 확인하지 않았을 때, 해당 자식 프로세스는 좀비 상태가 된다.
- 자식 프로세스의 대부분의 리소스는 해제되지만, 프로세스 제어 블록(PCB)와 같은 중요한 정보는 커널에 임시로 보존된다.
- 프로세스 제어 블록(PCB)은 운영체제에서 각 프로세스에 대한 중요한 정보를 추적하기 위해 사용하는 핵심 데이터 구조.
- 부모 프로세스는 자식 프로세스의 종료 정보를 파악해야 자식 프로세스이 완전한 종료를 할 수 있다.
- Only after the parent obtains the information preserved about the terminated child does the process formally exit and cease to exist even as a zombie.
- 부모 프로세스는 wait() 또는 waitpid()와 같은 시스템 호출을 사용하여 자식 프로세스의 종료 상태를 조회할 수 있다.
- 이 호출을 통해 부모 프로세스는 자식 프로세스의 종료 상태를 알게 되며, 커널은 좀비 프로세스의 마지막 정보를 해제한다.
- 만약 부모 프로세스가 자식 프로세스의 상태를 조회하지 않는다면 자식 프로세스는 계속해서 좀비 상태로 남아 있게 된다. 이러한 경우, 좀비 프로세스가 누적되면 시스템의 리소스가 낭비된다.
- 부모 프로세스가 종료되면 init 프로세스(프로세스 ID 1을 가진 프로세스)가 그 프로세스의 모든 자식 프로세스를 ‘입양’한다.
- init 프로세스는 주기적으로 wait()을 호출하여 종료된 자식 프로세스의 상태를 조회하므로, 좀비 프로세스는 자동으로 처리한다.
wait()
pid_t wait(int *status);
- return:
- 종료된 자식 프로세스의 PID
- 에러: -1
- 동작:
- 종료되지 않은 자식 프로세스가 없다면, wait 호출은 자식 프로세스가 종료될 때 까지 블록된다.
- 이미 종료된 자식 프로세스가 있다면, wait() 호출은 즉시 반환된다.
- Errno (에러 번호):
- wait() 함수는 에러 발생 시, 전역 변수 errno에 특정 값을 설정하여 해당 에러의 원인을 나타낸다.
- ECHILD:
- 부모 프로세스에게 대기할 자식 프로세스가 없을 때 설정된다.
- EINTR:
- wait() 호출 도중에 시그널을 수신하면 설정된다.
- ECHILD:
- wait() 함수는 에러 발생 시, 전역 변수 errno에 특정 값을 설정하여 해당 에러의 원인을 나타낸다.
waitpid()
- waitpid() 함수는 pid 파라미터를 통해 원하는 프로세스나 그룹의 프로세스에 대해 특정 동작을 수행할 수 있게 해준다.
- waitpid()는 wait()와 유사하지만, 특정 프로세스 ID(PID)를 인자로 받아 해당 프로세스만을 대기할 수 있다.
pid_t waitpid (pid_t pid, int *status, int options);
waitpid() parameter
return
- pid < -1:
- 절대값이 해당 값과 같은 프로세스 그룹 ID를 가진 모든 자식 프로세스를 기다린다
- 예시: -500을 전달하면 프로세스 그룹 500에 속한 모든 프로세스를 기다린다
- pid == -1:
- 어떠한 자식 프로세스든지 기다립니다. 이는 wait() 함수와 동일한 동작한다
- pid == 0:
- 호출하는 프로세스와 같은 프로세스 그룹에 속한 모든 자식 프로세스를 기다린다
- pid > 0:
- pid 값과 정확히 일치하는 자식 프로세스를 기다린다
- 예시: 500을 전달하면 pid 가 500인 자식 프로세스를 기다린다
- 이러한 waitpid() 함수의 동작 특성은 부모 프로세스가 자식 프로세스의 종료 상태를 좀 더 세밀하게 관리하거나, 특정 조건에 맞는 프로세스 그룹의 상태를 확인하고 싶을 때 유용하게 사용될 수 있다.
*status
- wait(0) 함수의 유일한 파라미터와 동일한 동작
- 이 파라미터는 종료된 자식 프로세스의 상태를 반환받음
- 파라미터에서 특정 정보를 추출 할 수 있다 (e.g., WIFEXITED, WEXITSTATUS)
options
- or 연산자와 사용 가능
- WNOHANG:
- 매칭되는 자식 프로세스가 아직 종료(또는 중지 또는 계속)되지 않았다면 블록하지 않고 즉시 반환한다.
- 옵션을 사용하면 waitpid() 호출이 블로킹 상태에 들어가지 않게 된다. 즉, 해당하는 자식 프로세스가 아직 종료되지 않았다면 waitpid()는 즉시 0을 반환하며 부모 프로세스는 다른 작업을 계속 수행할 수 있다.
- WUNTRACED:
- 설정되면, 호출 프로세스가 자식 프로세스를 추적하고 있지 않아도 반환된 status 파라미터의 WIFSTOPPED 비트가 설정된다. 이 플래그는 쉘과 같은 더 일반적인 작업 제어를 구현하기 위해 유용하다.
- 중지된 자식 프로세스가 계속(continue)됐을 때 부모 프로세스에 알리는 역할을 한다. waitpid()가 WCONTINUED 플래그와 함께 호출되면, 중지된 자식 프로세스가 다시 계속됐을 때 해당 상태를 반환
- WCONTINUED:
- 설정되면, 호출 프로세스가 자식 프로세스를 추적하고 있지 않아도 반환된 status 파라미터의 WIFCONTINUED 비트가 설정된다. WUNTRACED와 마찬가지로, 이 플래그는 쉘을 구현하기 위해 유용하다.
- 자식 프로세스가 중지(stop)됐을 때 부모 프로세스에 알리는 역할을 한다. waitpid()가 WUNTRACED 플래그와 함께 호출되면, 자식 프로세스가 중지 상태로 변경됐을 때 해당 상태를 반환
waitpid
- 성공 시 pid 반환
- 오류 시 -1.
- ECHILD:
- pid 인자에 의해 지정된 프로세스 또는 프로세스들이 존재하지 않거나, 호출 프로세스의 자식이 아니다.
- EINTR:
- WNOHANG 옵션이 지정되지 않았고, 대기 중에 시그널이 수신되었다.
- EINVAL:
- options 인자가 유효하지 않다.
E.g.
- waitid(): waitpid()와 유사. 두 개의 파라미터를 사용하여 더 상세한 정보를 제공한다.
- 프로세스 ID, 프로세스 그룹 ID, 또는 모든 자식 프로세스에 대한 정보를 제공하는 등 더 많은 유연성을 제공한다.
- wait(): 상태 정보를 인자로 받는다. 어떤 자식 프로세스든 상태가 변경되면 반환한다.
- waitpid(): 이 함수는 특정 프로세스 ID 또는 프로세스 그룹 ID에 대한 상태 변경 정보를 얻을 수 있게 해준다.
- 또한, 다양한 옵션 플래그를 사용하여 특정 조건에서 반환하는 방법을 지정할 수 있다.
waitid
int waitid (idtype_t idtype, id_t id, siginfo_t *infop, int options);
- idtype: 해당하는 type 의 id를 기다린다
- P_PID: id 매개변수로 주어진 프로세스 ID와 일치하는 자식 프로세스를 기다린다
- P_GID: id 매개변수로 주어진 프로세스 그룹 ID와 일치하는 자식 프로세스를 기다린다.
- P_ALL: 어떤 자식 프로세스든지 기다린다. 이 경우 id 매개변수는 무시한다.
- id: idtype에 따라 프로세스 또는 프로세스 그룹을 식별하는 데 사용
- id_t 타입을 사용. 일반적인 식별 번호를 나타내는 타입이다.
- id_t 타입은 다양한 ID를 나타낼 수 있기 때문에 waitid() 함수에서 사용된다.
- optioins:
- WEXITED: 함수는 종료된 자식 프로세스( id와 idtype에 의해 결정됨)를 기다린다
- WSTOPPED: 시그널의 수신에 응답하여 실행을 중단한 자식 프로세스를 기다린다
- WCONTINUED: 시그널의 수신에 응답하여 실행을 계속한 자식 프로세스를 기다린다
- WNOHANG: 함수는 차단되지 않고, 이미 종료(또는 중지 또는 계속)된 일치하는 자식 프로세스가 없는 경우 즉시 반환된다
- WNOWAIT: 함수는 일치하는 프로세스를 좀비 상태에서 제거하지 않습니다. 이 프로세스는 미래에 대기할 수 있다.
- infop: waitid() 함수가 성공적으로 자식 프로세스를 기다린 후에는, infop 매개변수를 채운다. 이 매개변수는 유효한 siginfo_t 타입을 가리키는 포인터여야 한다.
- si_pid: 자식 프로세스의 pid이다.
- si_uid: 자식 프로세스의 uid이다.
- si_code: 자식이 종료, 시그널로 인한 사망, 시그널로 중지, 또는 시그널로 계속될 때 각각 CLD_EXITED, CLD_KILLED, CLD_STOPPED, 또는 CLD_CONTINUED로 설정된다
- si_signo: SIGCHLD로 설정된다
- si_status:
- si_code가 CLD_EXITED인 경우, 이 필드는 자식 프로세스의 종료 코드이다
- 그렇지 않으면, 이 필드는 상태 변경의 원인이 된 자식에게 전달된 시그널의 번호이다.
- errno
- CHLD: id와 idtype에 의해 지정된 프로세스 또는 프로세스들이 존재하지 않음
- EINTR: options에 WNOHANG이 설정되지 않았고, 시그널이 실행을 중단
- EINVAL: options 인수 또는 id와 idtype 인수의 조합이 유효하지 않음.
BSD wants to Play: wait3() and wait4()
- 프로세스 종료에 대한 더 고급 정보를 제공해 준다.
- wait3(): 어떤 자식 프로세스든 상태가 바뀔 때까지 기다린다.
- pid 값 지정 불가
- wait4(): pid 매개변수로 지정된 특정 자식 프로세스의 상태 변화를 기다린다
- 특정 pid 값 지정
- options 인자는 waitpid()와 동일한 방식으로 동작한다
Launching and Waiting for a New Process
int system (const char *command);
- fork-exec-exit 과 같은 함수를 포함.
- 동기적 프로세스 호출 또는 자식 프로세스 종료 대기를 위한 함수이다.
- 실행 후 결과 값을 int 로 반환
- 성공적인 실행함에도 불구하고, 실패 리턴값을 받을 수 있는데: 이는 실행은 가능 하나 exec-exit 함수 실패 시
- Two cases of failure code even in success, either failure from invoking the command or failure from executing the command(shell)
- system() 함수를 사용할 때, 성공적으로 함수가 호출되어 명령어가 실행되었다 하더라도, 두 가지 경우에서 ‘실패 코드’를 반환할 수 있습니다.
- 명령어 호출 실패 (Invocation Failure): 이 경우는 system() 함수가 외부 명령어를 실행시키기 위해 쉘을 올바르게 시작하지 못했을 때 발생합니다. 예를 들어, 시스템에 /bin/sh 쉘이 없거나, 시스템 리소스에 문제가 있어 새로운 프로세스를 시작할 수 없는 상황에서 이러한 오류가 발생할 수 있습니다.
- 명령어 실행 실패 (Execution Failure): 이 경우는 쉘이 올바르게 시작되어 명령어를 호출했지만, 해당 명령어의 실행 자체에서 문제가 발생했을 때입니다. 예를 들어, 호출된 스크립트나 프로그램이 오류 코드를 반환하거나, 실행 도중 예상치 못한 문제가 발생하여 비정상적으로 종료됐을 때 이런 상태가 반환됩니다.
- system() 함수의 반환 값은 이러한 두 가지 유형의 실패를 포함하여, 실제로 실행된 명령어의 종료 상태를 나타내기 때문에, 함수가 성공적으로 명령어를 호출했더라도 명령어의 실행 결과에 따라 실패 상태를 반환할 수 있습니다. 이는 WEXITSTATUS 매크로를 사용하여 추출할 수 있는 종료 코드에서 확인할 수 있습니다.
명령어 실행 중:
SIGCHLD 시그널은 차단(blocked)됩니다. 이는 자식 프로세스가 종료되었을 때 부모 프로세스에 알리는 시그널로, system() 함수는 이 시그널을 처리하지 않기 때문에 기본적으로 차단합니다. SIGINT (인터럽트 시그널)과 SIGQUIT (종료 시그널)은 무시(ignored)됩니다. 이는 사용자가 키보드 입력 (Ctrl+C 또는 Ctrl+)을 통해 명령어 실행을 중단하려고 해도, system() 함수가 이러한 시그널을 무시하고 명령어가 완전히 실행되기를 기다린다는 것을 의미합니다. system() 함수를 반복문 내에서 호출하는 경우, 프로그램이 자식 프로세스의 종료 상태를 제대로 확인하도록 해야 합니다. 이는 반복문 내에서 여러 번의 system() 호출이 이루어질 때, 각 호출이 성공적으로 완료되었는지, 그리고 예상한 결과를 반환했는지를 확인하기 위함입니다. 종료 상태를 확인하지 않고 다음 반복으로 넘어가면, 예기치 않은 동작이나 오류를 놓칠 수 있습니다. system() 함수를 사용할 때 이러한 세부 사항을 이해하는 것은 중요합니다. 왜냐하면 이 함수는 외부 명령어를 실행할 때 시그널 처리를 어떻게 할지, 명령어의 종료 상태를 어떻게 확인할지 등 프로세스 관리와 오류 처리에 영향을 미치기 때문입니다.
#
Leave a comment