您當前的位置:首頁 > 收藏

android ota升級原理3

作者:由 Bgwan 發表于 收藏時間:2019-06-23

背景圖來源:click

又是一週,工作996,給小糰子芳寫的信斷斷續續也一直在進行,很快又是她生日了。本篇繼續來完成ota升級原理。

1。

android ota升級理論1。

2。

android ota升級實踐2。

3。

(很重要-糰子也值得一看)如何用android:sharedUserId屬性生成帶有系統許可權的apk?。

ota理論介紹了,ota系統升級有兩種方法:

手機升級,在正常使用手機時,會收到系統更新的通知,可以點選直接下載和安裝。一般多見於谷歌自己的nexus裝置和官方行貨機器。

刷機升級,手機破解後可以刷入其他第三方的ROM,ROM對應的系統版本升級了相應你的手機系統也升級了。

前者多用於手機廠商的客戶端線上升級,後者多用於開發和測試人員。但不管哪種,原理都是一樣的,都要在

recovery模式

下進行升級。

android中進行ota升級,首先需要使用原始碼中ota升級包打包工具

build/tools/releasetools/ota_from_target_files

生成ota有兩類:

整包

差分包

,在android系統編譯環境下,在終端下使用

make otapackage

生成,具體可以參考ota實踐中內容。

android平臺使用了提供了

frameworks/base/core/java/android/os/RecoverySystem。java

工具類支援升級,需要新增

android。Manifest。permission#REBOOT permission

,而REBOOT許可權需要新增

android:shareUserId=“android。uid。system”

,賦予升級平臺許可權後,才能支援升級,或是升級app在系統原始碼編譯環境中編譯後才能使用該功能。

下面是ota升級的一個流程,對應於ota實踐[圖4]的流程圖,我們就從這個流程來分析升級的原理。

[x] 1、獲取升級包,可以從服務端下載,也可以直接複製到SD卡中

[x] 2、獲取升級包路徑,驗證簽名,透過installPackage介面升級

[x] 3、系統重啟進入Recovery模式

[x] 4、在install。cpp進行升級操作

[x] 5、try_update_binary執行升級指令碼

[ ] 6、finish_recovery,重啟

**版權宣告CopyRight:

本內容作者:sunst,轉載或引用請

標明出處

,違者追究法律責任!!!

一:獲取升級包

獲取

update。zip

升級包,可以從服務端線上下載,也可以直接複製到SD卡中,也可以實踐2中自己製作,push到手機sdcard中。

ota線上下載(一般下載到/CACHE分割槽),手動複製到SD卡中,兩種方式獲得的

update。zip

包,在進入

Recovery模式

前,都未對這個zip包做處理。只是在重啟之前將zip包的路徑告訴了Recovery服務

二:獲取升級包路徑,驗證簽名,透過installPackage介面升級

RecoverySystem

提供了三個靜態方法

static

void

installPackage

Context

context

File

packageFile

//重啟裝置,安裝一個更新包

static

void

rebootWipeUserData

Context

context

//重啟裝置,清除使用者資料分割槽類似恢復出廠設定

static

void

verifyPackage

File

packageFile

RecoverySystem

ProgressListener

listener

File

deviceCertsZipFile

//

驗證加密簽名

1、 進行簽名驗證

/**

* Used to communicate with recovery。 See bootable/recovery/recovery。c。 * 透過/cache/recovery/command,與系統通訊,接收升級命令指令,詳細可以檢視原始碼: bootable/recovery/recovery。c。 */

private

static

File

RECOVERY_DIR

=

new

File

“/cache/recovery”

);

private

static

File

COMMAND_FILE

=

new

File

RECOVERY_DIR

“command”

);

/*

*@param packageFile 待驗證的升級包

*@param listener 升級進度監聽器,驗證的進度

*@param deviceCertsZipFile 驗證證書檔案,如果為null,系統預設的是“/system/etc/security/otacerts。zip”

* */

public

static

void

verifyPackage

File

packageFile

ProgressListener

listener

File

deviceCertsZipFile

throws

IOException

GeneralSecurityException

{

long

fileLen

=

packageFile

length

();

RandomAccessFile

raf

=

new

RandomAccessFile

packageFile

“r”

);

