IT/Linux Kernel

디버깅을 통해 배우는 리눅스 커널의 구조와 원리 1, 도서 공부하기 10 - 프로세스 생성

변화의 물결1 2025. 3. 17. 18:29

 

 

 안녕하세요.

 

 프로세스와 스레드에 대해 보았고, 이번에 생성하는 과정을 확인을 알아보겠습니다.

 진행하기 앞서서 유저 모드와 커널 모드 부분을 먼저 알고 시작해야 할 것 같아서 앞쪽 내용에 추가해 보았습니다. 알고 있는 내용이면 이 부분은 넘어가고 아래쪽 2. 프로세스 생성부터 보면 됩니다.

 


 

1. 유저 모드와 커널 모드 그리고 영역

 

 유저 모드와 커널 모드가 나오면, 권한에 관한 것이라고 알 수 있는데, 공간에 대한 개념이 같이 나오기 때문에 혼돈이 있을 것 같아서 나눠서 생각해 보면 좋을 것 같아서 추가해 보았습니다.

 

1) 유저 모드와 커널 모드

 

(1) 유저 모드(User Mode)

 

 - 권한 수준: 제한된 권한을 가진 모드입니다. 응용 프로그램(예: 웹 브라우저, 문서 편집기)이 주로 유저 모드에서 실행됩니다.

 - 역할: 시스템 자원(예: 메모리, CPU, 디스크 등)에 직접 접근하지 못하고, 운영 체제(OS)를 통해서만 간접적으로 접근할 수 있습니다.

 - 제약 사항:

 커널 공간(kernel space)에 접근할 수 없습니다.

 하드웨어 장치를 직접 제어하거나, 시스템 호출(System Call)을 통해서만 커널에 요청을 전달할 수 있습니다.

 - 장점: 코드의 결함이나 악성 소프트웨어로 인해 시스템 전체가 손상되는 위험을 줄입니다.

 

(2) 커널 모드(Kernel Mode)

 

 - 권한 수준: 시스템의 모든 자원에 접근할 수 있는 최고 권한을 가진 모드입니다. 운영 체제의 핵심 부분인 커널이 커널 모드에서 실행됩니다.

  - 역할:

 하드웨어 제어(디스크, 네트워크, 메모리 관리 등)를 직접 수행합니다.

 시스템 호출을 처리하여 유저 모드에서의 요청을 실행합니다.

 - 특징:

 커널 모드에서 실행되는 코드는 매우 신중하게 작성되어야 합니다. 오류가 발생하면 시스템 전체가 중단될 수 있기 때문입니다.

 모든 자원을 통제하고 보호하기 때문에 시스템의 기본적인 동작을 책임집니다.

  

(3) 유저 모드와 커널 모드의 전환

 

 유저 모드에서 커널 모드로 전환은 시스템 호출(System Call) 또는 인터럽트(Interrupt)를 통해 이루어집니다.

 

 예를 들어, 파일을 열거나 데이터를 읽는 작업을 요청하면, 유저 모드에서 시스템 호출을 통해 커널 모드로 전환됩니다. 커널 모드는 요청된 작업을 수행한 후 다시 유저 모드로 돌아갑니다.

 

 

2) 유저 영역과 커널 영역

 

 가상 메모리 공간 구조가 32-bit인 경우로 보겠습니다. 64bit와 차이가 있는 부분이 있습니다. 32-bit 시스템에서는 프로세스당 4GB(=2³²) 크기의 가상 주소 공간이 할당됩니다.

  

(1) 유저 영역(User Space)

 

 가상 메모리 공간에서 "유저 프로세스"가 사용하는 영역

 프로그램 실행에 필요한 코드, 데이터, 힙, 스택 등이 있음

 커널 메모리(커널 영역)에는 직접 접근할 수 없음

 하위 3GB (0x00000000 ~ 0xBFFFFFFF)

 일반 사용자 프로세스가 접근 가능

  

