본문 바로가기

리눅스 커널 Linux kernel

3장 태스크 관리 - 1

2장에서는 리눅스 커널의 내부 구조에 대해 간략하게 살펴보았습니다.

3장에선 태스크에 대해 배웁니다.

 

태스크란 '자원 소유권의 단위'를 말합니다.

프로세스란 동작중인 프로그램으로서 디스크에 저장되어있는 실행 가능한 형태의 파일입니다.

                                    ┕ 바이너리 기계 명령어, 수행에 필요한                                        자료들의 집합을 의미합니다.

실행파일 자체는 그저 디스크에 저장된 수동적인 존재로

프로그램이 동작하기 위해선

리눅스 커널로부터 CPU 등의 자원을 할당 받을 수 있는 동적인 객체가 되어야만 합니다.

                                ┕ 이를 프로세스 라 부르고 프로세스는 커널의                                      적절한 스케쥴링에 의해 할당 받은 자신만의                                      자원을 가지고 CPU가 기계어 명령들을 실행함                                    에 따라 끊임없이 변화하는 동적인 존재를                                    말합니다.

 

프로세스에 대해 사용자 입장과 커널 입장에서 살펴보겠습니다.

 

먼저 사용자 입장에서 프로세스!

32bit 환경에서 운영체제는 각 프로세스들에게 각각 4GB(=232bit, 가상메모리의 개념입니다.)의 가상 주소 공간을 할당해줍니다.

4(GB)

 

kernel space

  

3(GB)        stack

지역변수, 아래로 자람

 

 

 

user space

         ↓

 

         ↑

 

        heap

malloc/free 위로 자람

        data

전역변수 등을 담음

        text

명령, 함수

  • 각 영역을 세그먼트 또는 가상메모리 객체(vm_area_struct) 라고도 부릅니다.

이렇게 프로세스가 생성되면 가상 주소 공간을 포함하여 모든 자원들이 새로이 할당됩니다.

프로세스의 경우 자신을 생성해준 부모와는 서로 독립적인 관계를 갖습니다. 하지만

쓰레드 생성의 경우 같은 가상 주소 공간을 공유하게 됩니다.

 

결과적으로    ① 프로세스 생성은 독립적인 주소 공간을 갖습니다. 반면 쓰레드 생성은 서로 같은 주소 공간을 공유합니다.

 ② 같은 프로세스에 여러 개의 쓰레드를 같이 동작시킬 수 있습니다.

 ③ 자식에서의 결함은 부모에게 전파됩니다.

 

즉 쓰레드 모델은 자원의 공유에 적합하며, 프로세스 모델은 결함의 고립에 적합하다고 할 수 있습니다.

프로세스를 생성하는 시스템 호출에는 fork와 vfork가 있는데,

vfork는 fork 후 exec을 바로 호출하는 자식의 경우 부모의 주소 공간을 자식에게 복사한 것이 불필요해져

만들어 졌습니다.

 

다음으로 커널에서의 구현에 대해 살펴보겠습니다.

프로세스는 자신이 사용하는 자원과 그 자원에서 수행되는 수행 흐름으로 구성됩니다.

프로세스 혹은 쓰레드마다 task_struct 자료구조를 생성해 관리합니다.

리눅스는 1대1 모델을 기반으로 커널 내부에서 태스크라는 객체로 프로세스나 쓰레드를 관리합니다.

프로세스나 쓰레드가 생성될 때 이용되는 함수들을 따라가보면, 최종 호출 함수는 모두 do_fork()로 동일합니다.

do_fork()는 task_struct를 생성 -> 자원을 할당하고 -> 수행 가능한 상태로 만들어 줍니다.

그렇다면 프로세스던 쓰레드던 모두 task_struct로 표현되는데 왜 같은 명칭을 사용하지 않는 것일까요?

 

 



커널은 각 태스크를 task_struct 내의 pid 필드로 구분합니다.

여기서 task_struct에는 쓰레드를 그룹으로 관리하는 tgid : Thread Group ID, 라는 필드가 따로 존재합니다.

