임베디드월드

글: 라영호 | ratharn@naver.com / 2012-05-07


[연재 차례]

1. 안드로이드 시스템의 역사 및 동향
2. 안드로이드 시스템과 리눅스
3. 안드로이드 플랫폼의 이해
4. 안드로이드 바인더(Binder)의 이해
5. 안드로이드 서비스
6. 안드로이드 SurfaceFlinger와 프레임버퍼 드라이버
7. 안드로이드 User Interface와 ADK2012
8. Linux Sound Device와 안드로이드 사운드 시스템
9. 안드로이드 카메라 시스템
10. 안드로이드 카메라와 멀티미디어 프레임워크
11. 안드로이드 카메라와 멀티미디어 프레임워크 ②
12. 안드로이드 시스템 디버깅 및 기타




안드로이드 시스템 개발자를 위한 안드로이드 시스템의 분석 및 이해 ④
안드로이드 바인더(Binder)의 이해


바인더
안드로이드에서 바인더는 각각 독립된 프로세서들을 연결해 주는 역할을 한다. 리눅스에서 시스템의 기능을 이용하기 위해서 시스템 콜(System Call)을 사용하여 시스템에서 제공하는 프로세서, 파일 시스템 기능을 이용하도록 제공하고 있다. 하지만 안드로이드에서는 각 독립적으로 운영되는 프로세스, 특히 서비스의 기능을 이용할 수 있도록 제공하는 것이 바인더의 핵심이라고 하겠다. 응용 프로그램 개발자의 입장에서는 서비스를 제공하는 서비스 프로세서와 어떠한 식으로 연결하여 응용 프로그램이 운영 되는지 이해하기 위해 바인더에 대한 내용을 알아야 하고, 안드로이드 시스템을 개발하는 시스템 개발자의 입장에서는 기존 서비스의 변경이나 기능 추가 또는 새로운 서비스를 구성하기 위해 어떠한 표준 인터페이스를 통해 서비스를 구현해야 하는지 알아야 하기 때문에 바인더에 대해 이해를 하고 있어야 한다. 바인더에 대한 개념을 이해하기 위해서는 먼저 IPC의 개념에 대해 이해를 하고 있어야 한다.


IPC(Inter Process Communication)

[그림 1] IPC의 개념 그림

기존 UNIX 계열에서 독립된 두 프로세서간의 통신에는 시그널(Signal), 파이프(Pipe), 메시지 큐(Message Queue), 세마포어(Semaphore), 메모리 맵드 파일(Memory Mapped File)과 같은 방법이 사용되었다. 각각 방법의 장단점과 사용 방법은 차이가 있지만 프로세서간의 데이터를 전달하고 통신하기 위해서 고안된 방법들이다.
[그림 1]은 대표적인 IPC의 예를 보여준다. 프로세스 A에서 프로세스 B로 데이터를 전송할 때 공유 메모리를 사용하여 필요한 정보를 전달하는 간단한 동작 예를 그림으로 보여주고 있다.


대표적인 IPC
1) 메시지 큐(Message Queue)
프로세서 간 주고 받는 메시지를 커널에 복사하고 읽어가는 방식이다. IPC 내의 메시지를 하나의 구조체 형태로 통째로 주고 받을 수 있어서 구현하기에 편리하다. 여러 가지 이벤트를 하나로 관리 사용하기에 편리한 구조다.

2) 공유 메모리(Shared Memory)
프로세스간에 공유 메모리를 지정하여, 이 영역에 복사하고 읽어가는 방식이다.

3) UDS(Unix Domain Socket)
기존 소켓 API와 유사하게 사용할 수 있다. 또한 여러 가지 이벤트를 하나로 사용하기에 편리한 구조이다.
IPC는 각 프로세스간 어떠한 식으로 데이터를 전송하고 데이터를 받을지에 대한 약속일 뿐이다. 따라서 시스템 레벨에서 별도로 관리하지는 않는다. 각각의 프로세서가 정해진 방법으로 통신을 할 수 있도록 구현을 해야 한다.


