토요일, 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

Protected Server Libraries - 기본 개념

protected server libraries 즉 PSL이란 것. 가끔 wince 관련 기술문서들에서 그것도 아주 아주 가끔 등장하지만 정작 그게 뭔지 제대로 설명하는 글은 거의 찾을 수 없다. 단지 'thread가 process들간을 이동(migrate)한다'는 식의 별로 도움 안되는 짧은 설명들만 있을 뿐이다. 사실 알 필요가 없어서 이기도 하겠지만 그렇다면야 아예 언급을 하지 말던가. 언급은 해 놓고 설명을 안해주는 것은 대체 무슨 심보인가? 그나마 약간이라도 설명하고 있는 책이 있었으니 'Building Powerful Platform with Windows CE'에서다. PSL을 설명하는 부분의 내용을 이해하기 쉽도록 옮겨본다. 밝혀두지만 직역이 아니고 이해 하기 쉽도록 충분히 의역해서 원본과는 상당히 다른 글이 되었있다. 심지어 원본에 없는 부가 설명도 추가했고 원본의 설명이 틀렸다고 생각되는 부분은 내 생각으로 고쳐서 썼다. 원래 이 책은 친절한 책이 못되고 그래서 읽기도 무척 괴롭다.

Slot memory 구조때문에 process들의 memory space가 서로 구분/분리 되어 있는데, 한 process에 의해서 할당된 buffer가 다른 process에 의해서 어떻게 access될 수 있을까? 이것이 가능한 것도 바로 slot구조이기 때문이다. Slot구조에서는 모든 process들이 2GB 공간에서 각자의 서로 다른 공간을 할당 받은 상태이고, kernel은 이 2GB영역을 모두 access할 수 있으므로 kernel에 의한 translation에 의해서 이것이 가능해 진다.

하나의 process가 다른 process에 있는 function을 call한다고 할때 calling process를 client process, called process를 server process라고 하기로 하자.

Client process가 server process의 function을 API로써 call한다면, 그 API를 수행하는 thread는 server process의 context에서 실행 된다. 당연한 말인데 API를 수행하는 thread는 자신이 속한 process memory space에서 동작하는 것이다. 그런데 이것은 process간에 crash를 유발할 수 있다. 왜냐하면 API를 수행하는 server process의 thread는 client process memory space의 API parameter를 가지고 server process의 context에서 작업을 수행해야 하는데, 알다시피 Windows CE slot memory 구조에서는 process간의 memory space는 구분되어 있다.

이것을 해결하기 위한 일반적인 방법은 역시 위에서 언급한 것 처럼 2GB영역을 모두 볼 수 있는 kernel의 개입에 의해서 인데, client process에 의한 API call을 kernel이 trap한 후 API parameter를 server process의 API를 수행하는 thread로 copy하여 넘기고, server process가 API수행을 완료하면, 다시 kernel이 결과를 client process에게 copy해서 넘긴다.
그러나 이 방법에는 overhead가 있는데 client process가 API call을 하려면 server process에서 API function을 수행하는 thread가 있어야 하기 때문이다. thread가 있어야 한다는 것은 이것을 위한 stack과 memory가 있어야 한다는 말인데, resource가 제약된 embedded system에서는 이런 resource 소모는 최소화 되어야 좋다. 이런 overhead를 없애기 위한 것이 바로 Protected Server Libraries다. PSL 자체가 overhead를 없애기 위한 방법이 적용된 server process다. PSL은 API set를 구현하고 이것을 kernel에 등록 시킨다. 그럼 kernel은 그 API function의 signature를 기억해 두었다가, 이전과 같이 client process에서 PSL의 API를 call하면, kernel은 client process에서 발생된 API call을 trap한 후, client process에서 API call을 발생시킨 thread를 PSL의 process space에서 run하도록 만든다. 이렇게 해서 PSL에서는 API call을 수행하기 위해서 thread를 따로 생성해야 하는 overhead를 제거할 수 있다.

