2018年5月10日 星期四

Full-disk encryption

Source file:///xray/yulin/msm8996au-la-1-2/LINUX/android/docs/source.android.com/en/security/encryption/full-disk.html

Full-disk encryption is the process of encoding all user data on an Android device using an encrypted key. Once a device is encrypted, all user-created data is automatically encrypted before committing it to disk and all reads automatically decrypt data before returning it to the calling process.
Full-disk encryption was introduced to Android in 4.4, but Android 5.0 introduced these new features:
  • Created fast encryption, which only encrypts used blocks on the data partition to avoid first boot taking a long time. Only ext4 and f2fs filesystems currently support fast encryption.
  • Added the forceencrypt fstab flag to encrypt on first boot.
  • Added support for patterns and encryption without a password.
  • Added hardware-backed storage of the encryption key using Trusted Execution Environment’s (TEE) signing capability (such as in a TrustZone). See Storing the encrypted key for more details.
Caution: Devices upgraded to Android 5.0 and then encrypted may be returned to an unencrypted state by factory data reset. New Android 5.0 devices encrypted at first boot cannot be returned to an unencrypted state.

How Android full-disk encryption works

Android full-disk encryption is based on dm-crypt, which is a kernel feature that works at the block device layer. Because of this, encryption works with Embedded MultiMediaCard (eMMC) and similar flash devices that present themselves to the kernel as block devices. Encryption is not possible with YAFFS, which talks directly to a raw NAND flash chip.
The encryption algorithm is 128 Advanced Encryption Standard (AES) with cipher-block chaining (CBC) and ESSIV:SHA256. The master key is encrypted with 128-bit AES via calls to the OpenSSL library. You must use 128 bits or more for the key (with 256 being optional).
Note: OEMs can use 128-bit or higher to encrypt the master key.
In the Android 5.0 release, there are four kinds of encryption states:
  • default
  • PIN
  • password
  • pattern
Upon first boot, the device creates a randomly generated 128-bit master key and then hashes it with a default password and stored salt. The default password is: "default_password" However, the resultant hash is also signed through a TEE (such as TrustZone), which uses a hash of the signature to encrypt the master key.
You can find the default password defined in the Android Open Source Project cryptfs.c file.
When the user sets the PIN/pass or password on the device, only the 128-bit key is re-encrypted and stored. (ie. user PIN/pass/pattern changes do NOT cause re-encryption of userdata.) Note that managed device may be subject to PIN, pattern, or password restrictions.
Encryption is managed by init and voldinit calls vold, and vold sets properties to trigger events in init. Other parts of the system also look at the properties to conduct tasks such as report status, ask for a password, or prompt to factory reset in the case of a fatal error. To invoke encryption features in vold, the system uses the command line tool vdc’s cryptfs commands: checkpwrestartenablecryptochangepwcryptocompleteverifypwsetfieldgetfieldmountdefaultencryptedgetpwtypegetpw, and clearpw.
In order to encrypt, decrypt or wipe /data/data must not be mounted. However, in order to show any user interface (UI), the framework must start and the framework requires /data to run. To resolve this conundrum, a temporary filesystem is mounted on /data. This allows Android to prompt for passwords, show progress, or suggest a data wipe as needed. It does impose the limitation that in order to switch from the temporary filesystem to the true /data filesystem, the system must stop every process with open files on the temporary filesystem and restart those processes on the real /data filesystem. To do this, all services must be in one of three groups: coremain, and late_start.
  • core: Never shut down after starting.
  • main: Shut down and then restart after the disk password is entered.
  • late_start: Does not start until after /data has been decrypted and mounted.
To trigger these actions, the vold.decrypt property is set to various strings. To kill and restart services, the init commands are:
  • class_reset: Stops a service but allows it to be restarted with class_start.
  • class_start: Restarts a service.
  • class_stop: Stops a service and adds a SVC_DISABLED flag. Stopped services do not respond to class_start.

Flows

There are four flows for an encrypted device. A device is encrypted just once and then follows a normal boot flow.
  • Encrypt a previously unencrypted device:
    • Encrypt a new device with forceencrypt: Mandatory encryption at first boot (starting in Android L).
    • Encrypt an existing device: User-initiated encryption (Android K and earlier).
  • Boot an encrypted device:
    • Starting an encrypted device with no password: Booting an encrypted device that has no set password (relevant for devices running Android 5.0 and later).
    • Starting an encrypted device with a password: Booting an encrypted device that has a set password.
In addition to these flows, the device can also fail to encrypt /data. Each of the flows are explained in detail below.

Encrypt a new device with forceencrypt

