토요일, 8월 23, 2008

Protected Server Libraries 2 - PSL의 구현

이번에는 PSL의 구현과 구조를 좀더 자세히 설명한 글인데 최근 어느 blog에서 발견해서 감동적으로 읽었다. PSL에 대한 오랜 세월의 의문에 종지부를 찍었으니깐. 사실 Building Powerful Platforms with Windows CE에서 PSL관련 내용을 읽었을 때는 무슨 내용인지 잘 알수 없었는데 이 blog의 글을 읽고서 책의 내용을 이해할 수 있었다. 책에서는 결정적으로 PSL 자체가 server process라는 점을 언급하고 있지 않았기 때문이다. 이 blog에 의하면 user 역시 PSL을 작성하여 빠르고 overhead없이 동작하는 API를 작성할 수 있다. 원본은 긴 영문으로 작성되어 있고 역시 나중에 잊어버렸을 때의 나 자신을 위해, 나의 이해를 돕기 위해 충분히 의역을 했다.

소개

windows ce thread는 API를 실행하는 동안에 process간에 이동할 수 있다. 이것은 calling process(client process)의 thread를 called process(server process)에 위치시키는 것이다. 이러한 server process는 "procetected server libraries"라고 불리우고 이것은 DLL들처럼 service들(API set)을 export한다.

PSL들은 자신의 process address space에다 API set들을 생성하고 이 API set이 여러개의 client들에 의해서 동시에 call될 수 있도록 하기 위해서 사용된다. WIN32 API들과 device driver, DirectX interface들을 포함한 모든 Windows CE system API들은 PSL을 사용하여 구현된다. 각 API set은 PSL entry와 1대1로 mapping이 된다.

Windows CE는 PSL을 구현하기 위해서 processor fault condition을 이용하는데 이 processor fault condition 발생시 processor fault handler가 PSL entry point를 call 되게되는 구조다. System에는 32개 PSL entry로 이루어진 PSL table이 있다. 각 PSL entry는 API set에 해당하며, 이중 0~24까지는 이미 할당되어 있어 custom PSL이 사용할 수 없다. 즉 25~31까지의 7개의 API set은 user가 등록할 수 있다는 소리다. PSL이 RegisterAPISet()을 통해 자신의 API set를 등록하면 이때 PSL 자신속에 구현된 모든 API function들의 function pointer들로 구성된 table을 system에 제공함에 의해서 API set이 PSL table상의 특정 entry에 할당된다. psyscall.h에 정의되어 있는 PRIV_IMPLICIT_DECL()과 같은 macro들은 PSL ID와 index를 사용해서 function pointer로 사용되게 될 invalid memory address를 구성하게 된다. 이 PSL ID와 index를 통해서 PSL table상의 어느 PSL에서 그리고 그 PSL의 function table에서 어느 function이 call되어야 하는지 알 수 있다. 실제로 API가 call될때는 이 invalid address로의 call이 시도되고 이때 processor fault가 발생한다. 그러면 processor fault handler는 fault address를 보고 이것이 그냥 invalid address가 아닌 PSL address라는 것을 알게 되고 그 address에서 PSL ID와 function index를 구해내어 그것을 통해 적당한 PSL의 적당한 entry point를 call하게 된다. 이것은 caller에서 API function으로 direct jump를 한것과 같은 효과를 보이는데, 실제로는 client process address space에서 server process address space로의 jump가 일어난 것이다. PSL function이 종료되어 return할 때, OS는 PSL API function을 call한 application을 resume하며 return값도 넘긴다. 이것은 마치 client process에서 function이 수행을 끝내고 return한 것과 같은 효과를 보인다. PSI는 system에서 API가 구현되는 핵심적인 방법이고 API의 실행을 위한 code path가 아주 간소해진다.

PSL의 구성요소들

  1. PSL상의 function들 (entry point들)
  2. Function table (Vtable) - PSL상의 function pointer들의 array. 처음 두개의 entry는 kernel에 의해서 사용되는데, 첫번째 entry는 notification procedure로 thread나 process가 stop되거나 start 될 때, low memory가 발생할 때 kernel이 call한다. kernel이 call할 수 있도록 PSL에서 구현돼 있어야 한다. 두번째 entry는 kernel에 의해서 예약돼 있어 0값이 할당되어야 한다.
  3. Signature table - function table상의 각 function의 argument 갯수와 type을 정의한다. function table과 1대1로 대응되는 entry를 갖는 array이다. FNSIG0~12 macro를 사용하여 argument의 갯수를 정의하고 (0부터 12개 까지의 argument를 갖는 function) , DW, PTR, I64 macro를 사용하여 각각 32bit integer (unsigned long), pointer (void *), 64bit integer (long long)의 argument type을 정의한다. Signature table은 kernel에 의해서 사용되는데 kernel은 이것을 통해서 process address space간의 PSL function call에 대해 argument들을 정열한다.
  4. API를 call하는 client application과 link되는 library - 이 lib에는 invalid memory address로의 참조 code가 구현되어 있고 이것이 processor fault를 발생시켜 PSL이 실행되도록 한다. 이 code는 header file에서 inline function으로 구현될 수 있다. Custom API를 추가하려면 역시 user가 구현해 주어야 한다.