Binder란?
바인더의 역사는 BeOS라는 운영체제로부터 시작된다. 이제는 잊혀진 운영체제 및 컴퓨터 시스템이었지만 나름대로 신선한 충격을 주었던 운영체제다. BeOS에서 시작된 바인더의 개념은 Palm을 거쳐 안드로이드에 탑재가 된다. 바인더의 주 목적은 컴포넌트 기반 시스템 레벨 디자인을 지원할 수 있도록 설계된 오픈 소스 솔루션이다. 즉, 운영체제 혹은 시스템에서 제공해 주는 기능을 각각의 컴포넌트 혹은 모듈 형태로 만들고 이것을 운영할 수 있도록 만들어 주는 것이 바인더의 가장 큰 목적이다.



바인더의 특징
바인더의 가장 큰 특징은 객체지향(Object-oriented) 운영체제 환경을 제공한다는 것이다.
프로세스간의 리소스 관리를 지원하고, 응용 프로그램에 중심을 두기보다는 시스템 지향적이다. 새로운 컴포넌트 기반 시스템 레벨의 개발을 지원하고 있다. 다양한 기본서비스를 위한 컴포넌트/오브젝트를 가지고 있다. 하지만 바인더 자체로는 특정한 쓰레드(Thread) 모델을 지원하지 않는다. 바인더는 상용 수준의 시스템 레벨 서비스를 구현하는데 넓게 사용되어 왔다. 소형 모바일 장치 혹은 전용 하드웨어에서 실행하도록 설계된 플랫폼이다. 모바일 장치 세계는 데스크탑 보다 하드웨어의 기능에 있어 훨씬 다양한 분야에 사용이 된다.(크기, 배터리 수명 등)

바인더는 하드웨어를 이용하는 방법에 있어 더 많은 유연성을 가지고 시스템 설계를 할 수 있도록 도움을 준다. 특히, 메모리 보호기능과 프로세스간의 통신은 하이 레벨 운영체제에서는 상당한 오버헤드이기 때문에 바인더는 프로세스 구성방식에 묶여 있지 않는 시스템을 설계할 수 있도록 한다. 바인더는 프로세스가 실행되는 동안 특정한 속도/크기/안정성/보완성 등의 상충관계를 만들 수 있도록 다양한 부분을 할당할 수 있는 구조로 되어 있다.


안드로이드 바이더를 이용한 서비스의 구조


[그림 2] 안드로이드 IPC의 전체 구조

바인더를 이용한 안드로이드 시스템의 전체적인 구조는 [그림 2]와 같다.

● 바인더 드라이버 - 안드로이드 IPC 시스템의 핵심이다. 서비스 프로바이더와 서비스 유저 사이의 데이터를 전달하는 역할을 한다.
● 서비스 프로바이더 - 안드로이드 시스템에서 서비스를 제공하는 서비스 역할을 한다. 바인더 드라이버로부터 받은 데이터를 파싱하여 처리하는 역할을 한다.
● 서비스 매니저 - 특별한 서비스 프로바이더이다. 다른 서비스 프로바이더의 서비스를 관장한다.
● 서비스 유저 - 서비스 프로바이더의 기능을 호출하는 응용 프로그램이나 서비스다. 바인더의 핵심은 IPC 방법을 통하여 다른 프로세서, 즉 서비스 프로바이더의 기능을 호출하는 것이 목적이다.

바인더는 시스템적인 관점에서는 IPC지만 전체적인 동작 흐름은 서비스 프로바이더에 있는 기능(혹은 함수)를 원격 호출하고 그 결과를 리턴 받는 RPC(Remote Proce dure Call)과 같은 동작을 하게 된다. 즉, 서비스로 만들어진 여러 프로세서의 기능을 원활하게 이용할 수 있도록 만든 것이 바인더의 목적이라고 하겠다.


