반응형

유닉스(리눅스)시스템에서 모든 프로세스는 PID가 1인 init 프로세스의 자손이다. init 부트 과정의 최종단계에서 커널이 실행하는 프로세스다

 

시스템의 모든 프로세스는 정확히 하나의 부모 프로세스를 가지며 모든 프로세스는 하나이상의 자식 프로세스를 가질수 있다. 같은 부모 프로세스를 가지는 자식 프로세스를 형제(sibling)이라고 부른다.

 

task_struct 구조체에는 부모의 task_struct를 가리키는 parent라는 포인터와 task_struct 리스트를 가리키는 children 포인트가 있으며 결과적으로 주어진 현재 프로세스에서 부모 프로세스의 task_struct를 얻을 수 있다.

 

<부모 프로세스 찾기>

struct task_struct *my_parent = current->parent;

 

<자식 프로세스 찾기>

struct task_struct *task;

struct list_head *list;

 

list_for_each(list, current->children)

{

task = list_entry(list, struct task_struct, sibling)

// 이제 task는 현재 프로세스의 자식 프로세스 중 하나를 가르킨다

}

 

 

프로세스 생성

 

대부분의 운영체제는 스폰(Spawn)방식을 사용하여 새로운 주소공간에 새 프로세스를 만들고, 실행 파일을 읽은 다음 그 코드를 실행한다. 유닉스&리눅스는 이 과정을 fork(), exec()라는 두 함수로 나누어 실행한다

 

먼저 fork()는 현재 태스크를 복제해 자식 프로세를 만든다. 이렇게 만들어진 프로세스는 PID(프로세스의 고유값), PPID(부모 프로세스의 PID), 상속되지 않은 지연된 시그널과 같은 일부자원, 통계수치를 제외하고는 부모와 동일하다. 다음으로 exec()는 새로운 실행파일을 주소공간에 불러오고 실행하게 된다.

 

Copy-on-Write (기록사항 발생시 복사, COW)

 

리눅스에서는 copy on write 페이지를 이용해 fork() 함수를 구현했다. COW기능은 데이터 복사를 지연 또는 방지하는 기능이다. 프로세스 주소공간을 복사하는 대신 부모와 자식 프로세스가 같은 공간을 공유한다.

 

그러나 기록 사항이 발생해 데이터 변경이 필요하면 그 순간 사본이 만들어 지고 각 프로세스가 별도의 내용을 가지게 된다. 따라서 리소스 복사는 해당 리소스에 대한 기록이 발생하는 경우에만 발생한다.

 

이 방법은 주소공간에 실제 기록작업이 일어 날때가지 각 페이지의 복사 작업을 지연시키며 프로세스가 절대 기록을 하지 않는 경우 복사가 필요 없어 진다.

 

리눅스의 스레드

 

스레드를 이용해 메모리 주소공간을 공유하는 같은 프로그램 여러개를 동시에 실행 할 수 있다. 스레드를 통해 동시 프로그래밍(Concurrent Programming)이 가능하고, 다중 프로세서 시스템에서는 진정한 병렬처리를 구현 할 수 있다.

 

리눅스는 기본적인 프로세스로 모든 스레드를 구현한다.  별도의 자료구조나 스케줄링 기법없이 특정 자원을 다른 프로세스와 공유하는 특별한 프로세스로 다루게 된다. 각 스레드는 task_struct 구조체를 가지고 있으며 커널 입장에서는 주소공간과 같은 자원을  다른 프로세스와 공유하고 있는 프로세스이다.

 

스레드의 생성

 

정상적인 프로세스(태스크)와 같은 방식으로 만들어지지만 clone() 시스템에서 특정 자원을 공유할 수 있도록 Flag를 지정해 준다.

 

커널 스레드

 

커널의 일부 동작을 백 그라운드에서 실행하는 것이 좋을 때가 있다 . 커널 공간에서만 존재하는 표준 프로세스인 커널 스레드를 이용해 백그라운드 작업을 수행 할 수 있다.

 