Client process의 thread가 PSL로 진입할때, 이 thread는 PSL과 PSL address space로의 access권한을 자동으로 부여받게 되어 PSL 내에 존재하는 다른 thread들 처럼 동작할 수 있다. Thread가 PSL을 떠나게 되면 이 access권한도 제거된다. 모든 Slot내의 매 64Kb memory block 마다 read/write등의 여러가지 access 허가상태가 설정되어 있다. 이 slot내 memory block을 access하여 어떤 동작을 하려 하면, kernel은 그 동작을 하려는 thread가 그 memory block에 대해 올바른 access권한이 있는지 check한다. 그래서 만일 PSL이 아닌 다른 어떤 process내에서 동작중인 thread가 PSL의 memory를 modifiy하는 등의 동작을 하려하면 거부된다.

일요일, 8월 17, 2008

나의 인생 나의 기타

한달전쯤인가..
KBS 2 TV에서 토요 프리미어 극장인가 하는 토요명화 후속 영화 프로그램에서 '나의 인생 나의 기타'라는 영화를 봤다. 원제는 'the guitar'였는데.. 나에게는 매우 뜻 깊은 영화였다.
인터넷 영화평은 지루하다, 별 내용 없다 는 등의 말들이 많았지만, 기타리스트 혹은 뮤지션이 되는게 인생의 꿈이였고 아직도 내 장래 희망은 Rock'n Roll star 라고 이야기 하는 나에게 이 영화는 내가 전기기타에 대해 품고있던 환상을 다시금 일깨워 주었다.
영화 내용은 여자 주인공이 말기 암이란 의사의 진단을 듣고 얼마 안남은 인생에서 하고 싶던것들을 해 보고 살겠다며 고급 아파트를 임대해 마음대로 물건을 사고, 관계를 가지며 살아가게 된다.
그 중에 하나가 기타를 구입하서 배우는 것이다. 어렸을 적의 회상장면에서 턴테이블로 음악을 듣던 장면, 악기가게에 진열된 전기기타를 가지고 싶어하는 장면, 기타를 훔쳐 달아나다가 주인에게 잡혀 혼나는 장면들이 나오고, 여자는 죽기전에 해보고 싶은것들 중 하나로써 기타를 구입한다. 여자는 기타 뿐만 아니라 신용카드를 온갖가지의 것들을 사 들이는데, 어차피 죽을 것이니 지불계획같은 것은 신경쓰지 않는다.
아무튼 그렇게 시간 가는지 모르고 지내다 보니 의사가 선고한 죽어야 될때가 이미 지났는데도 아직 죽지 않고 있다는 것을 깨닫게 된다. 다시 찾아간 병원에서 의사가 말하길 너무 스스로를 많이 바꿔서 암이 환자를 인식하지 못해 사라졌다고 한다. 여자가 하고 싶은 것들을 하며 사는게 너무 큰 일탈 이었고, 이것때문에 사람이 너무 바뀌어 암세포가 '이것은 내가 살던 사람의 몸이 아니네' 하고 사라졌다는 것이다. 하고 싶은것을 하고 산다는 게 임박한 죽음조차 못 알아볼 정도로 나를 완전히 다른사람 처럼 인식되게 한다는 생각을 하니 우리의 인생이란게 참 불쌍하다는 생각이 들었다. 원래 이렇게 살라고 주어진 인생이 아니었을 텐데 우린 뭘 이루겠다고 이렇게 힘겹게 살아가고 있는 것인지...
여자는 암에서 완치 됐지만 신용카드를 너무 막 쓴 탓에 카드 빚 갚느라 기타만 남기고 그간 무분별하게 샀던 물건들을 팔아치웠고, 결국 노숙자 신세가 된다. 공원에서 푼돈이나마 벌기위해 기타를 치다가 어느 밴드에게 그녀의 기타실력이 주목을 받게 되고... 마지막 장면은 우리나라로 치면 홍대 클럽같은 곳에서 그 어느 밴드의 기타리스트로써 음악을 연주하는 장면으로 보여지면서 영화는 끝을 맺는다.
여자는 암을 계기로 자신을 바꿨고 그 댓가로 자신의 모든것도 잃게 된다. 하지만 그러면 어떠한가. 대신 정말 원하던 꿈을 이루게 되는데.
나도 내 오랜 꿈의 한 발자국을 내 딛었다.
나도 드디어 기타를 샀다.
초보자용으로 별로 대단한 제품은 아니다. 인터넷에서 초저가 초보자용 모델을 구입햇다.
제품명을 Dame사의 Saint T250 DBS(M).
기타 스탠드와 연습용 앰프, 기타 tunner등이 포함된 package로 된 상품이다.
취미겠지만 진지하게 해 볼 생각이다.
언니네 이발관이 내게 용기를 준 이후 10년이 넘게 지났다.

