Hi Folks,
I've just finished a fresh implementation of the android binder
driver, would love to see some suggestions or comments on the code as
well as the whole binder IPC idea. The driver can be found on Github
at
https://github.com/rong1129/android-binder-ipc
in the module/new directory. The rest in that project are a minimum
set of framework library, the service manager, and some test
applications.
Reason I did this project was because when I was exploring around the
Android kernel and framework stuff, I found the existing binder driver
wasn't implemented efficiently esp. in the context of SMP. There's a
big mutex (binder_lock) that locks everyone else out when one ioctl is
in progress. I spent hours thinking a way to remove it, but turned out
impossible - there are basically pointers shared and passed around
between processes all over the place, which is why most of the driver
is protected by that mutex. It's easy to manage but the downside is no
two or more IPCs can be executed at the same time, regardless how many
CPUs you have. Also the mutex around ioctls or any long operation can
significantly reduce a system's responsiveness.
So in the new implementation, I took a new approach - an sysV like
process message queue is implemented as the foundation of the driver.
Unlike the sysV queue, it's used only in the kernel - mainly for
drivers. I specially separated it out in the hope that it would also
be beneficial to other drivers. The queue is designed so queue
identifiers (addresses) can be passed across processes and queues can
be accessed by different processes as long as the proper get/set
methods are called.
The binder driver is built on top of the queue mechanism and have
other data structure designed carefully so maximum concurrency can be
reached while requiring minimum locking. For example, the binder node
and refs in the existing driver are replaced with a single structure
binder_obj. Objects (nodes and refs in the existing terms) are created
only in the current process context (shared by threads) and not
accessed by other processes.
In terms of performance, the current version is slightly better than
the existing driver, in particular with concurrent IPC call scenarios.
As I just finished the coding and some simple tests, not so much has
been done in terms of tuning or optimizations. But I will surely do
them in the following days together with completing whatever is left.
To summarize the status, I managed to implement most part of the
protocol, as for now, the standard test app binderAddInts application
can work properly. Most of the existing implementation details are
covered, except a few things below,
* mmap and user buffer allocating stuff - it's the only
incompatibility so far
The existing mmap mechanism does reduce data copying from twice to
once, but going into other process's space to allocate and manage
buffers would need a big lock to avoid a lot of nasty things and you
can't be guaranteed other processes are not killed while you writing
to their space. Also the extra buffer management overhead can easily
kill all the benefits it actually brings.
I implemented in a traditional way, where there are two data copying
in a transaction: process A to kernel and kernel to process B, which
is simple and most drivers would do (if DMA is not involved). This is
the only incompatible place in terms kernel/user API so far. There's
no difference for the kernel to read data from user space, but for
writes, the driver writes the transaction data to the supplied buffer
in binder_write_read structure, instead of a pre-allocated mmap-ed
buffer, as a result the application is expected to follow the same
logic to read the data back and of course provide a larger read buffer
when doing ioctl.
* File descriptor sharing across processes
It's not used by the test application so it's not considered yet. Also
I'm not convinced it's so useful, as one can easily implement the
similar thing in user space by re-opening the file, although having it
won't affect much of concurrency as it's already taken care of by
VFS.
* Priority inheritance
Not sure if it exists is to avoid priority inversion, but seems
there's also priority adjusting in the framework - confusing. I'm not
entirely sure how they work together. Will probably look into it a
litter later.
* Reference counting and etc.
The whole strong / weak refs in the kernel just complicates the whole
driver, IMHO. It should well be enforced just at the user level.
There's not so much point of a strong referencing at the driver level,
as a process can quit regardless it wishes or not, or whether it has
strong references to something or not. What it matters is the driver
has to provide a transparent channel and a proper closing-down
notification to the applications, so they can maintain those
references properly just between themselves.
At the moment, there's a hack in the implementation to send an acquire
command to the user when a binder object is written through the driver
- just to stop the application from crashing as if no one holds a
reference to the object, it will be destroyed right after addService()
call. Took me hours to figure it out.
That's it - a good summary for the last ten days or so working on the
driver and a lot more time trying to understand how it works :(.
Anyway, it's GPLed so feel free to try and contribute.
Cheers,
Rong