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

 SurfaceFlinger의 동작 원리에 대해 좀 더 정확히 이해하기 위해서는 Native 단계에서 Looper의 동작 방식을 파악할 필요가 있습니다. 비록 Looper는 안드로이드의 System Library에 있지만 Native 단계에서의 Framework에서 상당히 중요하므로 간단하게 짚고 넘어가도록 하겠습니다.


 Looper란 폴링이 이루어지고 있는 루프(loop)로서 File Discriptor 이벤트를 관찰하는 역할을 하고, 등록된 Callback 함수를 호출하는 역할을 수행하기도 합니다. 실제로 Native 단계에서의 Looper는 UNIX 명령어 집합 epoll() 함수를 사용합니다.


/system/core/libutils/Looper.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
30
31
32
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    int wakeFds[2];
    int result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0"Could not create wake pipe.  errno=%d", errno);
 
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
 
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0"Could not make wake read pipe non-blocking.  errno=%d",
            errno);
 
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0"Could not make wake write pipe non-blocking.  errno=%d",
            errno);
 
    mIdling = false;
 
    // Allocate the epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0"Could not create epoll instance.  errno=%d", errno);
 
    struct epoll_event eventItem;
    memset(& eventItem, 0sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0"Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
}
cs


 Looper 클래스의 생성자 소스코드를 살펴보시면 IPC 함수들이 눈에 띄는 것을 보실 수 있습니다. Looper의 Pipe File Descriptor를 Non-block로 설정되고 있는 모습 또한 확인하실 수 있습니다.


mEpollFd = epoll_create(EPOLL_SIZE_HINT);

 Looper에 등록된 File Descriptor를 관리할 epoll 인스턴스를 생성합니다. 인자로 인스턴스의 크기를 설정하며 return 값으로 해당 인스턴스의 FD를 돌려받습니다.


result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);

  epoll 인스턴스인 mEpollFd에 제어하고자 하는 File Descriptor인 mWakeReadPipeFd를 등록합니다.


 위의 과정을 거쳐 등록된 Looper는 이후 외부 이벤트를 통해 File Descriptor를 제어하게 됩니다. Looper가 생성된 이후 부터는 addFd()함수를 통해 File Descriptor가 등록됩니다.


/system/core/include/utils/Looper.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
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
/**
 * A looper callback.
 */
class LooperCallback : public virtual RefBase {
protected:
    virtual ~LooperCallback() { }
 
public:
    /**
     * Handles a poll event for the given file descriptor.
     * It is given the file descriptor it is associated with,
     * a bitmask of the poll events that were triggered (typically ALOOPER_EVENT_INPUT),
     * and the data pointer that was originally supplied.
     *
     * Implementations should return 1 to continue receiving callbacks, or 0
     * to have this file descriptor and callback unregistered from the looper.
     */
    virtual int handleEvent(int fd, int events, void* data) = 0;
};
 
....
 
/**
 * A polling loop that supports monitoring file descriptor events, optionally
 * using callbacks.  The implementation uses epoll() internally.
 *
 * A looper can be associated with a thread although there is no requirement that it must be.
 */
class Looper : public ALooper, public RefBase {
protected:
    virtual ~Looper();
 
....
 
private:
    struct Request {
        int fd;
        int ident;
        sp<LooperCallback> callback;
        void* data;
    };
 
    struct Response {
        int events;
        Request request;
    };
 
    struct MessageEnvelope {
        MessageEnvelope() : uptime(0) { }
 
        MessageEnvelope(nsecs_t uptime, const sp<MessageHandler> handler,
                const Message& message) : uptime(uptime), handler(handler), message(message) {
        }
 
        nsecs_t uptime;
        sp<MessageHandler> handler;
        Message message;
    };
 
    const bool mAllowNonCallbacks; // immutable
 
    int mWakeReadPipeFd;  // immutable
    int mWakeWritePipeFd; // immutable
    Mutex mLock;
 
    Vector<MessageEnvelope> mMessageEnvelopes; // guarded by mLock
    bool mSendingMessage; // guarded by mLock
 
    // Whether we are currently waiting for work.  Not protected by a lock,
    // any use of it is racy anyway.
    volatile bool mIdling;
 
