안드로이드 프레임워크 프로그래밍(19) [Native 단계의 ServiceManager 동작원리]

안드로이드/프레임워크 2015. 4. 10. 00:33

 지금까지 우리는 안드로이드 내의 System service가 ServiceManager에 의해 관리되고 있음을 알 수 있었습니다. IServiceManager를 통해 BpServiceManager를 생성하여 Binder를 통해 서비스를 등록하거나 찾는 과정 또한 확인했습니다.


/frameworks/native/libs/binder/IServiceManager.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android::sp<IServiceManager> IServiceManager::asInterface(                           
    const android::sp<android::IBinder>& obj)                       
    {
        android::sp<IServiceManager> intr;
        if (obj != NULL) {
            intr = static_cast<IServiceManager*>(
                obj->queryLocalInterface(
                    IServiceManager::descriptor).get());
        if (intr == NULL) {
            intr = new BpServiceManager(obj);
        }
    }
    return intr;
cs


이러한 과정을 거치는 한 편으로는 이런 생각을 가지신 분들도 계시리라 생각합니다.


"BpServiceManager와 BnServiceManager가 존재한다면 ServiceManager 클래스도 존재하지 않을까?"


 대부분의 분들이라면 분명 있으리라 생각하실 겁니다. 그러나 놀랍게도 ServiceManager는 Java 단계에서는 클래스가 존재합니다만 Native 단계에서는 ServiceManager 클래스를 확인하실 수 없습니다. 그렇다면 Native 단계에서는 ServiceManager가 사용되지 않는걸까요?

 사실 Native 단계에서 ServiceManager는 daemon 프로세스와 같이 백그라운드에서 지속적으로 동작하는 프로세스로 존재합니다. 비록 ServiceManager라는 이름은 아니지만 Binder를 등록하거나 검색을 할 수 있는 기능을 갖추어 놓고 있습니다. ServiceManager는 다음과 같은 파일들로 구성되어 있습니다.


/frameworks/native/cmds/servicemanager/binder.h

/frameworks/native/cmds/servicemanager/binder.c

/frameworks/native/cmds/servicemanager/service_manager.c


 시작하기에 앞서 Android.mk에 설정된 모습을 보도록 하겠습니다.

/frameworks/native/cmds/servicemanager/Android.mk

1
2
3
4
5
6
7
8
9
10
11
12
LOCAL_PATH:= $(call my-dir)
 
#include $(CLEAR_VARS)
#LOCAL_SRC_FILES := bctest.c binder.c
#LOCAL_MODULE := bctest
#include $(BUILD_EXECUTABLE)
 
include $(CLEAR_VARS)
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := service_manager.c binder.c
LOCAL_MODULE := servicemanager
include $(BUILD_EXECUTABLE)
cs


 이제 ServiceManager가 실행되는 모습을 보도록 합시다.


/frameworks/native/cmds/servicemanager/service_manager.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void *svcmgr_handle;
 
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)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
 
    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}
cs


/frameworks/native/cmds/servicemanager/binder.h

1
2
/* the one magic object */
#define BINDER_SERVICE_MANAGER ((void*0)
cs


 처음엔 binder_state 구조체 변수와 void 변수 svcmgr이 선언됩니다. binder_state에는 이름 그대로 바인더의 상태를 저장하기 위해 사용되는 구조체임을 알 수 있습니다. 여기서 main() 함수의 내용을 하나씩 살펴보겠습니다.


    bs = binder_open(128*1024);


 binder_open() 함수가 선언되어 있고 이를 통해 binder_state 구조체를 return 하는 모습을 보이고 있습니다. binder_open() 함수를 통해 binder가 설정되며 인자로 메모리에 할당할 용량을 설정합니다.


/frameworks/native/cmds/servicemanager/binder.c

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
30
31
32
33
34
35
36
37
38
39
40
41
42
struct binder_state
{
    int fd;
    void *mapped;
    unsigned mapsize;
};
 
struct binder_state *binder_open(unsigned mapsize)
{
    struct binder_state *bs;
 
    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return 0;
    }
 
    bs->fd = open("/dev/binder", O_RDWR);
    if (bs->fd < 0) {
        fprintf(stderr,"binder: cannot open device (%s)\n",
                strerror(errno));
        goto fail_open;
    }
 
    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        fprintf(stderr,"binder: cannot map device (%s)\n",
                strerror(errno));
        goto fail_map;
    }
 
        /* TODO: check version */
 
    return bs;
 
fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return 0;
}
cs

 binder_open() 함수의 내부를 살펴보도록 합시다. open() 함수가 호출됨으로서 바딘더의 File descriptor를 얻어낸 후 mmap() 함수를 호출하여 실제 메모리에 바인더를 할당하는 작업을 진행합니다. 모든 것이 완료되면 binder_state를 저장한 구조체 변수의 포인터를 return 합니다.


binder_become_context_manager(bs)