try

{

int

lastPercent

=

0

long

lastPublishTime

=

System

currentTimeMillis

();

if

listener

!=

null

{

listener

onProgress

lastPercent

);

}

raf

seek

fileLen

-

6

);

byte

[]

footer

=

new

byte

6

];

raf

readFully

footer

);

//升級包是否含有系統簽名信息-sunst2019-6-8註釋

if

footer

2

!=

byte

0xff

||

footer

3

!=

byte

0xff

{

throw

new

SignatureException

“no signature in file (no footer)”

);

}

int

commentSize

=

footer

4

&

0xff

|

((

footer

5

&

0xff

<<

8

);

//簽名的起始位置-sunst2019-6-8註釋

int

signatureStart

=

footer

0

&

0xff

|

((

footer

1

&

0xff

<<

8

);

Log

v

TAG

String

format

“comment size %d; signature start %d”

commentSize

signatureStart

));

byte

[]

eocd

=

new

byte

commentSize

+

22

];

raf

seek

fileLen

-

commentSize

+

22

));

raf

readFully

eocd

);

if

eocd

0

!=

byte

0x50

||

eocd

1

!=

byte

0x4b

||

eocd

2

!=

byte

0x05

||

eocd

3

!=

byte

0x06

{

throw

new

SignatureException

“no signature in file (bad footer)”

);

}

for

int

i

=

4

i

<

eocd

length

-

3

++

i

{

if

eocd

i

==

byte

0x50

&&

eocd

i

+

1

==

byte

0x4b

&&

eocd

i

+

2

==

byte

0x05

&&

eocd

i

+

3

==

byte

0x06

{

throw

new

SignatureException

“EOCD marker found after start of EOCD”

);

}

}

// The following code is largely copied from

// JarUtils。verifySignature()。 We could just *call* that

// method here if that function didn‘t read the entire

// input (ie, the whole OTA package) into memory just to

// compute its message digest。

BerInputStream

bis

=

new

BerInputStream

new

ByteArrayInputStream

eocd

commentSize

+

22

-

signatureStart

signatureStart

));

ContentInfo

info

=

ContentInfo

ContentInfo

ASN1

decode

bis

);

SignedData

signedData

=

info

getSignedData

();

if

signedData

==

null

{

throw

new

IOException

“signedData is null”

);

}

Collection

encCerts

=

signedData

getCertificates

();

if

encCerts

isEmpty

())

{

throw

new

IOException

“encCerts is empty”

);

}

// Take the first certificate from the signature (packages

// should contain only one)。

Iterator

it

=

encCerts

iterator

();

X509Certificate

cert

=

null

if

it

hasNext

())

{

cert

=

new

X509CertImpl

((

org

apache

harmony

security

x509

Certificate

it

next

());

}

else

{

throw

new

SignatureException

“signature contains no certificates”

);

}

//獲取到包中所有的簽名證書資訊-sunst2019-6-8註釋

List

sigInfos

=

signedData

getSignerInfos

();

SignerInfo

sigInfo

if

(!

sigInfos

isEmpty

())

{

sigInfo

=

SignerInfo

sigInfos

get

0

);

}

else

{

throw

new

IOException

“no signer infos!”

);

}

// Check that the public key of the certificate contained

// in the package equals one of our trusted public keys。

HashSet

<

Certificate

>

trusted

=

getTrustedCerts

deviceCertsZipFile

==

null

DEFAULT_KEYSTORE

deviceCertsZipFile

);

PublicKey

signatureKey

=

cert

getPublicKey

();

boolean

verified

=

false

//簽名驗證key比較-sunst2019-6-8註釋

for

Certificate

c

trusted

{

if

c

getPublicKey

()。

equals

signatureKey

))

{

verified

=

true

break

}

}

if

(!

verified

{

throw

new

SignatureException

“signature doesn’t match any trusted key”

);

}

String

da

=

sigInfo

getdigestAlgorithm

();

String

dea

=

sigInfo

getDigestEncryptionAlgorithm

();

String

alg

=

null

if

da

==

null

||

dea

==

null

{

// fall back to the cert algorithm if the sig one

// doesn‘t look right。

alg

=

cert

getSigAlgName

();

}

