Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

0112
737
82110


[리눅스 커널] 유저 모드란 4장. 프로세스 관리

유저 레벨 프로세스에 대해 알아보기 전에 유저 모드가 무엇인지 먼저 살펴봅시다.

우리가 라즈베리파이에서 바탕 화면에 있는 아이콘을 클릭해서 어떤 프로그램을 실행한다고 가정합시다. 이 때 프로그램은 유저 모드나 커널 모드 중 하나로 동작합니다. ftrace 로그로 커널 동작을 확인하면 정확히 유저 모드와 커널 모드를 자주 스위칭합니다.

유저 모드와 커널 모드로 나누는 기준은 무엇일까요? 이는 메모리 접근과 실행 권한으로 두 모드로 분류합니다.

실행 모드를 유저 모드와 커널 모드로 나누는 이유를 알기 위해 한 가지 예를 들겠습니다. 어떤 시스템 메모리 공간을 0~4G까지 가상 메모리에서 연속으로 쓰고 있다고 가정합시다. 이 때 커널 코드와 전역 변수가 0~4G 메모리 구간에 메모리 주소로 매핑되어 있는 상황입니다.

만약 어떤 운영체제에서 라즈베리파이 Geany와 같은 어플리케이션까지 리눅스 시스템 개발자가 구현하면 큰 문제가 발생하지 않을 것입니다. 리눅스 시스템 개발자는 커널 코드 실행 흐름이나 메모리 제어하는 동작을 잘 알고 있을 가능성이 높기 때문입니다. 이럴 때 유저와 커널 모드로 실행 흐름을 두 개로 나눠서 설계할 필요가 없을 것입니다.

그런데 시스템이나 커널 개발자가 아닌 리눅스 커널 세부 동작을 잘 모르는 응용 프로그램 개발자가 어플리케이션을 유저 모드와 커널 모드가 없는 운영체제에서 개발한다고 생각해봅시다. 응용 프로그램에서 메모리를 제대로 관리하면 문제가 되지 않지만 실수로 코드를 잘못 작성해서 커널 자료구조나 함수가 위치한 메모리 공간을 오염시키면 어떤 문제가 발생할까요? 커널 패닉 같이 시스템 오동작을 유발할 것입니다.

비슷한 이유로 메모리에 직접 접근 못하는 프로그램 언어가 설계됐고 이를 Managed 언어라고 합니다. 대표적인 예로 자바를 들 수 있습니다. 메모리에 직접 접근 가능한 언어는 대표적으로 C와 C++입니다. 
 
응용 어플리케이션을 개발하고 설치할 수 있는 라즈베리파이 같은 범용 운영체제에서는 0~2G 가상 메모리 공간까지는 유저 모드만 접근할 수 있고 커널 모드에서는 0~4G 메모리까지 접근할 수 있도록 제한을 걸어 둡니다. 

유저와 커널 모드로 메모리 접근 권한을 달리 주는 것은 운영체제 설계 기법 중 하나입니다. 

우리가 실행하는 모든 어플리케이션은 유저 모드와 커널 모드 중 하나 모드에서 동작한다고 했습니다. 그런데 유저 어플리케이션 입장에서 커널에 어떤 서비스를 요청해야 할 때가 있습니다. 유저 모드 어플리케이션에서 커널에 어떤 서비스를 요청할 때는 어떻게 해야 할까요?  파일을 읽고 쓰거나 현재 실행 중인 프로세스 정보를 얻고 싶을 때입니다.

이 때 시스템 콜을 실행하면 유저 모드에서 커널 모드로 전환하며 이 동작을 시스템 콜이라고 합니다. 유저 모드에서 커널 코드를 직접 실행하지 못하고 시스템 콜을 통해서만 커널 모드로 진입한 다음 특정 서비스를 요청하는 것입니다.

커널 입장에서는 유저 모드에서 요청한 서비스에 대해 어떻게 동작을 할까요? 먼저 유저 공간에서 시스템 콜을 실행하면서 전달한 인자에 오류가 있는 지 점검합니다. 유저 모드에서 어플리케이션 개발자가 시스템 콜로 인자를 잘못 전달했는데 커널에서 이 인자를 그대로 읽어서 처리하면 커널은 오동할 가능성이 높기 때문일 일 것입니다.

그래서 시스템 콜이 실행하고 호출되는 시스템 콜 핸들러 함수에서는 예외 처리 루틴이 많습니다.

유저 모드에 대해 조금 더 생각해 봅시다. 유저 모드에 해당하는 파일은 무엇일까요?

다음 파일 경로에 리눅스 시스템 구동에 필요한 라이브러리가 있습니다. 
root@raspberrypi:/home/pi# ls /usr/lib/arm-linux-gnueabihf/
alsa-lib libltdl.so.7
audit libltdl.so.7.3.1
avahi liblwres.so.141
bluetooth liblwres.so.141.0.3
caca liblz4.so.1
cifs-utils liblz4.so.1.7.1
coreutils libm.a
...

이 파일이 유저 모드 코드라 할 수 있습니다. 리눅스에서 실행 중인 유저 어플리케이션은 이 라이브러리와 링킹되어 동작합니다.

이번에는 유저 레벨 프로세스에 대해 생각해봅시다. 유저 레벨 프로세스는 어떻게 생성될까요? 여러분이 라즈베리파이 바탕 화면에 있는 아이콘을 더블 클릭하거나 다음과 같은 코드를 컴파일해서 생성되는 파일을 실행할 때 같은 흐름으로 실행을 시작합니다.

어떤 프로그램이던 main 이란 함수가 있는데 둘 다 main 이 실행한다고 보면 됩니다. 유저 레벨에서 프로세스를 생성할 때 커널 서비스에게 요청을 해야 프로세스 생성이 가능합니다.

문제는 유저 모드에서 혼자 프로세스를 생성하지 못합니다. 리눅스에서 제공하는 라이브러리 도움으로 프로세스 생성 요청이 가능합니다. 이런 역할을 GNU libc가 제공하며 이를 glibc이라고도 합니다.

유저 어플리케이션과 유저 레벨 프로세스는 어떤 의미일까요? 유저 어플리케이션은 라즈베리파이에서 보는 프로그램을 의미하고 유저 레벨 프로세스는 이 어플리케이션을 실행하는 주체를 의미합니다. 커널 입장에서는 유저 어플리케이션이나 유저 레벨 프로세스는 같은 의미입니다.

유저 레벨 프로세스와 커널 레벨 프로세스의 가장 큰 차이점은 무엇일까요? 실행 출발점이 다릅니다.

유저 레벨 프로세스는 유저 모드에서 fork() 나 pthread_create() 함수 호출로 glibc 리눅스 라이브러리 파일 도움으로 커널에 서비스를 요청하면서 실행됩니다.
root@raspberrypi:/home/pi# ls /usr/lib/arm-linux-gnueabihf/libc.a
/usr/lib/arm-linux-gnueabihf/libc.a

커널에 어떤 서비스를 요청할 일이 생기면 시스템 콜을 실행해야 합니다.

하지만 커널 레벨 프로세스는 커널 모드에서 실행합니다. 커널의 kthread_create() 함수 호출로 실행되는 겁니다.


덧글

댓글 입력 영역