월요일, 8월 11, 2008

Windows CE debugging tips

KITL을 사용할 수 없는 상황에서 debugging을 해야 하는 처지에 놓였을 때 예전에 Microsoft blog에서 ninja debugging skill이라고 묘사되었던 글들이 생각났다. 읽은지 2년정도 지난것 같다. 하지만 늘 그렇듯이 하나도 기억 안난다.
그 blog는 아직 남아 있지만 온통 장문의 영어로 된 글들이라 도대체 다시 읽을 엄두가 나지 않았다.
이번에 다시 한번 극기하며 공부하고 나중을 위해서 요점을 이곳에 정리해 두어야 겠다는 생각이 들었다.
사실 내 blog의 애초 주요 목적도 그것이었다.
많은 것을 매일 매일 배워나가지만 어느 하나 지속적으로 오래 사용하지 않으니 금방 잊어버리고, 다시 필요한 일이 생기면 처음 시작하는 마음으로 다시 공부해야 한다. 공부를 막 마쳤을 때 생생하게 이해하고 있는 내용을 쉽게 풀어 기록해 놓으면 다음에 잊어버렸다가 다시 필요해 질때 처음부터 다시 공부해야 하는 수고를 좀 덜지 않을까 하는 생각이 들었다. 생각해 보면 대부분의 쓸모있는 전문 정보들는 거의 영문으로 되어있다. 웹상에 한글 정보들은 대부분 놀고 먹고 즐기는 내용들뿐 정말 필요한 전문적 지식들은 거의 찾아 볼 수가 없다. 한국 인터넷 문화의 부끄러운 단면이라고나 할까?

목요일, 8월 07, 2008

WINCEOEM, _OEMINCPATH, _ISVINCPATH

WINCEOEM이 set이면 _OEMINCPATH가 INCLUDEPATH에 포함된다.
WINCEOEM이 not set이면 _ISVINCPATH가 INCLUDEPATH에 포함된다.

_OEMINCPATH와 _ISVINCPATH의 내용은 sources.cmn에서 정의되어 있고, WINCEOEM의 설정에 따른 INCLUDEPATH의 설정 변화는 makefile.def에서 정의되어 있다.

즉 WINCEOEM=1이면 해당 build는 OEM (original equipment manufacturer)에 의해서 사용된다는 것이고 그렇지 않으면 ISV(independent software vendor)에 의해서 사용된다는 것이다.

보통 _OEMINCPATH는 $(_WINCEROOT)\public\common\oak\inc;$(_WINCEROOT)\public\common\sdk\inc;$(_WINCEROOT)\public\common\ddk\inc;
_ISVINCPATH는 $(_WINCEROOT)\public\common\sdk\inc;
로 정의된다.

딱 보면 알겠지만 _ISVINCPATH는 이미 만들어진 target hardware와 NK.bin, SDK만을 받은 상태에서 application만을 개발하는 사람들을 위한 것이고, _OEMINCPATH는 application뿐만 아니라 driver, OAL, bootloader등의 system level program까지 개발하는 사람들을 위한 것이다.
original equipment manufacturer나 independent software vendor가 각각 그런 사람들을 뜻한다고 볼 수 있다.