Android系統中的進程管理:進程的建立

對於操做系統來講,進程管理是其最重要的職責之一。
考慮到這部分的內容較多,所以會拆分紅幾篇文章來說解。
本文是進程管理系統文章的第一篇,會講解Android系統中的進程建立。
本文適合Android平臺的應用程序開發者,也適合對於Android系統內部實現感興趣的讀者。html

概述

Android系統以Linux內核爲基礎,因此對於進程的管理天然離不開Linux自己提供的機制。例如:java

  • 經過fork來建立進行linux

  • 經過信號量來管理進程android

  • 經過proc文件系統來查詢和調整進程狀態
    ios

對於Android來講,進程管理的主要內容包括如下幾個部份內容:編程

  • 進程的建立api

  • 進程的優先級管理數組

  • 進程的內存管理服務器

  • 進程的回收和死亡處理微信

本文會專門講解進程的建立,其他部分將在後面的文章中講解。

主要模塊

爲了便於下文的講解,這裏先介紹一下Android系統中牽涉到進程建立的幾個主要模塊。
同時爲了便於讀者更詳細的瞭解這些模塊,這裏也同時提供了這些模塊的代碼路徑。
這裏提到的代碼路徑是指AOSP的源碼數中的路徑。
關於如何獲取AOSP源碼請參見這裏:Downloading the Source
本文以Android N版本的代碼爲示例,所用到的Source Code Tags是:android-7.0.0_r1。

相關模塊

  • app_process
    代碼路徑:frameworks/base/cmds/app_process

說明:app_process是一個可執行程序,該程序的主要做用是啓動zygotesystem_server進程。

  • Zygote
    代碼路徑:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

說明:zygote進程是全部應用進程的父進程,這是系統中一個很是重要的進程,下文咱們會詳細講解。

  • ActivityManager
    代碼路徑:frameworks/base/services/core/java/com/android/server/am/

說明:am是ActivityManager的縮寫。
這個目錄下的代碼負責了Android所有四大組件(Activity,Service,ContentProvider,BroadcastReceiver)的管理,而且還掌控了全部應用程序進程的建立和進程的優先級管理。
所以,這個部分的內容將是本系列文章講解的重點。

進程與線程

Android官方開發網站的這篇文章:Processes and Threads 很是好的介紹了Android系統中進程相關的一些基本概念和重要知識。

在閱讀下文以前,請務必將這篇文章瀏覽一遍。

關於進程

在Android系統中,進程能夠大體分爲系統進程應用進程兩大類。

系統進程是系統內置的(例如:initzygotesystem_server進程),屬於操做系統必不可少的一部分。系統進程的做用在於:

  • 管理硬件設備

  • 提供訪問設備的基本能力

  • 管理應用進程

應用進程是指應用程序運行的進程。這些應用程序多是系統出廠自帶的(例如Launcher,電話,短信等應用),也多是用戶本身安裝的(例如:微信,支付寶等)。
系統進程的數量一般是固定的(出廠或者系統升級以後就肯定了),而且系統進程一般是一直存活,常駐內存的。系統進程的異常退出將可能致使設備沒法正常使用。
而應用程序和應用進程在每一個人使用的設備上一般是各不同的。如何管理好這些不肯定的應用進程,就是操做系統自己要仔細考慮的內容。也是衡量一個操做系統好壞的標準之一。

在本文中,咱們會介紹initzygotesystem_server三個系統進程。
除此以外,本系列文章將會把主要精力集中在講解Android系統如何管理應用進程上

init進程

init進程是一切的開始,在Android系統中,全部進程的進程號都是不肯定的,惟獨init進程的進程號必定是1。
由於這個進程必定是系統起來的第一個進程。而且,init進程掌控了整個系統的啓動邏輯。
咱們知道,Android可能運行在各類不一樣的平臺,不一樣的設備上。所以,啓動的邏輯是不盡相同的。
爲了適應各類平臺和設備的需求,init進程的初始化工做經過init.rc配置文件來管理。
你能夠在AOSP源碼的system/core/rootdir/路徑找到這些配置文件。
配置文件的主入口文件是init.rc,這個文件會經過import引入其餘幾個文件。
在本文中,咱們統稱這些文件爲init.rc

init.rc經過Android Init Language來進行配置。
建議讀者大體閱讀一下其 語法說明

init.rc中配置了系統啓動的時候該作哪些事情,以及啓動哪些系統進程。
這其中有兩個特別重要的進程就是:zygotesystem_server進程。

  • zygote的中文意思是「受精卵「。這是一個頗有寓意的名稱:全部的應用進程都是由zygote fork出來的子進程,所以zygote進程是全部應用進程的父進程。

  • system_server 這個進程正如其名稱同樣,這是一個系統服務器。Framework層的幾乎全部服務都位於這個進程中。這其中就包括管理四大組件的ActivityManagerService

