반응형
출처 : http://www.ibm.com/developerworks/kr/library/l-user-space-apps/index.html

특정 커널 함수 호출(시스템 호출)은 GNU/Linux에서 애플리케이션 개발의 자연스러운 부분이다. 하지만 사용자 공간을 호출하는 커널 공간을 사용하는 것은 어떨까? 매일 사용할 수 있는 이 기능에 적합한 애플리케이션이 다수 있다는 사실이 밝혀졌다. 예를 들어, 커널에서 모듈을 로드해야 하는 장치를 찾으면 이 프로세스가 어떻게 발생하는가? 사용자 모드 헬퍼 프로세스를 통해 커널에서 동적 모듈 로딩이 발생한다.

사용자 모드 헬퍼, API(Application Programming Interface) 및 커널에서 이 기능이 사용되는 사례에 대한 일부 예제를 살펴보는 것에서부터 시작해 보자. 그런 다음 API를 사용하여 이 기능의 작동 방식 및 제한사항에 대해 더 잘 알 수 있도록 하는 샘플 애플리케이션을 빌드한다.

사용자 모드 헬퍼 API

사용자 모드 헬퍼 API는 잘 알려진 옵션 세트가 포함된 단순 API이다. 예를 들어, 사용자 공간에서 프로세스를 작성하려면 일반적으로 실행 파일의 이름, 실행 파일에 대한 옵션 및 환경 변수 세트를 제공한다(메인 페이지에서 execve 참조). 커널에서 프로세스를 작성하는 경우에도 동일하게 적용된다. 하지만 커널 공간에서 프로세스를 시작하기 때문에 몇 가지 추가적인 옵션을 사용할 수 있다.

커널 버전

이 기사에서는 2.6.27 커널에서 사용자 모드 헬퍼 API를 살펴본다.

표 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에 나열되어 있다.

사용자 모드 헬퍼 API 내부 구조

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에 대한 호출을 통해 호출되며 여기에는 이전에 정의된 pathargv 목록(사용자 공간 애플리케이션 이름) 및 환경이 포함된다. 이 프로세스가 완료되면 스레드가 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.ckmod.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 사용자와 의견을 교환해 보자. 
 
반응형
Posted by Real_G