Truyền thông liên tiến trình trên Android: IPC, RPC, Hệ thống Binder và Gọi hứng ứng dụng C
1. Khái niệm cơ bản
IPC (Inter-Process Communication) hay Truyền thông liên tiến trình là cơ chế một tiến trình gửi dữ liệu đến một tiến trình khác. Khi ứng dụng A cần trao đổi dữ liệu với ứng dụng B, cơ chế IPC được sử dụng.
RPC (Remote Procedure Call) hay Gọi thủ tục từ xa cho phép một tiến trình gọi hàm của tiến trình khác. Ví dụ, ứng dụng A muốn bật đèn LED, nó sẽ gọi hàm led_open. Thực tế, ứng dụng A sẽ gửi dữ liệu qua IPC đến ứng dụng B, ứng dụng B sẽ nhận dữ liệu và gọi hàm led_open của mình. Quá trình này tạo ra ảo rằng ứng dụng A đang trực tiếp gọi hàm led_open.
Trước khi hoạt động, tiến trình máy chủ cần đăng ký dịch vụ với servicemanager. Tiến trình khách sau đó sẽ truy vấn dịch vụ LED để nhận một handle trỏ đến tiến trình B.
Dữ liệu thường được lưu trữ trong bộ đệm char buf[1024]. Các tiến trình A và B trao đổi dữ liệu hai chiều qua bộ đệm này.
Tham khảo thư mục mã nguồn của Google:
binder.c (các hàm C được đóng gói sẵn bởi Google)
2. Quy trình hoạt động
Servicemanager được hệ thống khởi chạy đầu tiên:
- Mở trình điều khiển binder
- Thông báo cho trình điều khiển rằng đây là servicemanager
- Vòng lặp while(1) để đọc dữ liệu:
- Đọc dữ liệu từ trình điều khiển
- Nếu không có dữ liệu thì ngủ (sleep)
- Khi có dữ liệu, phân tích và xử lý:
- Đối với máy chủ đăng ký dịch vụ: ghi tên dịch vụ vào danh sách liên kết
- Đối với khách truy vấn dịch vụ: tra danh sách liên kết để tìm dịch vụ và trả về handle của tiến trình máy chủ
Tiến trình máy chủ:
- Mở trình điều khiển binder
- Đăng ký dịch vụ bằng cách gửi tên dịch vụ đến servicemanager
- Vòng lặp while(1) để đọc dữ liệu từ trình điều khiển:
- Nếu không có dữ liệu thì ngủ
- Phân tích dữ liệu và gọi hàm tương ứng ở tầng hệ thống
Tiến trình khách:
- Mở trình điều khiển binder
- Truy vấn dịch vụ từ servicemanager để nhận handle
- Gửi dữ liệu đến handle vừa nhận được
3. Ví dụ mã nguồn bctest.c
3.1 Đăng ký dịch vụ và mở trình điều khiển binder
Quá trình đăng ký dịch vụ:
- Chuẩn bị dữ liệu cần thiết
- Gửi dữ liệu đến đích mục tiêu
Giá trị handle này được định nghĩa trong tệp header, với giá trị 0 đại diện cho tiến trình servicemanager.
Thông qua hàm binder_call với tham số code: biểu thị việc gọi hàm "addservice" trong servicemanager.
3.2 Lấy dịch vụ
- Mở trình điều khiển
- Vòng lặp để truy vấn danh sách dịch vụ
if (binder_call(bs, &msg, &reply, target, SVC_MGR_CHECK_SERVICE)) return 0;
Vẫn sử dụng hàm binder_call, trong đó msg chứa tên dịch vụ cần lấy và reply chứa dữ liệu trả về từ servicemanager.
4. Hàm binder_call
Hàm này thực hiện gọi từ xa, xác định người nhận dữ liệu:
- target: tiến trình đích
- code: hàm cần gọi
- msg: tham số dữ liệu
- reply: giá trị trả về
Quá trình thực hiện:
- Xây dựng dữ liệu cần gửi và đặt vào bộ đệm, sử dụng cấu trúc binder_io
- Gọi ioctl để gửi dữ liệu
int binder_call(struct binder_state *bs,
struct binder_io *msg, struct binder_io *reply,
uint32_t target, uint32_t code)
{
int res;
struct binder_write_read bwr;
struct {
uint32_t cmd;
struct binder_transaction_data txn;
} __attribute__((packed)) writebuf;
unsigned readbuf[32];
if (msg->flags & BIO_F_OVERFLOW) {
fprintf(stderr,"binder: txn buffer overflow\n");
goto fail;
}
writebuf.cmd = BC_TRANSACTION;
writebuf.txn.target.handle = target;
writebuf.txn.code = code;
writebuf.txn.flags = 0;
writebuf.txn.data_size = msg->data - msg->data0;
writebuf.txn.offsets_size = ((char*) msg->offs) - ((char*) msg->offs0);
writebuf.txn.data.ptr.buffer = (uintptr_t)msg->data0;
writebuf.txn.data.ptr.offsets = (uintptr_t)msg->offs0;
bwr.write_size = sizeof(writebuf);
bwr.write_consumed = 0;
bwr.write_buffer = (uintptr_t) &writebuf;
hexdump(msg->data0, msg->data - msg->data0);
for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
// Gọi ioctl để gửi dữ liệu
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
fprintf(stderr,"binder: ioctl failed (%s)\n", strerror(errno));
goto fail;
}
res = binder_parse(bs, reply, (uintptr_t) readbuf, bwr.read_consumed, 0);
if (res == 0) return 0;
if (res < 0) goto fail;
}
fail:
memset(reply, 0, sizeof(*reply));
reply->flags |= BIO_F_IOERROR;
return -1;
}
Dữ liệu cần được chuyển đổi, tham số binder_io quản lý bộ đệm theo yêu cầu của trình điều khiển nhân. Lệnh ioctl nhận dữ liệu cũng sẽ nhận cấu trúc binder_write_read và chuyển đổi thành binder_io.
Xem mã nguồn svcmgr_lookup dưới đây:
uint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name)
{
uint32_t handle;
// Quản lý bộ đệm bằng binder_io
unsigned iodata[512/4];
// Khởi tạo cấu trúc binder_io
struct binder_io msg, reply;
// Khởi tạo để có thể đặt dữ liệu vào bộ đệm
bio_init(&msg, iodata, sizeof(iodata), 4);
bio_put_uint32(&msg, 0); // strict mode header
bio_put_string16_x(&msg, SVC_MGR_NAME);
bio_put_string16_x(&msg, name);
// Gửi dữ liệu đến trình điều khiển
if (binder_call(...)