프로세스는 추상적이고 다양한 의미를 담고 있어 다양한 관점으로 설명할 수 있습니다.

프로세스란 무엇일까요? 프로세스(Process)는 리눅스 시스템 메모리에서 실행 중인 프로그램을 말합니다. 스케줄링 대상인 태스크와 유사한 의미로 쓰입니다. 다수 프로세스를 실시간으로 사용하는 기법을 멀티프로세싱이라고 말하며 같은 시간에 멀티 프로그램을 실행하는 방식을 멀티태스킹이라고 합니다.

우리가 쓰고 있는 스마트폰 동작을 잠깐 생각해봅시다. 
전화를 하면서 메모를 남기고, 음악을 들으면서 브라우저를 볼 수 있습니다. 여러 어플리케이션이 동시에 실행하고 있습니다. 이것은 멀티태스킹을 수행해서 프로그램을 시분할 방식으로 처리하기 때문에 가능합니다.

이번에는 리눅스 개발자 입장에서 프로세스에 대해 생각해 봅시다. 프로세스는 리눅스 시스템 메모리에 적재되어 실행을 대기하거나 실행하는 실행 흐름을 의미합니다. 프로세스가 실행을 대기한다면 실행할 때 어떤 과정을 거칠까요? 프로세스는 어떤 구조체로 식별할까요? 다양한 의문이 생깁니다.

프로세스를 관리하는 자료구조에자 객체를 태스크 디스크립터라고 말하고 구조체는 struct task_struct 입니다. 이 구조체에 프로세스가 쓰는 메모리 리소스, 프로세스 이름, 실행 시각, 프로세스 아이디(PID), 프로세스 스택 최상단 주소가 저장돼 있습니다.

프로세스를 struct task_struct 이란 구조체로만 표현할 수 있을까요? 위에서 프로세스란 실행 흐름 그 자체라고 정의했습니다. 프로세스 실행 흐름은 어느 구조체에 저장할 수 있을까요?

프로세스는 실행할 때 리눅스 커널 함수를 호출합니다. 임베디드 리눅스 디버거의 전설인 Trace32 프로그램으로 콜 스택을 하나 봅시다.
1 -000|__schedule()
2 -001|schedule_timeout()
3 -002|do_sigtimedwait()
4 -003|sys_rt_sigtimedwait()
5 -004|ret_fast_syscall(asm)

위 함수 호출 방향은 5번째 줄에서 1번째 줄입니다. 콜 스택을 간단히 해석하면 유저 공간 프로그램에서 sigtimedwait() 이란 함수를 호출하면 이에 대응하는 시스템 콜 핸들러 함수인 sys_rt_sigtimedwait() 함수 실행 후 스케줄링되는 함수 흐름입니다.

프로세스는 함수를 호출하면서 실행을 합니다. 그런데 함수를 호출하고 실행할 때 어떤 리소스를 쓸까요? 프로세스 스택 메모리 공간입니다.

모든 프로세스들은 커널 공간에서 실행할 때 각자 스택 공간을 할당 받으며 스택 공간에서 함수를 실행합니다. 

위에서 본 프로세스가 스케줄러에 의해 다시 실행한다고 가정합시다. 그럼 어떻게 실행할까요?
1 -000|__schedule()
2 -001|schedule_timeout()
3 -002|do_sigtimedwait()
4 -003|sys_rt_sigtimedwait()
5 -004|ret_fast_syscall(asm)

1번 함수에서 5번 함수 방향으로 되돌아 올 겁니다. 이는 이 프로세스가 마지막에 실행했던 레지스터 세트와 실행 흐름이 프로세스 스택 공간에 저장돼 있었기 때문입니다.

프로세스를 실행 흐름을 표현하는 또 하나 중요한 공간은 프로세스 스택 공간이며 이 프로세스 스택 최상단 주소에 struct thread_info 란 구조체가 있습니다.

정리하면 프로세스는 추상적인 개념이지만 프로세스 정보와 프로세스 실행 흐름을 저장하는 구조체와 메모리 공간이 있습니다. 리눅스 커널에서 실시간으로 구동하는 프로세스에 대해 잘 알려면 이 자료구조를 잘 알 필요가 있습니다.