This is the normal first boot for an Android 5.0 device.
  1. Detect unencrypted filesystem with forceencrypt flag/data is not encrypted but needs to be because forceencrypt mandates it. Unmount /data.
  2. Start encrypting /datavold.decrypt = "trigger_encryption" triggers init.rc, which will cause vold to encrypt /data with no password. (None is set because this should be a new device.)
  3. Mount tmpfsvold mounts a tmpfs /data (using the tmpfs options from ro.crypto.tmpfs_options) and sets the property vold.encrypt_progress to 0. vold prepepares the tmpfs /data for booting an encrypted system and sets the property vold.decrypt to: trigger_restart_min_framework
  4. Bring up framework to show progressBecause the device has virtually no data to encrypt, the progress bar will often not actually appear because encryption happens so quickly. See Encrypt an existing device for more details about the progress UI.
  5. When /data is encrypted, take down the frameworkvold sets vold.decrypt to trigger_default_encryption which starts the defaultcrypto service. (This starts the flow below for mounting a default encrypted userdata.) trigger_default_encryption checks the encryption type to see if /data is encrypted with or without a password. Because Android 5.0 devices are encrypted on first boot, there should be no password set; therefore we decrypt and mount /data.
  6. Mount /datainit then mounts /data on a tmpfs RAMDisk using parameters it picks up from ro.crypto.tmpfs_options, which is set in init.rc.
  7. Start frameworkSet vold to trigger_restart_framework, which continues the usual boot process.

Encrypt an existing device

This is what happens when you encrypt an unencrypted Android K or earlier device that has been migrated to L.
This process is user-initiated and is referred to as “inplace encryption” in the code. When a user selects to encrypt a device, the UI makes sure the battery is fully charged and the AC adapter is plugged in so there is enough power to finish the encryption process.
Warning: If the device runs out of power and shuts down before it has finished encrypting, file data is left in a partially encrypted state. The device must be factory reset and all data is lost.
To enable inplace encryption, vold starts a loop to read each sector of the real block device and then write it to the crypto block device. vold checks to see if a sector is in use before reading and writing it, which makes encryption much faster on a new device that has little to no data.
State of device: Set ro.crypto.state = "unencrypted" and execute the on nonencrypted init trigger to continue booting.
  1. Check passwordThe UI calls vold with the command cryptfs enablecrypto inplace where passwd is the user's lock screen password.
  2. Take down the frameworkvold checks for errors, returns -1 if it can't encrypt, and prints a reason in the log. If it can encrypt, it sets the property vold.decrypt to trigger_shutdown_framework. This causes init.rc to stop services in the classes late_start and main.
  3. Create a crypto footer
  4. Create a breadcrumb file
  5. Reboot
  6. Detect breadcrumb file
  7. Start encrypting /datavold then sets up the crypto mapping, which creates a virtual crypto block device that maps onto the real block device but encrypts each sector as it is written, and decrypts each sector as it is read. voldthen creates and writes out the crypto metadata.
  8. While it’s encrypting, mount tmpfsvold mounts a tmpfs /data (using the tmpfs options from ro.crypto.tmpfs_options) and sets the property vold.encrypt_progress to 0. vold prepares the tmpfs /data for booting an encrypted system and sets the property vold.decrypt to: trigger_restart_min_framework
  9. Bring up framework to show progresstrigger_restart_min_framework causes init.rc to start the main class of services. When the framework sees that vold.encrypt_progress is set to 0, it brings up the progress bar UI, which queries that property every five seconds and updates a progress bar. The encryption loop updates vold.encrypt_progress every time it encrypts another percent of the partition.
  10. When /data is encrypted, update the crypto footerWhen /data is successfully encrypted, vold clears the flag ENCRYPTION_IN_PROGRESS in the metadata.
    When the device is successfully unlocked, the password is then used to encrypt the master key and the crypto footer is updated.
    If the reboot fails for some reason, vold sets the property vold.encrypt_progress to error_reboot_failed and the UI should display a message asking the user to press a button to reboot. This is not expected to ever occur.

Starting an encrypted device with default encryption

This is what happens when you boot up an encrypted device with no password. Because Android 5.0 devices are encrypted on first boot, there should be no set password and therefore this is the default encryption state.
  1. Detect encrypted /data with no passwordDetect that the Android device is encrypted because /data cannot be mounted and one of the flags encryptable or forceencrypt is set.
    vold sets vold.decrypt to trigger_default_encryption, which starts the defaultcrypto service. trigger_default_encryption checks the encryption type to see if /data is encrypted with or without a password.
  2. Decrypt /dataCreates the dm-crypt device over the block device so the device is ready for use.
  3. Mount /datavold then mounts the decrypted real /data partition and then prepares the new partition. It sets the property vold.post_fs_data_done to 0 and then sets vold.decrypt to trigger_post_fs_data. This causes init.rc to run its post-fs-data commands. They will create any necessary directories or links and then set vold.post_fs_data_done to 1.
    Once vold sees the 1 in that property, it sets the property vold.decrypt to: trigger_restart_framework. This causes init.rc to start services in class main again and also start services in class late_start for the first time since boot.
  4. Start frameworkNow the framework boots all its services using the decrypted /data, and the system is ready for use.

