2016-03-10

OSI 모델은 무엇인가

 OSI 모델은 추상화를 위해 프로토콜을 여러 계층의 layer로 나누고, 각 layer에서 자신이 책임진 일만 하도록 설계한 네트워크 모델이다. 각 layer는 완전히 독립되어 있으며, 각 layer에서 수행해야 하는 일만 제대로 된다면 그 구현은 다른 구현으로 대체 될 수 있도록 설계되어야 한다.

OSI 7 layer

 OSI layer는 보통 7계층으로 나누어지기 때문에 OSI 7 layer라고 불리기도 한다. 각 layer의 이름은 가장 아래부터 physical layer, data link layer, network layer, transport layer, session layer, presentation layer, application layer다. 이를 다시 크게 둘로 나누어서 network layer까지의 아래 3개 layer는 media layer라고 하고, transport layer부터 위의 4개 layer를 host layer라고 한다. media layer는 네트워크상에서 원하는 머신을 찾아 데이터를 보내는 역할을 하고, host layer는 전송된 데이터를 안전하게 사용하는 방법에 대한 역할을 한다.

 그러면 이제부터 각 layer에 대해 자세한 설명을 할 것인데 명심해야 할 것은 어떤 네트워크 프로토콜을 만들 때 OSI 모델을 지키는 것이 필수적인 것은 아니라는 것이다. 실제로 많이 사용되는 네트워크 프로토콜 중에서도 완벽하게 OSI의 원칙에 따라 설계된 프로토콜도 거의 없고, 7개 계층을 전부 구현하여 사용하는 것도 본 적이 없다. 그냥 네트워크 프로토콜은 이러한 역할이 필요하고, 그것은 이러한 순서로 지켜지는 게 안전하다는 가이드 정도로 생각하는 것이 좋다.

physical layer

 우선 가장 아래 계층은 physical layer다. physical layer에서는 전달 매체를 통해 전달된 신호를 어떻게 0과 1로 바꾸는지를 담당한다. 데이터의 전달 매체에 따라서 0과 1이 반드시 bit일 필요도 없고, 사실 전기 신호일 필요도 없다. 정말 다양한 방법으로 구현될 수 있지만 보통은 프로그래머가 신경 쓸 필요 없는 수준의 이야기다.

data link layer

 두 번째 계층은 data link layer다. data link layer에서 전송되는 data의 단위는 frame이라고 부르는데, 이 계층에서는 오류 없는 온전한 frame이 전달되는 것을 보장하거나 데이터를 전달할 매체에 접근하는 것을 컨트롤 해주는 계층이다. 다만 최종목적지까지 전달되는 것은 아니고, 로컬 네트워크 내에서만 전달해주거나, 로컬 네트워크 사이를 연결해주는 역할만 한다. 오류가 없는 것을 보장하기 위해 에러를 감지하거나 고칠 수 있는 데이터를 프레임에 추가하기도 한다.

network layer

 세 번째 계층은 network layer다. network layer에서 다루는 data의 단위는 packet이다. 일반적으로 frame은 packet을 감싼 1 : 1 관계지만, 꼭 그렇다고 할 수는 없다. 다양한 이유로 data link layer에서 packet들을 모아서 하나의 frame으로 만들거나, 하나의 packet을 쪼개서 여러 개의 frame으로 만들거나 한다. 이 계층에서는 data가 목적지까지 도착하도록 routing 해준다. 하지만 목적지까지 제대로 도착한다거나, 순서대로 도착하는 것을 보장해주지는 않는다.

transport layer

 네 번째 계층은 transport layer다. transport layer는 최종 노드까지의 안전한 통신을 보장해서 이다음 layer부터는 data의 무결성을 신경 쓰지 않을 수 있도록 해준다. 에러 검출이나 order의 보장 등을 할 지 말지에 따라서 TP0부터 TP4까지 4개의 class로 분류된다.