정상 프로세스와의 차이점은 커널 스레드에는 주소공간이 없다는 점이다. (mm포인터 값이 NULL) 커널 스레드는 커널 공간에서만 동작하며 사용자 공간에서만 동작하며 사용자 공간으로 컨텍스트 전환이 일어나지 않는다. 하지만 커널 스레도도 스케줄링이 되며 선점이 가능하다.

 

대표적인 예로는 flush 및 ksoftirq 작업을 예로 들 수 있다. 리눅스는 kthreadd 커널 프로세스가 모든 커널 스레드를 만드는 방식으로 커널 스레드를 관리한다.

 

struct task_struct *kthread_create(int (*threadfn)(void *data),
                                             void *data,
                                                 const char namefmt[], ...);

 

kthread_create()는 date 매개변수를 통해 전달된 threadfn 함수를 실행하며 namefmt문자열에 해당하는 이름을 갖게 된다.

 

 

처음 프로세스는 실행할 수 없는 상태로 만들어지기 때문에 wake_up_process()함수를 통해 명시적으로 깨워주지 않으면 실행되지 않는다.

 

커널 스레드는 한번 시작되면 스스로 do_exit() 함수를 호출하거나, 커널의 다른 부분에서 kthread_create()가 반환한 task_struct 구조체의 주소와 함께 kthread_stop() 함수를 호출할 때 까지 계속 실행된다
 

 

 

반응형
반응형

프로세스는 실행 중인 프로그램 ( 특정 매체에 저장된 오브젝트 코드) 입니다.

또한 프로세스는 사용중인 파일, 대기 중인 스그널, 커널 내부 데이터, 프로세서 상태, 하나 이상의 물리적 메모리 영역이 할 당 된 메모리 주소공간, 실행 중인 하나 이상의 스레드 정보등 ... 모든 자원을 포함 하는 개념입니다.

다시말해... 프로세스는 프로그램 코드를 실행하면서 생기는 무든 결과물이라고 할 수 있습니다.

 

스레드는 프로세스 내부에서 동작하는 객체입니다. 스레드는 프로그램 카운터와 프로세스 스택, 프로세스 레지스터를 갖고 있으며 커널은 프로세스가 아니라 각각의 스레드를 스케줄링합니다. 전통적인 유닉스 시스템에서는 프로세스가 하나의 스레드로 구성되지만 현대 시스템에서는 여러개의 스레드로 구성된 다중 스레드 프로그램을 볼수 있습니다. 또한 리눅스는 프로세스와 스레드를 구분하지 않으며 리눅스에 있어서는 스레드는 조금 특별한 형태의 프로세스입니다.

 

운영체제에서 프로세스는 가상 프로세서와 가상 메모리 라는 두가지의 가상환경을 제공합니다.

 

가상 프로세스는 실제로는 수백개의 프로세스가 프로세서(CPU)를 공유하는 상황일지라도 프로세스가 혼자 시스템을 사용하는 듯한 가상환경을 제공해줍니다.

가상 메모리는 프로세스가 시스템의 전체메모리를 혼자 차지하고 있는 것 처럼 메모리를 할당하고 관리할 수 있게 해준다.

 

스레드는 가상 프로세서를 할당 받지만, 가상 메모리는 공유합니다.

 

 

커널은  프로세스 목록을 테스크 리스트라고 부르는 환형 양방향 연결 리스트 형태로 저장한다. 각 항목은 <linux/sched.h>에 정의된 struct task_struct 형식으로 저장되어 있으며 프로세스 서술자라고 부르고 프로세스 서술자는 해당 프로세스와 관련된 모든 정보를 포함하고 있다.

 

 

