스터디 10주차 안드로이드 바인더 ipc 1
이번주는 안드로이드 바인더 IPC에 대하여 공부할 것이다.
바인더는 원래 IPC(Inter Process Communication) 도구이지만 안드로이드에서는 다른 프로세스에 있는 함수를 마치 현재 프로세스에 존재하는 함수처럼 사용할 수 있게 해주는 RPC(Remote Procedure Call)를 지원하는데 주료 이용된다.
1. 리눅스 메모리 공간과 바인더 드라이버
바인더를 이해하려면 먼저 안드로이드의 기반 커널인 리눅스 커널의 메모리 공간을 이해할 필요가 있다. 안드로이드의 프로세스는 리눅스와 마찬가지로 프로세스만의 고유한 가상 주소 공간에서 실행된다. 총 4GB에 달하는 가상 주소 공간은 3GB의 사용자 공간과 1GB의 커널 공간으로 나뉜다.(커널 설정으로 변경 가능함). 사용자 코드의 관련 라이브러리는 사용자 공간의 코드 영역, 데이터 영역, 스택 영역에서 동작하고 커널 공간에서 동작해야 할 코드는 커널 공간의 각 영역에서 동작한다. 그리고 프로세스는 아래 그림과 같이 각자 독립된 주소 공간을 가지고 별개로 동작한다.
그렇다면 자신의 독립된 공간을 가진 프로세스가 다른 프로세스에게 데이터를 어떻게 전달해야할까? 바로 프로세스 간 공유가 가능한 커널 공간을 이용하는 것이다. 프로세스는 독립적인 사용자 공간을 가지지만 커널 공간은 공유할 수 있다. 이는 커널 입장에서 봤을 때 프로세스는 하나의 작업 단위일 뿐이고 프로세스의 사용자 공간이 별도로 존재한다 하더라도 커널 공간에서 실행하는 테스크와 데이터와 코드는 서로 공유되기 때문이다.
물론 기존 리눅스에서도 커널 공간을 이용해 두 프로세스 사이의 통신을 수행하는 IPC 도구를 제공한다. 하지만 안드로이드에서는 단순히 메시지를 전달하는 IPC 개념이 아니라 상대방 프로세스에 존재하는 함수까지 호출할 수 있는 바인더라는 IPC 도구를 채택해 프로세스 간의 RPC를 지원한다.**(메시지만 전달 IPC, 함수까지 호출 RPC)**
바인더는 프로세스들이 사용자 공간을 공유하지 않는다는 제약 조건상에서 프로세스 간의 통신을 제공하기 위해 아래 그림처럼 커널 공간에서 동작하는 Binder(IPC) Driver라는 추상화된 드라이버를 사용한다.
안드로이드에서 바인더 드라이버를 추가해서 프로세스 간 통신을 수행하는 이유는 다음과 같다. 바인더는 리눅스의 뛰어난 메모리 관리 기법을 그대로 채용함으로써 커널 공간을 통해 데이터 전달시 데이터의 신뢰성을 확보할 수 있다. 그리고 사용자 공간에서 접근할 수 없는 공간인 커널 공간을 이용해 데이터를 주고 받기 때문에 IPC 간의 보안 문제도 동시에 해결할 수 있다.
2. 안드로이드 바인더 모델
위 그림을 살펴보면 서비스 클라이언트가 바인더를 통해 서비스 서버의 foo() 함수를 호출하는 과정을 볼 수 있다. 서비스 클라이언트는 바인더 IPC를 통해 서비스 서버의 foo() 함수를 호출하는 것과 같은 효과를 내기 위해 바인더 IPC 데이터를 서비스 서버로 전달한다. 여기서 바인더 드라이버는 foo() 함수를 호출하기 위한 바인더 IPC 데이터를 서비스 클라이언트에서 서비스 서버로 전달하는 역할을 한다.
IPC 데이터는 함수 호출과 관련된 내용, 즉 사용하고자 하는 **서비스에 해당하는 번호**와 **호출할 함수명**, **바인더 프로토콜(BINDER PROTOCOL)**로 구성된다. 먼저
-
서비스 번호 : 안드로이드에서 동작중인 여러 서비스를 구분하기 위한 것
-
함수명 : 서비스 서버에서 동작하는 여러 서비스 가운데 서비스 클라이언트가 호출할 함수를 찾기 위한 것
-
바인더 프로토콜 : 바인더 드라이버와 바인더를 이용하는 프로세스 간의 IPC 데이터를 처리하는 규약이다.
이러한 구성 요소는 IPC 데이터의 각 변수로 저장된다.
위 그림은 IPC 데이터의 포맷을 나타낸다.
-
핸들 : 서비스를 구별하는 번호를 의미한다. 바인더 드라이버에서는 이러한 핸들 값을 통해 어떤 서비스에 바인더 IPC 데이터를 전달해야 하는지를 결정한다.
-
RPC코드 : 서비스의 포함된 함수 중 어느 함수에 데이터를 전달해야 하는지를 나타낸다.
-
RPC데이터 : RPC 코드가 가리키는 함수에 전달할 인자 값을 의미한다.
-
바인더 프로토콜 : IPC 데이터의 처리 방법을 나타낸다.
2.1 바인더 IPC 데이터의 전달
바인더 드라이버는 문자 디바이스 드라이버처럼 open()이나 ioctl()과 같은 시스템 콜을 통해 접근 가능하다.
위 그림은 시스템 콜과 바인더 드라이버에 포함된 파일 연산 함수와의 연결 관계를 보여준다. 예를 들어 시스템 콜인 open() 함수는 바인더 드라이버의 binder_open() 함수와 연결된다.
바인더를 통해 RPC를 시도하는 애플리케이션은 open() 시스템 콜을 통해 바인더 드라이버의 파일 디스크립터를 얻는다. 그리고 mmap() 시스템 콜을 통해 커널 내에서 IPC 데이터를 수신하기 위한 공유 공간을 확보한다. 마지막으로 ioctl() 함수의 인자로 바인더 드라이버에 IPC 데이터를 전달한다. 바인더 드라이버의 ioctl() 함수는 다음과 같은 형태로 사용한다.
- ioctl(파일 디스크립터, ioctl 명령어, 데이터 타입)
ioctl() 함수의 첫 번째 인자는 바인더 드라이버를 OPEN 할 때 반환되는 파일 디스크립터이고, 세 번째 인자는 두 번째 인자인 ioctl 명령어의 종류에 따라 달라진다.
ioctl 명령어 | 바인더 드라이버의 동작 | 데이터 타입 |
---|---|---|
BINDER_WRITE_READ | 프로세스 간의 바인더 IPC 데이터를 주고받는다. | struct binder_write_read |
BINDER_SET_IDLE_TIMEOUT | 사용되지 않는다. | int64_t |
BINDER_SET_IDLE_PRIORITY | 바인더 드라이버에 등록된 프로세스의 바인더 스레드(BINDER THREAD)의 최대 개수를 조정한다. | size_t |
BINDER_SET_MAX_THREADS | 사용되지 않는다. | int |
BINDER_SET_CONTEXT_MGR | 바인더 드라이버의 특수 노드(Special Node)를 지정한다. | int |
BINDER_THREAD_EXIT | 바인더 스레드를 제거한다. | int |
BINDER_VERSION | 바인더 프로토콜의 버전을 제공한다. | struct binder_version |
이번 장에서 중점적으로 다루는 IPC 데이터는 BINDER_WRITE_READ ioctl 명령어를 통해 바인더 드라이버로 전달된다. BINDER_WRITE_READ 뿐 아니라 다른 ioctl 명령어도 바인더 IPC와 관련은 있다. 하지만 바인더 IPC 데이터를 전달하는 데 초점을 맞추기 위해 앞으로는 BINDER_WRITE_READ에 따른 바인더 드라이버의 동작에 관해서만 설명하겠다.
2.2 바인더 IPC 데이터의 흐름
지금까지 바인더 IPC 데이터의 역할과 IPC 데이터가 바인더 드라이버로 어떻게 전달하는지 살펴보았다. 그렇다면 바인더 IPC 데이터가 서비스 클라이언트에서 서비스 서버로 전달되기 까지 어떠한 과정을 거치는지 살펴보자.
위 그림은 서비스 클라이언트에서 서비스 서버에 존재하는 서비스의 함수를 호출하는 과정을 보여준다. 그림에서 서비스 클라이언트에서 서비스 서버의 foo() 함수를 호출하기 위해 RPC를 시도할 때 바인더 IPC 데이터가 어떤 식으로 전해지는지 확인할 수 있다.
위 그림에서 서비스 클라이언트가 바인더 드라이버에게 ioctl() 시스템 콜을 통해 바인더 IPC 데이터를 전달하면 바인더 드라이버는 이 데이터를 서비스 서버에게 전달한다. 이때 바인더 드라이버는 IPC 데이터에서 바인더 프로토콜을 파악하여 데이터 전달 여부를 결정하게 된다.
안드로이드는 서비스 클라이언트가 서비스를 사용하기 위한 몇 가지 추상 계층을 제공한다. 서비스 클라이언트는 하위 계층에서 제공하는 동작을 통해 서비스 계층에서 RPC를 수행한다. 서비스 계층 아래에 있는 계층에서는 서비스 계층의 RPC를 지원하기 위한 바인더 IPC 데이터를 생성한다.
-
서비스 계층 : 특정 기능을 수행하는 서비스의 함수가 존재하는 계층이다. 서비스 클라이언트는 이 계층에서 사용하고자 하는 서비스의 함수를 가상으로 호출하고, 서비스 서버는 서비스 클라이언트가 요청한 서비스의 함수를 실제로 호출한다.
-
RPC 계층 : 서비스 클라이언트는 이 계층에서 서비스의 함수를 호출하기 위한 RPC 코드와 RPC 데이터를 생성한다. 서비스 서버는 전달받은 RPC 코드를 코대로 함수를 찾고 RPC 데이터를 전달한다.
-
IPC 계층 : RPC 계층에서 만든 RPC 코드와 RPC 데이터를 바인더 드라이버에 전달하기 위한 바인더 IPC 데이터로 캡슐화하는 역할을 한다.
-
바인더 드라이버 계층 : IPC 게층으로부터 전달받은 바인더 IPC 데이터를 통해 서비스를 가진 서비스 서버를 찾은 후 IPC 데이터를 전달한다.
2.3 바인더 프로토콜
바인더 프로토콜은 바인더 IPC 데이터에 포함되어 IPC 계층에서 바인더 드라이버로 전달되거나 바인더 드라이버에서 IPC 계층으로 전달된다. 바인더 프로토콜은 전달 방향에 따라 두 가지로 분류된다. 바인더 프로토콜이 IPC 계층에서 바인더 드라이버로 전송될 때는 ‘BINDER COMMAND PROTOCOL’이라 하고, 바인더 드라이버에서 IPC 계층으로 전달될 때는 ‘BINDER RETURN PROTOCOL’이라 한다.
BINDER COMMAND PROTOCOL은 접두사로 ‘BC_‘를 가지며, BINDER RETURN PROTOCOL은 ‘BR_‘을 접두사로 가진다. 바인더 프로토콜은 흔히 네트워크 상에서 쓰는 프로토콜처럼 데이터를 송신하는 측과 수신하는 측에서 모두 알고 있는 규약이다. 따라서 바인더 IPC를 이용하는 프로세스와 바인더 드라이버는 헤더 파일에 바인더 프로토콜을 정의하고 있다.
분류 | 바인더 프로토콜 | 의미 |
---|---|---|
BINDER COMMAND PROTOCOL | BC_TRANSACTION | 바인더 IPC 데이터 송신 측이 바인더 드라이버를 통해 IPC 데이터를 수신 측으로 전달함. |
BINDER RETURN PROTOCOL | BR_TRANSACTION | 바인더 IPC 데이터 수신 측은 IPC 데이터를 해석하고 처리함. |
위 표는 IPC 데이터를 전달할 때 사용하는 바인더 프로토콜을 보여준다.
위 그림은 BC_TRANSACTION과 BR_TRANSACTION 바인더 프로토콜에 따른 바인더 드라이버와 IPC 데이터 수신 측의 동작을 간략히 나타낸 것이다.
-
서비스 클라이언트는 BC_TRANSACTION 프로토콜을 통해 바인더 드라이버에 IPC 데이터 전달을 명령한다.
-
바인더 드라이버는 전달받은 IPC 데이터를 토대로 바인더 프로토콜이 BC_TRANSACTION이면 IPC 데이터의 핸들을 통해 서비스 서버를 찾는다.
-
이후 바인더 드라이버는 바인더 프로토콜을 BR_TRANSACTION으로 변경하고, 이를 IPC 데이터에 실어서 서비스 서버로 전달한다.
-
서비스 서버는 전달받은 바인더 프로토콜이 BR_TRANSACTION이지 체크한 후, IPC 데이터를 분석해서 서비스 사용자가 요청한 함수를 호출한다.
-
결국 RPC 코드가 서비스 서버의 함수를 결정하는 것이다.
다음 절에서 RPC 코드와 서비스가 가진 함수와의 관계를 좀더 살펴보자.
2.4 RPC 코드와 RPC 데이터
일반적으로 소스 코드에서 이미 정의된 함수를 호출할 때는 함수의 이름과 함수에 전달된 인자를 명시해야 한다. 마찬가지로 바인더를 통해 상대방의 함수를 호출할 때에도 상대방의 함수 이름과 전달될 인자를 전달한다.
실제로 서비스 클라이언트는 서비스 서버에 존재하는 서비스의 함수를 사용하기 위해 각 함수에 해당하는 식별자를 바인더 IPC 데이터에 담아 전달하는데, 이를 RPC 코드라고 한다. 그리고 함수의 인자 역시 IPC 데이터에 담아 전달하는데, 이것을 RPC 데이터라고 한다. 서비스 서버가 가진 서비스의 함수를 호출하려면 서비스 클라이언트에서는 반드시 서비스 서버가 가진 RPC 코드를 알아야 한다.
2.5 바인더 어드레싱
안드로이드에는 다양한 서비스를 모두 목록화해서 관리하는 컨텍스트 매니저라는 특별한 프로세스가 있다. 컨텍스트 매니저는 서비스마다 핸들(바인더 IPC의 목적지 주소로 사용)이라는 번호 값을 할당하고, 서비스의 추가/검색 등의 관리 기능을 수행한다. 컨텍스트 매니저를 지금은 서비스 목록을 관리하는 시스템 프로세스 정도로만 생각하면 된다.
앞서 설명했듯이 바인더 드라이버는 IPC 데이터의 핸들을 가지고 서비스 서버를 찾는데, 이러한 과정을 바인더 어드레싱이라고 한다. 바인더 어드레싱을 위해서 먼저 서비스 서버는 자신이 가진 서비스에 대한 접근 정보를 컨텍스트 매니저에 등록해야 한다. 이러한 서비스 등록 과정에서 서비스 서버는 ADD_SERVICE라는 RPC 코드와 등록할 서비스 이름(RPC 데이터), 그리고 핸들을 0으로 지정한 IPC 데이터를 바인더 드라이버에 전달한다.
위 그림은 서비스 서버가 자신의 서비스를 컨텍스트 매니저에게 등록할 때의 바인더 어드레싱을 나타낸다.
-
바인더 드라이버는 먼저 핸들 0에 해당하는 바인더 노드를 찾는다. 핸들 0에 해당하는 바인더 노드는 컨텍스트 매니저를 의미하기 때문에 서비스 서버는 IPC 데이터를 컨텍스트 매니저에게 전달하게 된다.
-
이후 바인더 드라이버는 서비스 서버의 서비스 A에 해당하는 바인더 노드를 하나 생성한다.
-
그리고 컨텍스트 매니저가 생성된 순서대로 번호가 매겨지고, 이 번호는 IPC 데이터에 담겨 컨텍스트 매니저로 전달된다.
-
컨텍스트 매니저는 IPC 데이터에 들어 있는 서비스 이름과 바인더 노드의 번호를 서비스 리스트에 등록한다.
서비스 등록 과정은 안드로이드 부팅 단계에서 끝난다. 서비스 등록이 끝나면 위 그림과 같이 컨텍스트 매니저의 서비스 목록, 바인더 드라이버의 바인더 노드, 그리고 서비스 서버들의 서비스가 연결된다. 따라서 컨텍스트 매니저에 등록된 서비스는 다른 프로세스가 사용가능한 상태가 된다.
다음으로 서비스 클라이언트가 서비스를 검색하는 과정을 살펴보자. 위 그림은 서비스 클라이언트가 서비스를 검색할 때의 바인더 어드레싱을 나타낸다.
-
서비스 클라이언트는 GET_SERVICE라는 RPC 코드와 요청할 서비스 이름(RPC 데이터), 그리고 핸들을 0으로 지정한 IPC 데이터를 바인더 드라이버를 통해 컨텍스트 매니저에게 전달한다.
-
IPC 데이터를 전달받은 컨텍스트 매니저는 자신의 서비스 목록에서 서비스 이름에 해당하는 서비스 번호를 찾은 다음 이를 바인더 드라이버에 전달한다.
-
바인더 드라이버는 전달된 데이터에서 서비스 목록 번호에 해당하는 참조 데이터를 찾는다.
-
그러고 나서 서비스를 클라이언트 쪽에 참조 데이터를 생성해 컨텍스트 매니저의 참조 데이터가 가리키는 바인더 노드를 연결한다.
-
연결된 바인더 노드는 서비스 서버가 자신의 서비스를 위해 생성했던 바인더 노드에 해당한다.
-
그리고 바인더 드라이버는 생성한 참조 데이터를 생성된 순서대로 번호를 매겨 서비스 클라이언트에게 알려준다.
-
서비스 클라이언트는 이 번호를 핸들로 지정하여 서비스 서버의 바인더 노드를 찾게 된다.
-
마지막으로 서비스 클라이언트는 이전 단계에서 전달받은 참조 데이터의 번호를 핸들에 저장하고 사용할 서비스의 함수에 해당하는 RPC 코드와 RPC 데이터를 IPC 데이터에 담아 서비스 서버로 전달하여 서비스 A가 가진 함수를 호출한다.
위 그림은 바인더 어드레싱을 통해 서비스를 사용하는 과정을 보여준다. 서비스 서버는 IPC 데이터를 수신한 후 RPC 코드와 RPC 데이터에 따른 처리를 할 것이다. 바인더 어드레싱은 각 연결 주체가 굉장히 복잡한 형태의 자료구조로 연결되어있다. 따라서 추가적인 공부가 더 필요할 것이다.
3. 안드로이드 바인더 드라이버 분석
이번엔 바인더의 핵심인 바인더 드라이버 위주로 분석을 진행해보겠다. 따라서 바인더 드라이버를 통한 RPC 과정보다는 바인더 드라이버의 실제 역할인 IPC 과정에 초점을 맞춰서 알아볼 것이다. 그리고 서비스 클라이언트가 서비스 서버의 서비스를 사용하기까지의 IPC 과정을 각각 ‘서비스 등록’, ‘서비스 검색’, ‘서비스 사용’ 3단계로 나누고 이를 서비스 사용 과정이라 하겠다. 3.1에선 프로세스 관점으로 3.2에선 바인더 드라이버 관점으로 서비스 사용 과정에 대해서 살펴볼 것이다.
3.1 프로세스 관점에서의 서비스 사용
서비스 클라이언트가 서비스를 사용하려면 아래의 3단계를 거쳐야 한다. 이 과정은 모든 시스템 서비스에 동일하게 적용된다.
-
서비스 등록(서비스 서버와 컨텍스트 매니저 사이의 IPC)
-
서비스 검색(서비스 클라이언트와 컨텍스트 매니저 사이의 IPC)
-
서비스 사용(서비스 클라이언트와 서비스 서버 사이의 IPC)
서비스 등록
서비스 등록 단계에서는 서비스 서버에 존재하는 서비스를 컨텍스트 매니저의 서비스 목록에 등록한다. 이 단계에서 두 프로세스의 역할은 다음과 같다.
IPC 주체 | 역할 | |
---|---|---|
IPC 데이터 | IPC 응답 데이터 | |
컨텍스트 매니저 | 수신 | 송신 |
서비스 서버 | 송신 | 수신 |
서비스 서버는 컨텍스트 매니저의 서비스 등록 함수를 호출하기 위한 IPC 데이터를 생성한 후 이를 바인더 드라이버에 전달한다.
바인더 드라이버는 IPC 데이터를 컨텍스트 매니저에 전달하고, 컨텍스트 매니저는 전달받은 IPC 데이터의 RPC 코드에 해당하는 함수를 호출한다.
위 그림은 이 과정에서 발생하는 컨텍스트 매니저와 서비스 서버 사이의 상호작용을 보여준다. 이 단계에서 서비스 서버는 컨텍스트 매니저의 서비스 함수를 사용하는 서비스 클라이언트가 되고, 컨텍스트 매니저는 서비스 서버가 된다.(서비스 등록이므로 컨텍스트 매니저가 서버 역할을 하는 것이다.)
(1)~(3) 컨텍스트 매니저는 open() 시스템 콜을 통해 바인더 드라이버를 연다. 그러고 나서 mmap() 시스템 콜을 호출해서 커널 공간에 IPC 데이터 수신용 버퍼를 확보한다. 이후 컨텍스트 매니저는 IPC 데이터를 기다리는 대기 상태에 빠진다.
(4)~(5) 서비스 서버도 서비스 등록을 위해 드라이버를 연다. 그러고 나서 mmap() 시스템 콜을 이용해 IPC 응답 데이터를 받기 위한 수신용 버퍼를 확보한다.
(6) 서비스 서버는 등록한 서비스의 이름을 RPC 데이터에 저장한 후 컨텍스트 매니저의 서비스 등록 함수에 해당하는 RPC 코드(ADD_SERVICE)와 컨텍스트 매니저의 핸들 번호인 0을 담은 IPC 데이터를 생성한다.
(7) 서비스 서버는 ioctl() 시스템 콜을 통해 바인더 드라이버에 IPC 데이터를 전달한다. 바인더 드라이버는 서비스 서버의 IPC 데이터에서 컨텍스트 매니저(핸들 번호가 0인 프로세스)에 IPC 데이터를 전달한다.
(8)~(9) 컨텍스트 매니저는 IPC 데이터에서 RPC 코드를 파악해서 서비스 등록함수에 해당하는 RPC 코드일 경우 서비스 등록 함수를 호출한다. 해당 함수에서는 RPC 데이터인 서비스 이름을 파악해서 서비스 목록에 등록한다.
(10) 컨텍스트 매니저는 서비스가 정상적으로 등록됐음을 알리기 위해 IPC 응답 데이터를 생성하고, 이를 서비스 서버에서 전달해서 바인더 IPC 사이클을 마무리 한다.
서비스 등록을 마친 서비스는 서비스 클라이언트가 사용할 수 있는 상태가 된다.
서비스 검색
서비스 검색은 서비스 클라이언트가 서비스 서버의 서비스를 사용하기 위해 컨텍스트 매니저에게서 서비스의 번호를 요청하는 단계다. 서비스 검색 단계에서는 두 프로세스가 하는 역할은 다음과 같다.
IPC 주체 | 역할 | |
---|---|---|
IPC 데이터 | IPC 응답 데이터 | |
컨텍스트 매니저 | 수신 | 송신 |
서비스 클라이언트 | 송신 | 수신 |
서비스 검색 단계에서 서비스 클라이언트는 컨텍스트 매니저의 서비스 검색 함수를 호출한다. 서비스 클라이언트는 컨텍스트 매니저의 함수를 호출하기 위한 IPC 데이터를 생성한 후 바인더 드라이버에게 전달한다. 바인더 드라이버는 IPC 데이터를 컨텍스트 매니저에게 전달하고, 컨텍스트 매니저는 전달받은 IPC 데이터의 RPC 코드에 해당하는 함수를 호출한다.
(1) 서비스 등록을 마친 컨텍스트 매니저는 다음 IPC 데이터를 기다리는 상태로 들어간다.
(2)~(4) 서비스 클라이언트는 컨텍스트 매니저에 IPC 데이터를 전달하기 위해 바인더 드라이버를 연다. 그리고 mmap() 시스템 콜을 통해 IPC 응답 데이터를 받을 수신 버퍼를 확보한다. 서비스 클라이언트는 사용할 서비스의 이름을 RPC 데이터에 저장한 후 컨텍스트 매니저의 서비스 검색 함수에 해당하는 RPC 코드(GET_SERVICE)의 컨텍스트 매니저의 핸들 0을 담을 IPC 데이터를 생성한다.
(5) 서비스 클라이언트는 ioctl() 시스템 콜을 통해 바인더 드라이버에 IPC 데이터를 전달한다. 바인더 드라이버는 서비스 서버의 IPC 데이터에서 핸들 0에 해당하는 프로세스인 컨텍스트 매니저에게 IPC 데이터를 전달한다.
(6)~(8) 컨텍스트 매니저는 IPC 데이터에서 RPC 코드를 파악하고 서비스 검색에 해당하는 RPC 코드일 경우 서비스 검색 함수를 호출한다. 해당 함수 내에서 RPC 데이터인 서비스 이름을 서비스 목록에서 검색한 후 해당 서비스의 바인더 노드 번호를 찾는다. 컨텍스트 매니저는 IPC 응답 데이터에 바인더 노드의 번호를 담아 서비스 클라이언트에 전달한다.
(9) 서비스 클라이언트는 IPC 응답 데이터에서 사용하고자 하는 서비스의 바인더 노드 번호를 획득한다.
이 과정이 끝나면 서비스 클라이언트는 서비스를 사용할 준비가 완료된다.
서비스 사용
서비스 사용 단계에서는 서비스 클라이언트가 서비스 서버의 실제 서비스를 사용한다. 서비스 사용 단계에서 두 프로세스의 역할은 다음과 같다.
IPC 주체 | 역할 | |
---|---|---|
IPC 데이 | IPC 응답 데이터 | |
서비스 서버 | 수신 | 송신 |
서비스 클라이언트 | 송신 | 수신 |
(1) 서비스 서버도 컨텍스트 매니저처럼 지속적으로 IPC 데이터를 수신하는 상태에 있다. 그리고 IPC 데이터를 처리하고 나면 다시 대기 상태로 들어가서 다음 IPC 데이터를 기다린다.
(2)~(3) 서비스 클라이언트는 서비스를 사용하기 위해 IPC 데이터를 생성한다. 이때 IPC 데이터는 서비스 서버의 함수에 해당하는 RPC 코드와 함수의 인자인 RPC 데이터, 그리고 서비스에 대한 핸들로 구성된다. 그리고 ioctl() 시스템 콜을 통해 바인더 드라이버에 IPC 데이터를 전달한다. 바인더 드라이버는 서비스 클라이언트의 IPC 데이터에서 핸들에 해당하는 프로세스인 서비스 서버에 IPC 데이터를 전달한다.
(4)~(5) 서비스 서버는 IPC 데이터에서 RPC 코드를 파악한다. 그리고 RPC 코드에 해당하는 서비스의 함수를 호출한다. RPC 데이터는 함수의 인자와 같은 형태로 서비스의 함수 내부에서 사용된다. 함수를 실행하고 난 후 서비스 서버는 함수 실행이 완료됐음을 알리기 위해 IPC 응답 데이터에 바인더 노드 번호를 담아 서비스 클라이언트에 전달한다.
(6) 서비스 클라이언트는 IPC 응답 데이터에 따른 응답 처리를 한다.
지금까지 안드로이드의 서비스를 사용하기 위해 컨텍스트 매니저, 서비스 서버, 서비스 클라이언트가 어떻게 동작하는지 프로세스 관점에서 살펴보았다. 프로세스 관점에서 IPC가 이뤄지는 과정을 이해하고 나면 바인더 드라이버 입장에서의 IPC를 이해하기가 그리 어렵지 않을 것이다.
3.2 바인더 드라이버 관점에서의 서비스 사용
서비스 등록
컨텍스트 매니저는 다른 모든 서비스 서버에 앞서 실행되는 프로세스이므로 바인더 드라이버를 가장 먼저 사용한다. 이는 서비스 서버의 서비스 등록 요청이나 서비스 클라이언트의 서비스 검색 요청을 처리하기 위한 대기 상태에 먼저 진입하기 위해서이다.
위 그림은 바인더 드라이버 관점에서의 서비스 등록 단계이다.
(1)~(3)은 컨텍스트 매니저가 IPC 데이터 수신 대기 상태로 들어가기 위해 바인더 드라이버를 사용하는 과정이다. 바인더 드라이버에는 바인더 IPC 수행 시 필요한 구조체들이 존재한다. 이러한 구조체 가운데 binder_proc이라는 구조체는 IPC 동작에 필요한 다른 구조체를 포인터 형태로 접근할 수 있는 루트 구조체이다. 그리고 binder_proc 구조체는 프로세스당 하나씩 존재하는데, 이 구조체를 통해 상대편의 프로세스를 찾을 수 있고, 해당 프로세스가 가진 IPC 데이터 수신 버퍼를 찾는다.
(1) 컨텍스트 매니저는 open 시스템 콜을 통해 바인더 드라이버의 binder_open() 함수를 호출한다. 바인더 드라이버는 이 함수에서 컨텍스트 매니저를 위한 binder_proc 구조체를 생성하고 초기화한다.
(2) 컨텍스트 매니저는 IPC 데이터를 수신하기 위한 버퍼를 커널 공간에 생성한다. 컨텍스트 매니저는 mmap() 시스템 콜을 통해 바인더 드라이버의 binder_mmap() 함수를 호출한다. 바인더 드라이버는 binder_mmap() 함수를 통해 mmap() 함수 호출 시 인자로 전달되는 크기만큼 커널 공간에 IPC 데이터 수신 버퍼를 할당하여 binder_buffer라는 구조체에 저장된다. 그리고 binder_buffer 구조체는 binder_proc 구조체에 등록된다.
(3) 컨텍스트 매니저는 ioctl() 시스템 콜을 통해 바인더 드라이버의 binder_ioctl() 함수를 호출한다. binder_ioctl() 함수 안에서 컨텍스트 매니저는 상대편 프로세스(서비스 등록 단계에서는 서비스 서버)가 IPC 데이터를 보낼 때까지 대기한다.
(4) 서비스 서버는 open() 시스템 콜을 통해 바인더 드라이버의 binder_open() 함수를 호출한다. 바인더 드라이버는 서비스 서버의 binder_proc 구조체를 생성하고 초기화한다.
(5) 서비스 서버는 mmap() 시스템 콜을 통해 바인더 드라이버의 binder_mmap() 함수를 호출한다. 바인더 드라이버는 IPC 응답 데이터를 수신할 버퍼를 확보한 후 binder_buffer 구조체에 해당 버퍼를 저장한다. 이후 서비스 서버는 컨텍스트 매니저의 서비스 등록 함수를 사용하기 위한 IPC 데이터를 작성한다.
(6) 서비스 서버는 서비스를 등록하기 위해 IPC 데이터를 바인더 드라이버에 전달한다. 바인더 드라이버는 IPC 데이터에서 핸들에 해당하는 프로세스(컨텍스트 매니저)의 binder_node 구조체를 찾고, 소속된 binder_proc 구조체를 찾는다.
(7) 서비스 서버가 등록할 서비스에 대한 binder_node 구조체를 생성한다. 바인더 드라이버는 binder_node 구조체를 서비스 서버의 binder_proc 구조체와 컨텍스트 매니저의 binder_proc 구조체에 각각 등록한다.
(8) 바인더 드라이버는 (6)에서 찾은 컨텍스트 매니저의 binder_proc 구조체에서 binder_buffer 구조체에 등록된 수신 버퍼를 찾아 IPC 데이터를 송신한다.
(9) 바인더 드라이버는 IPC 데이터 송신 측 프로세스(서비스 서버)에 대한 binder_proc 구조체를 기억해둔다. 이는 컨텍스트 매니저가 서비스 서버로 응답 데이터를 보낼 때 서비스 서버에 대한 binder_proc 구조체를 찾기 위해서이다.
(10) 바인더 드라이버는 서비스 서버를 대기 상태로 변경하고, 컨텍스트 매니저를 대기 상태에서 깨워 IPC 데이터 수신 처리를 진행한다. 대기 상태에서 깨어난 컨텍스트 매니저는 서비스 서버가 전달한 IPC 데이터를 바인더 드라이버를 통해 수신한다. 컨텍스트 매니저는 수신한 IPC 데이터를 기반으로 서비스를 등록하고, 등록 완료를 알리기 위해 IPC 응답 데이터를 생성한다. 그리고 이를 바인더 드라이버로 전달한다.
(11) 바인더 드라이버는 (9)에서 기억해둔 서비스 서버의 binder_proc 구조체에서 binder_buffer 구조체에 등록된 수신 버퍼를 찾아 IPC 응답 데이터를 송신한다. 그리고 수신 측인 서비스 서버를 깨운다. 대기 상태에서 깨어난 서비스 서버는 IPC 응답 데이터를 수신하고, IPC 응답 데이터에 따른 처리를 한다.
서비스 검색
서비스 검색은 서비스 클라이언트가 사용할 서비스에 대한 핸들을 컨텍스트 매니저에게 요청하는 단계다. 서비스 클라이언트는서비스 요청에 대한 IPC 데이터를 컨텍스트 매니저로 송싱하고, 컨텍스트 매니저는 IPC 응답 데이터를 통해 서비스 클라이언트에게 핸들을 송신한다. 바인더 드라이버는 서비스 클라이언트가 요청한 서비스에 대한 binder_node 구조체를 서비스 클라이언트의 binder_proc 구조체에 등록하는 역할을 한다.
위 그림은 서비스 검색 단계의 바인더 드라이버의 동작을 나타낸 것이다.
(1) 컨텍스트 매니저는 IPC 데이터를 수신하기 위한 대기 상태에 있다.
(2)~(3) 서비스 클라이언트는 binder_proc 구조체를 생성하고 IPC 응답 데이터의 수신 버퍼를 확보하는 과정을 거친다.
(4) 서비스 클라이언트 ioctl() 시스템 콜을 통해 바인더 드라이버의 binder_ioctl() 함수를 호출한다. 바인더 드라이버는 서비스 등록 단계와 마찬가지로 핸들에 해당하는 컨텍스트 매니저의 binder_proc 구조체를 찾는다. 하지만 서비스 등록 단계와 달리 binder_node 구조체를 생성하지 않고 IPC 데이터만 컨텍스트 매니저의 수신 버퍼에 복사한다. 그리고 IPC 응답 데이터 수신 측을 찾기 위해 서비스 클라이언트의 binder_proc 구조체를 기억해둔다.
(5) 바인더 드라이버는 서비스 클라이언트를 대기 상태로 변경하고, 컨텍스트 매니저를 대기 상태에서 깨워 IPC 데이터 수신 처리를 진행한다. 대기 상태에서 깨어난 컨텍스트 매니저는 서비스 목록에서 요청한 서비스를 찾고 해당 서비스의 번호를 IPC 응답 데이터에 담아 바인더 드라이버로 전달한다.
(6) 바인더 드라이버는 전달받은 서비스 번호에 해당하는 binder_node 구조체를 찾는다. 그리고 (4)에서 기억한 서비스 클라이언트의 binder_proc 구조체에 해당하는 binder_node 구조체를 등록한다. 이 binder_node 구조체는 다음에 살펴볼 서비스 사용 단계에서 서비스 서버의 binder_proc 구조체를 찾는데 사용된다.
(7) 바인더 드라이버는 (6)에서 등록한 binder_node 구조체의 번호를 IPC 응답 데이터에 담아 서비스 클라이언트에 송신하고, 서비스 클라이언트를 깨운다. 서비스 클라이언트는 전달받은 번호를 서비스 사용 단계에서 핸들로 사용한다.
서비스 사용
서비스 검색 단계에서 바인더 드라이버는 서비스 등록 단계와 같은 형태로 동작하지만 IPC 데이터를 컨텍스트 매니저가 아닌 실제 서비스를 가진 서비스 서버에게 전달한다는 차이가 있다. 서비스 검색 단계를 거친 서비스 클라이언트는 서비스 서버가 가진 서비스의 바인더 노드 번호를 알고 있다. 따라서 서비스 클라이언트가 IPC 데이터를 생성할 때 컨텍스트 매니저의 핸들에 해당하는 0 대신 실제 사용하고자 하는 서비스의 바인더 노드 번호를 핸들로 지정한다.
(1) 서비스 서버는 IPC 데이터를 수신하기 위한 대기 상태에 있다.
(2) 서비스 클라이언트는 서비스 검색 단계에서 획득한 바인더 노드 번호를 핸들에 담아 IPC 데이터를 작성 후 바인더 드라이버로 전달한다. 바인더 드라이버는 전달받은 핸들에 해당하는 binder_node 구조체를 찾고 이를 통해 이 binder_node 구조체가 등록된 서비스 서버의 binder_proc 구조체를 찾는다. 그러고 나서 binder_proc 구조체에 등록된 binder_buffer 구조체를 통해 해당 수신 버퍼 영역에 IPC 데이터를 복사한다.
(3) 바인더 드라이버는 서비스 클라이언트의 태스크를 대기 상태로 변경하고, 서비스 서버를 대기 상태에서 깨운다. 대기 상태에서 깨어난 서비스 서버는 자신의 IPC 데이터를 수신하고, 해당 IPC 데이터의 RPC 코드와 RPC 데이터를 통해 서비스 함수를 수행한다.
(4) 서비스 서버는 서비스 함수 수행을 완료한 후에 IPC 응답 데이터 작성하고 바인더 드라이버를 전달한다. 바인더 드라이버는 서비스 등록 단계(10)와 동일하게 동작하여 IPC 응답 데이터를 서비스 클라이언트로 송신한다. 이로써 서비스 클라이언트의 서비스 사용을 위한 바인더 IPC 과정이 완료된다.
이로써 서비스 클라이언트가 서비스 서버에서 바인더 IPC데이터를 활용하여 서비스 기능을 이용하는 방법에 대하여 알아보았다. 다음주에는 실제 바인더 드라이버 코드를 분석하여 동작 상에 개선할 점은 없는지 탐색해볼 것이다.
참고문헌
인사이드 안드로이드