학습 주제
// 간략하게 정리한 주제를 작성
정리한 내용
8.4.1 Obtaining Process IDs (프로세스 ID 가져오기)
각각의 프로세스는 고유의 0이 아닌 양수의 프로세스 ID (PID)를 가진다.
getpid()는 호출하는 함수의 PID를 리턴한다.
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
getpid()와 getppid() 루틴은 pid_t타입의 정수 값을 리턴한다.
8.4.2 Creating and Terminating Processes (프로세스의 생성과 종료)
프로세스는 세 가지 상태 중 하나로 정의된다.
실행중Running. : 프로세스는 CPU에서 실행되고 있거나 실행을 기다리고 있으며, 커널에 의해서 스케쥴을 보장받는 상태.
정지Stopped. : 프로세스의 실행이 정지되었으며, 스케쥴되지 않는 상태. SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 시그널을 받을 시 정지하며, SIGCONT 시그널을 받을 때까지 정지 상태로 남아있는다.
종료Terminated. : 프로세스는 다음 세 가지 이유 중 하나로 영구적으로 정지된다.
- 프로세스를 종료하는 시그널을 받는다.
- 메인 루틴에서 리턴한다.
- exit 함수를 호출한다.
- 정지된 프로세스는 시그널 핸들러를 받지 않는다.

부모 프로세스는 fork()를 호출해 자식 프로세스를 생성한다.
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
새롭게 생성된 자식 프로세스는 코드, 데이터 세그먼트, 힙, 공유 라이브러리, 사용자 스택을 포함하는 부모 프로세스의 유저레벨 어드레스와 동일한 복사본을 갖는다.
- 자식은 부모가 오픈한 file descriptor와 동일한 사본을 갖는다.
- 부모가 fork()를 호출할 때 부모가 오픈한 파일 모두를 읽고 쓸 수 있다.
- fork() 된 자식은 부모와 완전히 같은 개체는 아니며, 부모 프로세스의 유저레벨 어드레스와 동일한, 그러나 분리된 클론을 가지며, 결정적으로 PID가 다르다.
fork()는 한번 호출되지만 두 번 리턴하는 성격을 가진다. 한 번은 호출한 프로세스에서, 다른 한 번은 새롭게 생성된 자식 프로세스에서.
- 부모 프로세스에서 fork()는 자식의 PID를 리턴한다.
- 자식 프로세스에서 fork()는 일반적으로 0을 리턴한다.
자식의 PID는 항상 0이 아니므로, 리턴값이 0인지의 여부를 통해 프로그램이 어느 프로세스에서 실행되는지를 확인할 수 있다.
int main()
{
pid_t pid;
int x = 1;
pid =fork();
if (pid == 0) { /* Child */
printf("child : x=%d\\n", ++x);
exit(0);
}
/* Parent */
printf("parent : x=%d\\n", --x);
exit(0);
}
- 예제 결과

- linux> ./fork parent: x=0 child: x=2
한 번 호출하고 두 번 리턴한다.
fork()는 부모에 의해서 한 번 호출되나 리턴은 두번 한다. : 한 번은 부모가, 다른 한 번은 자식이. 여러 개의 fork를 갖는 프로그램을 작성할 시 호출되는 리턴의 개수가 헷갈릴 수 있으므로 주의할 것.
동시 실행.
부모와 자식은 동시에 돌아가는 별도의 프로세스이다. 두 프로세스의 인스트럭션은 커널에 의해서 임의의 순서로 배정될 수 있다. 부모 프로세스 → 자식 프로세스로 스케쥴될 수 있는 반면, 자식 → 부모 와 같은 상황으로 진행될 수 있다. 프로그래머는 서로 다른 프로세스 내에서 인스트럭션 간의 중첩실행에 관해 절대로 지정된 순서를 가정할 수 없다.
중복된, 그러나 별도의 주소공간.
부모와 자식을 각 프로세스에서 fork()가 리턴한 직후에 중단할 수 있다면, 각 프로세스의 주소공간이 동일하다는 것을 알 수 있을 것이다.
그러나, 부모와 자식이 별도의 프로세스이므로, 이들은 자신만의 사적 주소공간을 가진다. 부모나 자식이 x에 대해 가하는 이후의 수정사항들은 모두 개인적이며, 다른 프로세스의 메모리에는 반영되지 않는다.
공유된 파일.
프로그램을 실행할 때, 부모와 자식 모두가 자신의 출력을 화면을 통해서 한다는 것을 알 수 있다. 자식이 부모가 오픈한 모든 파일들을 상속받았기 때문이다. 부모가 fork를 호출할 때 stdout파일은 열려 있으며, 화면으로 지정된다.