Starting an encrypted device without default encryption

This is what happens when you boot up an encrypted device that has a set password. The device’s password can be a pin, pattern, or password.
  1. Detect encrypted device with a passwordDetect that the Android device is encrypted because the flag ro.crypto.state = "encrypted"
    vold sets vold.decrypt to trigger_restart_min_framework because /data is encrypted with a password.
  2. Mount tmpfsinit sets five properties to save the initial mount options given for /data with parameters passed from init.rcvold uses these properties to set up the crypto mapping:
    1. ro.crypto.fs_type
    2. ro.crypto.fs_real_blkdev
    3. ro.crypto.fs_mnt_point
    4. ro.crypto.fs_options
    5. ro.crypto.fs_flags (ASCII 8-digit hex number preceded by 0x)
  3. Start framework to prompt for passwordThe framework starts up and sees that vold.decrypt is set to trigger_restart_min_framework. This tells the framework that it is booting on a tmpfs /data disk and it needs to get the user password.
    First, however, it needs to make sure that the disk was properly encrypted. It sends the command cryptfs cryptocomplete to voldvold returns 0 if encryption was completed successfully, -1 on internal error, or -2 if encryption was not completed successfully. vold determines this by looking in the crypto metadata for the CRYPTO_ENCRYPTION_IN_PROGRESS flag. If it's set, the encryption process was interrupted, and there is no usable data on the device. If vold returns an error, the UI should display a message to the user to reboot and factory reset the device, and give the user a button to press to do so.
  4. Decrypt data with passwordOnce cryptfs cryptocomplete is successful, the framework displays a UI asking for the disk password. The UI checks the password by sending the command cryptfs checkpw to vold. If the password is correct (which is determined by successfully mounting the decrypted /data at a temporary location, then unmounting it), vold saves the name of the decrypted block device in the propertyro.crypto.fs_crypto_blkdev and returns status 0 to the UI. If the password is incorrect, it returns -1 to the UI.
  5. Stop frameworkThe UI puts up a crypto boot graphic and then calls vold with the command cryptfs restartvold sets the property vold.decrypt to trigger_reset_main, which causes init.rc to do class_reset main. This stops all services in the main class, which allows the tmpfs /data to be unmounted.
  6. Mount /datavold then mounts the decrypted real /data partition and prepares the new partition (which may never have been prepared if it was encrypted with the wipe option, which is not supported on first release). It sets the property vold.post_fs_data_done to 0 and then sets vold.decrypt to trigger_post_fs_data. This causes init.rc to run its post-fs-data commands. They will create any necessary directories or links and then set vold.post_fs_data_done to 1. Once vold sees the 1 in that property, it sets the property vold.decrypt to trigger_restart_framework. This causes init.rc to start services in class main again and also start services in class late_start for the first time since boot.
  7. Start full frameworkNow the framework boots all its services using the decrypted /data filesystem, and the system is ready for use.

Failure

A device that fails to decrypt might be awry for a few reasons. The device starts with the normal series of steps to boot:
  1. Detect encrypted device with a password
  2. Mount tmpfs
  3. Start framework to prompt for password
But after the framework opens, the device can encounter some errors:
  • Password matches but cannot decrypt data
  • User enters wrong password 30 times
If these errors are not resolved, prompt user to factory wipe:
If vold detects an error during the encryption process, and if no data has been destroyed yet and the framework is up, vold sets the property vold.encrypt_progress to error_not_encrypted. The UI prompts the user to reboot and alerts them the encryption process never started. If the error occurs after the framework has been torn down, but before the progress bar UI is up, vold will reboot the system. If the reboot fails, it sets vold.encrypt_progress to error_shutting_down and returns -1; but there will not be anything to catch the error. This is not expected to happen.
If vold detects an error during the encryption process, it sets vold.encrypt_progress to error_partially_encrypted and returns -1. The UI should then display a message saying the encryption failed and provide a button for the user to factory reset the device.

Storing the encrypted key

The encrypted key is stored in the crypto metadata. Hardware backing is implemented by using Trusted Execution Environment’s (TEE) signing capability. Previously, we encrypted the master key with a key generated by applying scrypt to the user's password and the stored salt. In order to make the key resilient against off-box attacks, we extend this algorithm by signing the resultant key with a stored TEE key. The resultant signature is then turned into an appropriate length key by one more application of scrypt. This key is then used to encrypt and decrypt the master key. To store this key:
  1. Generate random 16-byte disk encryption key (DEK) and 16-byte salt.
  2. Apply scrypt to the user password and the salt to produce 32-byte intermediate key 1 (IK1).
  3. Pad IK1 with zero bytes to the size of the hardware-bound private key (HBK). Specifically, we pad as: 00 || IK1 || 00..00; one zero byte, 32 IK1 bytes, 223 zero bytes.
  4. Sign padded IK1 with HBK to produce 256-byte IK2.
  5. Apply scrypt to IK2 and salt (same salt as step 2) to produce 32-byte IK3.
  6. Use the first 16 bytes of IK3 as KEK and the last 16 bytes as IV.
  7. Encrypt DEK with AES_CBC, with key KEK, and initialization vector IV.