else

{

alg

=

da

+

“with”

+

dea

}

Signature

sig

=

Signature

getInstance

alg

);

sig

initVerify

cert

);

// The signature covers all of the OTA package except the

// archive comment and its 2-byte length。

// 對整個升級包開始驗證-sunst2019-6-8註釋

long

toRead

=

fileLen

-

commentSize

-

2

long

soFar

=

0

raf

seek

0

);

byte

[]

buffer

=

new

byte

4096

];

boolean

interrupted

=

false

while

soFar

<

toRead

{

interrupted

=

Thread

interrupted

();

if

interrupted

break

int

size

=

buffer

length

if

soFar

+

size

>

toRead

{

size

=

int

toRead

-

soFar

);

}

int

read

=

raf

read

buffer

0

size

);

sig

update

buffer

0

read

);

soFar

+=

read

if

listener

!=

null

{

long

now

=

System

currentTimeMillis

();

int

p

=

int

soFar

*

100

/

toRead

);

if

p

>

lastPercent

&&

now

-

lastPublishTime

>

PUBLISH_PROGRESS_INTERVAL_MS

{

lastPercent

=

p

lastPublishTime

=

now

//返回驗證進度,使用者上層ProgressListener監聽回撥-sunst2019-6-8註釋

listener

onProgress

lastPercent

);

}

}

}

if

listener

!=

null

{

listener

onProgress

100

);

// 驗證進度完畢

}

if

interrupted

{

throw

new

SignatureException

“verification was interrupted”

);

}

if

(!

sig

verify

sigInfo

getEncryptedDigest

()))

{

throw

new

SignatureException

“signature digest verification failed”

);

}

}

finally

{

raf

close

();

}

}

Tips:

只有當簽名驗證正確才返回,否則將丟擲異常。

在Recovery模式下進行升級時候也是會進行簽名驗證的,如果這裡先不進行驗證也不會有什麼問題。但是我們建議在重啟前,先驗證,以便及早發現問題。

如果簽名驗證沒有問題,就執行installPackage開始升級。

2。透過系統簽名驗證通過後,進行安裝

/**

* Reboots the device in order to install the given update * Requires the {@link android。Manifest。permission#REBOOT} permission。

* @param context the Context to use

* @param packageFile OTA升級包路徑 ,必須在可恢復的掛載區,一般在/cache and /data 都是安全的

*/

public

static

void

installPackage

Context

context

File

packageFile

throws

IOException

{

String

filename

=

packageFile

getCanonicalPath

();

Log

w

TAG

“!!! REBOOTING TO INSTALL ”

+

filename

+

“ !!!”

);

String

arg

=

“——update_package=”

+

filename

bootCommand

context

arg

);

//呼叫bootCommand進行升級包安裝

}

private

static

void

bootCommand

Context

context

String

。。。

args

throws

IOException

{

RECOVERY_DIR

mkdirs

();

// In case we need it

COMMAND_FILE

delete

();

// In case it’s not writable

LOG_FILE

delete

();

FileWriter

command

=

new

FileWriter

COMMAND_FILE

);

try

{

for

String

arg

args

{

if

(!

TextUtils

isEmpty

arg

))

{

command

write

arg

);

command

write

“\n”

);

}

}

}

finally

{

command

close

();

}

// Having written the command file, go ahead and reboot

PowerManager

pm

=

PowerManager

context

getSystemService

Context

POWER_SERVICE

);

pm

reboot

PowerManager

REBOOT_RECOVERY

);

throw

new

IOException

“Reboot failed (no permissions?)”

);

}

這裡定義了兩個引數,

bootCommand():在這個函式中才是Main System在重啟前真正做的準備。主要做了以下事情,首先建立/cache/recovery/目錄,刪除這個目錄下的command和log(可能不存在)檔案在sqlite資料庫中的備份。然後將–update_package=CACHE:ota。zip或–update_package=SDCARD:update。zip命令寫入到/cache/recovery/command檔案中,最終會被寫入到BCB中主要目的是將zip包的路徑告訴Recovery服務。下一步就是真正重啟了。接下來看一下在重啟函式reboot中所做的事情。

