HIDL

Ngôn ngữ định nghĩa giao diện HAL hoặc HIDL là một ngôn ngữ mô tả giao diện (IDL) để chỉ định giao diện giữa HAL và người dùng. HIDL cho phép chỉ định các loại và lệnh gọi phương thức, được thu thập vào giao diện và gói. Nói rộng ra, HIDL là một hệ thống để giao tiếp giữa các cơ sở mã có thể được biên dịch độc lập.

HIDL được dùng để giao tiếp giữa các quy trình (IPC). Các HAL được tạo bằng HDL được gọi là HAL liên kết vì các HAL này có thể giao tiếp với các lớp cấu trúc khác bằng các lệnh gọi giao tiếp liên quy trình (IPC) của liên kết. HAL được liên kết chạy trong một quy trình riêng biệt với ứng dụng sử dụng các HAL đó. Đối với các thư viện phải được liên kết với một quy trình, bạn cũng có thể sử dụng chế độ truyền tải (không được hỗ trợ trong Java).

HIDL chỉ định cấu trúc dữ liệu và chữ ký phương thức, được sắp xếp trong các giao diện (tương tự như một lớp) được thu thập vào các gói. Cú pháp của HIDL trông quen thuộc với các lập trình viên C++ và Java, nhưng có một bộ từ khoá khác. HIDL cũng sử dụng chú thích kiểu Java.

Thuật ngữ

Phần này sử dụng các thuật ngữ liên quan đến HIDL sau:

liên kết Cho biết HIDL đang được dùng cho các lệnh gọi quy trình từ xa giữa các quy trình, được triển khai thông qua cơ chế giống Binder. Xem thêm phần chuyển tiếp.
lệnh gọi lại, không đồng bộ Giao diện do người dùng HAL phân phát, được chuyển đến HAL (sử dụng phương thức HIDL) và được HAL gọi để trả về dữ liệu bất cứ lúc nào.
lệnh gọi lại, đồng bộ Trả về dữ liệu từ quá trình triển khai phương thức HIDL của máy chủ cho ứng dụng. Không dùng cho các phương thức trả về giá trị rỗng hoặc một giá trị gốc duy nhất.
client Quy trình gọi các phương thức của một giao diện cụ thể. Một quy trình khung HAL hoặc Android có thể là ứng dụng khách của một giao diện và máy chủ của một giao diện khác. Xem thêm về tính năng chuyển tiếp.
mở rộng Cho biết một giao diện thêm các phương thức và/hoặc loại vào một giao diện khác. Một giao diện chỉ có thể mở rộng một giao diện khác. Có thể dùng để tăng phiên bản nhỏ trong cùng một tên gói hoặc cho một gói mới (ví dụ: tiện ích của nhà cung cấp) để xây dựng trên một gói cũ.
tạo Cho biết một phương thức giao diện trả về giá trị cho ứng dụng. Để trả về một giá trị không phải giá trị gốc hoặc nhiều giá trị, một hàm gọi lại đồng bộ sẽ được tạo.
giao diện Tập hợp các phương thức và loại. Được dịch thành một lớp trong C++ hoặc Java. Tất cả phương thức trong một giao diện đều được gọi theo cùng một hướng: một quy trình ứng dụng gọi các phương thức do quy trình máy chủ triển khai.
một chiều Khi áp dụng cho một phương thức HIDL, cho biết phương thức không trả về giá trị nào và không chặn.
gói hàng Tập hợp các giao diện và loại dữ liệu dùng chung một phiên bản.
truyền qua Chế độ HIDL trong đó máy chủ là một thư viện dùng chung, do ứng dụng dlopen. Ở chế độ truyền tải, ứng dụng và máy chủ là cùng một quy trình nhưng có cơ sở mã riêng biệt. Chỉ dùng để đưa cơ sở mã cũ vào mô hình HIDL. Xem thêm phần Đã liên kết.
máy chủ Quy trình triển khai các phương thức của một giao diện. Xem thêm về tính năng chuyển tiếp.
giao thông Cấu trúc HIDL di chuyển dữ liệu giữa máy chủ và ứng dụng.
version Phiên bản của một gói. Bao gồm hai số nguyên, chính và phụ. Các mức tăng phiên bản nhỏ có thể thêm (nhưng không thay đổi) các loại và phương thức.

Thiết kế HIDL

Mục tiêu của HIDL là có thể thay thế khung Android mà không cần phải tạo lại HAL. HAL do các nhà cung cấp hoặc nhà sản xuất SOC tạo và đặt trong phân vùng /vendor trên thiết bị, cho phép thay thế khung Android trong phân vùng riêng bằng OTA mà không cần biên dịch lại HAL.