우리가 열심히 분석하는 리눅스 커널 소스 코드를 실행하는 주체가 프로세스이며 프로세스 스택 공간에서 실행하는 것입니다.

태스크란
태스크는 무엇일까요? 태스크는 리눅스 이외 다른 운영체제에서 예전부터 쓰던 용어입니다.
운영체제 이론을 다루는 예전 이론서는 대부분 태스크란 단어를 많이 볼 수 있습니다.

태스크는 운영체제에서 어떤 의미일까요? 말 그대로 실행(Execution)이라 할 수 있습니다.
운영체제 책들을 보면 첫 장에서 태스크에 대한 설명을 볼 수 있습니다. 최근 운영 체제에서는 대부분 기본으로 멀티 태스킹 환경에서 프로그램을 실행하나 예전에는 특정 코드나 프로그램 실행을 일괄 처리했습니다. 이 실행 및 작업 단위를 태스크라고 불렀습니다.


화면이 없는 간단한 시나리오의 임베디드 시스템에서는 태스크 2개로 서로 시그널을 주고 받으며 시스템 전체를 제어할 수 있습니다. 

하지만 태스크에 대한 개념은 현재 프로세스와 겹치는 부분이 많습니다. 태스크에 대한 의미가 프로세스와 스레드에 대한 개념이 도입하면서 발전했습니다. 태스크를 실행하는 단위인 실행(Execution)을 결정하는 기준이 스케줄링으로 바뀐 겁니다. 

예전에 쓰던 용어를 현재 소프트웨어에 그대로 쓰는 경우가 많습니다. 이를 레거시(Legacy)라고 말하고 과거 유물이란 뜻도 있습니다. 예전에 썼던 태스크란 용어를 리눅스 커널 용어나 소스 코드에서 그대로 쓰고 있습니다. 프로세스 속성을 표시하는 구조체 이름을 struct task_struct으로 쓰고 있습니다. 

프로세스 마다 속성을 표현하는 struct task_struct 구조체는 태스크 디스크립터라고 하며 프로세스 디스크립터라고도 말합니다.

리눅스 커널 함수 이름이나 변수 중에 task란 단어가 보이면 프로세스 관련 코드라 생각해도 좋습니다.

예를 들어 다음 함수는 모두 프로세스를 관리 및 제어하는 역할을 수행하며 함수 이름에 보이는 태스크는 프로세스로 바꿔도 무방합니다.
dump_task_regs
get_task_mm
get_task_pid
idle_task
task_tick_stop

리눅스 커널에서 태스크는 프로세스와 같은 개념으로 쓰는 용어입니다. 소스 코드나 프로세스에 대한 설명을 읽을 때 태스크란 단어를 보면 프로세스와 같은 개념으로 이해합시다.

스레드란
스레드는 무엇일까요? 간단히 말하면 유저 레벨에서 생성된 가벼운 프로세스라 말할 수 있습니다. 멀티 프로세스 실행 시 컨택스트 스위칭을 수행해야 하는데 이 때 비용(시간)이 많이 듭니다. 실행 중인 프로세스의 가상 메모리 정보를 저장하고 새롭게 실행을 시작하는 프로세스도 가상 메모리 정보를 로딩해야 합니다. 또한 스레드를 생성할 때는 프로세스를 생성할 때 보다 시간이 덜 걸립니다.

스레드는 자신이 속한 프로세스 내의 다른 스레드와 파일 디스크립터, 파일 및 시그널 정보에 대한 주소 공간을 공유합니다. 프로세스가 자신만의 주소 공간을 갖는 것과 달리 스레드는 스레드 그룹 안의 다른 스레드와 주소 공간을 공유합니다.

하지만 커널 입장에서는 스레드를 다른 프로세스와 동등하게 관리합니다. 대신 각 프로세스 식별자인 태스크 디스크립터(struct task_struct)에서 스레드 그룹 여부를 점검할 뿐입니다.


+ Recent posts