pm。reboot():重啟之前先獲得了PowerManager(電源管理)並進一步獲得其系統服務。然後呼叫了pm。reboot(“recovery”)函式。他就是/device/bionic/libc/unistd/reboot。c中的reboot函式。這個函式實際上是一個系統呼叫,即__reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,mode,NULL);從這個函式我們可以看出前兩個引數就代表了我們的組合鍵,mode就是我們傳過來的“recovery”。再進一步跟蹤就到了彙編程式碼了,我們無法直接檢視它的具體實現細節。但可以肯定的是 這個函式只將“recovery”引數傳遞過去了,之後將“boot-recovery”寫入到了MISC分割槽的BCB資料塊的command域中。這樣在重啟之後Bootloader才知道要進入Recovery模式。

在這裡我們無法肯定Main System在重啟之前對BCB的recovery域是否進行了操作。其實在重啟前是否更新BCB的recovery域是不重要的,因為進入Recovery服務後,Recovery會自動去/cache/recovery/command中讀取要進行的操作然後寫入到BCB的recovery域中。

至此,Main System就開始重啟並進入Recovery模式。在這之前Main System做的最實質的就是兩件事,一是將“boot-recovery”寫入BCB的command域,二是將–update_package=/cache/update。zip”或則“–update_package=/sdcard/update。zip”寫入/cache/recovery/command檔案中。

下面的部分就開始重啟並進入Recovery模式了==

三、系統重啟進入Recovery模式

系統重啟時會判斷/cache/recovery目錄下是否有command檔案,如果存在就進入recovery模式,否則就正常啟動。

進入到Recovery模式下,將執行recovery。cpp的main函式,下面貼出關鍵程式碼片段,

int arg;

while ((arg = getopt_long(argc, argv, “”, OPTIONS, NULL)) != -1) {

switch (arg) {

case ‘s’: send_intent = optarg; break;

case ‘u’: update_package = optarg; break;

case ‘w’: wipe_data = wipe_cache = 1; break;

case ‘c’: wipe_cache = 1; break;

case ‘t’: show_text = 1; break;

case ‘x’: just_exit = true; break;

case ‘l’: locale = optarg; break;

case ‘g’: {

if (stage == NULL || *stage == ‘\0’) {

char buffer[20] = “1/”;

strncat(buffer, optarg, sizeof(buffer)-3);

stage = strdup(buffer);

}

break;

}

case ‘p’: shutdown_after = true; break;

case ‘r’: reason = optarg; break;

case ‘?’:

LOGE(“Invalid command argument\n”);

continue;

}

這是一個While迴圈,用來讀取recovery的command引數,OPTIONS的不同選項定義如下,

static const struct option OPTIONS[] = {

{ “send_intent”, required_argument, NULL, ‘s’ },

{ “update_package”, required_argument, NULL, ‘u’ },

{ “wipe_data”, no_argument, NULL, ‘w’ },

{ “wipe_cache”, no_argument, NULL, ‘c’ },

{ “show_text”, no_argument, NULL, ‘t’ },

{ “just_exit”, no_argument, NULL, ‘x’ },

{ “locale”, required_argument, NULL, ‘l’ },

{ “stages”, required_argument, NULL, ‘g’ },

{ “shutdown_after”, no_argument, NULL, ‘p’ },

{ “reason”, required_argument, NULL, ‘r’ },

{ NULL, 0, NULL, 0 },

};

根據第二步寫入的命令檔案內容,將為update_package 賦值

if (update_package) {

// For backwards compatibility on the cache partition only, if

// we‘re given an old ’root‘ path “CACHE:foo”, change it to // “/cache/foo”。 if (strncmp(update_package, “CACHE:”, 6) == 0) {

int len = strlen(update_package) + 10;

char* modified_path = (char*)malloc(len);

strlcpy(modified_path, “/cache/”, len);

strlcat(modified_path, update_package+6, len);

printf(“(replacing path \”%s\“ with \”%s\“)\n”,

update_package, modified_path);

update_package = modified_path;

}

}

相容性處理。

int status = INSTALL_SUCCESS;

if (update_package != NULL) {

status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);