Binder Driver
바인더는 리눅스 커널에서의 드라이버 디렉토리 아래 2개의 파일로 구성되었다.

drivers/staging/android/binder.h
drivers/staging/android/binder.c


바인더 드라이버는 misc 디바이스 형태로 등록이 된다. “/dev/binder”로 시스템에 등록이 되어 나타난다. 통상 major 10, minor는 MISC_DYNAMIC_MINOR로 등록을 한다. 등록이 될 경우 proc 파일 시스템을 이용해서 정보를 확인할 수 있고 동작 상태에 대한 로그를 볼 수 있다. 바인더 문제에 대한 1차적인 분석은 /proc/bin der를 통하여 할 수 있다.

이 디렉토리에는 다음과 같은 정보가 담기게 된다.


proc 디렉토리
바인더를 호출한 프로세스의 내용
state
binder_read_proc_state() 함수를 이용해서 binder state 내용을 보여준다.
stats
binder_read_proc_stats()
transactions
binder_read_proc_transactions()
transaction_log
binder_read_proc_transaction_log(), 이 함수의 파라미터는 다음과 같다.
binder_transaction_log (type struct binder_transaction_log)
failed_transaction_log
binder_read_proc_transaction_log(), 이 함수의 파라미터는 다음과 같다.
binder_transaction_log_failed (type struct binder_transaction_log)




binder_proc structure의 주요 부분


* currnet process
* process ID
* memory mapping information
* 바인더의 통계정보
* thread 정보



사용자 영역에서 바인더를 제어하는 방법
바인더는 리눅스 시스템에서 운용되는 하나의 드라이버기 때문에 표준 스트림 인터페이스를 사용하여 제어를 하게 된다. 따라서 mmap(), poll(), ioctl() 등의 시스템 콜(system call)을 이용하여 바인더 드라이버를 오픈 하고 ioctl()을 통하여 제어하게 된다.

ioctl에서 사용되는 command는 다음과 같다.


#define BINDER_CURRENT_PROTOCOL_VERSION 7
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, int64_t)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t)
#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, int)
#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)
#define BINDER_THREAD_EXIT _IOW('b', 8, int)
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)




바인더 드라이버에서 ioctl 처리 함수의 일부분




static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;

//중략

ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
return ret;

mutex_lock(&binder_lock);
thread = binder_get_thread(proc);
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}

switch (cmd) {
case BINDER_WRITE_READ: {
struct binder_write_read bwr;
if (size != sizeof(struct binder_write_read)) {
ret = -EINVAL;
goto err;
}
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto err;
}








바인더 드라이버에 대한 ioctl 중 BINDER_WRITE_READ를 통하여 호출하는 프로세서로부터 바인더 드라이버까지 필요한 정보를 전달하게 된다.
바인더의 동작에는 바인더 드라이버로부터의 리턴 값, 드라이버로 명령을 내려 줘야 할 경우가 있는데 binder.h에는 다음과 같은 enum형들이 존재한다.



enum BinderDriverReturnProtocol {
BR_ERROR = _IOR('r', 0, int),
BR_OK = _IO('r', 1),
BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data),
BR_REPLY = _IOR('r', 3, struct binder_transaction_data),
BR_ACQUIRE_RESULT = _IOR('r', 4, int),
BR_DEAD_REPLY = _IO('r', 5),
BR_TRANSACTION_COMPLETE = _IO('r', 6),
BR_INCREFS = _IOR('r', 7, struct binder_ptr_cookie),
BR_ACQUIRE = _IOR('r', 8, struct binder_ptr_cookie),
BR_RELEASE = _IOR('r', 9, struct binder_ptr_cookie),
BR_DECREFS = _IOR('r', 10, struct binder_ptr_cookie),
BR_ATTEMPT_ACQUIRE = _IOR('r', 11, struct binder_pri_ptr_cookie),
BR_NOOP = _IO('r', 12),
BR_SPAWN_LOOPER = _IO('r', 13),
BR_FINISHED = _IO('r', 14),
BR_DEAD_BINDER = _IOR('r', 15, void *),
BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, void *),
BR_FAILED_REPLY = _IO('r', 17),
};
enum BinderDriverCommandProtocol {
BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data),
BC_REPLY = _IOW('c', 1, struct binder_transaction_data),