Changing the password

When a user elects to change or remove their password in settings, the UI sends the command cryptfs changepw to vold, and vold re-encrypts the disk master key with the new password.

Encryption properties

vold and init communicate with each other by setting properties. Here is a list of available properties for encryption.

Vold properties

PropertyDescription
vold.decrypt trigger_encryptionEncrypt the drive with no password.
vold.decrypt trigger_default_encryptionCheck the drive to see if it is encrypted with no password. If it is, decrypt and mount it, else set vold.decrypt to trigger_restart_min_framework.
vold.decrypt trigger_reset_mainSet by vold to shutdown the UI asking for the disk password.
vold.decrypt trigger_post_fs_dataSet by vold to prep /data with necessary directories, et al.
vold.decrypt trigger_restart_frameworkSet by vold to start the real framework and all services.
vold.decrypt trigger_shutdown_frameworkSet by vold to shutdown the full framework to start encryption.
vold.decrypt trigger_restart_min_frameworkSet by vold to start the progress bar UI for encryption or prompt for password, depending on the value of ro.crypto.state.
vold.encrypt_progressWhen the framework starts up, if this property is set, enter the progress bar UI mode.
vold.encrypt_progress 0 to 100The progress bar UI should display the percentage value set.
vold.encrypt_progress error_partially_encryptedThe progress bar UI should display a message that the encryption failed, and give the user an option to factory reset the device.
vold.encrypt_progress error_reboot_failedThe progress bar UI should display a message saying encryption completed, and give the user a button to reboot the device. This error is not expected to happen.
vold.encrypt_progress error_not_encryptedThe progress bar UI should display a message saying an error occurred, no data was encrypted or lost, and give the user a button to reboot the system.
vold.encrypt_progress error_shutting_downThe progress bar UI is not running, so it is unclear who will respond to this error. And it should never happen anyway.
vold.post_fs_data_done 0Set by vold just before setting vold.decrypt to trigger_post_fs_data.
vold.post_fs_data_done 1Set by init.rc or init.rc just after finishing the task post-fs-data.

init properties

PropertyDescription
ro.crypto.fs_crypto_blkdevSet by the vold command checkpw for later use by the vold command restart.
ro.crypto.state unencryptedSet by init to say this system is running with an unencrypted /data ro.crypto.state encrypted. Set by init to say this system is running with an encrypted /data.
ro.crypto.fs_type
ro.crypto.fs_real_blkdev
ro.crypto.fs_mnt_point
ro.crypto.fs_options
ro.crypto.fs_flags 
These five properties are set by init when it tries to mount /data with parameters passed in from init.rcvold uses these to setup the crypto mapping.
ro.crypto.tmpfs_optionsSet by init.rc with the options init should use when mounting the tmpfs /data filesystem.

Init actions

on post-fs-data
on nonencrypted
on property:vold.decrypt=trigger_reset_main
on property:vold.decrypt=trigger_post_fs_data
on property:vold.decrypt=trigger_restart_min_framework
on property:vold.decrypt=trigger_restart_framework
on property:vold.decrypt=trigger_shutdown_framework
on property:vold.decrypt=trigger_encryption
on property:vold.decrypt=trigger_default_encryption

Android啟動篇— init原理(一)

原文 http://www.cnblogs.com/pepsimaxin/p/6702945.html

================================================== ====== ============================================ ============
= 【原創文章】:參考部分博客內容,學習之餘進行了大量的篩減細化分析= = 【特殊申明】:避諱抄襲侵權之嫌疑,特此說明,歡迎轉載!=    
================================================= ======= =========================================== =============
************************************************** ************************* ************************* **************************************************
* Android源碼下載:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/                    * *源碼編譯可參考【牛肉麵大神之作】:http://blog.csdn.net/cjpx00008/article /details/60474883 *
************************************************** ************************* ************************* **************************************************
【開篇說明】
  在【Android啟示錄】中,提到了主要的分析對象和分享內容,拋開Android內核級的知識點,學習Android第一步便是“init”,作為天字第一號進程,代碼羞澀難懂,但是也極其重要,熟悉init的原理對後面Zygote -- SystemServer --核心服務等一些列源碼的研究是有很大作用的,所以既然說研究Android源碼,就先拿init “庖丁解牛”!