Zygote進程

init.rc文件會根據平臺不同,選擇下面幾個文件中的一個來啓動zygote進程:

  • init.zygote32.rc

  • init.zygote32_64.rc

  • init.zygote64.rc

  • init.zygote64_32.rc

這幾個文件的內容是大體一致的,僅僅是爲了避免同平臺服務的。這裏咱們以init.zygote32.rc的文件爲例,來看看其中的內容:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks

在這段配置文件中(若是你不明白這段配置的含義,請閱讀一下文檔:Android Init Language),啓動了一個名稱叫作zygote的服務進程。這個進程是經過/system/bin/app_process 這個可執行程序建立的。

而且在啓動這個可執行程序的時候,傳遞了`-Xzygote /system/bin --zygote --start-system-server
class main` 這些參數。
要知道這裏到底作了什麼,咱們須要看一下app_process的源碼。
app_process的源碼在這個路徑:frameworks/base/cmds/app_process/app_main.cpp
這個文件的main函數的有以下代碼:

int main(int argc, char* const argv[])
{
...
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        ...
    }
    ...
   if (!className.isEmpty()) {
        ...
    } else {
       ...
    
       if (startSystemServer) {
           args.add(String8("start-system-server"));
       }
    }
...
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}

這裏會判斷,

  • 若是執行這個命令時帶了--zygote參數,就會經過runtime.start啓動com.android.internal.os.ZygoteInit

  • 若是參數中帶有--start-system-server參數,就會將start-system-server添加到args中。

這段代碼是C++實現的。在執行這段代碼的時候尚未任何Java的環境。而runtime.start就是啓動Java虛擬機,並在虛擬機中啓動指定的類。因而接下來的邏輯就在ZygoteInit.java中了。
這個文件的main函數主要代碼以下:

public static void main(String argv[]) {
   ...

   try {
       ...

       boolean startSystemServer = false;
       String socketName = "zygote";
       String abiList = null;
       for (int i = 1; i < argv.length; i++) {
           if ("start-system-server".equals(argv[i])) {
               startSystemServer = true;
           } else if (argv[i].startsWith(ABI_LIST_ARG)) {
               ...
           }
       }
       ...
       registerZygoteSocket(socketName);
       ...
       preload();
       ...
       Zygote.nativeUnmountStorageOnInit();

       ZygoteHooks.stopZygoteNoThreadCreation();

       if (startSystemServer) {
           startSystemServer(abiList, socketName);
       }

       Log.i(TAG, "Accepting command socket connections");
       runSelectLoop(abiList);

       closeServerSocket();
   } catch (MethodAndArgsCaller caller) {
       caller.run();
   } catch (RuntimeException ex) {
       Log.e(TAG, "Zygote died with exception", ex);
       closeServerSocket();
       throw ex;
   }
}

在這段代碼中,咱們主要關注以下幾行:

  1. 經過 registerZygoteSocket(socketName); 註冊Zygote Socket

  2. 經過 preload(); 預先加載全部應用都須要的公共資源

  3. 經過 startSystemServer(abiList, socketName); 啓動system_server

  4. 經過 runSelectLoop(abiList); 在Looper上等待鏈接

這裏須要說明的是:zygote進程啓動以後,會啓動一個socket套接字,並經過Looper一直在這個套接字上等待鏈接。

全部應用進程都是經過發送數據到這個套接字上,而後由zygote進程建立的。

這裏還有一點說明的是:
在Zygote進程中,會經過preload函數加載須要應用程序都須要的公共資源。

預先加載這些公共資源有以下兩個好處:

  • 加快應用的啓動速度 由於這些資源已經在zygote進程啓動的時候加載好了

  • 經過共享的方式節省內存 這是Linux自己提供的機制:父進程已經加載的內容能夠在子進程中進行共享,而不用多份數據拷貝(除非子進程對這些數據進行了修改。)

preload的資源主要是Framework相關的一些基礎類和Resource資源,而這些資源正是全部應用都須要的:

開發者經過Android SDK開發應用所調用的API實現都在Framework中。

static void preload() {
   Log.d(TAG, "begin preload");
   Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "BeginIcuCachePinning");
   beginIcuCachePinning();
   Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
   Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClasses");
   preloadClasses();
   Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
   Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");
   preloadResources();
   Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
   Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
   preloadOpenGL();
   Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
   preloadSharedLibraries();
   preloadTextResources();

   WebViewFactory.prepareWebViewInZygote();
   endIcuCachePinning();
   warmUpJcaProviders();
   Log.d(TAG, "end preload");
}