// 중략

BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14, struct binder_ptr_cookie),
BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15, struct binder_ptr_cookie),
BC_DEAD_BINDER_DONE = _IOW('c', 16, void *),

struct binder_proc - 바인더를 사용하는 프로세스관련 구조체
struct binder_thread - drivers/staging/android/binder.c에 정의되어 있음
이 구조체의 멤버 중 rb_node
이 스트럭쳐는 include/linux/rbtree.h에 선언되어 있음. 스케줄링 관련 변수임
struct binder_write_read
drivers/staging/android/binder.h에 정의되어 있음
binder device driver로의 BINDER_WRITE_READ ioctl의 인자로 사용되는 구조체임.
struct binder_transaction
프로세스간 binder transaction에 사용되는 구조체
struct binder_transaction_data
binder transaction의 BR_XXX, BC_XXX관련에 사용되는 ioctl의 인자






바인더 드라이버내의 중요 정보를 담고 있는 구조체

struct binder_proc - 바인더를 사용하는 프로세스관련 구조체
struct binder_thread - drivers/staging/android/binder.c에 정의되어 있음
이 구조체의 멤버 중 rb_node
이 스트럭쳐는 include/linux/rbtree.h에 선언되어 있음. 스케줄링 관련 변수임
struct binder_write_read
drivers/staging/android/binder.h에 정의되어 있음
binder device driver로의 BINDER_WRITE_READ ioctl의 인자로 사용되는 구조체임.
struct binder_transaction
프로세스간 binder transaction에 사용되는 구조체
struct binder_transaction_data
binder transaction의 BR_XXX, BC_XXX관련에 사용되는 ioctl의 인자






바인더 ioctl에 주로 사용되는 구조체의 내용들


struct binder_write_read {
signed long write_size; /* bytes to write */
signed long write_consumed; /* bytes consumed by driver */
unsigned long write_buffer;
signed long read_size; /* bytes to read */
signed long read_consumed; /* bytes consumed by driver */
unsigned long read_buffer;
};

struct binder_transaction_data {
union {
size_t handle; /* target descriptor of command transaction */
void *ptr; /* target descriptor of return transaction */
} target;
void *cookie; /* target object cookie */
unsigned int code; /* transaction command */

/* General information about the transaction. */
unsigned int flags;
pid_t sender_pid;
uid_t sender_euid;
size_t data_size; /* number of bytes of data */
size_t offsets_size; /* number of bytes of offsets */

// 중략
}




Service Manager
서비스 매니저는 안드로이드의 IPC 구조의 핵심인 바인더에 대한 커널단 처리를 하는 daemon 프로세스이다. 서비스 매니저는 바인더 드라이버인 dev/binder 통신/제어를 관리하게 된다. 바인더를 위한 context manager로 바인더관련 핸들에 대한 등록과 검색 처리를 해준다.
안드로이드 루트 파일시스템에서 서비스 매니저의 실행파일은 /system/bin/servicemanager에 위치한다. 바인더를 사용하려는 모든 프로세스는 서비스 매니저에 등록을 해야만 사용이 가능한 상태가 된다. 서비스를 하는 프로세스의 경우나 서비스를 요청하는 클라이언트의 경우 모두 동작하기 전에 반드시 서비스 매니저에 등록해야 한다.

Service Manager 소스는 다음과 같은 위치에 존재한다.
OpenBinder 원본
● commands/binder/binder.c
● commands/binder/binder.h
● commands/binder/service_manager.c


안드로이드 시스템에서는 다음과 같은 위치에 존재한다.
● frameworks/base/cmds/servicemanager/binder.h
● frameworks/base/cmds/servicemanager/binder.c
● frameworks/base/cmds/servicemanager/service _manager.c