Thiết kế HIDL cân bằng các mối lo ngại sau:

  • Khả năng tương tác. Tạo các giao diện có thể tương tác một cách đáng tin cậy giữa các quy trình có thể được biên dịch bằng nhiều cấu trúc, chuỗi công cụ và cấu hình bản dựng. Giao diện HIDL được tạo phiên bản và không thể thay đổi sau khi phát hành.
  • Hiệu quả. HIDL cố gắng giảm thiểu số lượng thao tác sao chép. Dữ liệu do HIDL xác định được phân phối đến mã C++ trong cấu trúc dữ liệu bố cục tiêu chuẩn C++ mà bạn có thể sử dụng mà không cần giải nén. HIDL cũng cung cấp giao diện bộ nhớ dùng chung và vì RPC vốn có tốc độ hơi chậm, nên HIDL hỗ trợ hai cách để chuyển dữ liệu mà không cần sử dụng lệnh gọi RPC: bộ nhớ dùng chung và Hàng đợi thông báo nhanh (FMQ).
  • Trực quan. HIDL tránh các vấn đề phức tạp về quyền sở hữu bộ nhớ bằng cách chỉ sử dụng các tham số in cho RPC (xem Ngôn ngữ định nghĩa giao diện Android (AIDL)); các giá trị không thể được trả về một cách hiệu quả từ các phương thức sẽ được trả về thông qua các hàm gọi lại. Việc truyền dữ liệu vào HIDL để chuyển cũng như nhận dữ liệu từ HIDL đều không làm thay đổi quyền sở hữu dữ liệu – quyền sở hữu luôn thuộc về hàm gọi. Dữ liệu chỉ cần tồn tại trong thời gian của hàm được gọi và có thể bị huỷ ngay sau khi hàm được gọi trả về.

Sử dụng chế độ truyền thẳng

Để cập nhật các thiết bị chạy các phiên bản Android cũ lên Android O, bạn có thể gói cả HAL thông thường (và cũ) trong một giao diện HIDL mới phân phát HAL ở chế độ liên kết và chế độ cùng quy trình (chuyển tiếp). Việc bao bọc này minh bạch đối với cả HAL và khung Android.

Chế độ truyền tải chỉ dành cho các ứng dụng và phương thức triển khai C++. Các thiết bị chạy phiên bản Android cũ không có HAL được viết bằng Java, vì vậy, HAL Java vốn đã được liên kết.

Khi biên dịch tệp .hal, hidl-gen sẽ tạo thêm một tệp tiêu đề truyền tải BsFoo.h ngoài các tiêu đề dùng để giao tiếp với liên kết; tiêu đề này xác định các hàm cần được dlopen. Vì các HAL truyền tải chạy trong cùng một quy trình mà chúng được gọi, nên trong hầu hết các trường hợp, các phương thức truyền tải được gọi bằng lệnh gọi hàm trực tiếp (cùng một luồng). Các phương thức oneway chạy trong luồng riêng vì không nhằm mục đích chờ HAL xử lý các phương thức đó (nghĩa là mọi HAL sử dụng phương thức oneway ở chế độ truyền qua phải an toàn cho luồng).

Với IFoo.hal, BsFoo.h sẽ gói các phương thức do HIDL tạo để cung cấp các tính năng bổ sung (chẳng hạn như thực hiện các giao dịch oneway trong một luồng khác). Tệp này tương tự như BpFoo.h, tuy nhiên thay vì truyền các lệnh gọi IPC bằng trình liên kết, các hàm mong muốn sẽ được gọi trực tiếp. Các phương thức triển khai HAL trong tương lai có thể cung cấp nhiều phương thức triển khai, chẳng hạn như HAL FooFast và HAL FooAccurate. Trong những trường hợp như vậy, một tệp cho mỗi phương thức triển khai bổ sung sẽ được tạo (ví dụ: PTFooFast.cppPTFooAccurate.cpp).

Liên kết các HAL truyền qua

Bạn có thể liên kết các phương thức triển khai HAL hỗ trợ chế độ truyền tải. Với giao diện HAL a.b.c.d@M.N::IFoo, hai gói sẽ được tạo:

  • a.b.c.d@M.N::IFoo-impl. Chứa quá trình triển khai HAL và hiển thị hàm IFoo* HIDL_FETCH_IFoo(const char* name). Trên các thiết bị cũ, gói này được dlopen và quá trình triển khai được tạo bản sao bằng HIDL_FETCH_IFoo. Bạn có thể tạo mã cơ sở bằng cách sử dụng hidl-gen, -Lc++-impl-Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. Mở HAL truyền qua và tự đăng ký làm dịch vụ liên kết, cho phép triển khai cùng một HAL được dùng làm cả truyền qua và liên kết.

Với loại IFoo, bạn có thể gọi sp<IFoo> IFoo::getService(string name, bool getStub) để có quyền truy cập vào một thực thể của IFoo. Nếu getStub đúng, getService sẽ chỉ cố gắng mở HAL ở chế độ truyền thẳng. Nếu getStub là giá trị sai, getService sẽ tìm một dịch vụ liên kết; nếu không thành công, thì dịch vụ này sẽ tìm dịch vụ chuyển tiếp. Không bao giờ được sử dụng tham số getStub ngoại trừ trong defaultPassthroughServiceImplementation. (Các thiết bị chạy Android O là thiết bị được liên kết đầy đủ, vì vậy, bạn không được phép mở dịch vụ ở chế độ chuyển tiếp.)