(2) 커널 영역(Kernel Space)

 

 운영체제 커널이 사용하는 메모리 공간

 커널 코드, 커널 데이터, 시스템 호출 핸들러, 드라이버 등이 포함됨

 유저 프로세스는 시스템 콜을 통해서만 접근 가능

 상위 1GB (0xC0000000 ~ 0xFFFFFFFF)

 OS 커널 기능 수행 (메모리 관리, 프로세스 관리 등)

 유저 공간 접근 가능 (매핑된 경우)

 

 

3) 영역과 모드 비유

 

 위의 내용에서 영역과 모드가 헷갈릴 수 있기 때문에 회사 건물 비유할 수 있을 것입니다.

 

 유저 영역(User Space) = 직원 사무실 (일반 직원이 작업하는 공간)

 커널 영역(Kernel Space) = 서버룸, CEO 사무실 (보안 구역, 관리자만 출입 가능)

 유저 모드(User Mode) = 일반 직원 (회사 시스템을 사용할 수 있지만, 서버 직접 접근 불가)

 커널 모드(Kernel Mode) = CEO 또는 IT 관리자 (모든 시스템 관리 가능)

 

 

2. 프로세스는 어떻게 생성할까

 

 리눅스에서 구동되는 프로세스는 유저 레벨에서 프로세스 생성과 커널 레벨에서 생성되는 것으로 분류할 수 있습니다.

 

 유저 프로세스는 유저 공간에서 프로세스를 생성하는 라이브러리(glibc)를 사용해서 커널에게 프로세스 생성을 요청을 합니다.

 커널 프로세스는 커널 내부 kthread_thread() 함수를 호출해서 커널 프로세스를 생성합니다.

 

 유저 프로세스와 커널 프로세스가 생성하는 흐름 다르다고 생각할 수 있지만, 연결된 고리를 따라가면 _do_fork() 함수를 호출한다는 것을 알 수 있습니다.

 

1) _do_fork 함수

 

 유저 레벨 프로세스는 init(systemd) 프로세스, 커널 레벨 프로세스(Kernel Thread)는 kthreadd 프로세스가 생성합니다. 여기서 생성이라고 했지만, 실제로는 복제가 일어납니다.(?)

 

 무슨 말인가 할 수도 있는데, 프로세스를 생성하는 것은 리소스를 할당받으려면 시간이 오래 걸리기 때문에 이미 생성된 프로세스에게 물려받습니다. 프로세스 생성할 때 부모 프로세스가 쓰고 있는 자료 구조를 복제하는 것이 효율적이기 때문입니다

 

(1) _do_fork() 함수 확인

 

  _do_fork() 함수 구현부가 있는 fork.c 파일을 확인합니다.

/linux $ vim kernel/fork.c

 

 인자들을 값을 확인해 보면 아래와 같습니다.

 

 clone_flags: 이 플래그는 부모 프로세스와 자식 프로세스 간에 어떤 자원을 공유할지를 결정합니다. 예를 들어, 메모리 공간, 파일 디스크립터, 신호 처리 등을 공유할지 여부를 설정할 수 있습니다. ( OR 비트 연산결과로 저장)

 

플래그 설명
CLONE_VM 부모와 주소 공간을 공유 (스레드 생성 시 필요)
CLONE_FS 부모의 파일 시스템 정보 공유 (chroot 같은 설정 공유)
CLONE_FILES 부모의 파일 디스크립터 테이블 공유
CLONE_SIGHAND 부모의 시그널 핸들러 공유
CLONE_THREAD 부모와 같은 스레드 그룹에 속함 (pthread와 비슷)
CLONE_PARENT_SETTID parent_tidptr에 자식의 TID를 저장
CLONE_CHILD_SETTID child_tidptr에 자식의 TID를 저장
CLONE_CHILD_CLEARTID 자식이 종료될 때 child_tidptr 주소를 0으로 설정

 

 

 stack_start: 새로 생성된 프로세스의 스택 시작 주소를 지정합니다. 이 값은 주로 사용자 공간에서 실행되는 스레드의 스택을 설정하는 데 사용됩니다.

 

 stack_size: 스택의 크기를 지정합니다. 이 값은 스택의 메모리 할당 크기를 정의합니다.

 

 parent_tidptr: 부모 프로세스의 TID(Task Identifier)를 저장할 포인터입니다. 주로 스레드 간의 동기화에 사용됩니다.