binder_state의 값을 통하여 컨텍스트 매니저(ServiceManager)을 설정해줍니다.

/frameworks/native/cmds/servicemanager/binder.c

1
2
3
4
int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
cs

binder.h

1
#define BINDER_SET_CONTEXT_MGR _IOW('b'7int)
cs

 해당 함수는 ioctl() 함수를 사용하여 바인더의 입출력을 제어합니다.


binder_loop(bs, svcmgr_handler);


 이 함수가 실행됨으로서 ServiceManager의 실행은 loop에 들어갑니다. 즉, 시스템에 특별한 이상이 발생하지 않는 한 작동이 계속 되는 것으로 이해하시면 좋을 듯 합니다.


/frameworks/native/cmds/servicemanager/binder.c

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    unsigned readbuf[32];
 
    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    
    readbuf[0= BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(unsigned));
 
    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (unsigned) readbuf;
 
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
 
        if (res < 0) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }
 
        res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
        if (res == 0) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}
 
int binder_write(struct binder_state *bs, void *data, unsigned len)
{
    struct binder_write_read bwr;
    int res;
    bwr.write_size = len;
    bwr.write_consumed = 0;
    bwr.write_buffer = (unsigned) data;
    bwr.read_size = 0;
    bwr.read_consumed = 0;
    bwr.read_buffer = 0;
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    if (res < 0) {
        fprintf(stderr,"binder_write: ioctl failed (%s)\n",
                strerror(errno));
    }
    return res;
}
 
 
int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uint32_t *ptr, uint32_t size, binder_handler func)
{
    int r = 1;
    uint32_t *end = ptr + (size / 4);
 
    while (ptr < end) {
        uint32_t cmd = *ptr++;
#if TRACE
        fprintf(stderr,"%s:\n", cmd_name(cmd));
#endif
        switch(cmd) {
        case BR_NOOP:
            break;
        case BR_TRANSACTION_COMPLETE:
            break;
        case BR_INCREFS:
        case BR_ACQUIRE:
        case BR_RELEASE:
        case BR_DECREFS:
#if TRACE
            fprintf(stderr,"  %08x %08x\n", ptr[0], ptr[1]);
#endif
            ptr += 2;
            break;
        case BR_TRANSACTION: {
            struct binder_txn *txn = (void *) ptr;
            if ((end - ptr) * sizeof(uint32_t) < sizeof(struct binder_txn)) {
                ALOGE("parse: txn too small!\n");
                return -1;
            }
            binder_dump_txn(txn);
            if (func) {
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;
                int res;
 
                bio_init(&reply, rdata, sizeof(rdata), 4);
                bio_init_from_txn(&msg, txn);
                res = func(bs, txn, &msg, &reply);
                binder_send_reply(bs, &reply, txn->data, res);
            }
            ptr += sizeof(*txn) / sizeof(uint32_t);
            break;
        }
        case BR_REPLY: {
            struct binder_txn *txn = (void*) ptr;
            if ((end - ptr) * sizeof(uint32_t) < sizeof(struct binder_txn)) {
                ALOGE("parse: reply too small!\n");
                return -1;
            }
            binder_dump_txn(txn);
            if (bio) {
                bio_init_from_txn(bio, txn);
                bio = 0;
            } else {
                    /* todo FREE BUFFER */
            }
            ptr += (sizeof(*txn) / sizeof(uint32_t));
            r = 0;
            break;
        }
        case BR_DEAD_BINDER: {
            struct binder_death *death = (void**ptr++;
            death->func(bs, death->ptr);
            break;
        }
        case BR_FAILED_REPLY:
            r = -1;
            break;
        case BR_DEAD_REPLY:
            r = -1;
            break;
        default:
            ALOGE("parse: OOPS %d\n", cmd);
            return -1;
        }
    }
 
    return r;
}
cs


binder.h

1
2
3
4
5
6
7
8
9
10
struct binder_write_read {
 signed long write_size;
 signed long write_consumed;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
 unsigned long write_buffer;
 signed long read_size;
 signed long read_consumed;
 unsigned long read_buffer;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
};
cs



 제 이전 포스팅에서 Native 단계에서의 Parcel의 전송과정에 대해 다루었던 과정을 보셨던 분이라면 위의 소스코드를 읽었을 때 '앗!'이라는 생각을 하시는 분들이 계시리라 생각합니다. 그렇습니다. Parcel에서 파일이 전송되려 할 때 transact() 함수를 통해 전송되어 오는 Parcel의 값들을 처리하고 있는 것임을 본 포스팅을 통해 확실히 알게 되셨으리라 생각합니다.


 사실 위의 과정에서 좀 더 많은 설명을 해드리고 싶습니다만 글을 더 진행하기엔 포스팅의 분량도 많아질 뿐더러 내용 또한 어려워지기 때문에 이후 시간이 된다면 Parcel이 Binder와 어떻게 동작하는 지에 대해 자세히 다루어 보는 시간을 가져보도록 하겠습니다.

300x250