system_server進程

上文已經提到,zygote進程起來以後會根據須要啓動system_server進程。

system_server進程中包含了大量的系統服務。例如:

  • 負責網絡管理的NetworkManagementService

  • 負責窗口管理的WindowManagerService

  • 負責震動管理的VibratorService

  • 負責輸入管理的InputManagerService

等等。關於system_server,咱們從此會其餘的文章中專門講解,這裏不作過多說明。

在本文中,咱們只關注system_server中的ActivityManagerService這個系統服務。

ActivityManagerService

上文中提到:zygote進程在啓動以後會啓動一個socket,而後一直在這個socket等待鏈接。
而會鏈接它的就是ActivityManagerService。由於ActivityManagerService掌控了全部應用進程的建立。
全部應用程序的進程都是由ActivityManagerService經過socket發送請求給Zygote進程,而後由zygote fork建立的。
ActivityManagerService經過Process.start方法來請求zygote建立進程:

public static final ProcessStartResult start(final String processClass,
                             final String niceName,
                             int uid, int gid, int[] gids,
                             int debugFlags, int mountExternal,
                             int targetSdkVersion,
                             String seInfo,
                             String abi,
                             String instructionSet,
                             String appDataDir,
                             String[] zygoteArgs) {
   try {
       return startViaZygote(processClass, niceName, uid, gid, gids,
               debugFlags, mountExternal, targetSdkVersion, seInfo,
               abi, instructionSet, appDataDir, zygoteArgs);
   } catch (ZygoteStartFailedEx ex) {
       Log.e(LOG_TAG,
               "Starting VM process through Zygote failed");
       throw new RuntimeException(
               "Starting VM process through Zygote failed", ex);
   }
}

這個函數會將啓動進程所須要的參數組裝好,並經過socket發送給zygote進程。而後zygote進程根據發送過來的參數將進程fork出來。

ActivityManagerService中,調用Process.start的地方是下面這個方法:

private final void startProcessLocked(ProcessRecord app, String hostingType,
       String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
       
...
  Process.ProcessStartResult startResult = Process.start(entryPoint,
          app.processName, uid, uid, gids, debugFlags, mountExternal,
          app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
          app.info.dataDir, entryPointArgs);
...
}

下文中咱們會看到,全部四大組件進程的建立,都是調用這裏的startProcessLocked這個方法而建立的。
對於每個應用進程,在ActivityManagerService中,都有一個ProcessRecord與之對應。這個對象記錄了應用進程的全部詳細狀態。
PS:對於ProcessRecord的內部結構,在下一篇文章中,咱們會講解。
爲了查找方便,對於每一個ProcessRecord會存在下面兩個集合中。

  • 按名稱和uid組織的集合

/**
* All of the applications we currently have running organized by name.
* The keys are strings of the application package name (as
* returned by the package manager), and the keys are ApplicationRecord
* objects.
*/
final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>();
  • 按pid組織的集合:

/**
* All of the processes we currently have running organized by pid.
* The keys are the pid running the application.
*
* <p>NOTE: This object is protected by its own lock, NOT the global
* activity manager lock!
*/
final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();

下面這幅圖小節了上文的這些內容:

<img src="http://qiangbo-workspace.oss-... width="600">

關於應用組件

Processes and Threads 提到:
當某個應用組件啓動且該應用沒有運行其餘任何組件時,Android 系統會使用單個執行線程爲應用啓動新的 Linux 進程。
所以,四大組件中的任何一個先起來都會致使應用進程的建立。下文咱們就詳細看一下,它們啓動時,各自是如何致使應用進程的建立的。
PS:四大組件的管理自己又是一個比較大的話題,限於篇幅關係,這裏不會很是深刻的講解,這裏主要是講解四大組件與進程建立的關係。

在應用程序中,開發者經過:

  • startActivity(Intent intent) 來啓動Activity

  • startService(Intent service) 來啓動Service

  • sendBroadcast(Intent intent) 來發送廣播

  • ContentResolver 中的接口來使用ContentProvider

這其中,startActivitystartServicesendBroadcast還有一些重載方法。

其實這裏提到的全部這些方法,最終都是經過Binder調用到ActivityManagerService中,由其進行處理的。

這裏特別說明一下:應用進程和ActivityManagerService所在進程(即system_server進程)是相互獨立的,兩個進程之間的方法一般是不能直接互相調用的。