if (status == INSTALL_SUCCESS && wipe_cache) {

if (erase_volume(“/cache”)) {

LOGE(“Cache wipe (requested by package) failed。”);

}

}

if (status != INSTALL_SUCCESS) {

ui->Print(“Installation aborted。\n”);

// If this is an eng or userdebug build, then automatically

// turn the text display on if the script fails so the error // message is visible。 char buffer[PROPERTY_VALUE_MAX+1];

property_get(“ro。build。fingerprint”, buffer, “”);

if (strstr(buffer, “:userdebug/”) || strstr(buffer, “:eng/”)) {

ui->ShowText(true);

}

}

} else if (wipe_data) {

if (device->WipeData()) status = INSTALL_ERROR;

if (erase_volume(“/data”)) status = INSTALL_ERROR;

if (wipe_cache && erase_volume(“/cache”)) status = INSTALL_ERROR;

if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR;

if (status != INSTALL_SUCCESS) ui->Print(“Data wipe failed。\n”);

} else if (wipe_cache) {

if (wipe_cache && erase_volume(“/cache”)) status = INSTALL_ERROR;

if (status != INSTALL_SUCCESS) ui->Print(“Cache wipe failed。\n”);

} else if (!just_exit) {

status = INSTALL_NONE; // No command specified

ui->SetBackground(RecoveryUI::NO_COMMAND);

}

update_package不為空,執行install_package方法。

我們也可以看到擦除資料、快取的實現也是在這個裡執行的,這裡就不展開了。

四、在install。cpp進行升級操作

具體的升級過程都是在install。cpp中執行的,先看install_package方法,

int

install_package(const char* path, int* wipe_cache, const char* install_file,

bool needs_mount)

{

FILE* install_log = fopen_path(install_file, “w”);

if (install_log) {

fputs(path, install_log);

fputc(’\n‘, install_log);

} else {

LOGE(“failed to open last_install: %s\n”, strerror(errno));

}

int result;

if (setup_install_mounts() != 0) {

LOGE(“failed to set up expected mounts for install; aborting\n”);

result = INSTALL_ERROR;

} else {

result = really_install_package(path, wipe_cache, needs_mount);

}

if (install_log) {

fputc(result == INSTALL_SUCCESS ? ’1‘ : ’0‘, install_log);

fputc(’\n‘, install_log);

fclose(install_log);

}

return result;

}

這個方法中首先建立了log檔案,升級過程包括出錯的資訊都會寫到這個檔案中,便於後續的分析工作。繼續跟進,really_install_package,

static int

really_install_package(const char *path, int* wipe_cache, bool needs_mount)

{

ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);

ui->Print(“Finding update package。。。\n”);

// Give verification half the progress bar。。。

ui->SetProgressType(RecoveryUI::DETERMINATE);

ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);

LOGI(“Update location: %s\n”, path);

// Map the update package into memory。

ui->Print(“Opening update package。。。\n”);

if (path && needs_mount) {

if (path[0] == ’@‘) {

ensure_path_mounted(path+1);

} else {

ensure_path_mounted(path);

}

}

MemMapping map;

if (sysMapFile(path, &map) != 0) {

LOGE(“failed to map file\n”);

return INSTALL_CORRUPT;

}

// 裝入簽名檔案

int numKeys;

Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);

if (loadedKeys == NULL) {

LOGE(“Failed to load keys\n”);

return INSTALL_CORRUPT;

}

LOGI(“%d key(s) loaded from %s\n”, numKeys, PUBLIC_KEYS_FILE);

ui->Print(“Verifying update package。。。\n”);

// 驗證簽名

int err;

err = verify_file(map。addr, map。length, loadedKeys, numKeys);

free(loadedKeys);

LOGI(“verify_file returned %d\n”, err);

// 簽名失敗的處理

if (err != VERIFY_SUCCESS) {

LOGE(“signature verification failed\n”);

sysReleaseMap(&map);

return INSTALL_CORRUPT;

}

/* Try to open the package。

*/ // 開啟升級包 ZipArchive zip;

err = mzOpenZipArchive(map。addr, map。length, &zip);

if (err != 0) {

LOGE(“Can’t open %s\n(%s)\n”, path, err != -1 ? strerror(err) : “bad”);

sysReleaseMap(&map);

return INSTALL_CORRUPT;

}