Ngữ pháp HIDL

Theo thiết kế, ngôn ngữ HIDL tương tự như C (nhưng không sử dụng trình tiền xử lý C). Tất cả dấu câu không được mô tả bên dưới (ngoài việc sử dụng rõ ràng =|) đều là một phần của cú pháp.

Lưu ý: Để biết thông tin chi tiết về kiểu mã HIDL, hãy xem Hướng dẫn về kiểu mã.

  • /** */ cho biết một nhận xét về tài liệu. Bạn chỉ có thể áp dụng các loại này cho nội dung khai báo giá trị enum, phương thức, trường và loại.
  • /* */ cho biết một ghi chú nhiều dòng.
  • // cho biết một nhận xét đến cuối dòng. Ngoài //, dòng mới giống với mọi khoảng trắng khác.
  • Trong cú pháp mẫu bên dưới, văn bản từ // đến cuối dòng không phải là một phần của cú pháp mà là một chú thích về cú pháp.
  • [empty] có nghĩa là thuật ngữ có thể để trống.
  • ? theo sau một giá trị cố định hoặc thuật ngữ có nghĩa là không bắt buộc.
  • ... cho biết trình tự chứa không có hoặc có mục với dấu câu phân tách như đã chỉ định. Không có đối số biến trong HIDL.
  • Dấu phẩy phân tách các phần tử trình tự.
  • Dấu chấm phẩy kết thúc mỗi phần tử, bao gồm cả phần tử cuối cùng.
  • UPPERCASE là một ký tự không phải ký tự đầu cuối.
  • italics là một gia đình mã thông báo, chẳng hạn như integer hoặc identifier (các quy tắc phân tích cú pháp C tiêu chuẩn).
  • constexpr là một biểu thức hằng số kiểu C (chẳng hạn như 1 + 11L << 3).
  • import_name là tên gói hoặc giao diện, đủ điều kiện như mô tả trong phần Phiên bản HIDL.
  • words viết thường là mã thông báo cố định.

Ví dụ:

ROOT =
    PACKAGE IMPORTS PREAMBLE { ITEM ITEM ... }  // not for types.hal
  | PACKAGE IMPORTS ITEM ITEM...  // only for types.hal; no method definitions

ITEM =
    ANNOTATIONS? oneway? identifier(FIELD, FIELD ...) GENERATES?;
  |  safe_union identifier { UFIELD; UFIELD; ...};
  |  struct identifier { SFIELD; SFIELD; ...};  // Note - no forward declarations
  |  union identifier { UFIELD; UFIELD; ...};
  |  enum identifier: TYPE { ENUM_ENTRY, ENUM_ENTRY ... }; // TYPE = enum or scalar
  |  typedef TYPE identifier;

VERSION = integer.integer;

PACKAGE = package android.hardware.identifier[.identifier[...]]@VERSION;

PREAMBLE = interface identifier EXTENDS

EXTENDS = <empty> | extends import_name  // must be interface, not package

GENERATES = generates (FIELD, FIELD ...)

// allows the Binder interface to be used as a type
// (similar to typedef'ing the final identifier)
IMPORTS =
   [empty]
  |  IMPORTS import import_name;

TYPE =
  uint8_t | int8_t | uint16_t | int16_t | uint32_t | int32_t | uint64_t | int64_t |
 float | double | bool | string
|  identifier  // must be defined as a typedef, struct, union, enum or import
               // including those defined later in the file
|  memory
|  pointer
|  vec<TYPE>
|  bitfield<TYPE>  // TYPE is user-defined enum
|  fmq_sync<TYPE>
|  fmq_unsync<TYPE>
|  TYPE[SIZE]

FIELD =
   TYPE identifier

UFIELD =
   TYPE identifier
  |  safe_union identifier { FIELD; FIELD; ...} identifier;
  |  struct identifier { FIELD; FIELD; ...} identifier;
  |  union identifier { FIELD; FIELD; ...} identifier;

SFIELD =
   TYPE identifier
  |  safe_union identifier { FIELD; FIELD; ...};
  |  struct identifier { FIELD; FIELD; ...};
  |  union identifier { FIELD; FIELD; ...};
  |  safe_union identifier { FIELD; FIELD; ...} identifier;
  |  struct identifier { FIELD; FIELD; ...} identifier;
  |  union identifier { FIELD; FIELD; ...} identifier;

SIZE =  // Must be greater than zero
     constexpr

ANNOTATIONS =
     [empty]
  |  ANNOTATIONS ANNOTATION

ANNOTATION =
  |  @identifier
  |  @identifier(VALUE)
  |  @identifier(ANNO_ENTRY, ANNO_ENTRY  ...)

ANNO_ENTRY =
     identifier=VALUE

VALUE =
     "any text including \" and other escapes"
  |  constexpr
  |  {VALUE, VALUE ...}  // only in annotations

ENUM_ENTRY =
     identifier
  |  identifier = constexpr