스터디 11주차 안드로이드 바인더 ipc 2
이번 포스팅에선 저번주차에 했었던 바인더에 대한 코드를 직접 분석하면서 실제 흐름이 어떻게 이루어지는지 살펴볼 것이다.
1. 바인더 드라이버 함수 분석
1.1 binder_open() 함수 분석
-
binder_open() 함수는 프로세스가 바인더 드라이버를 사용하기 위해 가장 먼저 호출하는 함수이다. 이 함수는 드라이버를 작성할 때 시스템 콜인 하나의 open() 함수와 연결되도록 이미 구성되어 있다.
-
프로세스는 open() 시스템 콜을 사용할 때 바인더 드라이버의 노드 파일인 “/dev/binder”를 인자로 전달한다.
-
프로세스는 open() 시스템 콜의 반환값으로 바인더 드라이버의 파일 디스크립터를 얻는다.
-
이 파일 디스크립터는 이후 mmap()과 ioctl() 시스템 콜을 사용할 때 인자로 사용된다.
/dev/binder 디바이스 노드는 바인더 디바이스 드라이버의 소스코드인 binder.c와 binder_init() 함수 안의 misc_register() 함수를 통해 만들어진다.
binder_open() 함수는 드라이버를 연 프로세스에 대한 binder_proc 구조체를 생성하고 초기화한다. 그리고 프로세스를 대기 상태로 변경할 때 사용할 대기 큐를 초기화한다.
위 그림은 binder_open() 함수의 역할을 간략히 나타냈다.
binder_open() 함수를 분석하기에 앞서 바인더 IPC에서 가장 중요한 역할을 하는 binder_proc 구조체를 좀 더 살펴보자. binder_proc 구조체는 드라이버를 연 프로세스의 정보, IPC 데이터를 수신하기 위한 버퍼 정보, 바인더 IPC 상태 정보 등 바인더 IPC에 필요한 정보들을 관리하는 구조체다. 그리고 binder_proc 구조체는 드라이버 내 다른 구조체들의 포인터를 가지고 접근할 수 있기 때문에 바인더 드라이버의 루트 구조체라 볼 수 있다. 이 장에서 설명할 binder_proc 구조체의 주요 필드와 각 필드의 의미는 다음과 같다.
필드 | 의미 |
---|---|
struct rb_root threads | binder_thread 구조체를 가진 레드 블랙 트리의 루트 |
struct rb_root nodes | binder_node 구조체를 가진 레드 블랙 트리의 루트 |
struct rb_root refs_by_desc | binder_ref 구조체들을 가진 레드 블랙 트리의 루트이며, desc 번호를 통해 각 binder_ref 구조체를 구분한다. |
struct rb_root refs_by_node | binder_ref 구조체들을 가진 레드 블랙 트리의 루트이며, binder_node 구조체를 통해 각 binder_ref 구조체를 구분한다. |
int pid | binder_proc 구조체를 생성한 프로세스의 pid |
struct vm_area_struct *vma | binder_mmap()을 호출한 프로세스의 사용자 공간 정보(커널 공간 메모리 확보 시 사용됨) |
struct task_struct *tsk | binder_proc 구조체를 생성한 프로세스의 task_struct 구조체를 가리킴 |
void* buffer | 바인더 IPC 수신 버퍼 중 IPC 데이터를 수신하기 위한 binder_buffer 구조체의 포인터 |
size_t user_buffer_offset | IPC 데이터 수신 버퍼로 매핑된 커널 공간과 사용자 공간 사이의 주소 차이, 수신한 IPC 데이터를 사용자 공간으로 전달할 때 사용됨. |
struct list_head buffers | IPC 데이터 수신을 위해 할당된 binder_buffer 구조체의 리스트 |
struct rb_root free_buffers | IPC 데이터를 수신한 후 해제할 binder_buffer 구조체의 리스트 |
size_t buffer_size | 프로세스가 커널 공간에 확보한 수신 버퍼 영역의 크기 |
struct list_head todo | 프로세스가 대기 상태에서 깨어난 후 할 일을 가리킴. |
wait_queue_head_t wait | 프로세스를 대기 상태로 변경하기 위해 사용됨. |
본인의 android kernel version은 4.4.136+이다. about emulated device에서 system의 android version을 클릭해보면 알 수 있다.
그래서 android kernel source 받는 방법을 참조하여 android kernel source를 받은 다음 4.4.n버전의 branch로 checkout한 후 drivers/android/binder.c를 열어보았다.
그리고 우리가 지금 알아보고 있는 binder_open 함수를 살펴보았다.
-
3569번째 줄 :
proc->tsk = current->group_leader;
에서 바인더 드라이버를 연 프로세스의 정보를 담고 있는 task_struct 구조체를 binder_proc 구조체의 멤버 변수인 tsk에 등록한다. 이 변수는 바인더 IPC를 하는 프로세스들이 누구인지 알려주는 로그 정보를 출력할 때 사용된다. -
3571번째 줄 :
INIT_LIST_HEAD(&proc->buffers);
에서 바인더 IPC 수신 후 할 작업을 저장하는 proc->buffers 리스트를 초기화하고, 바인더 드라이버를 연 프로세스를 대기 상태로 변경하기 위한 대기 큐를 초기화한다. -
3583번째 줄 : binder_proc 구조체를 전역적으로 사용하기 위해 filp구조체의 private_data에 binder_proc 구조체를 등록한다. binder_open() 함수의 인자로 전달되는 filp 변수는 binder_mmap() 함수와 binder_ioctl() 함수에서도 동일하게 사용되므로,
struct binder_proc proc = filp -> private_data
같은 형태로 다른 함수에서도 binder_proc 구조체를 얻어올 수 있다. -
3589번째 줄 : binder_proc 구조체를 초기화하거나 바인더 IPC에 관한 정보를 보여주기 위해 /proc/binder/proc 디렉터리에 파일을 생성한다. 안드로이드 부팅 후 파일 시스템의 /proc/binder 디렉토리 이하를 살펴보면 바인더 IPC를 이용하는 프로세스들의 정보를 볼 수 있다.
1.2 binder_mmap() 함수 분석
프로세스 간의 사용자 공간은 공유할 수 없다. 하지만 커널 공간을 통해 프로세스 사이에 데이터를 주고 받는 것이 가능하므로 바인더 IPC를 하는 프로세스는 바인더 드라이버를 통해 커널 공간에 공유 공간을 확보한다. 따라서 프로세스는 binder_mmap() 함수를 통해 커널 공간에 IPC 데이터와 IPC 응답 데이터를 수신하는 버퍼를 생성한다.
binder_mmap() 함수를 분석하기 전에 mmap() 시스템 콜의 실제 사용 예를 살펴보자.
1
2
3
4
5
6
struct binder_state *binder_open(unsigned mapsize)
{
bs->fd = open("/dev/binder",0_RDWR);
bs->mapsize = mapsize; // 여기서 mapsize는 128KB
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
}
위 코드에서 프로세스는 mmap() 시스템 콜을 통해 mapsize만큼 커널 공간에 수신 버퍼를 확보한다. 일반적으로 mmap() 함수는 사용자 공간의 특정 영역과 커널 공간의 특정 영역을 매핑하는 용도로 사용된다. 이는 사용자 공간의 프로세스가 커널 공간에 바로 접근할 수 없기 때문에 커널 공간과 매핑된 특정 영역을 통해 커널 공간을 접근하기 위해서나 mmap() 함수를 호출하면 호출 코드의 사용자 공간에 매핑할 크기만큼 공간을 확보한 후 커널의 binder_mmap() 함수를 호출한다.
안드로이드에서 커널 공간과 mmap() 시스템 콜을 통해 매핑되는 영역은 미리 정해져 있는데, 이는 안드로이드에서 각 라이브러리가 링크될 주소를 미리 결정하는 Prelinked 방식을 이용하기 때문이다. 이러한 링크 정보는 안드로이드 플랫폼 코드의 /build/core/prelink-linux-arm.map에서 확인할 수 있다.
바인더를 사용하는 프로세스가 가진 사용자 공간의 가상 메모리 매핑 정보는 셸 상에서 cat /proc/[PID]/maps
명령어를 통해 확인할 수 있다. 여기서 PID는 확인하고자 하는 프로세스의 ID이다.
일반적으로 커널 공간에서 사용자 공간으로 데이터를 전달하기 위해서는 copy_to_user() 함수를 이용해서 사용자 공간으로 데이터를 복사한다. 하지만 바인더를 이용하는 프로세스는 바인더 드라이버의 mmap() 함수를 통해 사용자 공간과 매핑된 바인더 mmap() 영역을 커널공간에 만든다. 그리고 바인더 드라이버는 커널 공간의 데이터 수신 영역에 데이터를 저장만 하고, 사용자 공간에 커널 공간의 mmap 영역의 정보만 알려준다. 따라서 프로세스는 사용자 공간의 mmap 영역의 주소만 알고 있어도 커널 공간에 존재하는 데이터를 참조할 수 있다.
위 그림은 프로세스가 바라보는 가상 메모리 공간이다. 사용자 공간의 수신 영역과 커널 공간의 IPC 데이터 수신 영역은 binder_mmap() 함수를 통해 서로 매핑된다. 따라서 IPC 데이터를 수신하는 수신 상태의 프로세스는 바인더 드라이버가 매핑된 공간에 데이터를 저장하기만 하면 copy_to_user() 함수를 사용하지 않고도 사용자 공간으로 IPC 데이터를 전달할 수 있다.
바인더 드라이버의 binder_mmap() 함수 호출은 mmap() 시스템 콜을 통해 가능하다. 이 함수의 주요 목적은 사용자 프로세스가 전달한 크기만큼 커널 공간에 IPC 데이터 수신 버퍼를 확보하는 것이다.
위 그림은 binder_mmap() 함수의 역할을 그림으로 간단히 표현한 것이다. 바인더 드라이버는 binder_mmap() 함수를 통해 프로세스가 요청한 매핑 크기만큼 커널 공간의 버퍼를 확보한 후 사용자 공간의 버퍼와 매핑한다. 그리고 확보한 커널 공간의 버퍼를 binder_proc 구조체에 등록한다. 위 과정을 실제 코드를 통해 보도록 하겠다.
먼저 binder_mmap함수의 변수 선언부이다.
***binder_mmap() 함수의 첫 번째 인자***는 바인더 드라이버의 파일 디스크립터에 대한 file 구조체의 포인터이다. 그리고 file 구조체의 private_data는 driver_open() 함수에서 미리 등록한 현재 프로세스의 binder_proc 구조체이다.
***binder_mmap() 함수의 두 번째 인자***는 mmap() 함수를 통해 커널 공간과 매핑할 사용자 공간의 버퍼 정보이다. 이때 vma->vm_start와 vma->vm_end 멤버들은 각각 mmap() 함수를 호출할 때 전달된 크기만큼 연속된 공간에 대한 시작 주소와 끝 주소를 가리킨다. binder_mmap() 함수에서는 이 vma 구조체의 정보를 바탕으로 커널 공간에 버퍼를 확보하고 사용자 공간과 커널 공간의 버퍼를 매핑한다.
그리고 위 코드에서 get_vm_area() 함수는 커널 VMALLOC영역에 요청한 크기만큼 할당할 공간이 남아있다면 vm_struct 구조체를 하나 생성해서 할당 가능한 공간의 시작 주소를 vm_struct 구조체에 addr 변수에 설정한 후 반환한다.
-
3480번째 줄에서 get_vm_area() 함수에서 반환된 vm_struct 구조체의 변수인 area->addr이 커널의 연속된 공간의 시작 주소가 된다.
-
3486번째 줄에서 이 값을 proc->buffer에 저장함으로서 proc->buffer는 커널 공간에서 IPC 데이터를 수신할 버퍼의 시작 주소가 된다.
-
3487번째 줄에서 사용자 공간의 버퍼 주소와 커널 공간의 버퍼 주소의 오프셋을 구한다. proc->user_buffer_offset은 int형 변수이며, 사용자 공간에서 매핑된 영역의 시작 주소와 현재 할당된 커널 영역의 버퍼 시작 주소를 뺀 값인 마이너스 값을 나타낸다. 이 오프셋 정보는 바인더 드라이버가 IPC 데이터를 수신 후 IPC 데이터가 존재하는 커널 공간과 매핑된 사용자 공간의 버퍼 주소를 프로세스에게 알려주는데 사용된다.
위 코드들을 통해 커널 공간에 수신 버퍼를 확보했다면, 이제 사용자 공간의 수신 버퍼와 커널 공간의 수신 버퍼를 실제 물리적인 메모리상에 매핑해야 한다. 아래 코드를 통해 사용자 공간의 수신 버퍼와 커널 공간의 버퍼는 서로 동기화된다.
-
3517번째 줄에서 __binder_update_page_range() 함수에서 물리 메모리와 가상 메모리에 존재하는 커널 공간 수신버퍼, 사용자 공간 수신 버퍼를 매핑한다.
-
3523번째 줄에서 확보한 커널 공간의 데이터 영역은 binder_buffer 구조체의 data멤버에 등록되고 바인더 IPC를 수행할 때 여기서 확보한 binder_buffer 구조체에 IPC 데이터를 저장한다.
-
3526번째 줄에서 binder_insert_free_buffer() 함수를 통해 현재 프로세스의 binder_proc 구조체의 free_buffers변수에 binder_buffer 구조체를 등록한다. 이때 free_buffers 변수는 IPC 데이터의 수신 버퍼를 의미하며, IPC 데이터의 크기에 따라 free_buffers에 조금씩 할당받아 데이터를 복사한다.
1.3 binder_ioctl() 함수 분석
프로세스는 바인더 드라이버를 제어할 목적으로 ioctl() 시스템 콜을 통해 binder_ioctl() 함수를 호출한다. 바인더 드라이버는 ioctl() 시스템 콜과 함께 전달되는 ioctl 명령어를 해석해서 그에 따른 동작을 수행한다. ioctl 명령어는 이전 포스팅의 바인더 IPC 데이터의 전달에서 살펴보았다. 이번 포스팅에선 바인더 드라이버에 IPC 데이터와 IPC 응답 데이터의 송수신을 요청하는 ioctl 명령어인 BINDER_WRITE_READ를 기준으로 binder_ioctl() 함수를 분석하겠다.
위 그림은 binder_ioctl() 함수를 이용하는 두 프로세스를 보여주며, 그림에서 바인더 드라이버 내에서 IPC 데이터와 IPC 응답 데이터의 송수신 과정을 순서대로 나열했다.
binder_ioctl() 함수는 두 프로세스 사이에서 실제로 IPC 데이터와 IPC 응답 데이터를 송수신하는 일을 담당한다. 따라서 binder_ioctl() 함수는 IPC 데이터를 송신하는 프로세스와 IPC 데이터를 수신하는 프로세스 둘 다 고려한 상태에서 분석해야 올바르게 이해할 수 있다.
위 그림에서 프로세서 B는 BINDER_WRITE_READ 명령어와 함께 binder_ioctl() 함수를 호출한다. 이때 프로세스 B는 IPC 데이터 송신 → IPC 응답 데이터 수신 대기 → IPC 응답 데이터의 사용자 공간으로의 전달 과정이 이뤄진다. 그리고 프로세스 A는 IPC 데이터 수신 대기 → IPC 데이터 수신 후 사용자 공간 전달 → IPC 응답 데이터의 송신이라는 과정을 거친다.
위 그림은 BINDER_WRITE_READ ioctl 명령어를 사용할 때 인자로 전달되는 데이터인 binder_write_read 구조체를 보여준다. 이 구조체는 그림 7-24의 ioctl() 함수의 세 번째 인자인 &bwr에 해당한다. binder_write_read 구조체에는 write_buffer, read_buffer로 두 개의 버퍼가 존재한다. 위 정보는 ./include/uapi/linux/android/binder.h
에서 찾을 수 있다.
binder_write_read 구조체에서 write_buffer 변수는 바인더 드라이버를 통한 데이터 전송에 사용된다. 즉, IPC 데이터 또는 IPC 응답 데이터 송신 시에 사용된다.
binder_write_read 구조체의 read_buffer 변수는 바인더 드라이버로부터 데이터를 수신하는 데 사용된다. 즉, 바인더 드라이버는 상대방으로부트 IPC 데이터나 IPC 응답 데이터를 받으면 read_buffer에 데이터를 담아서 사용자 공간으로 전달한다.
write_size와 read_size는 각각 write_buffer와 read_buffer의 데이터가 얼마만큼 처리됐는지를 나타낸다. binder_write_read 구조체의 정의는 바인더 IPC를 수행하는 프로세스와 바인더 드라이버의 소스코드에서 헤더 파일로 포함돼있다. 이는 바인더 IPC를 수행하는데 사용되는 바인더 프로토콜이나 ioctl 명령어와 마찬가지로 binder_write_read 구조체도 프로세스와 바인더 드라이버에서 모두 공통적으로 사용되기 때문이다.
그렇다면 binder_write_read 구조체의 write_buffer와 read_buffer에 담기는 IPC 데이터와 IPC 응답 데이터는 어떤 형태를 지니고 있을까?
지금까지 IPC 데이터나 IPC 응답 데이터는 4가지 구성 요소인 핸들, RPC 코드, RPC 데이터, 바인더 프로토콜을 가진다고 했다. 여기서 핸들, RPC 코드, 그리고 RPC 데이터는 binder_transaction_data라는 구조체에 담긴다.
위 그림은 binder_transaction_data 구조체의 내부 멤버 변수와 IPC 데이터 구성요소와의 관계를 보여준다. 이를 실제 binder.h에서 살펴보았다.
그림과 같이 handle과 code, buffer를 가지고 있는 것을 알 수 있지만 handle과 buffer가 union으로 묶여있다는 사실을 알 수 있었다. 그리고 각각의 멤버에 대해서는 위의 주석으로 표기해서 알기 어렵지 않았다.
바인더 프로토콜은 위 그림에서 볼 수 있듯이 binder_transaction_data 구조체 앞에 배치되고, 이것은 binder_write_read 구조체의 write_buffer가 가리키는 공간에 존재한다.
binder_write_read 구조체의 write_buffer는 binder_transaction_data 구조체와 바인더 프로토콜을 가지는데 이것은 드라이버에서 IPC 데이터를 처리할 때 하나의 처리 단위가 된다. 이 처리 단위는 연속적으로 write_buffer에 적재될 수 있지만, 이번 챕터에서는 설명의 복잡함을 줄이기 위해 IPC 데이터가 하나인 경우만 설명한다. read_buffer도 마찬가지고 바인더 프로토콜과 binder_transaction_data 구조체로 구성된다.
사용자 공간에서 생성하는 IPC 데이터인 binder_write_read 구조체는 바인더 드라이버도 동일하게 가지고 있다. 사용자 공간에서 binder_write_read 구조체의 데이터를 설정한 후 ioctl() 함수를 통해 바인더 드라이버에 전달하면 바인더 드라이버는 copy_from_user()를 통해 드라이버에 정의된 binder_write_read 구조체에 사용자 공간의 데이터를 복사한다. 반대로 IPC 응답 데이터를 전달할 때는 바인더 드라이버에 정의된 binder_write_read 구조체를 copy_to_user() 를 통해 사용자 공간으로 복사한다.
binder_ioctl() 함수에서 바인더 드라이버의 IPC가 동작하는 방식은 각 서비스 사용 단계마다 다르다. 위 그림은 각 서비스 사용 단계마다 IPC 데이터와 IPC 응답 데이터가 어떻게 송수신되는지 설명한다. binder_ioctl() 함수의 코드 분석은 ‘바인더 드라이버 관점에서의 서비스 사용’에 바인더 드라이버의 동작 순서에 따라 진행하겠다.
서비스 등록 단계
서비스 등록 단계에서 binder_ioctl() 함수는 서비스 서버의 IPC 데이터를 컨텍스트 매니저로 송신하고, 컨텍스트 매니저의 IPC 응답 데이터를 서비스 서버로 전달하는 역할을 한다.
(1) 바인더 데이터 송수신 1단계
-
컨텍스트 매니저는 binder_open() 함수와 binder_mmap() 함수를 통해 binder_proc 구조체를 초기화하고 IPC 데이터를 수신하기 위한 binder_buffer 구조체를 이미 생성하였다.
-
그리고 사용자 공간에 버퍼를 하나 생성하고 binder_write_read 구조체에 read_buffer를 등록한 뒤 ioctl() 함수를 호출한다.
-
이때 binder_write_read 구조체의 read_size는 사용자 공간의 버퍼 크기를 가진다.
우리는 위 그림에서 ioctl() 시스템 콜이 바인더 드라이버의 binder_ioctl() 함수를 호출했을 때 일어나는 일에 대해서 알아볼 것이다.
먼저 위 코드는 binder.c의 binder_ioctl() 함수이다. 이 함수의 첫번째 매개변수는 바인더 드라이버의 파일 디스크립터에 대한 file 구조체의 포인터이다. 두번째 매개변수는 cmd의 ioctl() 명령어이고, 세번째 매개변수는 ioctl() 시스템 콜의 인자인 binder_write_read 구조체이다.
-
3335번째 줄 : binder_open() 함수에서 생성한 binder_proc 구조체를 얻어온다.
-
3350번째 줄 : binder_get_thread() 함수를 통해 새로운 binder_thread 구조체를 하나 생성하는데, 이것은 IPC 응답 데이터를 송신할 때 수신 측 프로세스의 binder_proc 구조체를 찾는데 사용된다.
-
3356번째 줄 : ioctl() 명령어를 해석한다. 바인더 데이터를 송수신할 때는 BINDER_WRITE_READ가 사용된다. 실제 소스코드의 ioctl() 함수를 살펴보면 BINDER_WRITE_READ 말고도 switch 구문에 다른 ioctl 명령어도 있음을 확인할 수 있다.
-
3358번째 줄 : ioctl() 명령어가 BINDER_WRITE_READ라면 binder_ioctl_write_read() 함수를 호출한다. 해당 코드는 아래에 있다.
위 코드는 binder.c의 binder_ioctl_write_read() 함수이다.
-
3241번째 줄 : 컨텍스트 매니저가 가진 사용자 공간의 binder_write_read 구조체를 커널 공간으로 복사한다.
-
3251번째 줄, 3264번째 줄 : read_size와 write_size 변수를 토대로 IPC 데이터 송신 과정인지 IPC 응답 데이터 수신 과정인지 판단한다. 컨텍스트 매니저는 IPC 데이터를 수신하기 위해 read_buffer를 생성했으므로 read_size는 0보다 크다.
-
3265번째 줄 : binder_thread_read() 함수를 호출하여 현재의 프로세스를 대기 상태로 변경한다.
(2) 바인더 데이터 송수신 2단계
서비스 서버는 binder_open() 함수와 binder_mmap() 함수를 통해 binder_proc 구조체를 초기화하고 IPC 응답 데이터를 수신하기 위한 binder_buffer 구조체를 이미 생성한 상태이다.
서비스 서버도 binder_write_read 구조체를 생성하지만 컨텍스트 매니저와 달리 write_buffer 변수와 read_buffer 변수를 모두 사용한다. 즉, read_buffer 변수는 IPC 응답 데이터를 수신할 때 사용되고 write_buffer는 IPC 데이터를 송신할 때 사용된다.
바인더 데이터 송수신 2단계에서 IPC 데이터의 RPC 데이터에는 서비스의 이름과 flat_binder_object라는 구조체가 등록된다. flat_binder_object 구조체는 서비스를 등록하거나 검색할 때 바인더 드라이버 내에서 바인더 노드를 생성하거나 찾는 용도로 사용된다.
flat_binder_object 구조체이다. 여기서는 binder_object_header라는 구조체를 선언하는데 이 구조체는 __u32 타입의 type이라는 매개변수를 가진다. 이 type 매개변수는 BINDER_TYPE_BINDER와 BINDER_TYPE_HANDLE 값을 가진다. BINDER_TYPE_BINDER의 경우 바인더 노드를 생성할 때 사용되고, BINDER_TYPE_HANDLE은 바인더 노드를 검색할 때 사용된다.
다음 코드는 서비스 서버가 binder_proc 구조체 초기화하고 IPC 응답 데이터를 위한 수신 버퍼를 확보하기 위하여 binder_buffer를 초기화하는 과정을 나타낸다.
컨텍스트 매니저가 binder_proc 구조체를 초기화할 때와 마찬가지로 binder_ioctl() 함수코드이다.
-
3350번째 줄 : 서비스 서버도 컨텍스트 매니저와 동일하게 binder_thread 구조체를 하나 생성한다.
-
3357번째 줄 : 만약 ioctl의 명렁어가 BINDER_WRITE_READ라면 binder_ioctl_write_read()함수를 호출한다.
-
3241번째 줄 : copy_from_user() 함수로 사용자 공간의 binder_write_read 구조체를 커널 공간으로 복사한다. 서비스 서버가 IPC 데이터를 송신할 목적으로 binder_write_read 구조체의 write_buffer 데이터를 저장했으므로 write_size는 0보다 크다.
-
3251번째 줄 : write_size에 의해 binder_thread_write() 함수를 호출한다. 이때 전달되는 proc은 서비스 서버의 binder_proc 구조체이다.
-
2316번째 줄 : 전달된 write_buffer에서 현재 처리할 IPC 데이터를 가리킨다. 앞서 IPC 데이터는 write_buffer에 여러 개가 적재될 수 있다고 했는데, consumed는 IPC 데이터의 개수를 가리킨다. 현재 처리한 IPC 데이터가 없으므로 *ptr은 첫번째 IPC 데이터를 가리킨다.
-
2320번째 줄 : get_user() 함수를 통해 IPC 데이터의 바인더 프로토콜을 커널 공간으로 복사한다. 서비스 등록 단계에서 사용되는 바인더 프로토콜은 BC_TRANSACTION이다.
.
.
.
그리고 switch문을 이용해서 cmd가 BC_TRANSACTION일 때를 찾는다.
-
2504번째 줄 : IPC 데이터에서 바인더 프로토콜을 제외한 데이터인 binder_transaction_data 구조체를 커널 공간으로 다시 복사한다. binder_ioctl() 함수에서 binder_write_read 구조체를 복사했는데도 다시 binder_transaction_data 구조체를 복사하는 이유는 binder_write_read 구조체의 write_buffer가 사용자 공간에 존재하는 binder_transaction_data 구조체의 포인터이므로 해당 포인터에 담긴 내용을 다시 커널 공간으로 복사해야 하기 때문이다.
-
2507번째 줄 : binder_transaction()을 호출한다. binder_transaction() 함수는 binder_transaction_data 구조체를 통해 어드레싱, 바인더 IPC 데이터 복사, 바인더 노드 생성 및 검색을 수행한다.
binder_transation() 함수는 동작에 필요한 구조체를 선언한다.
-
1889번째 줄 : binder_transaction 구조체는 IPC 데이터 수신 측이 대기 상태에서 벗어나 송신 측에서 전달할 IPC 데이터를 찾는데 사용된다. 이 구조체를 통해 수신 측과 송신 측이 IPC 데이터를 주고 받을 수 있다.
-
1894번째 줄 : binder_proc 구조체의 target_proc 포인터 변수는 바인더 IPC 데이터 수신 측의 binder_proc 구조체를 가리킨다.
-
1896번째 줄 : binder_node 구조체의 target_node 포인터 변수는 수신 측 binder_proc 구조체에 등록된 binder_node 구조체를 가리킨다. 그리고 target_wait는 수신 측을 대기 상태에서 벗어나게 하는데 사용된다.
binder_transaction() 함수는 수신 측의 바인더 노드를 획득한다.
-
1966번째 줄 : target_node는 전역변수로 선언된
context->binder_context_mgr_node
를 가지게 된다. 서비스 등록 단계의 바인더 IPC 데이터 수신 측은 컨텍스트 매니저 이므로 핸들은 0이다. 따라서 target_node는 0이 된다. 컨텍스트 매니저는 ioctl(“/dev/binder”,BINDER_SET_CONTEXT_MGR)을 통해 binder_node를 생성하고 전역변수인context->binder_context_mgr_node
로 등록되어 서비스 사용을 위한 binder_node를 획득하는 절차없이 핸들로 0만 지정하면 컨텍스트 매니저의 서비스 노드를 사용할 수 있다. -
1973번째 줄 : 컨텍스트 매니저의 바인더 노드인 target_node를 통해 컨텍스트 매니저의 binder_proc 구조체를 얻어온다. binder_proc 구조체의 proc 변수는 자신이 소속된 binder_proc 구조체를 가리킨다. 본 단계에서는 컨텍스트 매니저의 binder_proc 구조체를 찾은 것이다.
위 그림은 binder_node와 binder_proc의 관계를 나타낸 것이다.
계속해서 수신 측이 wait_queue를 획득하는 과정을 살펴보겠다. 바인더 데이터 송수신 1단계에서 컨텍스트 매니저는 IPC 데이터 수신 대기 상태로 들어갔다. 따라서 위 코드를 통해 컨텍스트 매니저를 대기 상태에서 벗어나게 해줄 대기 큐(wait_queue)를 찾는다.
-
2008번째 줄 : 컨텍스트 매니저가 대기 상태에서 깨어나서 할 일을 지정하기 위해 binder_proc 자료 구조의 todo 변수를 target_list에 가져온다.
-
2009번째 줄 : 컨텍스트 매니저의 waite_queue를 얻어온다.
-
2014번째 줄 : 소스 코드의 t는 binder_transaction 구조체에 대한 포인터 변수이다. IPC 데이터를 수신 측에 넘겨주기 위해 사용할 binder_transaction 구조체를 생성한다.
-
2051번째 줄 : 뒤에서 살펴볼 바인더 데이터 송수신 5단계에서는 IPC 응답 데이터를 송신하기 위해 현재 IPC 데이터 송신 측인 프로세스를 찾는다. 따라서 현재의 IPC 데이터 송신 측인 서비스 서버의 binder_thread 구조체를 binder_transaction 구조체의 from 변수에 등록해둔다. binder_thread 구조체를 통해 수신 측의 binder_proc 자료 구조를 찾는 과정은 바인더 데이터 송수신 5단계에서 살펴본다.
-
2057번째 줄 : IPC 데이터의 RPC 코드를 binder_transaction 구조체의 code 변수에 등록한다.
-
2063번째 줄 : binder_allow_buf() 함수는 수신 측의 IPC 데이터 수신 버퍼에서 IPC 데이터를 복사할 공간을 할당받는다. 이 수신 버퍼 공간은 수신 측이 binder_mmap() 함수에서 확보했으며, binder_proc 구조체의 free_buffers가 가리키는 영역이다. binder_allow_buff() 함수는 free_buffers에서 RPC 데이터의 크기만큼 binder_buffer 구조체의 data 변수에 할당한 후 해당 binder_buffer 구조체를 반환한다.
-
2082번째 줄 : RPC 데이터를 할당받은 binder_buffer 구조체의 data 변수에 복사한다.
위 그림은 커널 공간의 IPC 데이터 수신 버퍼와 사용자 공간과의 매핑, 그리고 IPC 데이터의 복사과정을 보여준다.
IPC 데이터에서 핸들, RPC 코드, 바인더 프로토콜은 데이터의 형태가 이미 정해져 있기 때문에 크기의 변경은 없으나 RPC 데이터의 경우 사용처에 따라 그 형태가 다르기 때문에 크기가 항상 가변적이다. 따라서 안드로이드는 바인더 IPC 과정에서 길이가 가변적인 RPC 데이터는 크기에 따라 복사할 공간을 동적으로 할당받게 했다. 그리고 이후에 미리 매핑된 공간에 데이터를 복사함으로써 대용량 RPC 데이터로 인해 발생하는 성능 저하를 방지하고자 했다.
이제 바인더 데이터 송신 측이 binder_transaction 구조체를 설정하는 과정은 끝났다. 이후 수신 측이 대기 상태에서 벗어나 이 구조체의 데이터를 가져가게 된다. 다음으로 각 서비스마다 존재하는 바인더 노드를 생성하는 과정을 살펴볼 것이다.
위 코드는 IPC 데이터의 RPC 데이터가 flat_binder_object 구조체를 포함할 때 수행하는 코드이다. 서비스 등록 단계에서 flat_binder_object 구조체의 type 변수는 BINDER_TYPE_BINDER가 된다.
- 2134번째 줄 : binder_translate_binder() 함수를 호출한다.
-
1627번째 줄 : binder_get_node()는 binder_proc 구조체에 등록된 바인더 노드 중 현재 등록하는 바인더 노드가 이미 있는지 확인한다. 없으며 NULL을 반환한다. 따라서 1629번 줄의 코드를 실행하게 된다.
-
1629번째 줄 : 서비스 등록 과정은 새로운 바인더 노드를 생성하는 단계이므로 binder_new_node() 함수에서 새로운 binder_node 구조체를 생성한 후 반환한다. 이 함수 내에서 현재 프로세스의 binder_proc 구조체에 생성한 binder_node 구조체를 등록한다.
-
1646번째 줄 : binder_get_ref_for_node() 함수는 첫 번째 인자의 binder_proc 구조체에 두 번째 인자인 바인더 노드가 이미 존재하는지 검색한다. 바인더 노드가 존재하면 바인더 노드가 소속된 binder_ref 구조체를 반환하고, 그렇지 않으면 새로운 binder_ref 구조체를 생성해 인자인 바인더 노드를 등록한 후 반환한다. 그리고 함수 내에서 binder_ref 구조체는 첫 번째 인자인 binder_proc 구조체의 refs_by_node 변수에 등록된다. binder_ref 구조체는 바인더 노드에 대한 인덱스이자 참조 데이터이다.
위 코드는 binder.c의 binder_ref 구조체이다. proc은 binder_ref 구조체가 소속된 binder_proc 구조체를 가진다. node는 현재 binder_ref 구조체가 가진 바인더 노드인 binder_node 구조체를 가진다. 그리고 desc는 현재 프로세스가 가진 바인더 노드를 구분하는 번호가 된다. 이 번호는 바인더 노드가 생성될 때마다 증가한다. 예를 들어 서비스 서버가 처음 서비스를 등록할 때 처음으로 바인더 노드가 생기므로 binder_ref 구조체의 desc는 1이 된다. 숫자가 1부터 시작하는 이유는 컨텍스트 매니저의 바인더 노드가 이미 0번으로 예약되어 있기 때문이다.
현재 단계에서 binder_get_ref_for_node() 함수는 target_node인 컨텍스트 매니저의 binder_proc 구조체에 새로운 binder_ref 구조체를 생성해 인자로 전달된 바인더 노드를 등록한다. binder_get_ref_for_node() 함수를 실행하고 나면 다음 그림과 같이 서비스 서버의 binder_node 구조체가 컨텍스트 매니저의 binder_proc 구조체에 등록된 형태가 된다.
현재 생성한 binder_ref 구조체에 desc 번호가 이후 컨텍스트 매니저가 관리하는 서비스 목록에서 각 서비스를 구분하는 번호로 등록된다. 지금까지의 과정을 통해 서비스 서버는 자신이 등록할 서비스의 바인더 노드를 생성하고 컨텍스트 매니저에 등록하는 과정까지 마친다. 이제 서비스 서버는 서비스 등록 단계에서 IPC 데이터를 전달하기 위한 모든 준비 과정을 마친 셈이다. 따라서 컨텍스트 매니저를 대기 상태에서 깨워 binder_transaction 구조체에 등록한 IPC 데이터를 가져가게 한다.
-
2255번째 줄 : 컨텍스트 매니저는 대기 상태에서 벗어나 binder_transaction 구조체에 들어 있는 binder_work라는 구조체의 type 변수의 값을 바탕으로 동작을 수행한다. 컨텍스트 매니저는 binder_work 구조체의 type 변수가 BINDER_WORK_TRANSACTION일 경우 서비스 서버가 전달한 IPC 데이터를 binder_transaction 구조체로부터 획득하게 된다.
-
2256번째 줄 : 컨텍스트 매니저는 바인더 데이터 송수신 4단계에서 자신의 binder_proc 구조체에 있는 todo를 확인한다. 수신 측이 wait_queue를 획득하는 과정에서 컨텍스트 매니저의 binder_proc 구조체가 가진 todo를 target_list에 얻어왔으므로 이곳에 2255번째 줄에서 설정한 binder_work 구조체를 등록한다.
-
2261,2263번째 줄 : target_wait를 wake_up_interruptible() 함수의 인자로 전달함으로써 컨텍스트 매니저를 깨운다. 컨텍스트 매니저는 다음 스케줄링 시 바인더 데이터 송수신 4단계를 진행하게 된다.
(3) 바인더 데이터 송수신 3단계
바인더 데이터 송수신 1단계에서 컨텍스트 매니저가 binder_write_read 구조체의 read_buffer를 통해 대기 상태로 들어가는 것을 살펴봤다. 마찬가지로 서비스 서버도 컨텍스트 매니저와 동일한 형태로 대기 상태에 들어가게 된다. 서비스 서버는 이후 바인더 데이터 송수신 6단계에서 깨어나 IPC 응답 데이터를 수신하게 된다. 서비스 서버가 대기 상태로 들어가는 과정은 컨텍스트 매니저가 대기 상태로 들어갔던 과정과 동일하다.
(4) 바인더 데이터 송수신 4단계
컨텍스트 매니저는 바인더 데이터 송수신 2단계를 통해 대기 상태에서 깨어났다. 따라서 컨텍스트 매니저는 대기 상태로 진입했던 과정을 빠져나와 다음 동작을 수행한다. 컨텍스트 매니저는 먼저 서비스 서버가 작성한 binder_transaction 구조체를 찾는다.
위 코드는 binder_thread_read() 함수의 컨텍스트 매니저를 대기 상태로 진입한 뒤의 코드이다. 위 코드의 binder_transaction_data 구조체는 사용자 공간으로 IPC 데이터를 전달하는데 사용된다. binder_work 구조체는 서비스 서버가 이전에 작성한 binder_transaction 구조체를 찾는데 사용되고, 찾은 binder_transaction 구조체를 포인터 변수인 t에 담는다.
- 2801번째 줄 : list_first_entry() 함수를 통해 서비스 서버가 수신 측 프로세스를 깨우는 코드에서 등록한 binder_work 구조체를 검색한다. list_first_entry() 함수는 멤버 변수를 통해 소속된 구조체의 시작 주소를 찾는 container_of() 함수의 래퍼 함수이다. 아래 그림이 list_first_entry() 함수가 하는 일을 보여준다.
- 2816번째 줄 : binder_work 구조체의 type 멤버 변수의 값이 BINDER_WORK_TRANSACTION 이므로 다시 container_of() 함수로 해당 binder_work 구조체를 가진 binder_transaction 구조체를 찾아낸다. 아래 그림은 container_of() 함수가 하는 일을 보여준다.
서비스 서버가 작성한 binder_transaction 구조체를 통해 IPC 데이터를 가져온다. 그리고 사용자 공간에 IPC 데이터를 전달하기 위해 binder_transaction_data 구조체에 IPC 데이터를 저장한다.
-
2962번째 줄 : 서비스 서버가 전달한 RPC 코드를 저장한다.
-
2977번째 줄 : 서비스 서버가 전달한 RPC 데이터를 저장한다. 커널 공간 수신 버퍼의 주소는 t->buffer->data가 된다. 그리고 proc->user_buffer_offset은 사용자 공간의 수신 버퍼와 커널 공간의 수신 버퍼와의 주소 차이이다. 따라서 tr.data.ptr.buffer는 사용자 공간 수신 버퍼의 주소를 가지게 된다.
사용자 공간에 존재하는 binder_write_read 구조체에 read_buffer는 tr.data.ptr.buffer가 가리키는 사용자 공간에 담긴 IPC 데이터를 참조할 수 있게 된다. 바인더 IPC는 수신 측에 데이터를 전달할 때 이와 같은 방법으로 copy_to_user() 함수를 사용하지 않고도 커널 공간의 데이터를 사용자 공간으로 전달한다. 이를 통해 두 공간 사이의 불필요한 데이터 복사작업을 제거하여 IPC 동작 시 성능 향상을 꾀할 수 있다.
위 두 과정을 통해 binder_transaction_data 구조체 설정을 마무리 했으니 이제 사용자 공간으로 이 구조체의 내용을 전달할 차례이다. 이번 단계부터는 컨텍스트 매니저의 read_buffer로 데이터가 저장된다.
2956번째 줄 : 컨텍스트 매니저의 IPC 데이터 처리를 지시할 바인더 프로토콜인 BR_TRANSACTION을 지정한다.
2984번째 줄 : *ptr은 바인더 데이터 송수신 1단계에서 컨텍스트 매니저가 대기 상태로 빠지기 전 ioctl() 시스템 콜의 인자로 전달한 binder_write_read 구조체의 read_buffer에 해당한다.
2987번째 줄 : 작성한 binder_transaction_data 구조체를 read_buffer에 복사한다. 위 과정들을 통해 read_buffer가 완성된다.
위 그림은 위 코드에서 binder_get_thread() 함수를 통해 생성한 binder_thread 구조체의 transaction_stack에 현재 binder_transaction 구조체를 등록해둔다. 이것이 바인더 IPC 데이터 송수신 5단계에서 IPC 응답 데이터 수신 측을 찾을 때 사용된다.
위 그림은 binder_thread 구조체를 이용해 상대방 프로세스를 찾는 과정을 그림으로 나타낸 것이다. 이 과정이 진행된 후 바인더 드라이버의 binder_ioctl() 함수는 종료되고 컨텍스트 매니저가 호출한 ioctl() 시스템 콜이 반환된다. 컨텍스트 매니저는 binder_write_read 구조체의 read_buffer에 담긴 바인더 프로토콜을 해석하고, binder_transaction_data 구조체에 담긴 RPC 코드에 해당하는 함수를 호출하고 RPC 데이터를 인자로 넘겨준다. 이 과정에서 서비스 서버의 서비스가 컨텍스트 매니저로 등록된다.
(5) 바인더 데이터 송수신 5단계
이 단계에서 컨텍스트 매니저가 전달받은 IPC 데이터를 처리하고, 서비스 서버에게 처리 완료를 알리기 위한 IPC 응답 데이터를 송신한다. 이 단계는 바인더 데이터 송수신 2단계와 데이터 전달 과정이 유사하지만 바인더 프로토콜이 각각 BC_REPLY(컨텍스트 매니저 → 바인더 드라이버)와 BR_REPLY(바인더 드라이버 서비스 서버)가 된다.
따라서 binder_thread_write() 함수에서 바인더 프로토콜에 따른 분기까지는 동일하게 진행되고, binder_transaction() 함수에서 BC_REPLY일 경우를 처리한다.
1916번째 줄 : binder_transaction 구조체를 얻어온다.
1936번째 줄 : binder_thread 구조체를 얻어온다.
1952번째 줄 : binder_thread 구조체를 통해 서비스 서버의 binder_proc 구조체를 얻어온다. 서비스 서버의 binder_proc 구조체를 얻었으므로 서비스 서버의 수신 버퍼를 획득할 수 있다. binder_transaction()에 IPC 데이터를 복사하는 코드를 이용하여 binder_transaction 구조체에 IPC 응답 데이터를 저장한다. 그리고 서비스 서버는 깨어나 해당 구조체를 찾고 IPC 응답 데이터를 사용자 공간으로 전달한다. 이 과정은 바인더 데이터 송수신 2단계와 매우 유사하다. 서비스 등록 단계의 IPC 응답 데이터에는 flat_binder_object 구조체가 존재하지 않으므로 바인더 노드를 생성하는 과정은 생략된다.
(6) 바인더 데이터 송수신 6단계
대기 상태에서 깨어난 서비스 서버는 바인더 데이터 송수신 4단계와 유사하게 동작하지만 바인더 프로토콜이 BR_REPLY인 것만 다르다. IPC 응답 데이터를 수신한 서비스 서버는 사용자 공간에서 IPC 응답 데이터에 따른 동작을 수행한다.
서비스 검색 단계
binder_ioctl() 함수의 서비스 검색 단계에서는 서비스 클라이언트의 IPC 데이터를 컨텍스트 매니저로 송신하고 컨텍스트 매니저의 IPC 응답 데이터를 서비스 클라리언트로 송신한다. 이 단계에서 서비스 클라이언트는 이전 서비스 등록 단계에서 등록한 서비스를 요청하는 것으로 가정한다.
(1) 바인더 데이터 송수신 1단계
서비스 등록 단계 바인더 데이터 송수신 1단계와 동일하게 컨텍스트 매니저는 IPC 데이터를 수신하기 위해 대기 상태로 들어간다.
(2) 바인더 데이터 송수신 2단계
이 단계에서 바인더 드라이버는 서비스 등록 단계에 바인더 데이터 송수신 2단계와 유사하게 동작한다. 하지만 IPC 데이터 송신 프로세스가 서비스 서버가 아닌 서비스 클라이언트로 바뀐다.
서비스 클라이언트는 서비스를 등록하는 것이 아니므로 RPC 데이터에 flat_binder_object 구조체를 저장하지 않는다.
따라서 서비스 클라이언트가 작성하는 IPC 데이터에는 위와 같이 컨텍스트 매니저의 핸들인 0과 서비스 검색 함수를 위한 RPC 코드로, 사용하고자 하는 서비스의 이름만 들어간다.
IPC 데이터에서 flat_binder_object 구조체가 존재하지 않기 때문에 새로운 binder_node 구조체를 생성하는 부분이 제외된다. 이외의 동작은 서비스 등록 단계의 바인더 데이터 송수신 2단계와 동일하다.
(3) 바인더 데이터 송수신 3단계
서비스 클라이언트도 서비스 서버와 동일하게 IPC 데이터를 송신하고 IPC 응답 데이터를 수신하므로 binder_write_read 구조체의 write_buffer와 read_buffer를 모두 사용한다. 따라서 서비스 클라이언트는 서비스 등록 단계의 IPC 데이터 송수신 3단계와 동일하게 IPC 응답 데이터 수신 대기 단계로 들어간다.
(4) 바인더 데이터 송수신 4단계
이번 단계도 서비스 등록 단계의 IPC 데이터 송수신 4단계와 유사하다.
하지만 컨텍스트 매니저는 IPC 데이터를 수신한 후 서비스 목록에서 서비스 이름에 해당하는 서비스 목록의 번호를 담은 binder_object 구조체를 생성한다. 그리고 구조체에 들어있는 type 변수를 BINDER_TYPE_HANDLE로 지정한 IPC 응답 데이터를 작성한다. 위 그림에서 binder_object 구조체는 바인더 드라이버의 flat_binder_object에 복사된 후 노드를 찾는데 사용된다.
(5) 바인더 데이터 송수신 5단계
이 단계는 binder_object 데이터를 통해 서비스 클라이언트에게 바인더 노드를 전달하는 과정이다. 서비스 클라이언트는 자신이 사용할 서비스에 대한 바인더 노드를 획득하게 된다. 서비스 등록 단계의 코드에서 flat_binder_object 구조체의 type이 BINDER_TYPE_BINDER가 되어 바인더 노드를 생성했다. 하지만 서비스 검색 단계에서 type값은 BINDER_TYPE_HANDLE이 되어 바인더 노드를 서비스 클라이언트에게 전달하게 된다. 따라서 BINDER_TYPE_BINDER 처리가 아닌 아래 코드에서 BINDER_TYPE_HANDLE에 대한 처리를 한다.
1542번째 줄에서 함수의 첫 번째 인자인 proc은 컨텍스트 매니저의 binder_proc 구조체이고 fp->handle은 컨텍스트 매니저가 전달한 서비스 클라이언트가 사용하고자 하는 서비스의 번호이다. 서비스의 번호는 binder_ref 구조체의 desc 변수의 값에 해당한다. desc값을 binder_get_ref() 함수에 전달해 컨텍스트 매니저가 가진 binder_ref 구조체 중에서 서비스 번호에 해당하는 binder_ref 구조체를 획득할 수 있다.
1552번째 줄에서 binder_dec_ref() 함수를 호출하고있다. 이때 2번째 인자로 hdr->type이 BINDER_TYPE_HANDLE와 같은지 비교한 BOOLEAN을 넘겨주고 있다.
넘겨받은 2번째 인자로 hdr->type이 BINDER_TYPE_HANDLE라면 ref의 strong 멤버를 1감소시키고 아니라면 weak멤버를 1감소시킨다.
(6) 바인더 데이터 송수신 6단계
서비스 클라이언트는 전달받은 IPC 응답 데이터의 바인더 노드 번호를 가지고 서비스 사용 단계로 들어간다.
서비스 사용 단계
서비스 사용 단계에서 서비스 클라이언트는 컨텍스트 매니저가 아닌 실제 서비스를 가진 서비스 서버와 IPC를 수행한다. 따라서 컨텍스트 매니저의 바인더 노드 번호가 아닌 서비스 검색 단계에서 획득한 바인더 노드 번호를 통해 서비스 서버에게 IPC 데이터를 송신한다. 서비스 사용 단계의 IPC 데이터 송신 측 프로세스는 서비스 클라이언트가 되며, 수신 측 프로세스는 서비스 서버가 된다.
(1) 바인더 데이터 송수신 1단계
서비스 사용 단계에서는 서비스 서버가 IPC 데이터를 수신하기 위한 대기 상태로 들어간다.
(2) 바인더 데이터 송수신 2단계
서비스 사용 단계부터 IPC 데이터는 사용하고자 하는 서비스의 함수에 해당하는 RPC 코드와 RPC 데이터를 가진다. 그리고 핸들은 서비스 검색 단계의 바인더 데이터 송수신 6단계에서 획득한 서비스의 바인더 노드 번호가 된다. 따라서 다음과 같은 IPC 데이터를 송신한다.
이번 단계는 서비스 등록 단계의 바인더 데이터 송수신 2단계와 유사하다. 하지만 binder_transaction() 함수에서 수신 측의 binder_proc 구조체를 찾는 과정이 다르다.
위 코드는 binder_transaction() 함수의 바인더 어드레싱 부분이다. 서비스 등록 단계에서는 핸들이 0이었기 때문에 컨텍스트 매니저의 binder_proc 구조체를 찾았지만, 서비스 사용 단계에서는 위의 코드와 같이 핸들에 해당하는 프로세스의 binder_proc 구조체를 찾는다.
1957번째 줄: binder_get_ref() 함수는 핸들과 같은 desc를 가진 binder_ref 구조체를 찾는다. 그리고 찾은 binder_ref 구조체의 node는 서비스 서버가 가진 binder_node 구조체를 가리킨다.
1964번째 줄에서 binder_node 구조체의 proc은 서비스 서버의 binder_proc 구조체를 가리키므로 IPC 데이터를 수신할 프로세스를 찾은 것이다.
서비스 사용 단계부터는 RPC 데이터에는 flat_binder_object 구조체가 없으므로 바인더 노드를 생성하거나 검색하는 과정은 없다. 나머지 과정은 서비스 등록이나 서비스 검색 단계의 바인더 데이터 송수신 2단계와 동일하다.
(3) 바인더 데이터 송수신 3단계
이번 단계도 서비스 등록 단계나 서비스 사용 단계의 과정과 유사하다. 다만 컨텍스트 매니저가 아닌 서비스 서버가 IPC 데이터를 수신한다. 서비스 서버는 IPC 데이터의 RPC 코드에 해당하는 함수를 호출하고 RPC 데이터를 인자로 전달한다. 이후의 과정은 바인더 서비스 등록 단계의 바인더 데이터 송수신 4,5,6단계와 동일하다.
이로써 서비스 클라이언트가 서비스 서버의 서비스를 사용하기 위해 거쳐야 할 과정을 바인더 드라이버의 동작 구조에 맞춰 살펴보았다. 다음 절에서는 바인더 IPC를 이용하는 프로세스의 동작 예로서 지금까지 살펴본 컨텍스트 매니저가 IPC 데이터를 처리하는 과정을 살펴보겠다.
2. 컨텍스트 매니저
컨텍스트 매니저(servicemanager)는 다른 서비스 서버나 서비스 클라이언트 이전에 미리 실행되야한다. 이는 다른 서비스 서버나 서비스 클라이언트의 서비스 관련 요청을 수행하기 위해서는 IPC 데이터를 수신 대기하는 상태에 있어야 하기 때문이다. 따라서 init.rc의 내용을 살펴보면 컨텍스트 매니저가 서비스 서버인 미디어 서버(/system/bin/mediaserver)나 시스템 서버(system_server) 이전에 실행되는 것을 볼 수 있다.
위 그림은 안드로이드 부팅 후 동작중인 프로세스들 중 servicemanager가 실행되고 있는 모습을 보여준다. 우리가 지금까지 컨텍스트 매니저라고 지칭한 것이 바로 저 servicemanager 프로세스를 의미한 것이다.
컨텍스트 매니저는 서비스 서버가 서비스를 등록할 때마다 자신의 서비스 목록에 서비스의 이름과 바인더 노드 번호를 등록해둔다. 이 서비스 목록은 루트 파일시스템 내 /system/service 프로그램을 통해 확인할 수 있다. 확인을 할 때는 안드로이드 쉘에 접속을 하거나 리눅스에서 (adb shell) service list
을 치면 된다.
service 프로그램은 컨텍스트 매니저의 서비스 목록에 담긴 서비스 이름을 IPC 응답 데이터로 받아온다. 그리고 받아온 서비스 이름을 역순으로 화면에 출력한다.
2.1 컨텍스트 매니저의 동작
컨텍스트 매니저는 바인더 드라이버와 밀접하게 동작하기 위해 안드로이드의 다른 서비스와 달리 C 코드로 작성돼 있다. 컨텍스트 매니저의 소스코드는 /frameworks/native/cmds/servicemanager/service_manager.c
파일에서 찾을 수 있다.
-
컨텍스트 매니저의 main() 함수는 크게 세 부분으로 나뉜다.
-
바인더 드라이버를 열고 IPC 데이터 수신 버퍼를 확보하는 binder_open() 함수
-
특수 노드(0번 바인더 노드)가 되기 위한 binder_become_context_manager() 함수, 그리고 지속적으로 IPC 데이터를 수신하는 binder_loop() 함수
.
.
.
394번째 줄 : binder_open() 함수는 바인더 드라이버를 사용하기 위한 open() 시스템 콜 호출과 mmap() 시스템 콜을 통한 IPC 데이터 수신 버퍼를 확보하는 동작을 한다. 참고로 컨텍스트 매니저는 IPC 데이터 수신 버퍼로 128KB를 사용한다.
407번째 줄 : 서비스 서버나 클라이언트와 달리 컨텍스트 매니저에만 있는 루틴으로 바인더 드라이버에 접근하여 자신의 바인더 노드를 특수 노드인 0번 노드로 지정하는 과정이다. binder_become_context_manager() 함수에는 ioctl() 함수를 호출하는 코드만 들어 있다. 바인더 드라이버에 BINDER_SET_CONTEXT_MGR ioctl 명령어를 전달하면 바인더 드라이버에서는 binder_ioctl() 함수에서 binder_new_mode() 함수를 통해 0번 바인더 노드를 생성해 binder_context_mgr_node 전역 변수에 등록한다. 이 변수가 수신측이 바인더 노드를 획득하는 과정에서 사용된다.
439번째 줄 : 컨텍스트 매니저는 binder_loop() 함수 내에서 for문을 통해 계속해서 IPC 데이터를 대기하게 된다. 그러다 IPC 데이터가 들어오면 binder_parse() 함수를 호출한다.
429번째 줄 : binder_write_read 구조체의 사용자 공간 버퍼를 등록한다.
433번째 줄 : ioctl() 시스템 콜을 통해 IPC 데이터를 수신 대기하는 상태로 들어간다. 이는 서비스 등록 단계와 서비스 검색 단계의 바인더 데이터 송수신 1단계에 해당한다.
440번째 줄 : 바인더 드라이버의 binder_ioctl() 함수를 리턴하면 수행된다. 바인더 드라이버를 통해 IPC 데이터를 수신한 컨텍스트 매니저는 binder_parse() 함수에서 IPC 데이터를 파싱한다.
컨텍스트 매니저는 먼저 IPC 데이터의 바인더 프로토콜을 해석한다. 서비스 등록 과정에서 전달되는 바인더 프로토콜은 BR_TRANSACTION이므로 285번째 줄의 func() 함수를 실행한다.
285번째 줄 : func() 함수는 service_manager.c의 main에서 등록한 svcmgr_handler() 함수가 된다. 바인더 드라이버가 전달한 IPC 데이터의 바인더 프로토콜 다음에 위치하는 것은 binder_transaction_data 구조체이다. 따라서 해당 구조체를 binder_txn 구조체로 치환한다. binder_txn 구조체는 binder_transaction_data 구조체와 동일하다. 이 구조체를 인자로 하여 다음 코드에서 svcmgr_handler() 함수를 호출한다.
위 코드는 service_manager.c의 svcmgr_handler() 함수이다. 컨텍스트 매니저가 하는 역할은 크게 서비스 등록과 서비스 검색으로 나눌 수 있으며 svcmgr_handler() 함수는 RPC 코드에 따라서 두가지 역할을 수행한다.
308번째 줄 : 서비스 클라이언트가 서비스를 검색할 때 실행되는 부분이다. 서비스 클라이언트가 RPC 데이터로 서비스의 이름을 전달했으므로 do_find_service() 함수에서 자신이 가진 서비스 리스트에서 해다 이름을 가진 서비스의 서비스 번호를 얻어온다. 그러고 나서 bio_put_ref() 함수에서 IPC 응답 데이터의 RPC 데이터에 들어갈 binder_object 구조체를 작성한다. 이것은 서비스 검색 단계의 바인더 데이터 송수신 4단계에 해당하는 부분이다. 이후 binder.c의 binder_parse() 함수에서 binder_send_reply() 함수를 통해 IPC 응답 데이터를 바인더 드라이버로 전달한다.
321번째 줄 : 서비스 서버가 서비스를 등록할 때 실행되는 부분이다. 컨텍스트 매니저는 IPC 데이터의 RPC 데이터에 담긴 서비스 이름과 바인더 노드 번호를 do_add_service() 함수를 통해 자신의 서비스 목록에 등록한다. 그러고 나서 binder_send_reply() 함수로 IPC 응답 데이터를 바인더 드라이버로 전달한다.
3. 정리
바인더는 안드로이드의 서비스를 보유한 서비스 서버, 서비스를 사용하고자 하는 서비스 클라이언트, 서비스의 위치를 알려주는 컨텍스트 매니저, 그리고 이들 모두를 중재하는 바인더 드라이버로 구성된다. 안드로이드에서 제공하는 각 기능들은 컴포넌트처럼 핵심 기능만을 갖춘 서비스로 세분화된다. 그리고 이것들을 사용하는 프로세스들은 일관적인 신호 체계인 IPC 데이터를 통해 서비스들과 상호 작용한다.
바인더는 안드로이드에 존재하는 수많은 서비스들을 이어주는 연결고리이다. 그리고 그 중심에 바인더 드라이버가 존재한다. 하지만 안드로이드 프레임워크나 애플리케이션 수준의 서비스 사용과정에서 바인더 드라이버의 복잡한 동작 방식은 감춰놓았다. 이는 안드로이드가 바인더 드라이버를 감싸주는 상위 계층을 두어 서비스 사용을 위한 보다 편한 인터페이스를 제공하기 때문이다. 다음에 살펴볼 서비스 매니저와 서비스 프레임워크가 바인더 드라이버를 기반으로 서비스들을 연결해주는 추상 계층이 된다. 바인더를 이해하기 위해서는 이들 추상 계층에 대해서도 알아야 한다. 지금까지 살펴본 바이더 드라이버의 동작은 이들 추상 계층에서 등장하는 IPC 매커니즘을 파악하기 위함이었으며, 잘 차려진 안드로이드의 프레임워크 내부 동작을 이해하기 위한 준비과정이었다.