task_struct 구조체는 객체 재용 및 캐시 컬러링 기능을 지원하는 슬랩 할당자(Slab Allocator)를 사용해 할당한다. 슬랩 할당자를 이용해 동적으로 프로세스 서술자를 만들기 때문에 thread_info라는 새로운 구조체를 스택 밑바닥이나 꼭대기에 두고 있다.

x86 시스템의 thread_info 구조체는 <asm/thread_info.h>에 다음과 같이 정의됩니다. 각 태스크의 thread_info 구조체는 프로세스 스택의 제일 끝부분에 할당이 되며 구조체의 task 포인터가 태스크의 실제 task_struct 구조체를 가르킵니다.

 

 

시스템은 고유한 프로세스 인식번호(PID)를 이용해 프로세스를 구별한다. PID는 보통 int형을 사용하고 PID의 최대 값은 32,768이며 4백만으로 상향 조정가능합니다.

 

프로세스 서술자의 state 항목은 현재 프로세스의 환경을 알려주고 시스템의 프로세스는 다섯가지 상태중에 하나에 있으며 각 상태값은 다음 다섯가지 플래그를 통해 표현 된다

 

1. TASK_RUNNING

프로세스가 실행 가능한 상태, 현재 실행 중이거나 실행되기 위해 실행되기 위해 실행 대기열에 있는 상태. 사용자 공간에서 실행된 프로세스는 이 상태만 가질수 있으며 커널 공간에서 실행 중인 프로세스도 이 상태에 속합니다.

2. TASK_INTERRUPTIBLE

프로세스가 특정 조건이 발생하기를 기다리며 쉬는 중이다(중단된 상태) 기다리는 조건이 발생하기를 기다리며 쉬는 중이고 기다리는 조건이 발생하면 커널의 프로세스의 상태를 TASK_RUNNING 상태로 바꾼다 프로세스가 시그널을 받은 경우 조건에 상관없이 실행 가능한 상태로 바꿉니다.

3. TASK_UNINTERRUPTIBLE

시그널을 받아도 실행가능상태로 바뀌지 않는 점을 빼면 TASK_INTERRUPTIBLE 상태와 같다. 이 상태는 기 다리는 조건이 금방 발생하는 경우에 사용되며 자주 사용되지는 않습니다.

4. __TASK_TRACED

디버거 등의 다른 프로세스가 ptrace를 통해 해당 프로세스를 추적하는 상태이다.

5. __TASK_STOPPED

프로세스 실행이 정지된 상태다 해당 테스크는 실행 중이지도 않고 실행가능한 상태도 아니다. 작업이 SIGSTOP, SGTSTP, SIGTTIN, SIGTTOU같은 시그널을 받은 경우 디버그 중에 시그널을 받은 경우에도 이상태가 된다.

 

 

 

커널 코드에서 프로세스의 상태를 바꿀 필요가 생기는 경우가 많이 발생한다. 이경우에는 다음의 함수를 사용하게 됩니다.

 

set_task_state(task, state); // 태스크 'task'의 상태를 'state' 상태로 설정

 

이 함수는 특정 태스크의 상태를 지정한 상태로 변경하며 필요한 경우, 메모리 보호 기능을 이용해 다른 프로세서와 작업 순서가 겹치는 것을 방해 한다.

 

이 함수의 동작은 아래와 같다.

task->state=state;

 

프로세스의 중요한 부분 중 하나는 실행 중인 프로그램 코드다. 프로그램이  시스템 호출을 하거나 예외 처리가 발생한 경우 프로그램은 커널 공간으로 진입하게되고 이런 상황을 커널은 '프로세스를 대신해 실행 중' 이거나 '커널이 프로세스 컨텍스트에 있다'라고 말한다. 커널이 작업을 끝내면 프로세스는 사용자 공간에서 실행르 계속한다.

 

시스템호출과 예외 처리기는 잘 정의된 커널 진입 인터페이스이며 프로 세스는 이 두가지 인터페이시를 통해 커널 공간으로 들어갈 수 있다.

 

 

반응형

+ Recent posts