2009년 10월 30일 금요일

윈도우7 vmware 네트워크 설정 NAT

Windows 7  이 Host OS 이고 VMware에서 기타 OS가 Guest 이면 정상적인 방법으로 NAT를 사용하실 수는 없습니다.

Windows 7 기능 중 ICS(Internet Connection Sharing) 기능을 이용하는 방법입니다.

제가 build 7068(?)인걸로 알고 있는데 이것을 이용하면 Virtual Network이 192.168.137.XXX으로 설정되게 됩니다.


1. Run the Virtual Network Editor as Administrator (Use RunAS or right click and Run as Administrator on vmnetcfg.exe)

-- Virtual Network Editor를 Administrator 모드로 시작합니다. (vmnetcfg.exe를 마우스 오른쪽 클릭하시고 Run as Administrator 를 클릭하세요)


2. Goto Host Virtual Adapters and remove all VMNet instances (VMNet1 and VMNet8 typically).

-- Host Virtual Adapters를 클릭하고 VMNet 으로 등록된 모든 어뎁터를 지웁니다. (보통 VMNet1 과 VMNet8 )


3. Click Apply.

--  적용(Apply) 버튼을 누릅니다.


4. Add New and Assign it the new adapter to VMnet1.

-- Add New 버튼을 누르고 Assign 버튼을 눌러서 VMNet1을 새로운 어뎁터로 설정합니다.


5. Click Apply.

-- 적용(Apply) 버튼을 누릅니다.


6. Select the Host Virtual Networking tab.

-- Host Virtual Networking 탭을 누르세요.


7. Click the > next to VMnet1 and change the address and subnet to the ICS network (192.168.0.0 / 255.255.255.0)

--  VMNet1 옆에 있는 ">" 버튼을 누른다음 subnet IP를 192.168.137.0/255.255.255.0 로 설정합니다.

역주) 192.168.0.0 은 Beta Version 중 build 7000 이하로 알고 있습니다. 제 버전은 7068(?) 인가 인데 192.168.137.0 을 ICS에서 사용하고 있습니다.

확인 하는 방법은

Control Panel\Network and Internet\Network and Sharing Center 로 이동하시고

Local Area Connection 을 클릭 -> Properties를 클릭 -> Sharing 탭 클릭 하신 후 첫번째 항목 클릭하시고

Network Adaptor를 하나 클릭하시면 경고 문구가 뜨면서 해당 IP 대역을 사용해야 한다고 알려줍니다.


8. Click Apply.

-- 적용(Apply) 버튼을 누릅니다.


9. Go to the NAT tab and select VMNet1.

-- NAT tab을 누르시고 VMNet1을 선택합니다.


10. Click Edit and change the Gateway to the ICS gateway IP (192.168.0.1)

-- Edit를 클릭하시고 Gateway IP를 192.168.137.1 로 변경합니다.

역주) VMWare는 Gateway IP를 192.168.XXX.2로 사용하는 것을 권고하지만 Windows의 ICS 를 이용하려면 192.168.XXX.1로 하여야 합니다.


11. Click Apply and restart the NAT service. (Counter-intuitive, I know.)

-- NAT 서비스 Restart 버튼을 누르고 적용버튼을 누릅니다.


12. Go to the DHCP tab.

-- DHCP 탭으로 이동합니다.


13. Add VMNet1 and remove all others.

-- VMNet1 을 추가하고 나머지는 모두 지웁니다.


14. Click Apply.

-- 적용 버튼을 누릅니다.


15. Select Properties of VMNet1 in DHCP Tab.

-- DHCP 탭 내에서 VMNet1의 properties 를 선택합니다.


16. Enter a Start and End Address for DHCP Scope (192.168.0.50 to 192.168.0.75 as an example)

-- DHCP 할당 IP 대역을 Start와 End Address에 각각 입력합니다. (기본 값으로 두셔도 상관 없습니다.)


17. Adjust client lease to a few days to avoid potential DHCP client renewal timeouts.

-- Renewal time을 되도록 길게 해서 DHCP IP가 Release되지 않도록 합니다. (저의 경우 한달로 해놨습니다.)


18. Click OK / Click Apply.

-- OK 버튼을 누르고 Apply 버튼을 누릅니다.


19. Select Host Only Networking for every VM that needs NAT out to your host network/internet.

-- VMWare의 모든 Guest OS의 Network Setting을 Host Only Networking으로 변경합니다.

역주) 물론 Guest OS는 DHCP 모드 로 되어야 하겠죠. Static IP도 사용 가능 할 것 으로 보입니다 만 실험해보진 않았습니다.


20. Enable ICS (Internet Connection Sharing) on the W7 Host network card that provides connectivity. Select VMNet1 as the network card that needs access.
-- Windows 7 에서 (7) 번에서 설명드린 것처럼 ICS를 활성화 하시고 Network Adopter를 VMNet1을 선택하시면 모든 셋팅이 끝납니다.

2009년 10월 29일 목요일

C/C++ volatile 키워드


2006년 9월 마이크로소프트웨어 기고글입니다.


약 60여개의 C++ 키워드 중에 가장 사용 빈도가 낮은 키워드는 무엇일까? 정답은 volatile이다. 대부분의 C/C++ 참고 서적은 1-2줄 정도로 volatile이 컴파일러의 최적화(optimization) 막아준다고만 설명하고 있다. 또한 Java5 이후로 명확한 메모리 모델이 확립된 자바와는 달리 C/C++의 경우 volatile에 대한 명확한 표준이 없고 컴파일러마다 구현에 차이가 있다는 점도 volatile 키워드의 사용을 어렵게 하고 있다. 하지만 임베디드 시스템이나 멀티쓰레드 프로그래밍이 보편화된 만큼, 이 글에서는 volatile 키워드의 기초부터 다시 살펴보고자 한다.