而Android系統中,專門提供了Binder框架來提供進程間通信和方法調用的能力。

調用關係以下圖所示:

<img src="http://qiangbo-workspace.oss-... width="600" >

Activity與進程建立

在ActivityManagerService中,對每個運行中的Activity都有一個ActivityRecord對象與之對應,這個對象記錄Activity的詳細狀態。

ActivityManagerService中的startActivity方法接受Context.startActivity的請求,該方法代碼以下:

@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
       Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
       int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
   return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
           resultWho, requestCode, startFlags, profilerInfo, bOptions,
           UserHandle.getCallingUserId());
}

Activity的啓動是一個很是複雜的過程。這裏咱們簡單介紹一下背景知識:

  • ActivityManagerService中經過Stack和Task來管理Activity

  • 每個Activity都屬於一個Task,一個Task可能包含多個Activity。一個Stack包含多個Task

  • ActivityStackSupervisor類負責管理全部的Stack

  • Activity的啓動過程會牽涉到:

    • Intent的解析

    • Stack,Task的查詢或建立

    • Activity進程的建立

    • Activity窗口的建立

    • Activity的生命週期調度

Activity的管理結構以下圖所示:

<img src="http://qiangbo-workspace.oss-... width="500">

在Activity啓動的最後,會將前一個Activity pause,將新啓動的Activity resume以便被用戶看到。
在這個時候,若是發現新啓動的Activity進程尚未啓動,則會經過startSpecificActivityLocked將其啓動。整個調用流程以下:

  • ActivityManagerService.activityPaused =>

  • ActivityStack.activityPausedLocked =>

  • ActivityStack.completePauseLocked =>

  • ActivityStackSupervisor.ensureActivitiesVisibleLocked =>

  • ActivityStack.makeVisibleAndRestartIfNeeded =>

  • ActivityStackSupervisor.startSpecificActivityLocked =>

  • ActivityManagerService.startProcessLocked

    ActivityStackSupervisor.startSpecificActivityLocked 關鍵代碼以下:

void startSpecificActivityLocked(ActivityRecord r,
       boolean andResume, boolean checkConfig) {
   // Is this activity's application already running?
   ProcessRecord app = mService.getProcessRecordLocked(r.processName,
           r.info.applicationInfo.uid, true);

   r.task.stack.setLaunchTime(r);

   if (app != null && app.thread != null) {
       ...
   }

   mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
           "activity", r.intent.getComponent(), false, false, true);
}

這裏的ProcessRecord app 描述了Activity所在進程。

Service與進程建立

Service的啓動相對於Activity來講要簡單一些。
在ActivityManagerService中,對每個運行中的Service都有一個ServiceRecord對象與之對應,這個對象記錄Service的詳細狀態。
ActivityManagerService中的startService方法處理Context.startServiceAPI的請求,相關代碼:

@Override
public ComponentName startService(IApplicationThread caller, Intent service,
       String resolvedType, String callingPackage, int userId)
       throws TransactionTooLargeException {
   ...
   synchronized(this) {
       final int callingPid = Binder.getCallingPid();
       final int callingUid = Binder.getCallingUid();
       final long origId = Binder.clearCallingIdentity();
       ComponentName res = mServices.startServiceLocked(caller, service,
               resolvedType, callingPid, callingUid, callingPackage, userId);
       Binder.restoreCallingIdentity(origId);
       return res;
   }
}

這段代碼中的mServices對象是ActiveServices類型的,這個類專門負責管理活動的Service。

啓動Service的調用流程以下:

  • ActivityManagerService.startService =>

  • ActiveServices.startServiceLocked =>

  • ActiveServices.startServiceInnerLocked =>

  • ActiveServices.bringUpServiceLocked =>

  • ActivityManagerService.startProcessLocked

ActiveServices.bringUpServiceLocked會判斷若是Service所在進程尚未啓動,

則經過ActivityManagerService.startProcessLocked將其啓動。相關代碼以下:

// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null && !permissionsReviewRequired) {
  if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
          "service", r.name, false, isolated, false)) == null) {
      String msg = "Unable to launch app "
              + r.appInfo.packageName + "/"
              + r.appInfo.uid + " for service "
              + r.intent.getIntent() + ": process is bad";
      Slog.w(TAG, msg);
      bringDownServiceLocked(r);
      return msg;
  }
  if (isolated) {
      r.isolatedProc = app;
  }
}

這裏的mAm 就是ActivityManagerService。

Provider與進程建立

在ActivityManagerService中,對每個運行中的ContentProvider都有一個ContentProviderRecord對象與之對應,這個對象記錄ContentProvider的詳細狀態。