/* Verify and install the contents of the package。

*/ ui->Print(“Installing update。。。\n”);

ui->SetEnableReboot(false);

// 執行升級指令碼檔案,開始升級

int result = try_update_binary(path, &zip, wipe_cache);

ui->SetEnableReboot(true);

ui->Print(“\n”);

sysReleaseMap(&map);

return result;

}

該方法主要做了三件事

1、驗證簽名

Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);

if (loadedKeys == NULL) {

LOGE(“Failed to load keys\n”);

return INSTALL_CORRUPT;

}

裝載簽名檔案,如果為空 ,終止升級;

int err;

err = verify_file(map。addr, map。length, loadedKeys, numKeys);

free(loadedKeys);

LOGI(“verify_file returned %d\n”, err);

// 簽名失敗的處理

if (err != VERIFY_SUCCESS) {

LOGE(“signature verification failed\n”);

sysReleaseMap(&map);

return INSTALL_CORRUPT;

}

呼叫verify_file進行簽名驗證,這個方法定義在verifier。cpp檔案中,此處不展開,如果驗證失敗立即終止升級。

2、讀取升級包資訊

ZipArchive zip;

err = mzOpenZipArchive(map。addr, map。length, &zip);

if (err != 0) {

LOGE(“Can‘t open %s\n(%s)\n”, path, err != -1 ? strerror(err) : “bad”);

sysReleaseMap(&map);

return INSTALL_CORRUPT;

}

執行mzOpenZipArchive方法,開啟升級包並掃描,將包的內容複製到變數zip中,該變數將作為引數用來執行升級指令碼。

3、執行升級指令碼檔案,開始升級

int result = try_update_binary(path, &zip, wipe_cache);

try_update_binary方法用來處理升級包,執行製作升級包中的指令碼檔案update_binary,進行系統更新。

五、try_update_binary執行升級指令碼

// If the package contains an update binary, extract it and run it。

static int

try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {

// 檢查update-binary是否存在

const ZipEntry* binary_entry =

mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);

if (binary_entry == NULL) {

mzCloseZipArchive(zip);

return INSTALL_CORRUPT;

}

const char* binary = “/tmp/update_binary”;

unlink(binary);

int fd = creat(binary, 0755);

if (fd < 0) {

mzCloseZipArchive(zip);

LOGE(“Can’t make %s\n”, binary);

return INSTALL_ERROR;

}

// update-binary複製到“/tmp/update_binary”

bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);

close(fd);

mzCloseZipArchive(zip);

if (!ok) {

LOGE(“Can‘t copy %s\n”, ASSUMED_UPDATE_BINARY_NAME);

return INSTALL_ERROR;

}

// 建立管道,用於下面的子程序和父程序之間的通訊

int pipefd[2];

pipe(pipefd);

// When executing the update binary contained in the package, the

// arguments passed are: // // - the version number for this interface // // - an fd to which the program can write in order to update the // progress bar。 The program can write single-line commands: // // progress // fill up the next part of of the progress bar // over seconds。 If is zero, use // set_progress commands to manually control the // progress of this segment of the bar // // set_progress // should be between 0。0 and 1。0; sets the // progress bar within the segment defined by the most // recent progress command。 // // firmware <“hboot”|“radio”> // arrange to install the contents of in the // given partition on reboot。 // // (API v2: may start with “PACKAGE:” to // indicate taking a file from the OTA package。) // // (API v3: this command no longer exists。) // // ui_print // display on the screen。 // // - the name of the package zip file。 //

const char** args = (const char**)malloc(sizeof(char*) * 5);

args[0] = binary;

args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android。mk

char* temp = (char*)malloc(10);

sprintf(temp, “%d”, pipefd[1]);

args[2] = temp;

args[3] = (char*)path;

args[4] = NULL;

// 建立子程序。負責執行binary指令碼

pid_t pid = fork();

if (pid == 0) {

umask(022);

close(pipefd[0]);

execv(binary, (char* const*)args);// 執行binary指令碼

fprintf(stdout, “E:Can’t run %s (%s)\n”, binary, strerror(errno));

_exit(-1);

}

close(pipefd[1]);