【正文開始】
  Init進程,它是一個由內核啟動的用戶級進程,當Linux內核啟動之後,運行的第一個進程是init,這個進程是一個守護進程,確切的說,它是Linux系統中用戶控件的第一個進程,所以它的進程號是1。它的生命週期貫穿整個linux內核運行的始終, linux中所有其它的進程的共同始祖均為init進程,可以通過“adb shell ps | grep init”查看進程號。
  Android init進程的入口文件在system/core/init/init.cpp中,由於init是命令行程序,所以分析init.cpp首先應從main函數開始:
複製代碼
int main(int argc,char ** argv){     // 入口函數main 
    if(!strcmp(basename(argv [ 0 ]),ueventd )){
         return ueventd_main(argc,argv);
    }

    if(!strcmp(basename(argv [ 0 ]),watchdogd )){
         return watchdogd_main(argc,argv);
    }

    // Clear the umask. 
    umask( 0 );     // 清除屏蔽字(file mode creation mask),保證新建的目錄的訪問權限不受屏蔽字影響。
    add_environment( " PATH " , _PATH_DEFPATH);
     
    bool is_first_stage = (argc == 1 ) || (strcmp(argv[ 1 ], " --second-stage " ) != 0 );     //判斷是否是系統啟動的第一階段,只有啟動參數中有--second-stage才為第二階段
    // 將我們需要的基本文件系統設置放在initramdisk     // 上,然後我們讓rc文件找出其餘的。 
    如果(is_first_stage){ 
        mount(tmpfs / dev tmpfs ,MS_NOSUID,mode = 0755 );                        // 掛載tmpfs文件系統 
         mkdir(/ dev / pts 0755 ); 
        mkdir(/ dev / socket 0755 );
        mount(devpts / dev / pts devpts 0,NULL);                                 (x)__STRING(x)        mount(proc / proc proc 0hidepid = 2,gid =  MAKE_STR(AID_READPROC));      // 掛載devpts文件系統 
        #define MAKE_STR // 掛載proc文件系統         mount(sysfs  
 
" /sys " , " sysfs " , 0 , NULL);                                        // 掛載sysfs文件系統 
     }
複製代碼
  以上代碼主要做的工作就是:【創建文件系統目錄並掛載相關的文件系統】