開發者經過ContentResolver中的insert, delete, update, query這些API來使用ContentProvider。在ContentResolver的實現中,不管使用這裏的哪一個接口,ContentResolver都會先經過acquireProvider 這個方法來獲取到一個類型爲IContentProvider的遠程接口。這個遠程接口對接了ContentProvider的實現提供方。

同一個ContentProvider可能同時被多個模塊使用,而調用ContentResolver接口的進程只是ContentProvider的一個客戶端而已,真正的ContentProvider提供方是運行自身的進程中的,兩個進程的通信須要經過Binder的遠程接口形式來調用。以下圖所示:

<img src="http://qiangbo-workspace.oss-... width="500">

ContentResolver.acquireProvider 最終會調用到ActivityManagerService.getContentProvider中,該方法代碼以下:

@Override
public final ContentProviderHolder getContentProvider(
       IApplicationThread caller, String name, int userId, boolean stable) {
   enforceNotIsolatedCaller("getContentProvider");
   if (caller == null) {
       String msg = "null IApplicationThread when getting content provider "
               + name;
       Slog.w(TAG, msg);
       throw new SecurityException(msg);
   }
   // The incoming user check is now handled in checkContentProviderPermissionLocked() to deal
   // with cross-user grant.
   return getContentProviderImpl(caller, name, null, stable, userId);
}

而在getContentProviderImpl這個方法中,會判斷對應的ContentProvider進程有沒有啓動,
若是沒有,則經過startProcessLocked方法將其啓動。

Receiver與進程建立

開發者經過Context.sendBroadcast接口來發送廣播。ActivityManagerService.broadcastIntent 方法了對應廣播發送的處理。
廣播是一種一對多的消息形式,廣播接受者的數量是不肯定的。所以發送廣播自己多是一個很耗時的過程(由於要逐個通知)。
在ActivityManagerService內部,是經過隊列的形式來管理廣播的:

  • BroadcastQueue 描述了一個廣播隊列

  • BroadcastRecord 描述了一個廣播事件

ActivityManagerService中,若是收到了一個發送廣播的請求,會先建立一個BroadcastRecord接着將其放入BroadcastQueue中。

而後通知隊列本身去處理這個廣播。而後ActivityManagerService本身就能夠繼續處理其餘請求了。
廣播隊列自己是在另一個線程處理廣播的發送的,這樣保證的ActivityManagerService主線程的負載不會過重。

BroadcastQueue.processNextBroadcast(boolean fromMsg) 方法中真正實現了通知廣播事件到接受者的邏輯。在這個方法,若是發現接受者(即BrodcastReceiver)尚未啓動,便會經過ActivityManagerService.startProcessLocked 方法將其啓動。相關以下所示:

final void processNextBroadcast(boolean fromMsg) {
    ...
       // Hard case: need to instantiate the receiver, possibly
       // starting its application process to host it.

       ResolveInfo info =
           (ResolveInfo)nextReceiver;
       ComponentName component = new ComponentName(
               info.activityInfo.applicationInfo.packageName,
               info.activityInfo.name);
    ...
       // Not running -- get it started, to be executed when the app comes up.
       if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
               "Need to start app ["
               + mQueueName + "] " + targetProcess + " for broadcast " + r);
       if ((r.curApp=mService.startProcessLocked(targetProcess,
               info.activityInfo.applicationInfo, true,
               r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
               "broadcast", r.curComponent,
               (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
                       == null) {
           // Ah, this recipient is unavailable.  Finish it if necessary,
           // and mark the broadcast record as ready for the next.
           Slog.w(TAG, "Unable to launch app "
                   + info.activityInfo.applicationInfo.packageName + "/"
                   + info.activityInfo.applicationInfo.uid + " for broadcast "
                   + r.intent + ": process is bad");
           logBroadcastReceiverDiscardLocked(r);
           finishReceiverLocked(r, r.resultCode, r.resultData,
                   r.resultExtras, r.resultAbort, false);
           scheduleBroadcastsLocked();
           r.state = BroadcastRecord.IDLE;
           return;
       }

       mPendingBroadcast = r;
       mPendingBroadcastRecvIndex = recIdx;
   }
}

至此,四大組件的啓動就已經分析完了。

結束語

進程管理自己是一個很是大的話題,本文講解了Android系統中進程建立的相關內容。
進程啓動以後該如何管理就是下一篇文章要講解的內容了。

敬請期待。

參考資料與推薦讀物

UNIX環境高級編程(第3版)

深刻理解LINUX內核(第3版)

Processes and Threads

Android Booting

相關文章
相關標籤/搜索