(pid필드와 tgid필드는 별개)

이때 한 프로세스 안의 여러 개의 쓰레드들은 동일한 pid를 공유하게 됩니다.

커널은 프로세스가 생성될 때는 유일한 pid를 할당하고 (이때 프로세스에 할당된 pid와 tgid는 같은 값을 갖습니다.)

커널이 쓰레드를 생성할 때의 그 쓰레드의 tgid 값은 부모 쓰레드의 tgid 값과 동일한 값으로 생성합니다.

 

이처럼 태스크와 관련된 모든 정보를 통틀어 '문맥'이라고 부릅니다.

문맥은 아래의 세가지로 구분할 수 있습니다.

시스템 문맥

태스크의 정보 유지, task_struct, 파일 디스크립터

메모리 문맥

텍스트, 데이터, 스택, heap, 스왑

하드웨어 문맥

문맥 교환 시 태스크의 현재 실행 위치 정보 유지

 

각 태스크들은 그 실행 상태에 따라 여러 가지 상태전이를 갖습니다.

TASK_RUNNING(ready) : 준비 상태

TASK_RUNNING(running) : 실제 CPU를 배정받아 명령어를 실행하고 있는 상태

 

여기서 또 running 상태에서 발생하는 사건에 따라 다양한 상태로 전이 됩니다.

Ⅰ exit() 호출 -> EXIT_ZOMBIE     : 태스크가 유지하던 자원(가상 주소 공간 등)을 커널에 반납합니다.

단지 소멸된 이유와 error가 생겼다면 해당되는 error 번호, 사용 자원의 통계 정보를 유지합니다.

부모가 wait() 함수를 호출에 EXIT_ZOMBIE 상태의 자식 정보를 가져갔다면 자식은 EXIT_DEAD 상태가 되고 남은 자원들을 반환합니다.

(※ 이 부분에서 제가 이해가 안된 부분이 복수개의 태스크가 동일한 태스크에게 wait를 호출하면

deadlock이 발생해 EXIT_ZOMBIE 상태가 추가된 것이라고 나와있습니다.

근데 전 이 상황이 서로가 서로의 자원을 원하는 deadlock 조건인지 이해가 안 되었습니다.

그래서 가정해 본 것입니다.

만약 태스크 1, 2, 3 이 main 태스크에 대해 wait를 호출 했을 경우 wait값의 결과가 태스크 1, 2, 3 중 한 곳으로만 전달돼서 이 값이 전달된 쪽은 계속해서

수행해 나갈 수 있는데, 값이 전달되지 않은 쪽에선 더 이상 진행할 수 없어서 deadlock이 되는 것인지? 의문이 남았습니다.

저의 이런 가정이 맞는지 잘 모르겠습니다. EXIT_ZOMBIE의 도입으로 main 태스크가 한 번만 EXIT_DEAD로 상 전이되고 다른 쪽엔 의미 없는 값을 보내는 것일까요?)

 

만약 부모가 자식보다 먼저 죽는다면 자식의 부모는 init 태스크가 됩니다.

 

Ⅱ 할당된 CPU 시간을 모두 사용해 준비 상태로 전환됩니다.

 

Ⅲ 특정한 사건을 기다리는 경우

예를 들어 잠금이 걸린 자원에 대한 잠금 해제 시도 혹은 디스크 입출력 대기의 경우

대기 상태의 태스크는 기다리는 사건에 따라 특정 큐에 매달려 대기합니다.

이때 더 이상 할 일이 없어진 CPU를 위해 스케쥴러는 ready 상태의 태스크를 running으로 변경합니다.


출처 : 리눅스 커널 내부구조 -  백승재, 최종무 공저

'리눅스 커널 Linux kernel' 카테고리의 다른 글

4장 메모리관리 - 2  (0) 2013.07.24
4장 메모리 관리 -1  (0) 2013.07.20
2장 리눅스 커널 구조  (0) 2013.07.17
1장 리눅스의 소개  (0) 2013.07.16
리눅스 커널 내부구조  (0) 2013.07.13