[그림 3] 서비스 매니저의 주요 역할 그림

[그림 3]과 같이 서비스 매니저의 동작은 다음과 같다. 서비스 매니저는 바인더 드라이버에 대한 제어를 담당하고 바인더 드라이버에 대해서 시스템 콜을 사용하여 제어를 하게 된다.

service_manager code 동작은 다음과 같다
● open (): 바인더 드라이버를 오픈 한다.
● mmap (): 128 * 1024 바이트의 크기만큼 메모리를 할당한다.
● ioctl (BINDER_SET_CONTEXT_MGR): set the context for the mgr
● 메인 루프인 binder_loop ()에서는
- ioctl (BINDER_WRITE_READ)를 이용해서 읽고
- binder_parse()를 이용해서 binder를 다루는 루틴으로 들어간다
● binder_parse()함수
- ioctl()로 read된 data를 cmd변수로 받고
- BR_TRANSACTION처리에서는 service를 증가시키고, 감시하기 위하여 “void binder_loop(struct binder_state *bs, binder_handler func)” binder_loop()의 두 번째 인자로 입력된 함수를 호출한다. 이 func는 svcmgr_handler() 이다.

service_manager.c의 메인 함수는 다음과 같다.



int main(int argc, char **argv){
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;
bs = binder_open(128*1024);
if (binder_become_context_manager(bs)) {
LOGE("cannot become context manager (%s)n", strerror(errno));
return -1; }
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}





