Contents #
- 프로세스
- 프로세스 기술자와 태스크 구조체
- References
프로세스 #
프로세스 (process)는 실행의 한복판에 있는 (어떤 물리적 매체에 오브젝트 코드가 저장된) 프로그램이다. 하지만 프로세스는 (흔히 유닉스에서 text section이라고 불리는) 프로그램 코드를 실행하는 것만을 의미하는 것이 아니다. 프로세스는 다음을 포함할 수 있다.
- open files
- pending signals
- internal kernel data
- processor state
- memory address space
- one or more threads of execution
- data section containing global variables
그리고 커널 (kernel)은 이러한 세부 사항들을 효율적이고 분명하게 관리해야 한다.
실행의 쓰레드 (Threads of execution), 또는 줄여서 쓰레드 (threads)라고 불리는 것은 프로세스 내에 있는 동작에 관여하는 오브젝트 (the objects of activity)이다. 각 쓰레드는 다음을 포함한다:
- unique program counter
- process stack
- processor registers
커널은 프로세스가 아닌 쓰레드를 스케줄링하며 (schedules), 전통적인 유닉스 시스템에서는 각 프로세스마다 하나의 쓰레드만 존재하였지만 현대 시스템에서는 멀티 쓰레드 프로그램이 일반적이다. 그런데 리눅스는 (후에 살펴보겠지만) 프로세스와 쓰레드를 구분하지 않으며, 쓰레드를 프로세스의 특수한 경우로 본다.
현대 운영체제에서 프로세스는 다음 두 가지 가상화 (virtualizations)를 제공한다:
- virtualized processor
- virtual memory
위 두 가지 가상화 중 전자는 프로세스가 마치 프로세서를 독점하는 것처럼 인식하도록 만들고, 후자는 프로세스가 마치 시스템에 있는 모든 메모리를 할당하고 관리하는 것처럼 인식하도록 만든다. 이때, 각 쓰레드는 고유한 가상화된 프로세서 (virtualized processor)를 가지지만, 가상 메모리 (virtual memory)는 공유한다는 것을 기억하라.
프로세스의 삶은 (어쩌면 당연하게도) 생성될 때 시작한다. 리눅스에서 이는 fork() 시스템 호출 (system call)에 의해 발생하며 이미 존재하는 것을 복사하여 새로운 프로세스를 생성한다. 이때 fork()를 호출하는 프로세스를 부모 프로세스 (parent), 생성된 새로운 프로세스를 자식 프로세스 (child)가 된다. 자식 프로세스가 생성되면, 부모 프로세스는 (fork()가 리턴되었을 때) 다시 자신의 작업을 재개하고, 자식 프로세스도 동작하기 시작한다. 그래서 fork()는 커널로부터 두 번 리턴하게 된다: 부모 프로세스에서 한 번, 자식 프로세스에서 한 번.
1Parent Process
2 |
3 |
4 V
5 fork()--------->Child Process
6 +<------------ |
7 | |
8 | |
9 V V
10 ... ...
이렇게 생성된 자식 프로세스는 일반적으로 다른 프로그램을 실행하게 된다. 이는 exec() 계열의 함수들을 호출하여 수행되며, 이 함수들은 새로운 주소 공간과 새로운 프로그램을 자식 프로세스에 로드한다.
1Parent Process
2 |
3 |
4 V
5 fork()--------->Child Process
6 +<------------ |
7 | exec()
8 | |
9 V V
10 ... ...
자식 프로세스는 로드된 프로그램이 exit() 시스템 호출을 통해 종료될 때, 죽음을 맞이한다. 이 시스템 호출은 프로세스를 종료하고 프로세스가 점유하고 있던 자원 (resources)을 모두 해제 (free)한다. 이때 부모 프로세스는 wait4() 시스템 호출을 통해 죽어버린 자식 프로세스를 부검할 수 있다. 이는 wait4() 시스템 호출의 기능이 특정 프로세스의 종료까지 기다리는 것이기 때문이다. 그래서 프로세스가 종료되면, 죽었지만 아직 완전히 죽지는 못한 좀비 상태에 있게 된다. 이 경우에는 부검이 끝나야 해당 프로세스가 완전한 죽음을 맞이할 수 있다.
1Parent Process
2 |
3 |
4 V
5 fork()--------->Child Process
6 +<------------ |
7 | exec()
8 | |
9 V V
10 wait() exit()
11 | |
12 | | <-- Zombie State
13 V - <-- Death
프로세스의 또다른 이름은 태스크 (task)이다. 리눅스는 내부적으로 프로세스를 태스크라고 부른다. 비록 여기서는 이 둘을 혼용할 것이지만, 일반적으로 태스크라고 부를 때는 커널의 관점에서 프로세스를 보고 있는 것임에 유의하라.
프로세스 기술자와 태스크 구조체 #
그럼 이제 좀 더 깊이 들어가보자.
References #
- Addison-Wesley, "Linux Kernel Development," Pearson Education, Inc., 2010.