複製代碼
int main( int argc, char ** argv) {
     /* 01.創建文件系統目錄並掛載相關的文件系統*/
     /* 02.屏蔽標準的輸入輸出/初始化內核log系統*/
     // We must have some place other than / to create the device nodes for
     // kmsg and null, otherwise we won't be able to remount / read-only
     // later on. Now that tmpfs is mounted on /dev, we can actually talk
     // to the outside world. 
    open_devnull_stdio();     // 重定向標準輸入輸出到/dev/_null_ -->定義在system/core/init/Util.cpp中
     // init進程通過klog_init函數,提供輸出log信息的設備-->定義在system/core/libcutils/Klog.c中 
    klog_init();       //對明智進行初始化          
    klog_set_level(KLOG_NOTICE_LEVEL);  // 通知級別
複製代碼
  繼續分析源碼,接下來要做的就是初始化屬性域:
複製代碼
int main( int argc, char ** argv) {
     /* 01.創建文件系統目錄並掛載相關的文件系統*/
    /* 02.屏蔽標準的輸入輸出/初始化內核log系統*/ 
    /* 03.初始化屬性域*/ 
    NOTICE( " init %s started!\n " , is_first_stage ? " first stage " : " second stage " );
     if (!is_first_stage) {       // 引入SELinux機制後,通過is_first_stage區分init運行狀態
         // Indicate that booting is in progress to background fw loaders, etc. 
        close(open( " /dev/.booting " , O_WRONLY | O_CREAT | O_CLOEXEC, 0000 ));       /* 檢測 /dev/.booting 文件是否可讀寫創建*/
        property_init();         // 初始化屬性域-->定義於system/core/init/Property_service.cpp

        // 如果在命令行和DT中都傳遞參數,
         // 在DT中設置的屬性總是優先於命令行的。
        process_kernel_dt();
        process_kernel_cmdline();     // 處理內核命令行
         // 傳播內核變量到內部變量
         // 由init以及當前必需屬性使用。
        export_kernel_boot_props();
    }
複製代碼
  看一下property_init方法:位於system/core/init/Property_service.cpp中
void property_init() {
     if (__system_property_area_init()) {          // 調用此函數初始化屬性域 
        ERROR( " Failed to initialize property area\n " );
        退出(1 );
    }
}
  繼續分析main函數:
複製代碼
int main( int argc, char ** argv) {
     /* 01.創建文件系統目錄並掛載相關的文件系統*/
    /* 02. 屏蔽標準的輸入輸出/初始化內核log系統*/
    /* 03.初始化屬性域*/ 
    /* 04.完成SELinux相關工作*/
     // Set up SELinux, including loading the SELinux policy if we're in the kernel domain. 
    selinux_initialize(is_first_stage);      // 調用selinux_initialize啟動SELinux
複製代碼
  詳細看一下selinux_initialize()函數:
複製代碼
static  void selinux_initialize( bool in_kernel_domain) {      // 區分內核態和用戶態 
    Timer t;       // 使用Timer計時,計算selinux初始化耗時

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;               // 用於打印Log的回調函數
    selinux_set_callback(SELINUX_CB_LOG, cb);
    cb.func_audit = audit_callback;                     // 用於檢查權限的回調函數
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    if (in_kernel_domain) {         // 內核態處理流程,第一階段in_kernel_domain為true   
        INFO( " Loading SELinux policy...\n " );         // 該行log打印不出,INFO級別 
         // 用於加載sepolicy文件。該函數最終將sepolicy文件傳遞給kernel,這樣kernel就有了安全策略配置文件
        if (selinux_android_load_policy() < 0 ) {
            錯誤(加載策略失敗:%s \ n ,strerror(errno));
            security_failure();
        }

        bool kernel_enforcing = (security_getenforce() == 1 );       // 內核中讀取的信息
        bool is_enforcing = selinux_is_enforcing();                 // 命令行中得到的信息
        if (kernel_enforcing != is_enforcing) {
         // 用於設置selinux的工作模式。selinux有兩種工作模式:
             // 1、”permissive”,所有的操作都被允許(即沒有MAC),但是如果違反權限的話,會記錄日誌
             // 2、”enforcing”,所有操作都會進行權限檢查。在一般的終端中,應該工作於enforing模式
            if (security_setenforce(is_enforcing)) {         // 設置selinux的模式,是開還是關 
                ERROR( " security_setenforce(%s) failed: %s\n " ,
                      is_enforcingtrue false ,strerror(errno));
                security_failure();     // 將重啟進入recovery mode 
            }
        }

        if(write_file(/ sys / fs / selinux / checkreqprot 0 )==  - 1 ){
            security_failure();
        }

        NOTICE((初始化SELinux%s佔用%.2fs。)\ n 
               is_enforcing ? " enforcing " : " non-enforcing " , t.duration());    //輸出selinux的模式,與初始化耗時
    } else { 
        selinux_init_all_handles(); // 如果啟動第二階段,調用該函數     } } 

複製代碼
   回到main函數中繼續分析:
複製代碼
int main( int argc, char ** argv) {
     /* 01.創建文件系統目錄並掛載相關的文件系統*/
    /* 02. 屏蔽標準的輸入輸出/初始化內核log系統*/
    /* 03. 初始化屬性域*/
    /* 04.完成SELinux相關工作*/ 
   /* 05.重新設置屬性*/
     // If we're in the kernel domain, re-exec init to transition to the init domain now
     // that the SELinux policy has been loaded . 
    if (is_first_stage) {
         if (restorecon( " /init " ) == - 1 ) {     //  selinux policy 要求,重新設置 init 文件屬性
            錯誤(restorecon失敗:%s \ n ,strerror(errno));
            security_failure();
        }
        char * path = argv [ 0 ];
        char * args [] = {path,const_cast < char *>(“-- second-stage ),nullptr};     // 設置參數 --second級

    if (execv(path, args) == - 1 ) {         //執行init進程,重新進入main函數 
            ERROR( " execv(\"%s\") failed: %s\n " , path, strerror(errno) );
            security_failure();
        }
    }

    // 這些目錄必須在初始策略加載之前創建
     // 因此需要將其安全上下文恢復為適當的值。
    // 這個必須在ueventd填充/ dev之前發生。
    NOTICE(“正在運行restorecon ... \ n );
    restorecon(/ dev );
    restorecon(/ dev / socket );
    restorecon(/ dev / __ properties__ );
    restorecon(/ property_contexts );

    restorecon_recursive(/ sys );

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);         //調用epoll_create1創建epoll句柄
     if (epoll_fd == - 1 ) {
        錯誤(epoll_create1失敗:%s \ n ,strerror(errno));
        退出(1 );
    }
複製代碼
  接著往下分析:
複製代碼
int main( int argc, char ** argv) {
     /* 01.創建文件系統目錄並掛載相關的文件系統*/
    /* 02. 屏蔽標準的輸入輸出/初始化內核log系統*/
    /* 03. 初始化屬性域*/
    /* 04. 完成SELinux相關工作*/·
    /* 05. 重新設置屬性*/
    /* 06.創建epoll句柄*/ 
    /* 07.裝載子進程信號處理器*/ 
    signal_handler_init();        //裝載子進程信號處理器
複製代碼
    Note  init是一個守護進程,為了防止init 的子進程成為殭屍進程 (zombie process),需要 init在子進程結束時獲取子進程的結束碼,通過結束碼將程序表中的子進程移除,防止成為殭屍進程的子進程佔用程序表的空間(程序表的空間達到上限時,系統就不能再啟動新的進程了,會引起嚴重的系統問題)。
  細化signal_handler_init()函數:
複製代碼
void signal_handler_init() {        //函數定位於:system/core/init/Singal_handler.cpp
     // 在linux當中,父進程是通過捕捉SIGCHLD信號來得知子進程運行結束的情況
     // Create a signalling mechanism for SIGCHLD. 
    int s[ 2 ];
     // 利用socketpair創建出已經連接的兩個socket,分別作為信號的讀、寫端
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0 , s) == - 1 ) {
        錯誤(socketpair失敗:%s \ n ,strerror(errno));
        退出(1 );
    }

    signal_write_fd = s [ 0 ];
    signal_read_fd = s [ 1 ];

    // 如果我們捕獲SIGCHLD,則寫入signal_write_fd。
    struct sigaction act;
    memset( &act, 0 , sizeof (act));
     // 信號處理器為SIGCHLD_handler,其被存在sigaction結構體中,負責處理SIGCHLD消息 
    act.sa_handler = SIGCHLD_handler;        // 信號處理器:SIGCHLD_handler 
    act.sa_flags = SA_NOCLDSTOP ;             // 僅當進程終止時才接受SIGCHLD信號
     // 調用信號安裝函數sigaction,將監聽的信號及對應的信號處理器註冊到內核中 
    sigaction(SIGCHLD, &act, 0 );
     // 相對於6.0的代碼,進一步作了封裝,用於終止出現問題的子進程
    ServiceManager::GetInstance().ReapAnyOutstandingChildren();

    register_epoll_handler(signal_read_fd,handle_signal);        // 定義在system / core / init / Init.cpp 
}
複製代碼
  Linux 進程通過互相發送接收消息來實現進程間的通信,這些消息被稱為“ 信號” 每個進程在處理其它進程發送的信號時都要註冊處理者,處理者被稱為信號處理器。
  注意到 sigaction 結構體的sa_flags SA_NOCLDSTOP 由於系統默認在子進程暫停時也會發送信號SIGCHLD init 需要忽略子進程在暫停時發出的SIGCHLD 信號,因此將act.sa_flags 置為SA_NOCLDSTOP ,該標誌位表示僅當進程終止時才接受SIGCHLD 信號。
  觀察SIGCHLD_handler 具體工作:
複製代碼
static  void SIGCHLD_handler( int ) {
     /* init進程是所有進程的父進程,當其子進程終止產生SIGCHLD信號時,SIGCHLD_handler對signal_write_fd執行寫操作,由於socketpair的綁定關係,這將觸發信號對應的signal_read_fd收到數據。*/ 
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, " 1 " , 1 )) == - 1 ) {
        錯誤(write(signal_write_fd)失敗:%s \ n ,strerror(errno));
    }
}
複製代碼
  在裝在信號監聽器的最後,有如下函數:register_epoll_handler(signal_read_fd, handle_signal);
複製代碼
void register_epoll_handler( int fd, void (* fn)()) {         //回到init.cpp中
    epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = reinterpret_cast< void *> (fn);
     // epoll_fd增加一個監聽對象fd,fd上有數據到來時,調用fn處理
     // 當epoll句柄監聽到signal_read_fd中有數據可讀時,將調用handle_signal進行處理。
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == - 1 ) {
        錯誤(epoll_ctl失敗:%s \ n ,strerror(errno));
    }
}
複製代碼
【小結】
  當 init 進程調用signal_handler_init 後,一旦收到子進程終止帶來的SIGCHLD 消息後,將利用信號處理者SIGCHLD_handler signal_write_fd 寫入信息;epoll 句柄監聽到signal_read_fd 收消息後,將調用handle_signal 進行處理。
  
  查看handle_signal函數:
複製代碼
static  void handle_signal(){       // - >位於system / core / init / signal_handler.cpp中
     // 清除未完成的請求。
    char buf [ 32 ];
    讀取(signal_read_fd,buf,sizeof (buf));

    的ServiceManager ::的GetInstance()ReapAnyOutstandingChildren()。
}
複製代碼
  從代碼中可以看出, handle_signal只是清空signal_read_fd中的數據,然後調用ServiceManager::GetInstance().ReapAnyOutstandingChildren() 
  繼續分析:
複製代碼
// 定義於system/core/init/service.cpp中,是一個單例對象。
ServiceManager::ServiceManager() {      // 默認private屬性
}

ServiceManagerServiceManager :: GetInstance(){
     靜態ServiceManager實例;
    返回實例;
}
void ServiceManager::ReapAnyOutstandingChildren() {
     while (ReapOneProcess()) {     // 實際調用了ReapOneProcess函數
    }
}
複製代碼
  接下來看下ReapOneProcess這個函數:
複製代碼
bool ServiceManager::ReapOneProcess() {
     int status;
     // 用waitpid函數獲取狀態發生變化的子進程pid
     // waitpid的標記為WNOHANG,即非阻塞,返回為正值就說明有進程掛掉了 
    pid_t pid = TEMP_FAILURE_RETRY(waitpid(- 1 , & status, WNOHANG));
     if (pid == 0 ) {
         return  false ;
    } else  if(pid ==  - 1 ){
        錯誤(waitpid失敗:%s \ n ,strerror(errno));
        返回 false ;
    }
    // 利用FindServiceByPid函數,找到pid對應的服務。
    // FindServiceByPid主要通過輪詢解析init.rc生成的service_list,找到pid與參數一直的svc 
    Service* svc = FindServiceByPid(pid);
    
    std :: string name;
    if (svc){
        name = android :: base :: StringPrintf(Service'%s'(pid%d)
                                           svc - > name()。c_str(),pid);
    } else {
        name = android :: base :: StringPrintf(Untracked pid%d ,pid);
    }

    如果(WIFEXITED(狀態)){
        NOTICE(%s退出狀態%d \ n ,name.c_str(),WEXITSTATUS(status));
    } else  if (WIFSIGNALED(status)){
        NOTICE( " %s killed by signal %d\n " , name.c_str(), WTERMSIG(status));          // 輸出服務結束原因 
    } else  if (WIFSTOPPED(status)) {
        NOTICE(%s由信號%d \ n停止,name.c_str(),WSTOPSIG(status));
    } else {
        NOTICE(%s state changed ,name.c_str());
    }

    如果(!svc){
         return  true ;
    }

    if (svc->Reap()) {                  // 結束服務,相對於6.0作了進一步的封裝,重啟一些子進程,不做具體分析 
        waiting_for_exec = false ;
        RemoveService( *svc);            // 移除服務對應的信息
    }

    返回 true ;
}
複製代碼
  繼續分析main()函數:
複製代碼
int main( int argc, char ** argv) {
     /* 01.創建文件系統目錄並掛載相關的文件系統*/
    /* 02. 屏蔽標準的輸入輸出/初始化內核log系統*/
    /* 03. 初始化屬性域*/
    /* 04. 完成SELinux相關工作*/·
    /* 05. 重新設置屬性*/
    /* 06. 創建epoll句柄*/
    /* 07.裝載子進程信號處理器*/ 
    /* 08.啟動匹配屬性的服務端*/ 
    property_load_boot_defaults();       // 進程調用property_load_boot_defaults進行默認屬性配置相關的工作
    export_oem_lock_status();

    std:: string bootmode = property_get( " ro.bootmode " );       // 獲取啟動模式
    if (strncmp(bootmode.c_str(), " ffbm " , 4 ) == 0 ){
    property_set(ro.logdumpd 0 );
    } else {
    property_set(ro.logdumpd 1 );
    }
    start_property_service();      //啟動屬性服務
複製代碼
  看下property_load_boot_defaults()函數:位於system/core/init/Property_service.cpp中
// property_load_boot_defaults實際上就是調用load_properties_from_file解析配置文件        /* 09.設置默認系統屬性*/
 // 然後根據解析的結果,設置系統屬性
void property_load_boot_defaults() {
    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT,NULL);
}
  接著繼續分析main:
複製代碼
int main( int argc, char ** argv) {
     /* 01.創建文件系統目錄並掛載相關的文件系統*/
    /* 02. 屏蔽標準的輸入輸出/初始化內核log系統*/
    /* 03. 初始化屬性域*/
    /* 04. 完成SELinux相關工作*/·
    /* 05. 重新設置屬性*/
    /* 06. 創建epoll句柄*/
    /* 07. 裝載子進程信號處理器*/
    /* 08. 設置默認系統屬性*/
    /* 09.啟動配置屬性的服務端*/    /* 10.匹配命令和函數之間的對應關係  */
    const BuiltinFunctionMap function_map;           // system/core/init/builtins.cpp 
    Action::set_function_map(&function_map);         // 在Action中保存function_map對象,記錄了命令與函數之間的對應關係
複製代碼
【結尾】
  由於init涉及的知識點是相當多,代碼之間的邏輯也是極其複雜,我在看別人的博客過程中,最反感一篇博客要看很久,往往因為瑣事而放棄堅持(確切的說,隨手把網頁關掉了),所以我就分章節分析,盡量少源碼多講解。
  接下來,在Android啟動篇— init原理(二)中將詳細分析init.rc的解析過程