int binder_parse(struct binder_state *bs, struct binder_io *bio,uint32_t *ptr, uint32_t size, binder_handler func) {

while (ptr < end) {
uint32_t cmd = *ptr++;
switch(cmd) {

case BR_TRANSACTION: {

binder_dump_txn(txn);
if (func) {

res = func(bs, txn, &msg, &reply); // 여기서 svcmgr_handler() 호출
binder_send_reply(bs, &reply, txn->data, res);
… }
… }






다양한 서비스들은 svclist 라는 linked list에 저장이 되며 이 리스트는 binder_ 로 시작하는 함수들에서 사용된다 (ex> binder_acquire()로 입력되는 값으로 사용된다)
binder_parse()의 switch 문에서 BR_REPLY를 처리할 때 binder_io type의 data node를 채운다.


안드로이드 프레임워크에서 바인더


[그림 4] 바인더의 동작 그림

utils 라이브러리는 Android의 Binder 관련 핵심이다. 이 핵심부분은 컴파일 된 후 libutils.so로 구성되며 안드로이드 시스템에서의 public library로 사용된다.

[그림 4]는 안드로이드 시스템에서 어떻게 서비스가 동작되고 사용되는지 보여주는 그림이다.
● 서비스 매니저는 바인더에서 컨텍스트 매니저(Context Manager)로 등록된다.
● Service B는 처음 안드로이드 시스템이 시작될 때 서비스 매니저를 통하여 서비스로써 등록이 된다.
● App A는 Service B에 있는 foo()라는 함수를 호출하는 것이 목적이다. 즉, App A의 입장에서는 단순히 Service B에 있는 foo()라는 함수를 호출하는 것이지만, 시스템 내부에서는 바인더의 복잡한 동작이 이루어지게 된다.
● 즉, App A에서 Service B에 있는 foo()라는 함수를 호출하는 것은 App A에서 Service B의 foo() 함수를 RPC(Remote Procedure Call)하는 것이지만 실제 안드로이드 시스템 내부에서는 바인더 기능을 사용해서 IPC가 이루어지는 것이다.
● IPC의 모든 내용들은 서비스 매니저와 바인더 드라이버를 통하여 이루어지게 된다.


[그림 5]바인더 동작의 내부 구조

[그림5]는 바인더의 전체적인 동작 구조를 보여준다. Libutil.so를 사용하여 바인더 드라이버를 통하여 통신이 이루어지고, 전체적인 관장은 서비스 매니저를 통해 이루어지게 된다.

두 개의 프로세스, 프로세스 1은 서비스의 기능을 사용하는 응용 프로그램 프로세스이고, 프로제스 2는 실제 서비스를 제공하는 서비스로 구성된다. 프로세스 1에서 호출하는 모든 기능들은 BnABC 형태의 함수로 프로세스 2에 구현이 되어 있고, 이 서비스를 호출하는 프로세스 1에서는 BpABC 형태로 구성된 함수를 호출하게 된다. 여기서 두 BpABC, BnABC 형태의 함수는 Proxy, Native 라는 이름으로 불리며, 서비스를 이용하는 응용 프로그램에서 마치 함수처럼 호출하여 서비스내의 기능을 이용할 수 있도록 하는 역할을 한다. 자세한 내용은 뒤에서 다루도록 하겠다.


[그림 6] 응용 프로그램에서 서비스 호출 구조

바인더 관련 파일들

Android 소스에서의 경로
frameworks/base/include/binder/*
frameworks/base/libs/binder/*

Android binder관련 메인 헤더 파일들
utils/RefBase.h - Reference count를 다루는 RefBase class에 대한 정의.
Parcel.h - IPC transmission에 사용되는 data container에 대한 정의.
IBinder.h - Binder abstract object interface, IBinder Class에 대한 정의
Binder.h - binder, binder object의 기본적인 함수, BpRefBase에 대한 class를 정의.
BpBinder.h - BpBinder functions, BpBinder class에 대한 정의
IInterface.h - 일반적인 class를 통한 binder에 대한 추상적인 인터페이스를 정의
IInterface, class template BnInterface, class template BpInterface 에 대한 정의가 있음.
ProcessState.h - process의 state에 대한 class인 ProcessState를 정의
IPCThreadState.h - IPC thread의 상태를 정의, IPCThreadState class를 정의.
IServiceManager.h:

Service Manager인터페이스 관련 header
IPermissionController.h: - 접근 제어 클래스


struct binder_proc - 바인더를 사용하는 프로세스관련 구조체
struct binder_thread - drivers/staging/android/binder.c에 정의되어 있음
이 구조체의 멤버 중 rb_node
이 스트럭쳐는 include/linux/rbtree.h에 선언되어 있음. 스케줄링 관련 변수임
struct binder_write_read
drivers/staging/android/binder.h에 정의되어 있음
binder device driver로의 BINDER_WRITE_READ ioctl의 인자로 사용되는 구조체임.
struct binder_transaction
프로세스간 binder transaction에 사용되는 구조체
struct binder_transaction_data
binder transaction의 BR_XXX, BC_XXX관련에 사용되는 ioctl의 인자




끝으로
지금까지 안드로이드 바인더의 구조 및 전체적인 내용에 대해 살펴봤다. 바인더에 대한 기초적인 접근을 하였고 세부적인 내용은 다음호에서 좀 더 다루고자 한다. 안드로이드에서 바인더는 서비스를 구성하는 핵심이다. 이러한 이해를 통해 안드로이드 시스템에 대해 보다 자세히 이해하는 시간이 되었으면 한다.


/필/자/소/개/

필자

라영호

국내 스마트폰의 초창기인 Cellvic에서부터 스마트폰을 개발하였고 윈도 모바일 및 다양한 임베디드 시스템을 개발하고 있다. 현재는 안드로이드 시스템 포팅 및 임베디드 시스템 개발, 컨설팅, 교육 등을 진행하는 회사를 운영하고 있다. 개인 블로그(www.embeddedce.com)를 통해 임베디드 시스템개발에 대한 다양한 생각과 방법론을 함께 생각해 보고자 노력 중이다.

※ 본 내용은 (주)테크월드(http://www.embeddedworld.co.kr)의 저작권 동의에 의해 공유되고 있습니다.
    Copyright ⓒ Techworld, Inc. 무단전재 및 재배포 금지

맨 위로
맨 위로