    int mEpollFd; // immutable
 
    // Locked list of file descriptor monitoring requests.
    KeyedVector<int, Request> mRequests;  // guarded by mLock
 
    // This state is only used privately by pollOnce and does not require a lock since
    // it runs on a single thread.
    Vector<Response> mResponses;
    size_t mResponseIndex;
    nsecs_t mNextMessageUptime; // set to LLONG_MAX when none
 
    int pollInner(int timeoutMillis);
    void awoken();
    void pushResponse(int events, const Request& request);
 
    static void initTLSKey();
    static void threadDestructor(void *st);
};
cs


/system/core/libutils/Looper.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
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
int Looper::addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data) {
    return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data);
}
 
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p"this, fd, ident,
            events, callback.get(), data);
#endif
 
    if (!callback.get()) {
        if (! mAllowNonCallbacks) {
            ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
            return -1;
        }
 
        if (ident < 0) {
            ALOGE("Invalid attempt to set NULL callback with ident < 0.");
            return -1;
        }
    } else {
        ident = ALOOPER_POLL_CALLBACK;
    }
 
    int epollEvents = 0;
    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
 
    { // acquire lock
        AutoMutex _l(mLock);
 
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.callback = callback;
        request.data = data;
 
        struct epoll_event eventItem;
        memset(& eventItem, 0sizeof(epoll_event)); // zero out unused members of data field union
        eventItem.events = epollEvents;
        eventItem.data.fd = fd;
 
        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.add(fd, request);
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.replaceValueAt(requestIndex, request);
        }
    } // release lock
    return 1;
}
cs

 Looper 이외의 클래스로부터 File Descriptor를 등록받게 되변 epoll을 통해 해당 File Descriptor를 등록하게 되고 해당 File Descriptor가 epoll에 의해 실행이 요청되었을 때, callback 함수가 동작할 수 있도록 Vector 변수인 mRequests에 등록됩니다.


/system/core/libutils/Looper.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
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
int Looper::pollInner(int timeoutMillis) {
 
....
 
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
 
    // No longer idling.
    mIdling = false;
 
    // Acquire lock.
    mLock.lock();
 
    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error, errno=%d", errno);
        result = ALOOPER_POLL_ERROR;
        goto Done;
    }
 
    // Check for poll timeout.
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout"this);
#endif
        result = ALOOPER_POLL_TIMEOUT;
        goto Done;
    }
 
    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds"this, eventCount);
#endif
 
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
Done: ;
 
....
 
}
cs

 Looper에서 pollInner()함수가 호출되면 epoll을 통해 등록되었던 File Descriptor를 통해 이벤트가 들어왔는지 확인한 후 이를 처리하도록 합니다.


int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

 epoll에 등록된 File Descriptor 중 파일 읽기가 준비된 FD의 갯수를 return 합니다. 이후 준비된 이벤트를 처리하는 과정을 거치게 됩니다.


awoken();

 만약 준비된 File Descriptor가 Looper의 것일 경우 위 함수를 실행합니다.


/system/core/libutils/Looper.cpp

1
2
3
4
5
6
7
8
9
10
11
void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ awoken"this);
#endif
 
    char buffer[16];
    ssize_t nRead;
    do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}
cs


pushResponse(events, mRequests.valueAt(requestIndex));

 만약 준비된 File Descriptor가 addFd에 의해 등록된 것의 경우 위 함수를 실행합니다.


/system/core/libutils/Looper.cpp

1
2
3
4
5
6
void Looper::pushResponse(int events, const Request& request) {
    Response response;
    response.events = events;
    response.request = request;
    mResponses.push(response);
}
cs


 위에서 보시는 바와 같이 mResponses에 이벤트가 등록되는 것을 확인하였습니다. 이제 해당 이벤트의 Callback이 호출되는 과정을 살펴보도록 하겠습니다.


/system/core/libutils/Looper.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
int Looper::pollInner(int timeoutMillis) {
 
....
 
    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == ALOOPER_POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd);
            }
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = ALOOPER_POLL_CALLBACK;
        }
    }
    return result;
}
cs


 위 함수를 통해 Looper에 등록된 Callback 함수가 실행됨을 확인할 수 있습니다.