session layer

 다섯 번째 계층은 session layer다. 두 node 사이의 인증을 해주거나, 연결이 끊어졌을 때 다시 연결을 해주거나 하는 것을 담당한다. SSH protocol처럼 지속적인 연결이 보장되어야 하는 프로토콜에서 사용된다.

presentation layer

 여섯 번째 계층은 presentation layer다. 교환되는 정보의 형태를 결정한다. endian을 결정하기도 하고, 전송할 data field의 크기를 결정하는 등 전송될 데이터의 모양을 만든다. 하지만 현실적으로 별도의 프로토콜 레이어로 구현되지 않고, 서버와 클라이언트 간의 암묵적인 약속으로 보장해준다.

application layer

 일곱 번째 계층은 application layer다. 이 계층의 프로토콜은 사용자가 네트워크를 이용해 실제로 하고자 하던 것. 즉, file을 주고받는다거나, 명령을 보낸다거나 하는 내용을 명시한다.

2016-03-07

[Android] AsyncTask - UI 스레드에서는 시간이 오래 걸리는 일을 하면 안 된다

 안드로이드는 메인 스레드에서 UI와 관련된 일을 처리한다. 그래서 메인 스레드를 UI 스레드라고 부르기도 한다. 그런데 UI 스레드에서 오랫동안 CPU를 점유하는 일을 하게 되면, "Application Not Responding"이라는 메시지가 나온다. 이러면 앱에 대한 컨트롤을 잃게 되며, 응답할 때까지 기다리거나 강제종료할 수밖에 없다.
 이런 일을 방지하기 위해서는 새로운 스레드를 생성하여 다른 스레드에 시켜야 한다. 하지만 로우레벨의 스레드를 바로 사용하면 동기화하는 과정에서 쉽게 실수할 수 있으므로 실수 없이 쉽게 스레드를 사용할 수 있게 하려고 안드로이드는 AsyncTask라는 클래스를 지원한다.

 AsyncTask는 UI 스레드에서 잠시 동안 백그라운드로 일을 호출하고 싶을 때 사용된다. 잠시 동안이라는 것을 문서상으로는 should ideally be used for short operations (a few seconds at the most.)1)라고 표현하고 있다. 만약 길게 실행되는 일을 백그라운드에서 실행하고 싶으면 스레드를 직접 사용하거나 다른 방법을 이용해야 한다. 그 이유는 몇 가지 있다.
 대표적인 문제는, AsyncTaskActivity에 종속되지 않기 때문에, AsyncTask를 호출했던 Activity가 먼저 죽었을 경우 처리가 복잡해진다.
 또 다른 문제는 백그라운드 스레드를 점유해버린다는 것이다. AsyncTaskAsyncThread가 새로 만들어질 때마다 스레드를 생성하지 않는다. 고정된 크기의 스레드 풀이 있고 이 스레드 풀을 모든 AsyncTask가 공유한다. API 버전에 따라서 1개의 태스크만 실행될 수도 있고, 여러 개의 태스크가 병렬적으로 동시에 수행될 수도 있는데, 최신 버전은 1개만 실행되며 모든 태스크는 순차적으로 실행하게 되어 있다. 따라서 어떤 태스크가 데 시간이 오래 걸린다면 다른 태스크가 실행되지 못한다.

 AsyncTask를 사용하는 대략적인 과정은 다음과 같다. AsyncTask를 상속받는 클래스를 만들어서 필요한 함수를 오버라이드한다. 그 뒤 UI 스레드에서 AsyncTask를 상속한 클래스의 객체를 생성하여 execute 메소드를 실행시킨다.
 AsyncTask는 3개의 타입 파라미터를 받는다. 각각은 순서대로 Params, Progress, Result라고 부른다. Params는 태스크를 실행시킬 때 넘기는 파라미터의 타입이고, Progress는 UI 스레드에 태스크의 진행 상황을 넘기기 위해서 사용되는 파라미터의 타입이고, Result는 태스크의 결과를 UI 스레드에 넘길 때 사용되는 타입이다.

 AsyncTask를 상속받는 클래스는 반드시 doInBackground 함수를 구현해야 한다. 이 함수는 태스크를 백그라운드에서 실행시킬 일을 의미한다. 이 함수는 자동으로 백그라운드 스레드에서 실행되며, 수동으로 실행시켜서는 안 된다. 또한 이는 백그라운드 스레드에서 실행되기 때문에 UI를 수정하면 안 된다. 중간에 UI를 수정하고 싶으면 publishProgress 함수를 호출하여 UI 스레드에서 콜백이 실행되도록 해야 한다.  그 외에 필요에 따라서 onPreExecute, onPostExecute, onProgressUpdate, onCancelled 함수를 구현할 수 있다. 각각 태스크가 수행되기 전, 수행된 후, publishProgress 함수가 불렸을 때, 태스크가 취소됐을 때 UI 스레드에서 호출된다. 위의 상황에서 UI를 변경시키고자 할때는 위의 함수들을 구현해서 사용해야 한다.