8.4.3 Reaping Child Processes (자식 프로세스의 청소)
프로세스가 어떤 이유로 종료할 때, 커널은 시스템에서 즉시 제거하지 않고 부모가 청소할 때 까지 종료된 상태로 남아있는다.
부모가 종료된 자식을 청소할 때 커널은 자식의 exit 상태를 부모에게 전달하며, 그 후 종료된 프로세스를 없애며 이 시점에서 프로세스가 사라지게 된다. 종료되었지만 아직 청소되지 않은 프로세스를 좀비라고 한다.
부모 프로세스가 종료할 때, 커널은 init 프로세스로 하여금 모든 고아가 된 자식들의 입양된 부모가 되도록 한다. 이 init 프로세스는 PID 1번이며, 시스템의 초기화 과정에서 커널에 의해 생성되고, 결코 종료되지 않으며, 모든 프로세스의 조상이다.
만일 어떤 부모 프로세스가 자신의 좀비 자식을 소거하지 않고 종료한다면, 커널은 init 프로세스에게 이들을 소거하도록 한다.
프로세스는 waitpid()를 호출해서 자식이 종료되거나 정지되기를 기다린다.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statusp, int options);
waitpid()는 기본적으로 options = 0 일 때 wait set 내의 하나의 자식 프로세스가 종료할 때까지 호출한 프로세스의 실행을 정지시킨다.
만일 wait set 중 하나가 호출 시에 이미 종료한 상태라면, waitpid()는 즉시 리턴한다.
이 지점에서 종료된 자식은 시스템에서 삭제되었으며, 커널은 시스템에서 이 프로세스의 모든 흔적을 제거한다.
wait set 멤버 구성하기
wait set 멤버들은 pid 인자에 의해서 결정된다.
- pid < -1 : 프로세서 그룹 ID가 pid 의 절대값과 같은 자식 프로세스를 기다린다.
- pid == - 1 : 임의의 자식프로세스를 기다린다.
- pid == 0 : 프로세스 그룹 ID가 호출 프로세스의 ID와 같은 자식프로세스를 기다린다.
- pid > 0 : 프로세스 ID가 pid 의 값과 같은 자식 프로세스를 기다린다.
기본 동작의 수정
options을 상수들의 조합을 사용해 설정하면 기본 동작을 변경할 수 있다.
- WNOHANG : 대기 집합 내의 자식 프로세스 중 아무것도 종료되지 않았다면, 즉시 리턴
- 자식이 종료하기를 기다리는 동안 다른 유용한 작업을 계속하기를 원하는 경우에 유용함.
- WUNTRACED : 대기 집합의 프로세스가 종료되거나 정지될 때까지 호출 프로세스를 정지한다.
- 종료되거나 정지된 자식들 모두에 대해 체크할 때 유용함.
- WCONTINUED : 프로세스가 종료되거나, SIGCONT시그널을 받고 다시 실행을 시작할 때까지 호출 프로세스를 정지한다.
options는 OR을 사용해 연결할 수 있다.
청소된 자식의 exit 상태 체크하기
statusp 인자가 NULL이 아닐 경우, waitpid는 리턴을 하도록 만든 자식 프로세스에 대한 상태 정보를 statusp로 인코딩한다.
- WIFEXITED(status) : exit() 호출 또는 리턴을 통해서 자식이 정상적으로 종료되었다면 true를 리턴.
- WEXITSTATUS(status) : 정상 종료된 자식의 exit status를 리턴한다. WIFEXITED == true일 때만 정의된다.
- WIFSIGNALED(status) : 시그널을 받지 못했기 때문에 자식 프로세스가 종료된다고 판단하여 true를 리턴한다.
- WTERMSIG(status) : 자식 프로세스를 종료하게 한 시그널의 수를 리넡한다. 이 status는 WIFSIGNALED()가 true를 리턴하는 경우에만 정의된다.
- WIFSTOPPED(status) : 리턴을 하게 한 자식이 현재 정지된 상태라면 true를 리턴한다.
- WSTOPSIG(status) : 자식을 정지하게 한 시그널의 수를 리턴한다. 이 status는 WIFSTOPPED()가 true를 리턴하는 경우에만 정의된다.
- WIFCONTINUED(status) :만일 자식 프로세스가 SIGCONT를 받아 재시작되었다면, true를 리턴한다.
에러 조건
호출하는 프로세스가 자식이 없다면, waitpid()는 -1을 리턴하며 errno를 ECHILD로 설정한다. waitpid()가 어떤 시그널에 의해 중단되었다면, -1을 리턴하며 errno를 EINTR로 설정한다.
wait() : simple method
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statusp);
8.4.4 Putting Processes to Sleep (프로세스 재우기)
sleep()는 일정 기간동안 프로세스를 정지시킨다.
#include <unistd.h>
unsigned int sleep(unsigned int secs);
sleep()은 요청한 시간이 경과하면 0을 리턴하고, 그렇지 않은 경우에는 남은 시간 동안 잠을 잔다. sleep() 가 시그널에 의해 중단되어 완료되지 못한 채 리턴하는 경우가 있으나 자세한 건 8.5에서.
pause()는 호출하는 함수를 시그널이 프로세스에 의해서 수신될 때까지 잠을 재우는 함수다.
#include<unistd.h>
int pause(void);
8.4.5 Loading and Running Programs (프로그램의 로딩과 실행)
execve()는 현재 프로그램의 컨텍스트 내에서 새로운 프로그램을 로드하고 실행한다.
#include <unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
execve()는 실행가능 목적파일 filename과 인자 리스트 argv, 환경변수 리스트 envp를 사용해서 로드하고 실행한다.
한번 호출되고 절대로 리턴하지 않는 특징이 있다.