volatile 소개

volatile로 선언된 변수는 외부적인 요인으로 그 값이 언제든지 바뀔 수 있음을 뜻한다. 따라서 컴파일러는 volatile 선언된 변수에 대해서는 최적화를 수행하지 않는다. volatile 변수를 참조할 경우 레지스터에 로드된 값을 사용하지 않고 매번 메모리를 참조한다. 왜 volatile이라는 키워드가 필요한지 이해하려면 먼저 일반적인 C/C++ 컴파일러가 어떤 종류의 최적화를 수행하는지 알아야 한다. 가상의 하드웨어를 제어하기 위한 다음 코드를 살펴보자.


*(unsigned int *)0x8C0F = 0x8001

*(unsigned int *)0x8C0F = 0x8002;

*(unsigned int *)0x8C0F = 0x8003;

*(unsigned int *)0x8C0F = 0x8004;

*(unsigned int *)0x8C0F = 0x8005;

<!--[if !supportEmptyParas]--> 잘못된 하드웨어 제어 코드

<!--[endif]-->


이 코드를 보면 5번의 메모리 쓰기가 모두 같은 주소인 0x8C0F에 이루어짐을 알 수 있다. 따라서 이 코드를 수행하고 나면 마지막으로 쓴 값인 0x8005만 해당 주소에 남아있을 것이다. 영리한 컴파일러라면 속도를 향상시키기 위해서 최종적으로 불필요한 메모리 쓰기를 제거하고 마지막 쓰기만 수행할 것이다. 일반적인 코드라면 이런 최적화를 통해 수행 속도 면에서 이득을 보게 된다.

하지만 이 코드가 MMIO(Memmory-mapped I/O)처럼 메모리 주소에 연결된 하드웨어 레지스터에 값을 쓰는 프로그램이라면 이야기가 달라진다. 각각의 쓰기가 하드웨어에 특정 명령을 전달하는 것이므로, 주소가 같다는 이유만으로 중복되는 쓰기 명령을 없애버리면 하드웨어가 오동작하게 될 것이다. 이런 경우 유용하게 사용할 수 있는 키워드가 volatile이다. 변수를 volatile 타입으로 지정하면 앞서 설명한 최적화를 수행하지 않고 모든 메모리 쓰기를 지정한 대로 수행한다.


*(volatile unsigned int *)0x8C0F = 0x8001

*(volatile unsigned int *)0x8C0F = 0x8002;

*(volatile unsigned int *)0x8C0F = 0x8003;

*(volatile unsigned int *)0x8C0F = 0x8004;

*(volatile unsigned int *)0x8C0F = 0x8005;

올바른 하드웨어 제어 코드


특정 메모리 주소에서 하드웨어 레지스터 값을 읽어오는 프로그램의 경우도 마찬가지다. 아래 코드의 경우 같은 주소에서 반복적으로 메모리를 읽으므로, 최적화 컴파일러라면 buf[i] = *p;에서 *p를 한 번만 읽어온 후에 그 값을 반복해 사용할 것이다. 하지만 volatile 키워드가 있다면 *p를 참조할 때마다 직접 메모리에서 새 값을 가져와야 한다. 이 경우는 하드웨어가 메모리 0x8C0F 번지 값을 새롭게 변경해 주는 경우이다.


void foo(char *buf, int size)

{

     int i;

     volatile char *p = (volatile char *)0x8C0F;

<!--[if !supportEmptyParas]--> <!--[endif]-->

     for (i = 0 ; i < size; i++)

     {

         buf[i] = *p;

         ...

     }

}

하드웨어 레지스터 읽기


가시성


volatile 키워드는 앞서 살펴본 하드웨어 제어를 포함하여 크게 3가지 경우에 흔히 사용된다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

(1) MMIO(Memory-mapped I/O)

(2) 인터럽트 서비스 루틴(Interrupt Service Routine)의 사용

(3) 멀티 쓰레드 환경

<!--[if !supportEmptyParas]--> <!--[endif]-->

세 가지 모두 공통점은 현재 프로그램의 수행 흐름과 상관없이 외부 요인이 변수 값을 변경할 수 있다는 점이다. 인터럽트 서비스 루틴이나 멀티 쓰레드 프로그램의 경우 일반적으로 스택에 할당하는 지역 변수는 공유하지 않으므로, 서로 공유되는 전역 변수의 경우에만 필요에 따라 volatile을 사용하면 된다.


int done = FALSE;

<!--[if !supportEmptyParas]--> <!--[endif]-->

void main()