1) http://developer.android.com/intl/ko/reference/android/os/AsyncTask.html

2016-03-04

멀티 쓰레드 환경에서 fork는 조심해야 한다.

 리눅스나 유닉스 같은 POSIX 시스템에서는 fork를 이용해서 자신과 똑같은 프로세스를 만들 수 있다. 이때 fork를 호출한 프로세스를 부모 프로세스라고 하고, 새로 생성된 프로세스를 자식 프로세스라고 부르는데, 자식 프로세스는 부모 프로세스의 모든 메모리를 복사한다.

 fork는 그 뒤 exec을 해서 다른 바이너리를 실행시키는 fork-exec이 일반적인 사용법이다. 하지만 자식 프로세스부모 프로세스와 완전히 같은 메모리를 가지기 때문에, 스레드가 존재하지 않던 시절에는 exec을 하지 않고 병렬 처리를 하기 위해서도 자주 사용되었다. 지금은 스레드를 사용하는 것이 더 사용하기 쉽고 가벼운 방식이기에 스레드를 병렬처리를 위해 스레드를 주로 사용하지만, 스레드보다 서로 간에 독립적이기 때문에 일을 분리하기 위해서 사용하기도 한다.

 분명히 fork는 없어서는 안 될 기능이지만 fork에는 태생적 한계가 있다. 애초에 fork는 스레드라는 개념이 존재하지 않던 시절에 만들어졌기 때문에 thread-safe 하지 않다. 따라서 멀티스레드 환경에서 사용하려면 조심해서 사용해야 한다.

 fork가 멀티스레드 환경에서 문제를 일으키는 이유는 fork부모 프로세스의 메모리를 전부 복사하지만, fork를 호출한 스레드를 제외한 나머지 스레드들은 죽어버리기 때문이다. 따라서 fork를 호출한 스레드 이외의 스레드에서 획득한 자원은 아무것도 해제되지 않는다. 메모리 릭도 충분히 문제지만, 이는 단순한 메모리 릭만을 의미하지 않는다.  fork가 복사한 메모리에는 힙이나 스택 이외에 mutexcondition variable 들도 포함된다. 만약 mutexcondition variable이 다른 스레드에서 사용된 채로 fork 된다면 해당 변수로 보호되는 critical section에는 다시는 진입할 수 없게 된다. 특히나 malloc 같은 thread-safe 한 함수는 대부분 내부적으로 글로벌한 mutex를 사용하기 때문에 내 코드에 mutex가 없어도 안심할 수 없다. 따라서 멀티스레드 환경에서는 fork 하기 전에 다른 스레드가 전부 멈추는 것을 확인하는 것이 좋다.

 하지만 fork 후 바로 exec 하면 이런 것을 신경 쓸 필요 없다. exec의 경우 메모리를 완전히 새로 만들기 때문에 이런 문제가 발생하지 않는다.