커널에서 사용자 공간 어플리케이션 실행하기 (Run application in user space from the kernel module)
특정 커널 함수 호출(시스템 호출)은 GNU/Linux에서 애플리케이션 개발의 자연스러운 부분이다. 하지만 사용자 공간을 호출하는 커널 공간을 사용하는 것은 어떨까? 매일 사용할 수 있는 이 기능에 적합한 애플리케이션이 다수 있다는 사실이 밝혀졌다. 예를 들어, 커널에서 모듈을 로드해야 하는 장치를 찾으면 이 프로세스가 어떻게 발생하는가? 사용자 모드 헬퍼 프로세스를 통해 커널에서 동적 모듈 로딩이 발생한다.
사용자 모드 헬퍼, API(Application Programming Interface) 및 커널에서 이 기능이 사용되는 사례에 대한 일부 예제를 살펴보는 것에서부터 시작해 보자. 그런 다음 API를 사용하여 이 기능의 작동 방식 및 제한사항에 대해 더 잘 알 수 있도록 하는 샘플 애플리케이션을 빌드한다.
사용자 모드 헬퍼 API는 잘 알려진 옵션 세트가 포함된 단순 API이다. 예를 들어, 사용자 공간에서 프로세스를 작성하려면 일반적으로 실행 파일의 이름, 실행 파일에 대한 옵션 및 환경 변수 세트를 제공한다(메인 페이지에서 execve
참조). 커널에서 프로세스를 작성하는 경우에도 동일하게 적용된다. 하지만 커널 공간에서 프로세스를 시작하기 때문에 몇 가지 추가적인 옵션을 사용할 수 있다.
표 1에서는 사용자 모드 헬퍼 API에서 사용할 수 있는 핵심 커널 함수 세트를 보여준다.
표 1. 사용자 모드 헬퍼 API의 핵심 함수
API 함수 | 설명 |
---|---|
call_usermodehelper_setup |
사용자 지역 호출을 위한 핸들러 준비 |
call_usermodehelper_setkeys |
헬퍼의 세션 키 설정 |
call_usermodehelper_setcleanup |
헬퍼의 정리 함수 설정 |
call_usermodehelper_stdinpipe |
헬퍼의 stdin 파이프 작성 |
call_usermodehelper_exec |
사용자 지역 호출 호출 |
또한 이 표에는 표 2에서 커널 함수를 캡슐화하는 몇 가지 단순화 함수가 있다(복수의 호출 대신 단일 호출이 필요함). 이러한 단순화 함수는 대부분의 경우에 유용하므로 가능하면 이러한 함수를 사용하는 것이 좋다.
표 2. 사용자 모드 헬퍼 API의 단순화
API 함수 | 설명 |
---|---|
call_usermodehelper |
사용자 지역 호출 작성 |
call_usermodehelper_pipe |
stdin 파이프를 사용하여 사용자 지역 호출 작성 |
call_usermodehelper_keys |
세션 키를 사용하여 사용자 지역 호출 작성 |
먼저 핵심 함수에 대해 살펴본 후 단순화 함수에서 제공하는 기능에 대해 알아본다. 핵심 API는 subprocess_info
구조라는 핸들러 참조를 사용하여 작동한다. ./kernel/kmod.c에서 찾을 수 있는 이 구조는 지정된 사용자 모드 헬퍼 인스턴스에 필요한 모든 요소를 집계한다. 이 구조 참조는 call_usermodehelper_setup
에 대한 호출에서 리턴된다. 구조(및 후속 호출)는 call_usermodehelper_setkeys
(신임 저장용), call_usermodehelper_setcleanup
및 call_usermodehelper_stdinpipe
에 대한 호출을 통해 추가로 구성된다. 마지막으로 구성이 완료되면 call_usermodehelper_exec
에 대한 호출을 통해 구성된 사용자 모드 애플리케이션을 호출할 수 있다.
핵심 함수는 제어의 상당 부분을 제공하며 여기서는 헬퍼 함수가 단일 호출로 더 많은 작업을 수행한다. 파이프 관련 호출(call_usermodehelper_stdinpipe
및 헬퍼 함수 call_usermodehelper_pipe
)은 헬퍼가 사용할 연관된 파이프를 작성한다. 구체적으로 파이프가 작성된다(커널에 있는 파일 구조). 파이프는 사용자 공간 애플리케이션이 읽을 수 있고 커널측에서 쓸 수 있다. 이러한 쓰기를 수행할 때 사용자 모드 헬퍼와 함께 파이프를 사용할 수 있는 유일한 애플리케이션은 코어 덤프이다. 이 애플리케이션(./fs/exec.c do_coredump()
)에서 코어 덤프는 파이프를 통해 커널 공간으로부터 사용자 공간으로 작성된다.
이러한 함수와 sub_processinfo
사이의 관계는 subprocess_info
구조에 대한 세부 사항과 함께 그림 1에 설명되어 있다.
그림 1. 사용자 모드 헬퍼 API 관계
표 2에 있는 단순화 함수는 call_usermodehelper_setup
함수 및 call_usermodehelper_exec
함수를 내부적으로 수행한다. 표 2에서 마지막 두 호출은 call_usermodehelper_setkeys
및 call_usermodehelper_stdinpipe
를 각각 호출한다. call_usermodehelper_pipe
에 대한 소스는 ./kernel/kmod.c에서 찾을 수 있고 call_usermodehelper
및 call_usermodhelper_keys
에 대한 소스는 ./include/linux/kmod.h에서 찾을 수 있다.
커널에서 사용자 공간 애플리케이션을 호출하는 이유는 무엇일까?
이제 커널에서 사용자 모드 헬퍼 API를 사용할 일부 위치에 대해 살펴보자. 표 3은 유일한 애플리케이션 목록을 제공하지는 않지만 흥미로운 사용의 단면을 보여준다.
표 3. 커널에서 사용자 모드 헬퍼 API 애플리케이션
애플리케이션 | 소스 위치 |
---|---|
커널 모듈 로딩 | ./kernel/kmod.c |
전원 관리 | ./kernel/sys.c |
제어 그룹 | ./kernel/cgroup.c |
보안 키 생성 | ./security/keys/request_key.c |
커널 이벤트 전달 | ./lib/kobject_uevent.c |
사용자 모드 헬퍼 API의 가장 간단한 애플리케이션 중 하나는 커널 공간에서 커널 모듈을 로드하는 것이다. request_module
함수는 사용자 모드 헬퍼 API의 기능을 캡슐화하고 단순한 인터페이스를 제공한다. 일반적인 사용 모델에서는 커널이 장치 또는 필요한 서비스를 식별하고 request_module
에 대한 호출을 작성하여 모듈을 로드한다. 사용자 모드 헬퍼 API를 통해 모듈이 modprobe
를 통해 커널에 로드된다(request_module
을 통해 사용자 공간에서 호출된 애플리케이션).
모듈 로딩과 비슷한 애플리케이션은 장치 핫 플러깅을 사용하여 런타임 시 장치를 추가 또는 제거하는 것이다. 이 기능은 사용자 모드 헬퍼 API를 통해 사용자 공간에서 /sbin/hotplug 유틸리티를 호출하여 구현된다.
request_module
을 통한 사용자 모드 헬퍼 API의 흥미로운 애플리케이션은 텍스트 검색 API(./lib/textsearch.c)이다. 이 애플리케이션에서는 커널에서 구성 가능한 텍스트 검색 인프라를 제공한다. 이 애플리케이션에서는 검색 알고리즘을 로드 가능한 모듈로 동적으로 로드하여 사용자 모드 헬퍼 API를 사용한다. 2.6.30 커널 릴리스에서는 Boyer-Moore(./lib/ts_bm.c), 순수 유한 상태 기계 접근 방식(./lib/ts_fsm.c) 및 Knuth-Morris-Pratt 알고리즘(./lib/ts_kmp.c)이라는 세 가지 알고리즘이 지원된다.
사용자 모드 헬퍼 API도 순차적인 시스템 종료 시 Linux를 지원한다. 시스템 종료가 필요한 경우 커널은 사용자 공간에서 /sbin/poweroff 명령을 호출하여 시스템을 종료한다. 기타 애플리케이션은 소스 위치와 함께 표 3에 나열되어 있다.
kernel/kmod.c에서 사용자 모드 헬퍼 API에 대한 API 및 소스를 찾는다(기본적으로 커널 공간 커널 모듈 로더로 사용됨을 보여줌). 구현에서는 kernel_execve
를 사용하여 잡다한 작업을 처리한다. kernel_execve
는 부팅 시 init
프로세스를 시작하는 데 사용되는 함수이며 사용자 모드 헬퍼 API는 사용하지 않는다.
사용자 모드 헬퍼 API의 구현은 매우 단순하고 간단하다(그림 2 참조). 사용자 모드 헬퍼의 작업은 사전 구성된 subprocess_info
구조에서 사용자 공간 애플리케이션을 시작하는 데 사용되는 call_usermodehelper_exec
에 대한 호출로 시작한다. 이 함수는 subprocess_info
구조 참조와 열거 유형(기다리지 않을 것인지, 프로세스가 시작되기를 기다릴 것인지 아니면 전체 프로세스가 완료되길 기다릴 것인지)이라는 두 가지 인수를 승인한다. 그러면 호출을 비동기로 수행하는 작업 구조(khelper_wq
)로 subprocess_info
(또는 이 구조의work_struct
요소)가 큐에 삽입된다.
그림 2. 사용자 모드 헬퍼 API의 내부 구현
요소가 khelper_wq
에 배치되면 작업 큐에 대한 핸들러 함수(이 경우에는 __call_usermodehelper
)가 호출되어 khelper
스레드를 통해 실행된다. 이 함수는 사용자 공간 호출에 필요한 모든 정보가 포함된 subprocess_info
구조를 큐에서 제거하여 시작된다. 다음 경로는wait
변수 열거에 따라 다르다. 요청자가 전체 프로세스가 완료될 때까지 기다리길 원하는 경우(사용자 공간 호출(UMH_WAIT_PROC
) 또는 전혀 기다리지 않음(UMH_NO_WAIT
) 포함) 커널 스레드가 wait_for_helper
함수에서 작성된다. 그렇지 않으면 요청자는 단순히 사용자 공간 애플리케이션이 호출될 때까지 기다리고(UMH_WAIT_EXEC
) 완료될 때까지는 기다리지 않으려고 한다. 이 경우 커널 스레드는 ____call_usermodehelper()
를 위해 작성된다.
wait_for_helper
스레드에서는 SIGCHLD 신호 핸들러가 설치되고 다른 커널 스레드가 ____call_usermodehelper
를 위해 작성된다.wait_for_helper
스레드에서는 ____call_usermodehelper
커널 스레드(SIGCHLD 신호에 의해 표시됨)의 종료를 대기하는 데 필요한sys_wait4
에 대한 호출이 작성된다. 그런 다음 스레드는 필요한 정리 작업을 수행한다(UMH_NO_WAIT
에 대한 구조를 비우거나 단순히 완료 알림을 다시 call_usermodehelper_exec()
에 전송함).
____call_usermodehelper
함수는 사용자 공간에서 애플리케이션을 시작하는 실제 작업이 발생하는 위치이다. 이 함수는 모든 신호를 차단 해제하고 세션 키 링을 설정하여 시작된다. 또한 이 함수는 stdin
파이프를 설치한다(요청된 경우). 초기화를 좀 더 수행하면 사용자 공간 애플리케이션이 kernel/syscall.c의 kernel_execve
에 대한 호출을 통해 호출되며 여기에는 이전에 정의된 path
, argv
목록(사용자 공간 애플리케이션 이름) 및 환경이 포함된다. 이 프로세스가 완료되면 스레드가 do_exit()
에 대한 호출을 통해 존재한다.
또한 이 프로세스에서는 세마포어와 비슷한 조작인 Linux 완료를 사용한다. call_usermodehelper_exec
함수가 호출되면 완료가 선언된다. subprocess_info
구조가 khelper_wq
에 배치되면 완료 변수를 유일한 인수로 사용하여 wait_for_completion
에 대한 호출이 작성된다. 이 변수는 subprocess_info
구조에 complete
필드로도 저장되어 있다. 하위 스레드에서 call_usermodehelper_exec
함수를 호출하려는 경우에는 subprocess_info
구조의 완료 변수를 나타내는 커널 메소드 complete
를 호출한다. 이 호출은 함수가 계속 수행될 수 있도록 함수를 잠금 해제한다. 이 API의 구현은 include/linux/completion.h에서 찾을 수 있다.
참고자료 섹션에 있는 링크를 따라 사용자 모드 헬퍼 API에 대한 자세한 정보를 찾을 수 있다.
이제 사용자 모드 헬퍼 API의 단순한 사용에 대해 살펴보자. 먼저 표준 API에 대해 살펴본 후 헬퍼 함수를 사용하여 항목을 단순화하는 방법에 대해 알아본다.
이 데모에서는 API를 호출하는 로드 가능한 단순 커널 모듈을 개발한다. Listing 1에는 모듈 입력 및 종료 함수를 정의하는 상용구 모듈 함수가 제공된다. 이러한 두 함수는 모듈의 modprobe
또는 insmod
(모듈 입력 함수)와 모듈의 rmmod
(모듈 종료)에서 호출된다.
Listing 1. 모듈 상용구 함수
#include <linux/module.h> #include <linux/init.h> #include <linux/kmod.h> MODULE_LICENSE( "GPL" ); static int __init mod_entry_func( void ) { return umh_test(); } static void __exit mod_exit_func( void ) { return; } module_init( mod_entry_func ); module_exit( mod_exit_func ); |
사용자 모드 헬퍼 API의 사용이 Listing 2에 표시되며 이 Listing을 자세하게 살펴본다. 함수는 다양한 필요한 변수 및 구조의 선언으로 시작된다. 사용자 공간 호출을 수행하는 데 필요한 모든 정보가 포함된 subprocess_info
구조로 시작한다. call_usermodehelper_setup
을 호출하면 이 호출이 초기화된다. 그런 다음 argv
라는 인수 목록을 정의한다. 이 목록은 일반적인 C
프로그램에 사용된 argv
목록과 비슷하며 애플리케이션(배열의 첫 번째 인수) 및 인수 목록을 정의한다. 목록의 끝을 표시하려면 NULL 터미네이터가 필요하다. 여기서argv
목록의 길이는 알려져 있기 때문에 argc
변수(인수 수)는 내재적이라는 것에 주의한다. 이 예제에서 애플리케이션 이름은 /usr/bin/logger이며 인수는 help!
이고 NULL로 종료된다. 다음으로 필요한 변수는 환경 배열(envp
)이다. 이 배열은 사용자 공간 애플리케이션의 실행 환경을 정의하는 매개변수의 목록이다. 이 예제에서는 쉘에 대해 정의된 몇 가지 일반적인 매개변수를 정의하고 NULL 항목으로 종료한다.
Listing 2. 단순 usermode_helper API 테스트
static int umh_test( void ) { struct subprocess_info *sub_info; char *argv[] = { "/usr/bin/logger", "help!", NULL }; static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL }; sub_info = call_usermodehelper_setup( argv[0], argv, envp, GFP_ATOMIC ); if (sub_info == NULL) return -ENOMEM; return call_usermodehelper_exec( sub_info, UMH_WAIT_PROC ); } |
다음으로 call_usermodehelper_setup
에 대한 호출을 작성하여 초기화된 subprocess_info
구조를 작성한다. 이전에 초기화된 변수를 메모리 초기화에 필요한 GFP 마스크를 표시하는 네 번째 매개변수와 함께 사용한다. 설정 함수에 대해 내부적으로는 커널 메모리를 할당하고 0으로 설정하는 kzalloc
에 대한 호출이 있다. 이 함수에는 GFP_ATOMIC
또는 GFP_KERNEL
플래그(전자는 호출 대기 상태가 안 되도록 정의하고 후자는 대기 상태가 가능하도록 정의함)가 필요하다. 새 구조의 빠른 테스트 수행 후(즉, NULL이 아님)call_usermodehelper_exec
함수를 사용하여 호출을 계속 작성한다. 이 함수에서는 subprocess_info
구조 및 열거를 사용하여 대기 여부를 정의한다(내부 구조 섹션에 설명되어 있음). 이렇게만 하면 된다. 모듈이 로드되면 /var/log/messages 파일에 메시지가 표시된다.
call_usermodehelper_setup
및 call_usermodehelper_exec
함수를 함께 수행하는 call_usermodehelper
API 함수를 사용하여 이 프로세스를 더 단순화할 수 있다. Listing 3에 표시된 것처럼 함수만 제거되는 것이 아니라 호출자가 subprocess_info
구조를 관리해야 할 필요성도 없어진다.
Listing 3. 더 단순한 사용자 모드 헬퍼 API 테스트
static int umh_test( void ) { char *argv[] = { "/usr/bin/logger", "help!", NULL }; static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL }; return call_usermodehelper( argv[0], argv, envp, UMH_WAIT_PROC ); } |
Listing 3에서는 호출 설정과 작성에 대해 동일한 요구사항이 존재한다(예: argv
및 envp
배열 초기화). 여기서 유일한 차이점은 헬퍼 함수는 setup
및 exec
함수를 수행한다는 점이다.
사용자 모드 헬퍼 API는 커널 모듈 로딩, 장치 핫 플러깅 및 udev에 대한 이벤트 분배에 이르기까지 광범위하고 다양한 용도로 사용된다는 점에서 커널에 중요한 의미를 가진다. API의 순수 애플리케이션을 유효성 검증하는 것이 중요하긴 해도 이는 알고 있어야 할 커널의 중요한 특징이기 때문에 Linux 커널 툴킷에 추가하면 유용하다.
교육
- 사용자 모드 헬퍼 API에 대한 정보는 거의 존재하지 않지만 구현은 매우 깔끔하고 단순하게 수행된다. 모든 소스 개정에 대한 소스 브라우저인 LXR(Linux Cross Referencer)을 통해 구현을 검토할 수 있다. 유용한 두 가지 기본 파일은 kmod.c와kmod.h이다.
- /proc 파일 시스템은 커널과 사용자 공간 사이의 통신에 필요한 메소드를 제공한다(즉, 가상 파일 시스템을 통함). "/proc 파일 시스템을 활용한 리눅스 커널 접근"(developerworks, 2006년 3월)에서 /proc 파일 시스템에 대해 자세히 알아볼 수 있다.
- Linux 시스템 호출 인터페이스에서는 사용자 공간 애플리케이션이 커널 기능을 호출하는 방법을 제공한다. 새 시스템 호출을 추가하는 방법을 포함한 Linux 시스템 호출에 대한 자세한 정보는 "리눅스 시스템 호출을 활용한 커널 명령"(developerworks, 2007년 3월)을 참조한다.
- 사용자 모드 헬퍼 API에 대해 설명하기 위해 이 기사에서는 로드 가능한 커널 모듈을 사용하여 테스트 애플리케이션을 커널에 설치한다. 로드 가능한 커널 모듈과 그 구현에 대한 자세한 정보는 "리눅스 커널 동적 적재 모듈 분석"(developerworks, 2008년 7월)을 참조한다.
- 2.6 커널 작업 큐 인터페이스에 대한 자세한 정보는 커널 작업 큐의 조작 및 API에 대해 적절하게 소개하는 2003년부터의 Linux Journal 기사를 참조한다.
- developerWorks 리눅스 영역에서는 리눅스 개발자에게 도움이 되는 여러 가지 리소스를 제공하고 있다.
- developerWorks 기술 행사 및 웹 캐스트를 통해 최신 정보를 얻을 수 있다.
- Twitter의 developerWorks 페이지를 살펴보자.
제품 및 기술 얻기
- developerWorks에서 직접 다운로드할 수 있는 IBM 시험판 소프트웨어를 사용하여 Linux와 관련된 후속 개발 프로젝트를 구현해 볼 수 있다.
토론
- developerWorks community에 참여하자. 개발자 중심 블로그, 포럼, 그룹 및 Wiki 검색 중에 다른 developerWorks 사용자와 의견을 교환해 보자.
'Linux > Linux Kernel' 카테고리의 다른 글
(예제) 커널에서 사용자 공간 어플리케이션 실행하기 샘플코드 (0) | 2012.02.28 |
---|---|
<<<< kernel 컴파일, 커널 빌드할때 에러나면 이렇게 해 >>>> (0) | 2011.07.15 |
리눅스 커널 2.6.30 릴리스, request_threaded_irq (0) | 2011.03.09 |