Task, Process, Thread.
리눅스에서는 프로세스, 쓰레드 구분 없이 전부 태스크로 처리.
프로세스와 쓰레드를 생성할 때
공유메모리를 가질지 안가질지
공유파일디스크립터 테이블을 가질지 안가질지 등등을 선택할 뿐이다.
프로그램이 fork()를 호출해서 프로세스를 생성해도 커널 입장에서는 Task를 생성하는 거고 pthread_create()로 쓰레드를 생성해도 커널입장에서는 Task를 생성한다.
프로세스 역할을 하는 Task를 생성할 때는 독립된 가상메모리 영역을 하나 만들어서 task_struct에 그 정보를 기록해주고
쓰레드 task를 생성할 때는 가상메모리 영역을 새로 만들지 않고 쓰레드 생성 함수를 호출한 Task가 가진 메모리 정보를 task_struct에 기록한다는 차이밖에 없다는 것이다.
컨택스트 스우이칭 루틴에서는 걍 task_struct에 기술되어 있는 메모리 정보에 따라 MMU를 프로그래밍할 뿐이다. 스케줄러는 스위칭되는 task가 프로세스인지 쓰레드인지 개의치 않다.
정리하자면 리눅스에서는 프로세스와 쓰레드가 정확하게 같은 메카니즘으로 생성되고 같은 메카니즘으로 스위칭되며 가지고 있는 데이터 구조나 컨트롤 블록 등의 모든 구성요소가 같다. 커널 영역에서 돌아가든 유저 영역에서 돌아가든 전부 동일하게 취급하며 리눅스를 만든 사람들은 그것을 Task라고 부른다. 프로세스와 쓰레드는 개념상으로 구분해 두었을 뿐이고 실제로는 리눅스에서 task밖에 없다는 것이 사실이다.
--------------------------------------------------------------
커널 입장에서 보면 모든 작업은 task 단위로 이루어집니다.
실행 단위는 task인데, 이걸 개념적으로 프로세스와 쓰레드로 나누어 놓았습니다.
하나의 실행 프로그램은 하나의 프로세스로 이루어집니다.
프로그램 A는 프로세스 a를 가지고, 프로그램 B는 프로세스 b를 가집니다.
a와 b는 각각 별개의 독립된 가상 메모리 공간을 가지고 실행됩니다.
프로세스 a,b는 각각 쓰레드 a0,b0라고 보면 됩니다.
또한 프로세스 a,b는 각각 자신에 속하는 복수개의 쓰레드 a1,a2,a3,b1,b2,b3...를 생성할 수 있습니다.
쓰레드들은 자신이 속한 프로세스 주소 공간 안에서 메모리를 공유합니다.
즉 a0,a1,a2,a3는 같은 주소 공간 안에서 메모리를 공유합니다. b그룹도 마찬가지입니다.
이렇게 개념적인 프로세스와 쓰레드의 구분이 있지만, 리눅스 커널은 이들을 분리해서 관리하지 않습니다.
리눅스에서 프로세스와 쓰레드는 그들을 생성할때 어떤 주소 공간에 맵핑시키는가에 따라 구분되며
생성된 후에는 모두가 동일한 알고리즘에 의해 Task로서 관리됩니다.
결론적으로,
하나의 쓰레드 == 하나의 task입니다.
프로세스는 프로그램이 실행될때 최초로 생성되는 task이며 OS로부터 가상메모리 공간을 새로 할당 받습니다.
이후에 생성되는 모든 쓰레드 역시 task로 생성되고 최초 생성 task와 같은 메모리 공간을 사용하게 됩니다.
--------------------------------------------------------------
아래는 www.ppomppu.co.kr 개발자 포럼에서 퍼온 글입니다.
쓰레드와 포크 그리고 task에서 논쟁이 붙었더군요. 읽어보시면 재밌습니다~~
아래 컨텍스트 스위치 관련해서 글을 쓰셨길래..
뭐 일반적으로 뻔히 인터넷찾아보고 책찾아보면 아는 내용은 있고 전 다른내용을 좀 써봅니다..
태스크와 프로세스, 쓰레드의 차이점을 이해하면 컨텍스트 스위칭을 이해하는게 쉽습니다..
우선 태스크랑 프로세스의 차이는 간단히 말하면 자신만의 메모리 어드레스를 가지고 있냐는 겁니다..
통상적으로 시스템은 가상메모리 주소체계를 쓰고 물리적인 주소와의 변환과정에서 MMU를 씁니다..
이때 필요한 변환참조 테이블이 MMU테이블인 거구요.. 이건 길게 설명안할테니 잘모르시는 분은 알아서 찾아보시기 바랍니다.
그래서 태스크란건 뭐냐면.. 모든 태스크는 하나의 MMU테이블을 사용하는 겁니다..
이말은 태스크1(T1)이 바라보는 메모리나 태스크2(T2)가 바라보는 메모리는 같다는 말입니다.
같은 메모리변환참조테이블을 사용하니까요..
반대로 프로세스는 프로세스1(P1), 프로세스2(P2) 등등 모든 프로세스마다 자신만의 메모리변환참조 테이블을 갖는겁니다..
T1이 바라보는 0x100000 번지는 T2가 바라보는 0x100000 번지와 같습니다.
같은 메모리변환참조테이블을 쓰니 두개가 똑같은걸로 변환하는거죠..
근데 P1이 바라보는 0x100000번지는 P2가 바라보는 0x100000번지와 가상주소는 같을지몰라도 물리주소는 다릅니다..
주소변환참조테이블이 다르니까요..
쓰레드는 프로세스내에서 주소변환참조테이블을 같이쓰는 애들을 말하는겁니다..
그러니까 자연히 프로세스내에서만 쓰레드는 구현이되는것이고.. 암튼 여러분들이 책에서 배우는 쓰레드의 특성이 나오는겁니다..
메모리자원을 공유하기 쉽다 뭐 이런거요..
장단점은 알아서 찾아보시고..
그래서 우리가 흔히 프로세스 생성이라고 하는게..
MMU테이블을 새로 하나 만드는겁니다.. 그리고 메모리를 조금만 할당해주죠.. 필요하면 더 할당해주고(그게 동적메모리할당이에요.)
그리고 프로세스간 메모리를 공유해야할 필요가 있는 경우, P1과 P2가 서로 양쪽에서 접근할수 있도록 MMU테이블을 열어줘야 하는겁니다..
근데 문제가 뭐냐하면... 태스크라는건 주소변환참조테이블을 하나를 쓰기때문에 컨텍스트 스위칭할때..
컨텍스트백업하고 스케쥴러가 우선순위 높은애를 픽해서 새로 실행만 하면 그만인데..
프로세스의 경우에는 몇가지 더 할일이 있습니다..
주소변환참조테이블(MMU테이블)이 프로세스 마다 다르기때문에 프로세스마다 컨텍스트 스위칭이 일어날때는 태스크와는 달리
이것도 같이 바꿔줘야 합니다.. 그리고 주소변환을 빠르게 하기위한 TLB도 내용을 비워줘야 하고요.. TLB도 설명생략하니 찾아보시기 바랍니다..
근데 이것보다 더 큰 문제는 뭐냐면.. 캐시입니다..
T1이 바라보는 가상주소(VA) 0x100000 번지는 T2가 바라보는 VA 0x100000번지와 같습니다..
근데 이런경우르 한번 따져봅시다..
P2가 0x100028번지까지 실행하다 컨텍스트 스위칭해서 P1이 돌았는데 P1이 0x100024번지까지 실행하다 다시 P2로 컨텍스트 스위칭되었다고 합시다..
P2가 0x100032번지를 실행하기 위해 instruction cache(이하 I$)에서 가져온 0x100032에 대한 내용은 정말 P2의 명령어일까요?
아니면 P1의 명령어일까요?
프로세스간의 컨텍스트 스위칭에서는 이런 애매한 문제가 발생하기 때문에,
컨텍스트 스위칭을 하면서 I$를 다 날려버리고 메모리에서 데이터를 새로 다 가져옵니다..
그러다보니 프로세스간의 컨텍스트 스위칭이 일어날때마다 캐시는 다 날라가버리고.. 새로가져와야하니
엄청 느린거죠..
캐시란건 구조적으로 L1, L2 뭐 이렇게 구분할수 있고..
용도에 따라 I$나 D$냐로 구분이됩니다..
그리고 캐시를 인덱싱하고 태깅하는 방법에 따라 VIVT, VIPT, PIVT, PIPT 이 네가지가 더있습니다..
보통 뭐 젤 뒤에 네가지는 잘 모르시는데..
이게뭐냐면.. 캐시를 인덱싱하고 태깅해서 데이터를 히트하는건 배우셨을텐데..
그럼 그런 캐시를 인덱스하고 태깅할때는 가상주소로 하는게 맞는건지..
위에처럼 가상주소로 하게되면 프로세스마다 가상주소는 같고 물리주소는 다른경우에는 컨텍스트 스위칭이 일어날때마다 매번
캐시를 다 비워줘야 하자나요..
그래서 나온게 VIPT란건데.. 이건 쉽게말해서
캐시를 인덱싱하는건 가상주소로 하되.. 태깅하는건 물리주소로 한다는겁니다..
이래서 TLB같은게 필요한거죠.. 가상주소를 물리주소로 더빨리변환할려고..
그래서 아까 P1과 P2의 상황에서 VIVT방식을 쓰면.. 캐시에서 가상주소가 겹쳐 잘못된 캐시 데이터를 읽어올 문제가 있지만.
VIPT에서는 그런 문제가 없다는거죠.. (아예없진 않습니다.. VIPT도 여전히 같은문제가 있고 PIPT가 되야만 그런문제가 없습니다..)
시스템에서 가상주소는 프로세스마다 같을수있지만 물리주소는 절대 같을수 없으니까요.. (같으면 그건 공유메모리이고..)
그래서 리눅스커널에서 컨텍스트 스위칭관련 코드찾아보면.. 스케줄러가 하는게있고 컨텍스트 백업하고 리스토어하는거말고..
제가 위에서 말했던 저런문제점들을.. 저런걸 처리하는게 잔뜩들어가있습니다..
ARM926 초기에는 I$가 VIVT방식이었다 현재 C A9같은경우 I$는 VIPT D$는 PIPT방식으로 쓰고있어요..
그리고 제품개발하는데 리눅스는 다 제공되는데 왜케오래걸리냐 뭐 이러고 따지시는데..
리눅스커널은 되게 stable합니다.. 그얘기는 뭐냐면.. 반대로 성능따위보다 stable한게 더 중요하단거에요..
다됬있는거 가져와서 뭘고치냐 뭐 이러시는데 캐시관련 코드만 하더라도.. 리눅스 커널은 컨텍스트 스위칭하면 캐시 다날려버리고
메모리에 있는거 새로 다 가져옵니다.. 그게 제일 stable하니까요.. 근데 뭐 그렇게 내놓을순없자나요..
그래서 제조사에서 고치는 항목도 많은거고.. 뭐 그렇습니다..
RTOS가 리눅스보다 공부하기 쉬운이유는 별거없습니다..
태스크기반이라는거.. 그얘기는 모든 메모리스페이스는 하나의 테이블로 관리되기때문에 메모리 사용제약이 덜하고..
뭐 그런거죠..
그리고 컨텍스트 스위칭시 스케줄러가 어떤걸 선택하고 이런건 아래에 기술사 뭐 하고 올려주신거나 그런거 찾아보면 많이 나오니까
설명안하겠습니다..
Ps:
http://kldp.org/node/107287 <- 이거 설명 엉터리입니다.. 혹시 누가 이걸 인터넷에서 찾아서 읽어볼까바 하는 말입니다..