*wipe_cache = 0;

// 父程序負責接受子程序傳送的命令去更新ui顯示

char buffer[1024];

FILE* from_child = fdopen(pipefd[0], “r”);

while (fgets(buffer, sizeof(buffer), from_child) != NULL) {

char* command = strtok(buffer, “ \n”);

if (command == NULL) {

continue;

} else if (strcmp(command, “progress”) == 0) {

char* fraction_s = strtok(NULL, “ \n”);

char* seconds_s = strtok(NULL, “ \n”);

float fraction = strtof(fraction_s, NULL);

int seconds = strtol(seconds_s, NULL, 10);

ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);

} else if (strcmp(command, “set_progress”) == 0) {

char* fraction_s = strtok(NULL, “ \n”);

float fraction = strtof(fraction_s, NULL);

ui->SetProgress(fraction);

} else if (strcmp(command, “ui_print”) == 0) {

char* str = strtok(NULL, “\n”);

if (str) {

ui->Print(“%s”, str);

} else {

ui->Print(“\n”);

}

fflush(stdout);

} else if (strcmp(command, “wipe_cache”) == 0) {

*wipe_cache = 1;

} else if (strcmp(command, “clear_display”) == 0) {

ui->SetBackground(RecoveryUI::NONE);

} else if (strcmp(command, “enable_reboot”) == 0) {

// packages can explicitly request that they want the user

// to be able to reboot during installation (useful for // debugging packages that don‘t exit)。 ui->SetEnableReboot(true);

} else {

LOGE(“unknown command [%s]\n”, command);

}

}

fclose(from_child);

int status;

waitpid(pid, &status, 0);

if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {

LOGE(“Error in %s\n(Status %d)\n”, path, WEXITSTATUS(status));

return INSTALL_ERROR;

}

return INSTALL_SUCCESS;

}

try_update_binary函式,是真正實現讀取升級包中的指令碼檔案並執行相應的函式的地方。在此函式中,透過呼叫fork函式創建出一個子程序,在子程序中開始讀取並執行升級指令碼檔案。在此需要注意的是函式fork的用法,fork被呼叫一次,將做兩次返回,在父程序中返回的是子程序的程序ID,為正數;而在子程序中,則返回0。子程序建立成功後,開始執行升級程式碼,並透過管道與父程序互動,父程序則透過讀取子程序傳遞過來的資訊更新UI。

六、finish_recovery,重啟

上一步完成之後,回到main函式

// Save logs and clean up before rebooting or shutting down。

finish_recovery(send_intent);

儲存升級過程中的log,清除臨時檔案,包括command檔案(不清除的話,下次重啟還會進入recovery模式),最後重啟。

透過以上ota 4篇內容介紹,相信你也會利用程式碼給系統升級了吧,到這裡

ota升級

的內容,從理論到原理終於全部介紹完了。

當然本篇只是從

ota升級流程

出發,本質是透過

RecoverySystem

升級原理的剖析。在我做系統ota升級功能的時候,查了不少資料,如果想對ota升級有更新更深的理解,這裡推薦下面幾篇還不錯的文章參考。

OTA本質與實現流程分析。--含系統分割槽的內容/

Android OTA系統的更新升級。--伺服器下載模式/

Android OTA升級原理和流程分析。--zip目錄結構偏底層C語言/

Android Recovery升級原理。--含Android啟動模式等概念/

哦,對了補充:

手動升級的流程也基本差不多,透過power key + volume上鍵組合,進入

recovery模式

,進入prompt_and_wait函式等待使用者按鍵事件。當用戶從sdcard選擇zip包後,同樣也是執行install_package函式,然後就與前面自動升級的流程是一樣的了。

android ota升級原理3

圖1-android系統recovery模式

請尊重勞動成果,注意文中

版權宣告

,Android專欄不定時更新,可以點選關注我知乎。也可以同時關注人工智慧專欄,本內容作者

sunst

,有問題請溝通

qyddai@gmail。com

sunst 2019-06-23 17:05

@知乎小管家 本文以及前面一文為markdown匯入, 但格式及其難看,希望這個功能有待改進,一般都是markdown在本地編寫。

標簽: UPDATE  package  cache  recovery  Command