{

     ...

     while (!done)

     {

         // Wait

     }

     ...

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

interrupt void serial_isr(void)

{

     ...

     if (ETX == rxChar)

     {

         done = TRUE;

     }

     ...

}

serial.c


위 시리얼 통신 예제는 전역 변수로 done을 선언해서 시리얼 통신 종료를 알리는 ETX 문자를 받으면 main 프로그램을 종료시킨다. 문제는 done이 volatile이 아니므로 main 프로그램은 while(!done)을 수행할 때 매번 메모리에서 done을 새로 읽어오지 않는다는 점이다. 따라서 serial_isr() 루틴이 done 플래그를 수정하더라도 main은 이를 모른 채 계속 루프를 돌고 있을 수 있다. done을 volatile로 선언해주면 매번 메모리에서 변수 값을 새로 읽어오므로 이 문제가 해결된다.

인터럽트의 경우와 마찬가지로 멀티 쓰레드 프로그램도 수행 도중에 다른 쓰레드가 전역 변수 값을 임의로 변경할 수 있다. 하지만 컴파일러가 코드를 생성할 때는 다른 쓰레드의 존재 여부를 모르므로 변수 값이 변경되지 않았다면 매번 새롭게 메모리에서 값을 읽어오지 않는다. 따라서 여러 쓰레드가 공유하는 전역 변수라면 volatile로 선언해주거나 명시적으로 락(lock)을 잡아야 한다.

이처럼 레지스터를 재사용하지 않고 반드시 메모리를 참조할 경우 가시성(visibility) 이 보장된다고 말한다. 멀티쓰레드 프로그램이라면 한 쓰레드가 메모리에 쓴 내용이 다른 쓰레드에 보인다는 것을 의미한다.


문법과 타입


C/C++의 volatile은 상수(constant)를 선언하는 const와 마찬가지로 타입에 특정 속성을 더해주는 타입 한정자(type qualifier)이다. const int a = 5; 라고 선언했을 경우 a라는 변수는 정수 타입의 변수이면서 동시에 상수의 속성을 가짐을 의미한다. 같은 방식으로 volatile int a; 라고 선언해주면 정수 변수 a는 volatile 속성을 가지게 된다.

조심해야 할 것은 포인터 타입에 volatile을 선언하는 경우이다. int *를 volatile로 선언하는 몇 가지 방법을 비교해보자.


volatile int* foo;

int volatile *foo;

int * volatile foo;

int volatile * volatile foo;

volatile의 선언


복잡하게 보이지만 원리는 const 타입 한정자와 동일하다. *를 기준으로 왼쪽에 volatile 키워드가 올 경우 포인터가 가리키는 대상이 volatile함을 의미하고, * 오른쪽에 volatile 키워드가 올 경우에는 포인터 값 자체가 volatile임을 의미한다. volatile이 *의 양쪽에 다 올 경우는 포인터와 포인터가 지시하는 대상이 모두 volatile함을 의미한다. 하드웨어 제어의 예처럼 일반적으로 포인터가 가리키는 대상이 volatile 해야 할 경우가 많으므로 volatile int * 형태가 가장 많이 사용된다. volatile int*와 int volatile*은 동일한 의미이다.


01: int foo(int& a)

02: {

03: }

04:

05: int bar(volatile int& b)

06: {

07: }

08:

09: int main()

10: {

11:     volatile int a = 0;

12:     int b = 0;

13:

14:     foo(a);

15:     bar(b);

16: }

<!--[if !supportEmptyParas]--> <!--[endif]-->

$ g++ a.cc

a.cc: In function `int main()':

a.cc:14: error: invalid initialization of reference of type 'int&' from expression of type 'volatile int'

a.cc:2: error: in passing argument 1 of `int foo(int&)'


또한 int는 volatile int의 서브타입에 해당한다. 서브 타입을 쉽게 설명하면 A를 요구하는 곳에 언제든지 B를 사용할 수 있다면 B는 A의 서브 타입이다. 쉬운 예로 Class의 경우 Derived 클래스는 Base 클래스의 서브 타입이다. volatile int를 요구하는 곳에 언제든지 int를 넘길 수 있으므로 int는 volatile int의 서브 타입이라고 말할 수 있는 것이다. 그 예로 위 C++ 프로그램은 컴파일 에러가 난다. volatile int를 받는 bar() 함수에 int 인자로 호출하는 것은 아무 문제없지만, int를 요구하는 foo() 함수에 volatile int 타입인 a를 넘기면 컴파일 에러가 나는 것이다.


01: int foo(int& a)

02: {

03: }

04:

05: int bar(const int& b)

06: {

07: }

08:

09: int main()

10: {

11:     const int a = 0;

12:      int b = 0;

13:

14:     foo(a);

15:      bar(b);

16: }

foobar2.cc


이 관계가 쉽게 이해되지 않는다면 위의 예처럼 volatile 키워드를 같은 타입 한정자인 const로 대체해보자. 변수 a는 const int 타입이므로 이미 상수로 선언되었다. const int인 a를 int를 요구하는 foO() 함수에 넘기면 const라는 가정이 깨어지므로 컴파일 에러가 된다. 반대로 b의 경우 원래 const가 아니었지만 bar로 넘기면서 const 속성을 새롭게 부여받게 된다. const 속성을 부여받는다고 말하면 무엇인가 기능이 추가되는 것 같지만, 이는 바꿔 말해서 원래 int의 2가지 기능인 읽기, 쓰기에서 쓰기 기능이 사라지는 것으로 볼 수도 있다. 이를 클래스로 표현해 보면 다음과 같을 것이다.


class ConstInteger {

public:

     ConstInteger(int v) : value(v) {}

     int get() { return value; }

<!--[if !supportEmptyParas]--> <!--[endif]-->

protected:

     int value;

};

<!--[if !supportEmptyParas]--> <!--[endif]-->

class Integer : public ConstInteger {

public:

     Integer(int v) : ConstInteger(v) {}

     void set(int v) { value = v; }

};

ConstInteger.cc


위 클래스를 두고 보면 volatile/const int와 int의 관계가 명확해진다. Integer는 ConstInteger을 상속한 클래스이므로 ConstInteger를 요구하는 곳 어디에나 쓸 수 있는 것이다. 반대로 Integer가 필요한 곳에 ConstIntger를 넘기면 set() 메쏘드가 없으므로 문제가 된다. 따라서 컴파일러는 이를 금지하는 것이다.

volatile의 const와 같은 맥락에서 생각할 수 있다. volatile 속성을 부여받는 다는 것은 바꿔 말하면 컴파일러가 최적화를 할 자유를 잃는다고 말할 수 있다. ConstInteger의 경우만큼 명확하지는 않지만 이 관계를 클래스로 생각해 본다면 아마 다음과 같을 것이다.


class VolatileInteger {

public:

     VolatileInteger(int v) : value(v) {}

     int get() { return value; }

     void set(int v) { value = v; }

<!--[if !supportEmptyParas]--> <!--[endif]-->

protected:

     int value;

};

<!--[if !supportEmptyParas]--> <!--[endif]-->

class Integer : public VolatileInteger {

public:

     Integer(int v) : VolatileInteger(v) {}

     void optimize();

};

VolatileInteger.cc


재배치(reordering)


지금까지 volatile 키워드의 일반적인 기능과 문법에 대해서 살펴보았다. C 표준은 volatile 키워드와 메모리 모델에 대한 명확한 정의를 내리지 않고 있기 때문에 컴파일러마다 그 구현에 다소 차이가 있다. C++ 표준은 volatile 대해 별도의 정의하지 않고 가능한 한 C 표준을 따르라고만 하고 있다.

마이크로소프트의 Visual C++를 예로 들어보면, volatile 키워드에 앞서 살펴본 가시성(visibility) 뿐만 아니라 재배치(reordering) 문제에 대한 해결책도 추가하였다. Visual C++의 volatile 변수는 다음과 같은 기능을 추가로 한다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

(1) volatile write: volatile 변수에 쓰기를 수행할 경우, 프로그램 바이너리 상 해당 쓰기보다 앞선 메모리 접근은 모두 먼저 처리되어야 한다.

(2) volatile read: volatile 변수에 읽기를 수행할 경우, 프로그램 바이너리 상 해당 읽기보다 나중에 오는 메모리 접근은 모두 이후에 처리되어야 한다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

재배치(reordering)는 컴파일러가 메모리 접근 속도 향상, 파이프라인(pipeline) 활용 등 최적화를 목적으로 제한된 범위 내에서 프로그램 명령의 위치를 바꾸는 것을 말한다. 우리가 프로그램에 a = 1; b = 1; c = 1; 이라고 지정했다고 해서 컴파일된 바이너리가 반드시 a, b, c 순서로 메모리를 쓰지 않을 수 있다는 뜻이다. 만약 a, c가 같은 캐시(cache)에 있거나 인접해 있어서 같이 쓸 경우 속도 향상을 볼 수 있다면 a = 1; c = 1; b = 1; 로 순서가 바뀔 수 있는 것이다.

Visual C++의 경우 volatile을 사용하면 컴파일러가 수행하는 이러한 재배치에 제약을 주게 된다. a = 1; b = 1; c = 1;에서 c가 volatile로 선언된 변수였다면 a = 1;과 b=1;은 반드시 c에 1을 대입하기 전에 일어나야 한다. 물론 a와 b 사이에는 순서가 없으므로 b = 1; a = 1; c = 1; 과 같은 형태로 재배치가 일어날 수는 있다. 재배치가 일어나지 않도록 보장하는 문제가 왜 중요한지는 MSDN에서 발췌한 다음 예를 통해 살펴보자.


#include <iostream>

#include <windows.h>

using namespace std;

<!--[if !supportEmptyParas]--> <!--[endif]-->

volatile bool Sentinel = true;

int CriticalData = 0;

<!--[if !supportEmptyParas]--> <!--[endif]-->

unsigned ThreadFunc1( void* pArguments ) {

while (Sentinel)

Sleep(0); // volatile spin lock

<!--[if !supportEmptyParas]--> <!--[endif]-->

// CriticalData load guaranteed after every load of Sentinel

cout << "Critical Data = " << CriticalData << endl;

return 0;

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

unsigned ThreadFunc2( void* pArguments ) {

Sleep(2000);

CriticalData++; // guaranteed to occur before write to Sentinel

Sentinel = false; // exit critical section

return 0;

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

int main() {

HANDLE hThread1, hThread2;

DWORD retCode;

<!--[if !supportEmptyParas]--> <!--[endif]-->

hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadFunc1,

NULL, 0, NULL);

hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadFunc2,

NULL, 0, NULL);

<!--[if !supportEmptyParas]--> <!--[endif]-->

retCode = WaitForSingleObject(hThread1,3000);

<!--[if !supportEmptyParas]--> <!--[endif]-->

CloseHandle(hThread1);

CloseHandle(hThread2);

<!--[if !supportEmptyParas]--> <!--[endif]-->

if (retCode == WAIT_OBJECT_0 && CriticalData == 1 )

cout << "Success" << endl;

else

cout << "Failure" << endl;

}

<!--[if !supportEmptyParas]--> volatile.cpp

<!--[endif]-->


프로그램 수행은 간단하다. 이 프로그램은 쓰레드를 2개 생성하는데 ThreadFunc1은 Sentinel 플래그가 true인 동안 루프를 돌고, ThreadFunc2는 잠시 기다렸다 Sentinel 플래그를 false로 만들어준다. ThreadFunc2는 Sentinel을 false로 만들기 전에 전역 변수인 CriticalData을 1만큼 증가시킨다..

이 프로그램에서 만약 Sentinel이 volatile로 선언되지 않았다면 ThreadFunc1은 가시성을 보장받지 못하므로, ThreadFunc2가 Sentinel의 값을 바꾸더라도, 레지스터에 든 값을 사용해 영원히 루프를 돌 수 있음은 이미 살펴보았다. 그럼 이번에는 volatile로 선언해서 Sentinel의 가시성이 보장된다면 이 프로그램의 수행 결과는 어떻게 될까?

간단히 생각하면 CriticalData를 1증가 시킨 이후에 Sentinel을 false로 바꾸므로 ThreadFunc1은 1이라는 값을 찍을 것이라고 생각할 것이다. 하지만 CriticalData는 volatile이 아니므로 여전히 메모리가 아닌 ThreadFunc2의 레지스터에만 남아있을 확률이 있다. 이 경우 ThreadFunc1은 변경된 CriticalData가 아닌 0이라는 값이 나올 수 있다. 수행 타이밍에 따라서 0이 되기도 1이 되기도 하는 것이다.

가정을 바꿔서 CriticalData 또한 volatile이라고 해보자. 모든 문제가 해결된 것 같지만, 결과는 여전히 0 혹은 1이 나온다. CriticalData가 volatile이면 레지스터가 아닌 메모리에 직접 쓰므로 가시성은 확보되지만, 재배치의 문제가 남아있다. 컴파일러가 보기에 ThreadFunc2의 CriticalData++과 Sentinel = false는 전혀 관계없는 변수이다. 따라서 최적화를 이유로 이 순서를 뒤집어 Sentinel = false를 먼저 수행하고 CiriticalData=+을 수행할 수 있다. 이 경우 ThreadFunc2에서 Sentinel = false만 수행하고 컨텍스트 스위치(context switch)가 일어난 경우 ThreadFunc1은 아직 CriticalData++이 수행되기 전에 CriticalData 값인 0을 읽을 수 있다.

여기서 Visual C++가 추가한 시멘틱(semantic)을 적용해보자. ThreadFunc2에서 Sentinel = false는 volatile write이므로 프로그램 바이너리에서 그 이전에 수행되어야 할 명령은 모두 volatile write 이전에 수행되게 된다. 따라서 CriticalData++;은 반드시 Sentinel = false; 이전에 수행된다. ThreadFunc1은 Sentinel을 volatile read하므로 그 이후에 실행되는 CriticalData 읽기는 반드시 Sentinel을 읽은 후에 수행된다. 따라서 위 프로그램은 정확히 1을 출력하는 올바른 프로그램이 된다.

또한 재배치가 일어나지 않음을 보장할 경우 Sentinel이 volatile이기만 하면 CriticalData는 volatile이 아니더라도 가시성(visibility)이 보장되는 효과도 있다. 이렇게 다른 volatile 변수로 인해 공짜로 가시성을 얻는 경우 피기백킹(piggybacking, 돼지 등을 타고 공짜로 달린다는 의미)이라고 부른다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

좋은 코딩 습관으로 생각되었다가 재배치 문제로 안전하지 않음이 밝혀진 예로 더블 체크 이디엄(double check idiom)이 있다. 아래 코드처럼 initialized == false로 초기화 여부를 확인하고 객체를 생성해 얻어올 경우 반드시 락을 잡아줘야 한다. read-test-write는 원자적(atomic)이지 않기 때문에 여러 쓰레드가 동시에 초기화를 시작할 수 있기 때문이다. 문제는 한 번 초기화가 된 이후에도 매번 객체를 얻어갈 때마다 락을 잡고 풀어야 한다는 점이다.


Foo* Foo::getInstance()

{

mutex.lock();

<!--[if !supportEmptyParas]--> <!--[endif]-->

if (instance == 0) {

     instance = new Foo();

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

mutex.unlock();

<!--[if !supportEmptyParas]--> <!--[endif]-->

return instance;

}

check.cc


이러한 오버헤드를 피하기 위해 다음과 같은 일단 초기화 여부를 확인한 이후에 실제로 락을 잡아서 다시 한 번 정말 초기화되지 않았는지 확인하는 패턴이 널리 사용되었다. 이 경우는 앞선 예와는 달리 한 번 초기화가 이루어지고 나면 더 이상 락을 잡지 않고 객체를 얻어올 수 있다.

이 코드는 언뜻 보기에 무척 효율적으로 보이지만 재배치와 관련해 큰 문제가 있다. 특히 instance = new Foo(); 의 수행 순서가 문제가 된다. 메모리를 할당 받아 생성자를 호출한 후에 메모리 주소를 instance를 대입한다고 하면 별 문제가 없겠지만, 일부 필드의 초기화 과정과 instance의 포인터 대입의 순서가 컴파일러 재배치로 인해 바뀔 수 있다. 이 경우 아직 일부 필드가 초기화되지 않은 상태에서 instance가 0이 아니게 되므로, 다른 쓰레드가 객체를 얻을 수 있다.


Foo* Foo::getInstance()

{

if (instance == 0) {

     mutex.lock();

<!--[if !supportEmptyParas]--> <!--[endif]-->

     if (instance == 0) {

         instance = new Foo();

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

mutex.unlock();

}

<!--[if !supportEmptyParas]--> <!--[endif]-->

return instance;

}

double_check.cc


정리


지금까지 C/C++의 volatile 키워드의 기본적인 기능과 관련된 문제들을 살펴보았다. volatile은 미묘한 키워드라 잘 알고 쓰면 큰 도움이 될 수 있지만, 또한 여러 가지 문제를 일으키는 근원이 되기도 한다. 특히 명확한 표준이 있는 게 아니므로, 사용하는 자신이 사용하는 C/C++ 컴파일러의 매뉴얼을 꼼꼼히 읽고 volatile을 어떻게 지원하는지 파악하는 게 중요하다.

volatile은 단일 CPU 환경에서 컴파일러 재배치 문제는 해결해주지만, MMU나 멀티CPU에 의한 재배치에 대해서는 완전한 대안을 제공하지 못한다. 또한 변수를 읽은 후에 값을 수정하고 다시 쓰는 read-modify-write를 원자적으로 수행할 수 있게 해주지도 않는다. a += 5; 같은 단순한 명령도 실제로는 a를 메모리에서 읽고 5를 더한 후에 다시 메모리 쓰는 복잡한 연산이므로 a를 volatile로 선언하는 것만으로는 이 코드를 멀티쓰레드에서 안전하게 수행할 수는 없다는 뜻이다. 유용성과 한계를 충분히 인지하고 필요에 따라 적절히 volatile을 사용하자.

<!--[if !supportEmptyParas]--> <!--[endif]-->

참고문헌


[1] The "Double-Checked Locking is Broken" Declaration http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

<!--[if !supportEmptyParas]-->
<!--[endif]-->



프로페셔널 안드로이드 애플리케이션 개발

프로페셔널 안드로이드 애플리케이션 개발



출판사      
제이펍

원출판사    Wrox (원서 ISBN 978-0470344712)

원서명       Professional Android Application Development

저자명       리토 마이어(Reto Meier )

역자명       조성만

출판일       2009년 7월 22일

페이지       600쪽

판  형        4*6배판 변형(188*245) 반양장(Soft Cover)

정  가        30,000원

ISBN         978-89-962410-1-0  부가기호: 13560

분  야        자바 / 오픈 소프트웨어 / 리눅스 / 모바일


원서 1-2장 및 번역서 1-2장 PDF 파일: 


번역서 앞부속물(Front Matter) PDF 파일:
원출판사 제공 소스코드 압축파일:
원서 P2P 포럼: http://p2p.wrox.com/book-professional-android-application-development-isbn-978-0-470-34471-2-432/

역자 홈페이지: http://www.chosungmann.org (오픈 예정)


도서 소개

안드로이드 SDK 1.5 버전으로 완전 업데이트!


안드로이드는 구글에서 만든 모바일 개발 플랫폼으로서 최근 전 세계 개발자들의 관심을 한 몸에 받고 있다. 그 이유는 화려한 UI를 내세워 스마트폰 시장의 최강자인 아이폰을 능가할 만한 가능성을 가진 플랫폼이기 때문이다. 안드로이드 운영체제를 탑재하여 큰 인기를 끌고 있는 대만 HTC의 G1, G2뿐만 아니라 올해는 삼성전자, LG전자, 모토로라, 소니에릭슨 등 세계 유수의 단말기 제조업체들이 안드로이드 운영체제를 탑재한 스마트폰을 출시할 예정이며, 이에 한 발 더 나아가 안드로이드는 스마트폰뿐만 아니라 넷북, 네비게이션, PMP 등 모바일 기기 전 영역으로 그 기세를 확대해가고 있다. 향후에는 모바일 기기뿐 아니라 액정화면이 달린 모든 전자제품에 안드로이드가 탑재될 시기가 도래할 수도 있을 것이다.


현재까지 출간된 안드로이드 개발서적 중 가장 좋은 평가를 받고 있는 이 책은, 안드로이드 개발에 입문하는 사람에게는 모바일 개발에 대한 기본 개념뿐만 아니라 안드로이드의 오픈된 철학까지 소개하고, 중급 개발자로 도약하기 위한 탄탄한 기본기를 제공할 것이며, 중급 개발자들에게는 안드로이드의 다양한 기능을 실제 작동하는 예제를 통해 빠른 시일 안에 안드로이드 주요 기능을 학습시켜 줄 것이다.


저자는 안드로이드의 거의 모든 주제를 군더더기 없이 명확하고 간결하게 그리고 적절한 수준과 실용적인 예제로 잘 풀어쓰고 있다는 평가를 받고 있으며, 역자는 모바일 개발 경험을 살려 정확한 용어와 친절한 역주 등으로 국내 독자들이 보다 이해하기 쉽도록 번역한 것도 이 책의 장점이다.


뒷표지 내용

안드로이드는 열린 개발 환경을 제공함과 동시에 혁신적인 모바일용 애플리케이션을 작성하기 위한 새로우면서도 흥미로운 기회를 제공하고 있다. 이 책은 최신의 안드로이드 소프트웨어 개발 킷을 사용해 이러한 혁신적인 모바일용 애플리케이션을 만들기 위한 실무 가이드를 제공하며, 안드로이드를 최대한 활용하기 위한 새로운 기능과 기법을 소개하는 일련의 샘플 프로젝트로 여러분을 안내한다. 이를 통해 여러분은 기본적인 안드로이드의 모든 기능에 대해 배울 것이며, 간결하면서도 유용한 예제들을 통해 고급 기능을 활용하는 방법도 알게 될 것이다.


저자는 안드로이드 소프트웨어 스택을 소개하는 것으로 시작해, 휴대폰을 위한 견고하면서도 일관적이며 매력적인 애플리케이션을 제작하는 과정 이면에 담긴 철학까지도 안내하고 있다. 여러분은 현재 널리 사용되고 있는 안드로이드 SDK 1.5로 커스터마이징된 모바일 애플리케이션을 작성하는 데 필요한 기초 지식을 얻을 것이며, 추가로 최첨단 솔루션 구축을 위한 향후 개선사항에 발 빠르게 적응하는 유연성 또한 얻을 것이다.


이 책에서 배우게 될 주요 내용은 다음과 같다.

- 안드로이드 모바일 개발 베스트 프랙티스

- 액티비티, 인텐트, 매니페스트, 리소스에 대한 소개

- 레이아웃과 커스텀 뷰를 가지고 사용자 인터페이스를 만드는 방법

- 애플리케이션 데이터를 저장하고 공유하는 기법

- GPS와 지오코딩 위치(geocoding locations) 등의 위치기반 서비스를 사용한 맵기반 애플리케이션 제작 지침

- 백그라운드 서비스(Services)와 알림(Notifications)을 만들고 사용하는 방법

- 가속도 센서, 나침반, 카메라 하드웨어 사용법

- 전화 API, SMS, 네트워크 관리 같은 전화 및 네트워킹 하드웨어에 관한 모든 것

- 보안과 IPC 그리고 몇 가지 고급 그래픽스와 유저 인터페이스 기법 등을 포함한 고급 개발 주제


이 책의 대상 독자

이 책은 안드로이드 휴대폰 플랫폼을 위한 애플리케이션 제작에 관심 있는 모든 사람을 대상으로 한다. 이 책에는 숙련된 모바일 개발자와 이제 막 모바일 애플리케이션 제작에 입문한 사람 모두를 위한 값진 정보가 담겨 있다.


이 책의 구성

이 책은 서로 다른 개발 배경을 가진 독자들이 고급 안드로이드 애플리케이션을 작성하기 위한 방법을 배우도록 돕기 위해 논리적인 순서로 구조화되어 있다.


각 장을 순차적으로 읽어야 할 필요는 없지만, 몇몇 샘플 프로젝트는 여러 장으로 구성된 과정을 따라 개발되며, 새로운 기능과 다른 향상이 각 단계에 더해진다.


안드로이드 개발 환경에서 작업해본 숙련된 모바일 개발자는 처음 두 개 장(모바일 개발에 대한 소개와 여러분의 개발 환경 구축을 위한 지침이 담긴)은 건너 뛰고 3장에서 6장으로 뛰어들 수 있다. 이들은 안드로이드 개발의 기본을 다루고 있으므로, 이들이 설명하는 개념을 확실히 이해하는 것이 중요하다. 여기서 다룬 내용을 가지고, 여러분은 맵, 위치기반 서비스, 백그라운드 애플리케이션, 그리고 하드웨어 상호작용과 네트워킹 같은 고급 주제를 살펴보는 남은 장들로 이동할 수 있다.


이 책의 주요 내용

1장은 안드로이드가 무엇이며 기존 모바일 개발에 어떻게 들어맞는지 등을 포함하여 안드로이드에 대해 소개한다. 개발 플랫폼으로서 안드로이드가 제공하는 것은 무엇이고 또 왜 안드로이드가 휴대폰 애플리케이션 개발을 위한 흥미로운 기회가 되는지 보다 자세히 살펴본다.


2장은 모바일 개발을 위한 베스트 프랙티스 몇 가지를 다루며, 안드로이드 SDK를 다운로드하는 방법과 애플리케이션 개발을 시작하는 방법을 설명한다. 또한 안드로이드 개발자 도구를 소개하고 새로운 애플리케이션을 처음부터 만드는 방법을 설명한다.


3장에서 6장까지는 기본적인 안드로이드 애플리케이션 컴포넌트에 대해 자세히 살펴본다. 안드로이드 애플리케이션을 구성하는 부분들에 대해 살펴보는 것을 시작으로, 애플리케이션 매니페스트와 외부 리소스로 빠르게 옮겨가며, “액티비티”와 그의 수명 그리고 수명 주기에 대해 배운다.


그런 뒤엔 레이아웃과 뷰를 이용해 사용자 인터페이스를 만드는 방법에 대해 배울 것이며, 액션을 수행하고 애플리케이션 컴포넌트 간에 메시지를 보내는 데 이용되는 인텐트 메커니즘을 소개할 것이다. 이어서 인터넷 리소스를 다루며, 데이터 저장소, 검색, 그리고 공유에 대해 자세히 살펴본다. 여러분은 환경설정 저장 메커니즘을 시작으로 파일 처리와 데이터베이스로 이동할 것이다. 그리고 콘텐트 공급자를 이용한 애플리케이션 데이터 공유에 대해 자세히 살펴보는 것으로 이번 절을 마친다.


7장에서 10장까지는 고급 주제에 대해 살펴본다. 맵과 위치기반 서비스를 시작으로, 여러분은 서비스, 백그라운드 스레드, 그리고 알림 이용으로 옮겨갈 것이다.


다음으로 인스턴트 메시징과 SMS를 통한 메시지 송수신을 포함한 안드로이드의 통신 능력에 대해 살펴본다. 이어서 미디어 기록 및 재생을 시작으로 하드웨어에 대해 다루며, 카메라, 가속도 센서, 그리고 나침반 센서를 소개한다. 10장은 전화 통신 API로 시작해 블루투스와 네트워크 관리(Wi-Fi 및 모바일 데이터 연결)로 이어지는 전화와 네트워킹 하드웨어를 살펴보는 것으로 마친다.


11장은 여러 고급 개발 주제, 그 중에서도 보안, IPC, 고급 그래픽스 기법, 그리고 사용자-하드웨어 상호작용을 포함한다.


부록은 안드로이드 1.5 SDK 릴리즈 노트와 안드로이드 1.5 플랫폼 주요 기능, 안드로이드 1.5 NDK, 릴리즈 1에 대한 소개 그리고 맵 API 키 얻는 방법을 소개하고 있다.


저자 소개

리토 마이어(Reto Meier)

서호주 퍼스 출신으로 현재 런던에서 살고 있다. 리토는 GUI 어플리케이션 구조, 디자인, 개발에 10년이 넘는 경험을 가진 소프트웨어 개발자다. 해양 석유 및 가스 등을 포함한 다양한 업계에서 근무하였으며, 런던으로 옮기고부터는 금융 계통에서 일하고 있다. 새로운 기술에 관심이 많은 리토는, 2007년에 안드로이드의 초기 릴리즈가 공개된 이래로 안드로이드에 몰두해왔으며, 여가 시간에는 WPF와 개발 도구를 포함한 구글의 다양한 개발 플랫폼을 연구하고 있다. 리토에 대해 궁금하다면 그가 운영하는 웹 사이트인 The Radioactive Yak(http://blog.radioactiveyak.com)을 방문해보자.

테크니컬 에디터 소개

댄 울러리(Dan Ulery)

댄은 .NET, 자바, PHP 개발과 더불어 배포 공학에 경험을 가진 소프트웨어 엔지니어다. 미국 아이다호 대학교에서 컴퓨터 과학 학사학위를 받았으며 부전공으로 수학을 공부했다.


역자 소개

조성만

옮긴이 조성만은 단국대학교에서 컴퓨터과학을 공부했다. 클라우드 컴퓨팅 기술에 기반을 둔 소프트웨어 퍼블리싱과 서로 다른 컴퓨팅 환경을 넘나드는 크로스 플랫폼 영역 그리고 모바일 컴퓨팅 환경에서의 사용자 경험에 관심 있다. 현재 ㈜인프라웨어에서 모바일 웹브라우저에 탑재되는 고성능 그래픽스 엔진 개발에 참여하고 있으며, 오픈 모바일 환경인 안드로이드와 오픈모코에 푹 빠져 지내고 있다. 옮긴 책으로는 『Qt 4를 이용한 C++ GUI 프로그래밍, 제2판』(ITC, 2009)이 있다.


추천자 소개

박성서 _ 제1회 안드로이드 개발자 챌린지 국내 유일 입상자

한동호 _ 안드로이드사이드 커뮤니티 운영자

이학준 _ IT 트렌드 전문 블로그 학주니닷컴 운영자

추천사 보기

더보기

펼쳐두기..

더보기

펼쳐두기..

더보기

펼쳐두기..


 

차 례

1장 헬로, 안드로이드

    가볍게 살펴보는 배경지식

    안드로이드에 대한 오해

    모바일 개발을 위한 오픈 플랫폼

    네이티브 안드로이드 애플리케이션

    안드로이드 SDK의 특징

    오픈 핸드셋 얼라이언스 소개

    안드로이드의 발전 추이

    안드로이드의 개발 이유

    개발 프레임워크 소개

    요약


2장 시작하기

    안드로이드 어플리케이션 개발하기

    모바일 어플리케이션 개발하기

    할일 목록 예제

    안드로이드 개발 도구

    요약


3장 애플리케이션과 액티비티 만들기

    안드로이드 애플리케이션 구성요소

    애플리케이션 매니페스트 소개

    매니페스트 에디터 사용하기

    안드로이드 애플리케이션 수명 주기

    애플리케이션 우선순위와 프로세스 상태 이해하기

    리소스 외부화하기

    안드로이드 액티비티 좀더 자세히 살펴보기

    요약


4장 사용자 인터페이스 만들기

    기본적인 안드로이드 UI 디자인

    뷰 소개

    레이아웃 소개

    새로운 뷰 만들기

    메뉴 만들고 사용하기

    요약


5장 인텐트, 브로드캐스트 수신자, 어댑터, 그리고 인터넷

    인텐트 소개

    어댑터 소개

    인터넷 리소스 사용하기

    다이얼로그 소개

    지진 뷰어 만들기

    요약


6장 데이터 저장, 검색, 그리고 공유

    안드로이드의 데이터 저장 기법

    간단한 애플리케이션 데이터 저장하기

    파일 저장하고 읽어 들이기

    안드로이드의 데이터베이스

    콘텐트 공급자 소개

    요약


7장 맵, 지오코딩, 그리고 위치기반 서비스

    위치기반 서비스 이용하기

    테스트 공급자를 이용해 에뮬레이터 설정하기

    위치 공급자 선택하기

    내 위치 찾기

    근접 경보 사용하기

    지오코더 사용하기

    맵기반 액티비티 만들기

    지진 매핑 예제

    요약


8장 백그라운드에서 작업하기

    서비스 소개

    백그라운드 작업자 스레드 이용하기

    토스트 만들기

    알림 소개

    알람 이용하기

    알람을 이용해 지진 업데이트하기

    요약


9장 피어-투-피어 통신

    안드로이드 인스턴트 메시징 소개

    SMS 소개

    요약


10장 안드로이드 하드웨어 접근하기

    미디어 API 이용하기

    카메라 이용하기

    센서 관리자 소개

    가속도 센서와 나침반 이용하기

    안드로이드 전화 통신

    블루투스 이용하기

    네트워크 및 Wi-Fi 연결 관리하기

    장치 진동 제어하기

    요약


11장 고급 안드로이드 개발

    편집증 환자, 안드로이드

    AIDL을 이용해 서비스를 위한 IPC 지원하기

    인터넷 서비스 이용하기

    리치 유저 인터페이스 구축하기

    요약


부록 I  안드로이드 1.5 SDK 릴리즈 노트

부록 II  안드로이드 1.5 플랫폼 주요 기능

부록 III  안드로이드 1.5 NDK, 릴리즈 1 소개

부록 IV  맵 API 키 얻