- argv : 스트링 배열 (마지막은 NULL)
- argv[0]은 실행가능 목적파일의 이름이다. (관습에 의해)
- envp : 환경변수 스트림 포인터 배열 (마지막은 NULL)
- name = value 형태의 이름-값 쌍이다.
execve()가 filename을 로드한 후에, 시작코드를 호출.(7.9 참고)
시작 코드는 스택을 설정하고 제어를 새 프로그램의 메인 루틴으로 전달하며, 이 루틴은 다음과 같은 형태의 프로토타입을 가진다.
int main(int argc, char **argv, char **envp);
or
int main(int argc, char *argv[], char *envp[]);
main이 실행을 시작할 때, 사용자 스택은 다음과 같은 구성을 가진다.

- 가장 먼저 environment 스트링과 arg 스트링.
- NULL로 끝나는 환경변수 스트림 포인터의 배열
- 각각은 환경변수 스트링을 가리킨다.
- 전역변수 environ은 이 포인터의 첫번째 원소 envp[0]을 가리킨다.
- 곧이어 NULL로 끝나는 argv[]배열이 온다.
- 각각은 스택 상의 인자 스트링을 가리킨다.
- 스택의 최상단은 시스템 초기화 함수 libc_start_main에 대한 스택 프레임이 위치한다.
x86-64 스택 규범을 준수하며 레지스터에 저장된다.
- argc : argv[] 배열에 null이 아닌 포인터들의 수
- argv : argv[] 배열의 첫 항목의 포인터
- envp : envp[] 배열의 첫 항목의 포인터
리눅스는 환경 배열을 조작하기 위한 몇 개의 함수를 제공한다.
#include <stdlib.h>
char *getenv(const char *name);
getenv()는 환경 배열에서 “name=value”스트링을 검색한다. 찾으면 해당 값의 포인터를 리턴하며, 그 외는 NULL을 리턴한다.
#include <stlib.h>
int setenv(const char *name, const char *newvalue, int overwrite);
void unsetenv(const char *name);
환경 배열이 “name=oldvalue”형태의 스트링을 포함하면, unsetenv는 이것을 삭제하고 setenv는 oldvalue를 newvalue로 교체한다.
- overwite가 0이 아닐 때만 위와같이 동작하며, name을 찾을 수 없다면 “name=newvalue”를 새로 추가한다.
- 다시…(overwrite가 0일때?)
'Computer Science > 컴퓨터 구조' 카테고리의 다른 글
[CSAPP] 8.6 Nonlocal Jumps(논로컬 점프) (0) | 2023.02.28 |
---|---|
[CSAPP] 8.1 Exceptions(예외) (0) | 2023.02.27 |
[CSAPP] 8.5 Signal(시그널) (0) | 2023.02.22 |
[CSAPP] 7.9 Loading Executable Object Files(실행 가능한 객체 파일 로딩) (0) | 2023.02.21 |
[CSAPP] 7.8 Executable Object Files(실행 가능한 객체 파일) (0) | 2023.02.21 |
- 8.4.1 Obtaining Process IDs (프로세스 ID 가져오기)
- 8.4.2 Creating and Terminating Processes (프로세스의 생성과 종료)
- 8.4.3 Reaping Child Processes (자식 프로세스의 청소)
- wait set 멤버 구성하기
- 기본 동작의 수정
- 청소된 자식의 exit 상태 체크하기
- 에러 조건
- wait() : simple method
- 8.4.4 Putting Processes to Sleep (프로세스 재우기)
- 8.4.5 Loading and Running Programs (프로그램의 로딩과 실행)