안드로이드 프레임워크 프로그래밍(17) [Binder 드라이버 호출과정]

 안드로이드 Binder 부분을 연구하는데 상당히 많은 지식이 필요하군요. 이제는 안드로이드 Application 단계에서는 상상도 못했던 linux 시스템 프로그래밍 단계까지 내려와보니 분석 난이도도 많이 높아졌음을 실감하고 있습니다.


 이번 포스팅에서는 안드로이드 IPC 통신의 핵심이라 할 수 있는 Binder 드라이버가 호출되는 과정을 살펴보겠습니다. 본 포스팅을 이해하기 위해서는 linux 단계에서의 시스템 프로그래밍을 이해하셔야 이해가 수월하실 듯 합니다. 그럼 분석을 시작해 보도록 하겠습니다.


 Binder 드라이버는 PrecessState가 생성될 때 함께 생성되는 구조로 되어 있습니다. 아래 코드를 보면서 이해해보도록 합시다.


/frameworks/native/libs/binder/ProcessState.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ProcessState::ProcessState()
    : mDriverFD(open_driver())
    , mVMStart(MAP_FAILED)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) {
        // XXX Ideally, there should be a specific define for whether we
        // have mmap (or whether we could possibly have the kernel module
        // availabla).
#if !defined(HAVE_WIN32_IPC)
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
#else
        mDriverFD = -1;
#endif
    }
 
    LOG_ALWAYS_FATAL_IF(mDriverFD < 0"Binder driver could not be opened.  Terminating.");
}
cs

  현재 ProcessSate가 생성되면서 Binder 드라이버와 System Service를 등록하기 위한 mVMStart 부분을 보고 계십니다. 여기서 우리는 바인더를 호출하는 open_driver() 부분을 살펴볼 것입니다.


mDriverFD(open_driver())

 open_driver() 함수는 binder의 file descriptor을 반환해줍니다. 이 함수를 통해 Binder가 호출됩니다.


/frameworks/native/libs/binder/ProcessState.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static int open_driver()
{
    int fd = open("/dev/binder", O_RDWR);
    if (fd >= 0) {
        fcntl(fd, F_SETFD, FD_CLOEXEC);
        int vers;
        status_t result = ioctl(fd, BINDER_VERSION, &vers);
        if (result == -1) {
            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
            close(fd);
            fd = -1;
        }
        if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
            ALOGE("Binder driver protocol does not match user space protocol!");
            close(fd);
            fd = -1;
        }
        size_t maxThreads = 15;
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
        if (result == -1) {
            ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
        }
    } else {
        ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
    }
    return fd;
}
cs

 오늘 제가 주로 설명하게 될 코드입니다. 중요한 부분마다 자세하게 설명을 해 보도록 하겠습니다.


int fd = open("/dev/binder", O_RDWR);

 open() 함수를 통해 binder 파일을 생성합니다. 보시면 아시듯이 binder는 하나의 파일에 불과했다는 것을 아실 수 있습니다.


fcntl(fd, F_SETFD, FD_CLOEXEC);

 fd에 설정된 Binder를 Control 하는 함수 입니다. F_SETFD는 Binder인 fd에 FD_CLOEXEC 플래그를 설정하라는 의미입니다. 이를 설정해주었을 경우 exec를 통해 프로그램이 실행될 때 Binder가 close 되도록 해주는 역할을 합니다.


 FD_CLOEXEC 설정에 대해 좀 더 자세히 알고 싶으신 분은 아래 링크를 통해 확인바랍니다.(영문)

http://stackoverflow.com/questions/6125068/what-does-the-fd-cloexec-fcntl-flag-do


status_t result = ioctl(fd, BINDER_VERSION, &vers);

 Binder 의 입출력 설정을 바꾸어주는 역할을 합니다. BINDER_VERSION은 요청 코드 번호를 말하며, &vers는 포인터를 보내 값을 얻기 위해 사용합니다.


/bionic/libc/kernel/common/linux/binder.h

1
2
3
4
5
6
struct binder_version {
 signed long protocol_version;
};
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
....
#define BINDER_VERSION _IOWR('b'9struct binder_version)
cs


/bionic/libc/kernel/common/asm-generic/ioctl.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define _IOC_SIZEBITS 14
#define _IOC_DIRBITS 2
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
#define _IOC_NONE 0U
#define _IOC_WRITE 1U
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define _IOC_READ 2U
#define _IOC(dir,type,nr,size)   (((dir) << _IOC_DIRSHIFT) |   ((type) << _IOC_TYPESHIFT) |   ((nr) << _IOC_NRSHIFT) |   ((size) << _IOC_SIZESHIFT))
extern unsigned int __invalid_size_argument_for_IOC;
#define _IOC_TYPECHECK(t)   ((sizeof(t) == sizeof(t[1]) &&   sizeof(t) < (1 << _IOC_SIZEBITS)) ?   sizeof(t) : __invalid_size_argument_for_IOC)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
....
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
cs

size_t maxThreads = 15;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
 Binder의 최대 쓰레드 수를 15개로 한 후 이를 ioctl을 통해 설정해주는 모습입니다.

return fd;

return을 통해 Binder의 File Description을 반환해 줍니다.

300x250