사용 예

다음은 custom API 추가의 예를 보여준다.

// 1. Functions
// 1.1 Notify function
// kernel에 의해서 사용되는 function table의 첫번째 entry의 구현

VOID
PmAPINotifyProc(DWORD dwEvent, DWORD dwProcessId, DWORD dwThreadId)
{
switch (dwEvent)
{
case DLL_PROCESS_ATTACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_ATTACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_THREAD_ATTACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_THREAD_ATTACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_THREAD_DETACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_THREAD_DETACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_PROCESS_DETACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_DETACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_PROCESS_EXITING:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_EXITING ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_SYSTEM_STARTED:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_SYSTEM_STARTED ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
default:
break;
}
}

// 1.2 Real API
// PSL API function implementationEXTERN_C HANDLE WINAPI
PmSetSystemPowerRequirement(LPCTSTR pszName){
HANDLE hRequirement = NULL;
//...
return hRequirement;
}

// 2. API function table
static VOID PmAPINotifyProc(DWORD dwEvent, DWORD dwProcessId, DWORD dwThreadId);
const PFNVOID VTable[] = {
(PFNVOID)PmAPINotifyProc,
(PFNVOID)NULL,
(PFNVOID)PmSetSystemPowerRequirement,};

// 3. Signatures table
#define NUM_APIS (sizeof(VTable) / sizeof(VTable[0]))
const DWORD SigTable[NUM_APIS] = {
FNSIG3(DW, DW, DW), // PmAPINotifyProc
FNSIG0(), // Kernel reserved entry
FNSIG1(PTR), // Signature of PmSetSystemPowerRequirement
};

// 4. Client APIs (defined in header file)
// Client가 link하는 lib의 구현
#define PM_APISET_ID 25 // APISet ID
#define PM_APISET_NAME "CPM" // APISet Name
#define PmSetSystemPowerRequirement_xxx \
PRIV_IMPLICIT_DECL(HANDLE, PM_APISET_ID, 3, (LPCTSTR psz))

__inline HANDLE
SetSystemPowerRequirement(LPCTSTR pszState) {
ASSERT(QueryAPISetID(PM_APISET_NAME) == PM_APISET_ID);
return IsAPIReady(PM_APISET_ID) ? PmSetSystemPowerRequirement_xxx(pszState) : NULL;
}

// 5. Create and register APISet
// PSL에서의 Initialization - API set을 등록하는 부분
hPmAPISet = CreateAPISet(PM_APISET_NAME, NUM_APIS, VTable, SigTable);
if (ghPmAPISet == NULL) {
PMLOGMSG(ZONE_ERROR, (_T("%s: CreateAPISet() failed: %d\r\n"), pszFname, GetLastError()));
return FALSE;
}
if (!RegisterAPISet(ghPmAPISet, PM_APISET_ID)) {
PMLOGMSG(ZONE_ERROR, (_T("%s: RegisterAPISet() failed: %d\r\n"), pszFname, GetLastError()));
CloseHandle(ghPmAPISet);
return FALSE;
}


Code에 대해서 몇가지 부가 설명하자면
PRIV_IMPLICIT_DECL - PSL ID와 function index가 encode된 invalid function pointer 생성
IsAPIReady - API set이 등록되었는지 여부를 나타낸다. 만일 API set가 등록될 때까지 대기해야 한다면 이것을 주기적으로 call하면서 polling했어야 했는데 CE6.0에서는 이 함수가 지원되지 않는다. 대신 CE6.0에서는 WaitForAPIReady를 사용한다. 이것을 API가 등록될 때까지 대기하는 기능을 가지고 있다.
CreateAPISet - API set 생성
RegisterAPISet - API set 등록

영문원본 출처 - http://bhduan.blogspot.com/2006/11/using-protected-server-libraries-psl.html

댓글 없음: