Các bản cập nhật hệ thống A/B cũ (còn gọi là bản cập nhật liền mạch) đảm bảo hệ thống khởi động hoạt động được vẫn còn trên ổ đĩa trong quá trình cập nhật qua mạng (OTA). Phương pháp này làm giảm khả năng thiết bị không hoạt động sau khi cập nhật, tức là ít phải thay thế thiết bị và cài đặt lại ROM hơn tại các trung tâm bảo hành và sửa chữa. Các hệ điều hành cấp thương mại khác như ChromeOS cũng sử dụng thành công bản cập nhật A/B.
Để biết thêm thông tin về bản cập nhật hệ thống A/B và cách hoạt động của các bản cập nhật này, hãy xem phần Chọn phân vùng (khe).
Bản cập nhật hệ thống A/B mang lại các lợi ích sau:
- Quá trình cập nhật OTA có thể diễn ra trong khi hệ thống đang chạy mà không làm gián đoạn người dùng. Người dùng có thể tiếp tục sử dụng thiết bị trong quá trình cập nhật qua mạng. Thời gian ngừng hoạt động duy nhất trong quá trình cập nhật là khi thiết bị khởi động lại vào phân vùng ổ đĩa đã cập nhật.
- Sau khi cập nhật, quá trình khởi động lại sẽ không mất nhiều thời gian hơn so với khi khởi động lại thông thường.
- Nếu không áp dụng được bản cập nhật OTA (ví dụ: do ổ flash bị hỏng), người dùng sẽ không bị ảnh hưởng. Người dùng sẽ tiếp tục chạy hệ điều hành cũ và ứng dụng có thể thử lại quá trình cập nhật.
- Nếu bạn áp dụng bản cập nhật OTA nhưng không khởi động được, thiết bị sẽ khởi động lại vào phân vùng cũ và vẫn có thể sử dụng được. Ứng dụng có thể thử cập nhật lại.
- Mọi lỗi (chẳng hạn như lỗi I/O) chỉ ảnh hưởng đến nhóm phân vùng không sử dụng và có thể thử lại. Các lỗi như vậy cũng ít xảy ra hơn vì tải I/O được cố ý giảm để tránh làm giảm trải nghiệm người dùng.
-
Bạn có thể truyền trực tuyến bản cập nhật đến các thiết bị A/B mà không cần tải gói xuống trước khi cài đặt. Truyền trực tuyến có nghĩa là người dùng không cần có đủ dung lượng trống để lưu trữ gói cập nhật trên
/data
hoặc/cache
. - Phân vùng bộ nhớ đệm không còn được dùng để lưu trữ các gói cập nhật OTA, vì vậy, bạn không cần phải đảm bảo rằng phân vùng bộ nhớ đệm đủ lớn cho các bản cập nhật trong tương lai.
- dm-verity đảm bảo thiết bị sẽ khởi động một hình ảnh không bị hỏng. Nếu không khởi động được do lỗi OTA hoặc dm-verity, thiết bị có thể khởi động lại vào một hình ảnh cũ. (Android Xác minh quy trình khởi động không yêu cầu bản cập nhật A/B.)
Giới thiệu về bản cập nhật hệ thống A/B
Bản cập nhật A/B yêu cầu thay đổi cả ứng dụng và hệ thống. Tuy nhiên, máy chủ gói OTA không cần phải thay đổi: các gói cập nhật vẫn được phân phát qua HTTPS. Đối với các thiết bị sử dụng cơ sở hạ tầng OTA của Google, tất cả thay đổi về hệ thống đều nằm trong AOSP và mã ứng dụng do Dịch vụ Google Play cung cấp. Các OEM không sử dụng cơ sở hạ tầng OTA của Google sẽ có thể sử dụng lại mã hệ thống AOSP nhưng cần cung cấp ứng dụng của riêng họ.
Đối với OEM cung cấp ứng dụng của riêng mình, ứng dụng đó cần:
- Quyết định thời điểm cập nhật. Vì các bản cập nhật A/B diễn ra ở chế độ nền, nên người dùng không còn khởi tạo các bản cập nhật này nữa. Để tránh làm gián đoạn người dùng, bạn nên lên lịch cập nhật khi thiết bị ở chế độ bảo trì rảnh, chẳng hạn như qua đêm và khi có Wi-Fi. Tuy nhiên, ứng dụng của bạn có thể sử dụng bất kỳ phương pháp phỏng đoán nào mà bạn muốn.
- Kiểm tra với máy chủ gói OTA và xác định xem có bản cập nhật hay không. Mã này chủ yếu giống với mã ứng dụng hiện có, ngoại trừ việc bạn sẽ muốn báo hiệu rằng thiết bị hỗ trợ A/B. (Ứng dụng của Google cũng có nút Kiểm tra ngay để người dùng kiểm tra bản cập nhật mới nhất.)
-
Gọi
update_engine
bằng URL HTTPS cho gói cập nhật, giả sử có một gói cập nhật.update_engine
sẽ cập nhật các khối thô trên phân vùng hiện không sử dụng khi truyền trực tuyến gói cập nhật. -
Báo cáo kết quả cài đặt thành công hoặc không thành công cho máy chủ của bạn, dựa trên mã kết quả
update_engine
. Nếu bản cập nhật được áp dụng thành công,update_engine
sẽ yêu cầu trình tải khởi động khởi động vào hệ điều hành mới trong lần khởi động lại tiếp theo. Trình tải khởi động sẽ quay lại hệ điều hành cũ nếu hệ điều hành mới không khởi động được, vì vậy, ứng dụng không cần làm gì cả. Nếu không cập nhật được, ứng dụng cần quyết định thời điểm (và liệu có) thử lại hay không, dựa trên mã lỗi chi tiết. Ví dụ: một ứng dụng tốt có thể nhận ra rằng gói OTA một phần ("diff") không thành công và thử một gói OTA đầy đủ.
Nếu muốn, ứng dụng có thể:
- Hiển thị thông báo yêu cầu người dùng khởi động lại. Nếu bạn muốn triển khai một chính sách khuyến khích người dùng cập nhật thường xuyên, thì bạn có thể thêm thông báo này vào ứng dụng của mình. Nếu ứng dụng không nhắc người dùng, thì người dùng sẽ nhận được bản cập nhật vào lần khởi động lại tiếp theo. (Ứng dụng của Google có độ trễ có thể định cấu hình cho mỗi bản cập nhật.)
- Hiển thị thông báo cho người dùng biết liệu họ đã khởi động vào phiên bản hệ điều hành mới hay không, hoặc liệu họ có dự kiến khởi động vào phiên bản hệ điều hành mới nhưng lại quay lại phiên bản hệ điều hành cũ hay không. (Ứng dụng của Google thường không làm cả hai việc này.)
Về phía hệ thống, bản cập nhật hệ thống A/B ảnh hưởng đến những vấn đề sau:
-
Lựa chọn phân vùng (khe), trình nền
update_engine
và các hoạt động tương tác với trình tải khởi động (mô tả bên dưới) - Quy trình tạo bản dựng và gói cập nhật OTA (mô tả trong phần Triển khai bản cập nhật A/B)
Lựa chọn phân vùng (khung giờ)
Bản cập nhật hệ thống A/B sử dụng hai bộ phân vùng được gọi là khe (thường là khe A và khe B). Hệ thống chạy từ khe hiện tại trong khi các phân vùng trong khe không sử dụng không được hệ thống đang chạy truy cập trong quá trình hoạt động bình thường. Phương pháp này giúp các bản cập nhật chống lỗi bằng cách giữ lại khe không sử dụng làm phương án dự phòng: Nếu lỗi xảy ra trong hoặc ngay sau khi cập nhật, hệ thống có thể quay lại khe cũ và tiếp tục hoạt động. Để đạt được mục tiêu này, không có phân vùng nào mà khe hiện tại sử dụng được cập nhật trong bản cập nhật OTA (bao gồm cả các phân vùng chỉ có một bản sao).
Mỗi khe có một thuộc tính bootable (có thể khởi động) cho biết liệu khe đó có chứa hệ thống chính xác để thiết bị có thể khởi động hay không. Khe hiện tại có thể khởi động khi hệ thống đang chạy, nhưng khe còn lại có thể có phiên bản hệ thống cũ (vẫn chính xác), phiên bản mới hơn hoặc dữ liệu không hợp lệ. Bất kể khe hiện tại là gì, sẽ có một khe là khe đang hoạt động (khe mà trình tải khởi động sẽ khởi động từ lần khởi động tiếp theo) hoặc khe ưu tiên.
Mỗi khe cũng có một thuộc tính thành công do không gian người dùng đặt, thuộc tính này chỉ có liên quan nếu khe cũng có thể khởi động. Một khe thành công phải có thể tự khởi động, chạy và cập nhật. Bộ tải khởi động phải đánh dấu một khe khởi động không được đánh dấu là thành công (sau khi thực hiện một số lần thử khởi động từ khe đó) là không thể khởi động, bao gồm cả việc thay đổi khe đang hoạt động thành một khe khởi động khác (thường là khe đang chạy ngay trước khi thử khởi động vào khe mới, đang hoạt động). Thông tin chi tiết cụ thể về giao diện được xác định trong
boot_control.h
.
Cập nhật trình nền của công cụ
Bản cập nhật hệ thống A/B sử dụng một trình nền có tên là update_engine
để chuẩn bị cho hệ thống khởi động vào một phiên bản mới, đã cập nhật. Trình nền này có thể thực hiện các thao tác sau:
- Đọc từ các phân vùng A/B của khe hiện tại và ghi mọi dữ liệu vào các phân vùng A/B của khe không sử dụng theo hướng dẫn của gói OTA.
- Gọi giao diện
boot_control
trong quy trình làm việc được xác định trước. - Chạy chương trình sau khi cài đặt từ phân vùng mới sau khi ghi tất cả các phân vùng khe không sử dụng, theo hướng dẫn của gói OTA. (Để biết thông tin chi tiết, hãy xem phần Sau khi cài đặt).
Vì trình nền update_engine
không tham gia vào quá trình khởi động, nên trình nền này bị hạn chế về những việc có thể làm trong quá trình cập nhật theo các chính sách và tính năng SELinux trong khe hiện tại (không thể cập nhật các chính sách và tính năng đó cho đến khi hệ thống khởi động vào phiên bản mới). Để duy trì một hệ thống mạnh mẽ, quá trình cập nhật không được sửa đổi bảng phân vùng, nội dung của các phân vùng trong khe hiện tại hoặc nội dung của các phân vùng không phải A/B không thể bị xoá bằng cách đặt lại về trạng thái ban đầu.
Cập nhật nguồn công cụ
Nguồn update_engine
nằm trong system/update_engine
. Các tệp dexopt OTA A/B được phân chia giữa installd
và trình quản lý gói:
-
frameworks/native/cmds/installd/
ota* bao gồm tập lệnh sau cài đặt, tệp nhị phân cho chroot, bản sao installd gọi dex2oat, tập lệnh di chuyển cấu phần phần mềm sau OTA và tệp rc cho tập lệnh di chuyển. -
frameworks/base/services/core/java/com/android/server/pm/OtaDexoptService.java
(cộng vớiOtaDexoptShellCommand
) là trình quản lý gói chuẩn bị các lệnh dex2oat cho các ứng dụng.
Để biết ví dụ về cách hoạt động, hãy tham khảo /device/google/marlin/device-common.mk
.
Cập nhật nhật ký công cụ
Đối với các bản phát hành Android 8.x trở xuống, bạn có thể tìm thấy nhật ký update_engine
trong logcat
và trong báo cáo lỗi. Để cung cấp nhật ký update_engine
trong hệ thống tệp, hãy vá các thay đổi sau vào bản dựng:
Những thay đổi này sẽ lưu một bản sao của nhật ký update_engine
gần đây nhất vào /data/misc/update_engine_log/update_engine.YEAR-TIME
. Ngoài nhật ký hiện tại, 5 nhật ký gần đây nhất sẽ được lưu trong /data/misc/update_engine_log/
. Người dùng có mã nhận dạng nhóm nhật ký sẽ có thể truy cập vào nhật ký hệ thống tệp.
Hoạt động tương tác với trình tải khởi động
boot_control
HAL được update_engine
(và có thể là các trình nền khác) sử dụng để hướng dẫn trình tải khởi động về nội dung cần khởi động. Sau đây là các trường hợp ví dụ phổ biến và các trạng thái liên quan:
- Trường hợp bình thường: Hệ thống đang chạy từ khe hiện tại, khe A hoặc khe B. Hiện chưa có nội dung cập nhật nào được áp dụng. Khe hiện tại của hệ thống có thể khởi động, thành công và là khe đang hoạt động.
- Đang cập nhật: Hệ thống đang chạy từ khe B, vì vậy, khe B là khe khởi động, thành công và đang hoạt động. Khe A được đánh dấu là không thể khởi động vì nội dung của khe A đang được cập nhật nhưng chưa hoàn tất. Việc khởi động lại ở trạng thái này sẽ tiếp tục khởi động từ khe B.
- Đã áp dụng bản cập nhật, đang chờ khởi động lại: Hệ thống đang chạy từ khe B, khe B có thể khởi động và khởi động thành công, nhưng khe A được đánh dấu là đang hoạt động (và do đó được đánh dấu là có thể khởi động). Khe A chưa được đánh dấu là thành công và trình tải khởi động sẽ thực hiện một số lần thử khởi động từ khe A.
-
Hệ thống khởi động lại vào bản cập nhật mới: Hệ thống đang chạy từ khe A lần đầu tiên, khe B vẫn có thể khởi động và thành công trong khi khe A chỉ có thể khởi động và vẫn hoạt động nhưng không thành công. Trình nền không gian người dùng,
update_verifier
, sẽ đánh dấu khe A là thành công sau khi thực hiện một số bước kiểm tra.
Hỗ trợ cập nhật nội dung phát trực tuyến
Thiết bị của người dùng không phải lúc nào cũng có đủ dung lượng trên /data
để tải gói cập nhật xuống. Vì cả nhà sản xuất thiết bị gốc (OEM) và người dùng đều không muốn lãng phí dung lượng trên phân vùng /cache
, nên một số người dùng không nhận được bản cập nhật vì thiết bị không có nơi nào để lưu trữ gói cập nhật. Để giải quyết vấn đề này, Android 8.0 đã hỗ trợ thêm tính năng truyền trực tuyến các bản cập nhật A/B ghi trực tiếp các khối vào phân vùng B khi tải xuống mà không cần lưu trữ các khối trên /data
. Bản cập nhật A/B truyền trực tuyến hầu như không cần bộ nhớ tạm thời và chỉ cần dung lượng lưu trữ đủ cho khoảng 100 KiB siêu dữ liệu.
Để bật tính năng cập nhật trực tuyến trong Android 7.1, hãy chọn các bản vá sau:
- Cho phép huỷ yêu cầu phân giải proxy
- Khắc phục lỗi chấm dứt quá trình chuyển trong khi phân giải proxy
- Thêm kiểm thử đơn vị cho TerminateTransfer giữa các dải ô
- Dọn dẹp RetryTimeoutCallback()
Bạn cần có các bản vá này để hỗ trợ tính năng truyền trực tuyến bản cập nhật A/B trong Android 7.1 trở lên, cho dù sử dụng Dịch vụ Google Play (GMS) hay bất kỳ ứng dụng cập nhật nào khác.
Vòng đời của bản cập nhật A/B
Quá trình cập nhật bắt đầu khi có gói OTA (được gọi trong mã là trọng tải) để tải xuống. Các chính sách trong thiết bị có thể trì hoãn việc tải trọng xuống và áp dụng dựa trên mức pin, hoạt động của người dùng, trạng thái sạc hoặc các chính sách khác. Ngoài ra, vì quá trình cập nhật chạy ở chế độ nền, nên người dùng có thể không biết rằng quá trình cập nhật đang diễn ra. Tất cả những điều này có nghĩa là quá trình cập nhật có thể bị gián đoạn bất cứ lúc nào do các chính sách, việc khởi động lại ngoài dự kiến hoặc hành động của người dùng.
Siêu dữ liệu trong gói OTA có thể cho biết bản cập nhật có thể được truyền trực tuyến (không bắt buộc); bạn cũng có thể sử dụng chính gói này để cài đặt không truyền trực tuyến. Máy chủ có thể sử dụng siêu dữ liệu để thông báo cho ứng dụng khách rằng ứng dụng khách đang truyền trực tuyến để ứng dụng khách chuyển OTA đến update_engine
một cách chính xác. Nhà sản xuất thiết bị có máy chủ và ứng dụng riêng có thể bật tính năng cập nhật trực tuyến bằng cách đảm bảo máy chủ xác định bản cập nhật đang truyền trực tuyến (hoặc giả định tất cả bản cập nhật đều đang truyền trực tuyến) và ứng dụng thực hiện lệnh gọi chính xác đến update_engine
để truyền trực tuyến. Nhà sản xuất có thể sử dụng thực tế là gói thuộc biến thể truyền trực tuyến để gửi cờ đến ứng dụng nhằm kích hoạt việc chuyển sang bên khung dưới dạng truyền trực tuyến.
Sau khi có tải trọng, quy trình cập nhật sẽ diễn ra như sau:
Bước | Hoạt động |
---|---|
1 |
Khung hiện tại (hoặc "khung nguồn") được đánh dấu là thành công (nếu chưa được đánh dấu) bằng markBootSuccessful() .
|
2 |
Khe không sử dụng (hoặc "khe mục tiêu") được đánh dấu là không thể khởi động bằng cách gọi hàm setSlotAsUnbootable() . Khe hiện tại luôn được đánh dấu là thành công ở đầu quá trình cập nhật để ngăn trình tải khởi động quay lại khe không sử dụng, khe này sẽ sớm có dữ liệu không hợp lệ. Nếu hệ thống đã đạt đến điểm có thể bắt đầu áp dụng bản cập nhật, thì khung thời gian hiện tại sẽ được đánh dấu là thành công ngay cả khi các thành phần chính khác bị hỏng (chẳng hạn như giao diện người dùng trong vòng lặp sự cố) vì có thể đẩy phần mềm mới để khắc phục các sự cố này. Trọng tải cập nhật là một blob mờ có hướng dẫn cập nhật lên phiên bản mới. Gói tải cập nhật bao gồm những phần sau:
|
3 | Tải siêu dữ liệu trọng tải xuống. |
4 | Đối với mỗi toán tử được xác định trong siêu dữ liệu, theo thứ tự, dữ liệu liên kết (nếu có) sẽ được tải xuống bộ nhớ, toán tử được áp dụng và bộ nhớ liên kết sẽ bị loại bỏ. |
5 | Toàn bộ các phân vùng được đọc lại và xác minh dựa trên hàm băm dự kiến. |
6 | Chạy bước sau khi cài đặt (nếu có). Trong trường hợp xảy ra lỗi trong quá trình thực thi bất kỳ bước nào, quá trình cập nhật sẽ không thành công và sẽ được thử lại với tải trọng khác. Nếu tất cả các bước cho đến nay đều thành công, thì quá trình cập nhật sẽ thành công và bước cuối cùng sẽ được thực thi. |
7 |
Khe không sử dụng được đánh dấu là đang hoạt động bằng cách gọi setActiveBootSlot() .
Việc đánh dấu khe không sử dụng là đang hoạt động không có nghĩa là khe đó sẽ hoàn tất quá trình khởi động. Trình tải khởi động (hoặc chính hệ thống) có thể chuyển đổi khe đang hoạt động trở lại nếu không đọc được trạng thái thành công.
|
8 |
Sau khi cài đặt (như mô tả bên dưới), bạn có thể chạy một chương trình từ phiên bản "bản cập nhật mới" trong khi vẫn chạy trong phiên bản cũ. Nếu được xác định trong gói OTA, bước này là bắt buộc và chương trình phải trả về bằng mã thoát 0 ; nếu không, quá trình cập nhật sẽ không thành công.
|
9 |
Sau khi hệ thống khởi động thành công vào khe mới và hoàn tất các bước kiểm tra sau khi khởi động lại, khe hiện tại (trước đây là "khe mục tiêu") sẽ được đánh dấu là thành công bằng cách gọi markBootSuccessful() .
|
Sau khi cài đặt
Đối với mọi phân vùng được xác định bước cài đặt sau, update_engine
sẽ gắn phân vùng mới vào một vị trí cụ thể và thực thi chương trình được chỉ định trong OTA tương ứng với phân vùng được gắn. Ví dụ: nếu chương trình sau khi cài đặt được xác định là usr/bin/postinstall
trong phân vùng hệ thống, thì phân vùng này từ khe không sử dụng sẽ được gắn ở một vị trí cố định (chẳng hạn như /postinstall_mount
) và lệnh /postinstall_mount/usr/bin/postinstall
sẽ được thực thi.
Để cài đặt thành công sau này, hạt nhân cũ phải có khả năng:
- Gắn định dạng hệ thống tệp mới. Loại hệ thống tệp không thể thay đổi trừ khi có hỗ trợ cho loại hệ thống tệp đó trong hạt nhân cũ, bao gồm cả thông tin chi tiết như thuật toán nén được sử dụng nếu sử dụng hệ thống tệp nén (tức là SquashFS).
-
Tìm hiểu định dạng chương trình sau khi cài đặt của phân vùng mới. Nếu sử dụng tệp nhị phân có định dạng Tệp có thể thực thi và có thể liên kết (ELF), thì tệp đó phải tương thích với hạt nhân cũ (ví dụ: một chương trình mới 64 bit chạy trên hạt nhân 32 bit cũ nếu cấu trúc chuyển đổi từ bản dựng 32 bit sang 64 bit). Trừ phi trình tải (
ld
) được hướng dẫn sử dụng các đường dẫn khác hoặc tạo tệp nhị phân tĩnh, thư viện sẽ được tải từ hình ảnh hệ thống cũ chứ không phải hình ảnh mới.
Ví dụ: bạn có thể sử dụng tập lệnh shell làm chương trình sau cài đặt do tệp nhị phân shell của hệ thống cũ diễn giải (có điểm đánh dấu #!
ở trên cùng), sau đó thiết lập đường dẫn thư viện từ môi trường mới để thực thi một chương trình sau cài đặt nhị phân phức tạp hơn. Ngoài ra, bạn có thể chạy bước sau khi cài đặt từ một phân vùng nhỏ hơn chuyên dụng để cho phép cập nhật định dạng hệ thống tệp trong phân vùng hệ thống chính mà không gặp phải vấn đề về khả năng tương thích ngược hoặc cập nhật bước đệm; điều này sẽ cho phép người dùng cập nhật trực tiếp lên phiên bản mới nhất từ hình ảnh ban đầu.
Chương trình mới sau khi cài đặt bị giới hạn bởi các chính sách SELinux được xác định trong hệ thống cũ. Do đó, bước sau khi cài đặt phù hợp để thực hiện các tác vụ theo yêu cầu của thiết kế trên một thiết bị nhất định hoặc các tác vụ tối ưu khác. Bước sau khi cài đặt không phù hợp với các bản sửa lỗi một lần trước khi khởi động lại mà yêu cầu các quyền không lường trước.
Chương trình sau khi cài đặt đã chọn sẽ chạy trong ngữ cảnh postinstall
SELinux. Tất cả tệp trong phân vùng được gắn mới sẽ được gắn thẻ bằng postinstall_file
, bất kể thuộc tính của các tệp đó là gì sau khi khởi động lại vào hệ thống mới đó. Những thay đổi đối với các thuộc tính SELinux trong hệ thống mới sẽ không ảnh hưởng đến bước sau khi cài đặt. Nếu chương trình sau cài đặt cần thêm quyền, thì bạn phải thêm các quyền đó vào ngữ cảnh sau cài đặt.
Sau khi khởi động lại
Sau khi khởi động lại, update_verifier
sẽ kích hoạt quy trình kiểm tra tính toàn vẹn bằng dm-verity.
Quá trình kiểm tra này bắt đầu trước zygote để tránh các dịch vụ Java thực hiện bất kỳ thay đổi không thể đảo ngược nào
có thể ngăn việc khôi phục an toàn. Trong quá trình này, trình tải khởi động và hạt nhân cũng có thể kích hoạt quá trình khởi động lại nếu tính năng xác minh quy trình khởi động hoặc dm-verity phát hiện thấy bất kỳ lỗi hỏng nào. Sau khi kiểm tra xong, update_verifier
sẽ đánh dấu quá trình khởi động là thành công.
update_verifier
sẽ chỉ đọc các khối được liệt kê trong /data/ota_package/care_map.txt
, nằm trong gói OTA A/B khi sử dụng mã AOSP. Ứng dụng cập nhật hệ thống Java, chẳng hạn như GmsCore, sẽ trích xuất care_map.txt
, thiết lập quyền truy cập trước khi khởi động lại thiết bị và xoá tệp đã trích xuất sau khi hệ thống khởi động thành công vào phiên bản mới.