child_tidptr: 자식 프로세스의 TID를 저장할 포인터입니다. 이 역시 스레드 간의 동기화에 중요한 역할을 합니다.

 

 tls: 새로 생성될 스레드 로컬 스토리지(Thread Local Storage)를 설정하는 데 사용됩니다. 주로 멀티스레드 환경에서 각 스레드가 고유한 데이터를 가질 수 있도록 지원합니다.

 

 

 

 반환값은 long형으로 되어 있고 PID를 반환합니다. 프로세스 생성에 에러가 발생하면 PTR_ERR()에 지정된 에러 값을 반환합니다.

 

 

 

(2) 커널에서 _do_fork() 함수 호출 시점

 

 유저모드에서 생성한 프로세서는 sys_clone() 시스템 콜 핸들러 함수, 커널 모드에서는 생성한 커널 스레드는 kernel_thread()를 통해 _do_fork() 함수를 호출합니다.

 

[유저 레벨에서의 태스크 생성, "코드로 알아보는 ARM 리눅스 커널 참조"]

 

 

 유저모드에서 시스템 콜을 통해 서비스를 호출하면 대략적으로 아래와 같이 동작합니다.

  시스템 콜에 전달한 인자의 오류 점검

  유저 모드에서 요청한 서비스 종류에 따라 커널 내부 함수 호출

  유저 프로그램에 요청한 정보를 자료 타입에 맞게 반환

 

 유저 모드에서는 스스로 프로세스를 생성하지 못합니다. 대신 리눅스에서 제공하는 라이브러리의 통해 프로세스 생성을 요청할 수 있습니다. 유저 프로그램은 그 자체로 프로그램을 의미하고 유저 레벨 프로세스는 이 프로그램을 실행하는 주체를 의미합니다. 이 부분은 뒷 내용에서 나옵니다.

 

 유저 레벨 프로세스와 커널 레벨 프로세스와 차이는 앞에서 나온 이야기한 것처럼 프로세스 생성에서는 동일한 함수를 호출합니다. 그러나 생성하는 시작 과정에서 유저 모드에서는 fork(), pthread_create() 함수를 glibc의 리눅스 라이브러리 도움을 받아 커널에 서비스를 요청해서 생성한다는 것입니다.

 

 

 

 커널 레벨 프로세스 경우 커널 모드에서 실행하며 커널의 kthread_create() 함수를 호출해서 프로세스를 생성합니다.

  

 

 유저 모드, 커널 모드, 유저 레벨, 커널 레벨 등 구분해야 하는 것이 많은데, 처음 알아가는 입장에서는 어려운 것이 사실입니다. 간단하게 크게 두 가지 영역으로 물리적, 소프트웨어적 부분에서 보안과 효율 등을 위해서 접근과 제어를 통제하기 위해 나눈다라고 생각하면 어떨까 합니다.

 

 이번 시간에는 프로세서 생성에 대한 큰 개념에 대해서 훑어본 것이라고 생각하다고 다음 내용부터 좀 더 깊게 소스코드에 대해서 확인하게 될 것입니다.

 

 

감사합니다.

 

 

<참고 자료>

1. [도서] 디버깅을 통해 배우는 리눅스 커널의 구조와 원리, wikibook

2. [도서] 코드로 알아보는 ARM 리눅스 커널, p500, Jpub

 

반응형