Android系統(tǒng)啟動(dòng)流程(一)解析init進(jìn)程

相關(guān)文章
Android系統(tǒng)架構(gòu)與系統(tǒng)源碼目錄

前言

作為“Android框架層”這個(gè)大系列中的第一個(gè)系列,我們首先要了解的是Android系統(tǒng)啟動(dòng)流程,在這個(gè)流程中會(huì)涉及到很多重要的知識(shí)點(diǎn),這個(gè)系列我們就來(lái)一一講解它們,這一篇我們就來(lái)學(xué)習(xí)init進(jìn)程。

1.init簡(jiǎn)介

init進(jìn)程是Android系統(tǒng)中用戶空間的第一個(gè)進(jìn)程,作為第一個(gè)進(jìn)程,它被賦予了很多極其重要的工作職責(zé),比如創(chuàng)建zygote(孵化器)和屬性服務(wù)等。init進(jìn)程是由多個(gè)源文件共同組成的,這些文件位于源碼目錄system/core/init。本文將基于Android7.0源碼來(lái)分析Init進(jìn)程。

2.引入init進(jìn)程

說(shuō)到init進(jìn)程,首先要提到Android系統(tǒng)啟動(dòng)流程的前幾步:
1.啟動(dòng)電源以及系統(tǒng)啟動(dòng)
當(dāng)電源按下時(shí)引導(dǎo)芯片代碼開(kāi)始從預(yù)定義的地方(固化在ROM)開(kāi)始執(zhí)行。加載引導(dǎo)程序Bootloader到RAM,然后執(zhí)行。
2.引導(dǎo)程序Bootloader
引導(dǎo)程序是在Android操作系統(tǒng)開(kāi)始運(yùn)行前的一個(gè)小程序,它的主要作用是把系統(tǒng)OS拉起來(lái)并運(yùn)行。
3.linux內(nèi)核啟動(dòng)
內(nèi)核啟動(dòng)時(shí),設(shè)置緩存、被保護(hù)存儲(chǔ)器、計(jì)劃列表,加載驅(qū)動(dòng)。當(dāng)內(nèi)核完成系統(tǒng)設(shè)置,它首先在系統(tǒng)文件中尋找”init”文件,然后啟動(dòng)root進(jìn)程或者系統(tǒng)的第一個(gè)進(jìn)程。
4.init進(jìn)程啟動(dòng)

講到第四步就發(fā)現(xiàn)我們這一節(jié)要講的init進(jìn)程了。關(guān)于Android系統(tǒng)啟動(dòng)流程的所有步驟會(huì)在本系列的最后一篇做講解。

3.init入口函數(shù)

init的入口函數(shù)為main,代碼如下所示。
system/core/init/init.cpp

int main(int argc, char** argv) {
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }
    umask(0);
    add_environment("PATH", _PATH_DEFPATH);
    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
    //創(chuàng)建文件并掛載
    if (is_first_stage) {
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        mount("sysfs", "/sys", "sysfs", 0, NULL);
    }
    open_devnull_stdio();
    klog_init();
    klog_set_level(KLOG_NOTICE_LEVEL);
    NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");
    if (!is_first_stage) {
        // Indicate that booting is in progress to background fw loaders, etc.
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
        //初始化屬性相關(guān)資源
        property_init();//1
        process_kernel_dt();
        process_kernel_cmdline();
        export_kernel_boot_props();
    }
 ...
    //啟動(dòng)屬性服務(wù)
    start_property_service();//2
    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);
    Parser& parser = Parser::GetInstance();
    parser.AddSectionParser("service",std::make_unique<ServiceParser>());
    parser.AddSectionParser("on", std::make_unique<ActionParser>());
    parser.AddSectionParser("import", std::make_unique<ImportParser>());
    //解析init.rc配置文件
    parser.ParseConfig("/init.rc");//3
   ...   
       while (true) {
        if (!waiting_for_exec) {
            am.ExecuteOneCommand();
            restart_processes();
        }
        int timeout = -1;
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }
        if (am.HasMoreCommands()) {
            timeout = 0;
        }
        bootchart_sample(&timeout);
        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }
    return 0;
}

init的main方法做了很多事情,我們只需要關(guān)注主要的幾點(diǎn),在注釋1處調(diào)用 property_init來(lái)對(duì)屬性進(jìn)行初始化并在注釋2處的 調(diào)用start_property_service啟動(dòng)屬性服務(wù),關(guān)于屬性服務(wù),后面會(huì)講到。注釋3處 parser.ParseConfig("/init.rc")用來(lái)解析init.rc。解析init.rc的文件為system/core/init/init_parse.cpp文件,接下來(lái)我們查看init.rc里做了什么。

4.init.rc

init.rc是一個(gè)配置文件,內(nèi)部由Android初始化語(yǔ)言編寫(xiě)(Android Init Language)編寫(xiě)的腳本,它主要包含五種類型語(yǔ)句:
Action、Commands、Services、Options和Import。init.rc的配置代碼如下所示。
system/core/rootdir/init.rc

on init
    sysclktz 0
    # Mix device-specific information into the entropy pool
    copy /proc/cmdline /dev/urandom
    copy /default.prop /dev/urandom
...

on boot
    # basic network init
    ifup lo
    hostname localhost
    domainname localdomain
    # set RLIMIT_NICE to allow priorities from 19 to -20
    setrlimit 13 40 40
...    

這里只截取了一部分代碼,其中#是注釋符號(hào)。on init和on boot是Action類型語(yǔ)句,它的格式為:

on <trigger> [&& <trigger>]*     //設(shè)置觸發(fā)器  
   <command>  
   <command>      //動(dòng)作觸發(fā)之后要執(zhí)行的命令  

為了分析如何創(chuàng)建zygote,我們主要查看Services類型語(yǔ)句,它的格式如下所示:

service <name> <pathname> [ <argument> ]*   //<service的名字><執(zhí)行程序路徑><傳遞參數(shù)>  
   <option>       //option是service的修飾詞,影響什么時(shí)候、如何啟動(dòng)services  
   <option>  
   ...  

需要注意的是在Android 7.0中對(duì)init.rc文件進(jìn)行了拆分,每個(gè)服務(wù)一個(gè)rc文件。我們要分析的zygote服務(wù)的啟動(dòng)腳本則在init.zygoteXX.rc中定義,這里拿64位處理器為例,init.zygote64.rc的代碼如下所示。
system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -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

其中service用于通知init進(jìn)程創(chuàng)建名zygote的進(jìn)程,這個(gè)zygote進(jìn)程執(zhí)行程序的路徑為/system/bin/app_process64,后面的則是要傳給app_process64的參數(shù)。class main指的是zygote的class name為main,后文會(huì)用到它。

5.解析service

接下來(lái)我們來(lái)解析service,會(huì)用到兩個(gè)函數(shù),一個(gè)是ParseSection,它會(huì)解析service的rc文件,比如上文講到的init.zygote64.rc,ParseSection函數(shù)主要用來(lái)搭建service的架子。另一個(gè)是ParseLineSection,用于解析子項(xiàng)。代碼如下所示。
system/core/init/service.cpp

bool ServiceParser::ParseSection(const std::vector<std::string>& args,
                                 std::string* err) {
    if (args.size() < 3) {
        *err = "services must have a name and a program";
        return false;
    }
    const std::string& name = args[1];
    if (!IsValidName(name)) {
        *err = StringPrintf("invalid service name '%s'", name.c_str());
        return false;
    }
    std::vector<std::string> str_args(args.begin() + 2, args.end());
    service_ = std::make_unique<Service>(name, "default", str_args);//1
    return true;
}

bool ServiceParser::ParseLineSection(const std::vector<std::string>& args,
                                     const std::string& filename, int line,
                                     std::string* err) const {
    return service_ ? service_->HandleLine(args, err) : false;
}

注釋1處,根據(jù)參數(shù),構(gòu)造出一個(gè)service對(duì)象,它的classname為"default"。當(dāng)解析完畢時(shí)會(huì)調(diào)用EndSection:

void ServiceParser::EndSection() {
    if (service_) {
        ServiceManager::GetInstance().AddService(std::move(service_));
    }
}

接著查看AddService做了什么:

void ServiceManager::AddService(std::unique_ptr<Service> service) {
    Service* old_service = FindServiceByName(service->name());
    if (old_service) {
        ERROR("ignored duplicate definition of service '%s'",
              service->name().c_str());
        return;
    }
    services_.emplace_back(std::move(service));//1
}

注釋1處的代碼將service對(duì)象加入到services鏈表中。上面的解析過(guò)程總體來(lái)講就是根據(jù)參數(shù)創(chuàng)建出service對(duì)象,然后根據(jù)選項(xiàng)域的內(nèi)容填充service對(duì)象,最后將service對(duì)象加入到vector類型的services鏈表中。,

6.init啟動(dòng)zygote

講完了解析service,接下來(lái)該講init是如何啟動(dòng)service,在這里我們主要講解啟動(dòng)zygote這個(gè)service。在zygote的啟動(dòng)腳本中我們得知zygote的class name為main。在init.rc有如下配置代碼:
system/core/rootdir/init.rc

...
on nonencrypted    
    # A/B update verifier that marks a successful boot.  
    exec - root -- /system/bin/update_verifier nonencrypted  
    class_start main         
    class_start late_start 
...    

其中class_start是一個(gè)COMMAND,對(duì)應(yīng)的函數(shù)為do_class_start。我們知道m(xù)ain指的就是zygote,因此class_start main用來(lái)啟動(dòng)zygote。do_class_start函數(shù)在builtins.cpp中定義,如下所示。

system/core/init/builtins.cpp

static int do_class_start(const std::vector<std::string>& args) {
        /* Starting a class does not start services
         * which are explicitly disabled.  They must
         * be started individually.
         */
    ServiceManager::GetInstance().
        ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
    return 0;
}

來(lái)查看StartIfNotDisabled做了什么:
system/core/init/service.cpp

bool Service::StartIfNotDisabled() {
    if (!(flags_ & SVC_DISABLED)) {
        return Start();
    } else {
        flags_ |= SVC_DISABLED_START;
    }
    return true;
}

接著查看Start方法,如下所示。

bool Service::Start() {
    flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
    time_started_ = 0;
    if (flags_ & SVC_RUNNING) {//如果Service已經(jīng)運(yùn)行,則不啟動(dòng)
        return false;
    }
    bool needs_console = (flags_ & SVC_CONSOLE);
    if (needs_console && !have_console) {
        ERROR("service '%s' requires console\n", name_.c_str());
        flags_ |= SVC_DISABLED;
        return false;
    }
  //判斷需要啟動(dòng)的Service的對(duì)應(yīng)的執(zhí)行文件是否存在,不存在則不啟動(dòng)該Service
    struct stat sb;
    if (stat(args_[0].c_str(), &sb) == -1) {
        ERROR("cannot find '%s' (%s), disabling '%s'\n",
              args_[0].c_str(), strerror(errno), name_.c_str());
        flags_ |= SVC_DISABLED;
        return false;
    }

...
    pid_t pid = fork();//1.fork函數(shù)創(chuàng)建子進(jìn)程
    if (pid == 0) {//運(yùn)行在子進(jìn)程中
        umask(077);
        for (const auto& ei : envvars_) {
            add_environment(ei.name.c_str(), ei.value.c_str());
        }
        for (const auto& si : sockets_) {
            int socket_type = ((si.type == "stream" ? SOCK_STREAM :
                                (si.type == "dgram" ? SOCK_DGRAM :
                                 SOCK_SEQPACKET)));
            const char* socketcon =
                !si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();

            int s = create_socket(si.name.c_str(), socket_type, si.perm,
                                  si.uid, si.gid, socketcon);
            if (s >= 0) {
                PublishSocket(si.name, s);
            }
        }
...
        //2.通過(guò)execve執(zhí)行程序
        if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
            ERROR("cannot execve('%s'): %s\n", args_[0].c_str(), strerror(errno));
        }

        _exit(127);
    }
...
    return true;
}

通過(guò)注釋1和2的代碼,我們得知在Start方法中調(diào)用fork函數(shù)來(lái)創(chuàng)建子進(jìn)程,并在子進(jìn)程中調(diào)用execve執(zhí)行system/bin/app_process,這樣就會(huì)進(jìn)入framework/cmds/app_process/app_main.cpp的main函數(shù),如下所示。
frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{
    ...
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);//1
    } 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;
    }
}

從注釋1處的代碼可以得知調(diào)用runtime(AppRuntime)的start來(lái)啟動(dòng)zygote。

7.屬性服務(wù)

Windows平臺(tái)上有一個(gè)注冊(cè)表管理器,注冊(cè)表的內(nèi)容采用鍵值對(duì)的形式來(lái)記錄用戶、軟件的一些使用信息。即使系統(tǒng)或者軟件重啟,它還是能夠根據(jù)之前在注冊(cè)表中的記錄,進(jìn)行相應(yīng)的初始化工作。Android也提供了一個(gè)類似的機(jī)制,叫做屬性服務(wù)。
在本文的開(kāi)始,我們提到在init.cpp代碼中和屬性服務(wù)相關(guān)的代碼有:
system/core/init/init.cpp

  property_init();
  start_property_service();

這兩句代碼用來(lái)初始化屬性服務(wù)配置并啟動(dòng)屬性服務(wù)。首先我們來(lái)學(xué)習(xí)服務(wù)配置的初始化和啟動(dòng)。

屬性服務(wù)初始化與啟動(dòng)

property_init函數(shù)具體實(shí)現(xiàn)的代碼如下所示。
system/core/init/property_service.cpp

void property_init() {
    if (__system_property_area_init()) {
        ERROR("Failed to initialize property area\n");
        exit(1);
    }
}

__system_property_area_init函數(shù)用來(lái)初始化屬性內(nèi)存區(qū)域。接下來(lái)查看start_property_service函數(shù)的具體代碼:

void start_property_service() {
    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                    0666, 0, 0, NULL);//1
    if (property_set_fd == -1) {
        ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
        exit(1);
    }
    listen(property_set_fd, 8);//2
    register_epoll_handler(property_set_fd, handle_property_set_fd);//3
}

注釋1處用來(lái)創(chuàng)建非阻塞的socket。注釋2處調(diào)用listen函數(shù)對(duì)property_set_fd進(jìn)行監(jiān)聽(tīng),這樣創(chuàng)建的socket就成為了server,也就是屬性服務(wù);listen函數(shù)的第二個(gè)參數(shù)設(shè)置8意味著屬性服務(wù)最多可以同時(shí)為8個(gè)試圖設(shè)置屬性的用戶提供服務(wù)。注釋3處的代碼將property_set_fd放入了epoll句柄中,用epoll來(lái)監(jiān)聽(tīng)property_set_fd:當(dāng)property_set_fd中有數(shù)據(jù)到來(lái)時(shí),init進(jìn)程將用handle_property_set_fd函數(shù)進(jìn)行處理。
在linux新的內(nèi)核中,epoll用來(lái)替換select,epoll最大的好處在于它不會(huì)隨著監(jiān)聽(tīng)fd數(shù)目的增長(zhǎng)而降低效率。因?yàn)閮?nèi)核中的select實(shí)現(xiàn)是采用輪詢來(lái)處理的,輪詢的fd數(shù)目越多,自然耗時(shí)越多。

屬性服務(wù)處理請(qǐng)求
從上文我們得知,屬性服務(wù)接收到客戶端的請(qǐng)求時(shí),會(huì)調(diào)用handle_property_set_fd函數(shù)進(jìn)行處理:
system/core/init/property_service.cpp

static void handle_property_set_fd()
{  
...

        if(memcmp(msg.name,"ctl.",4) == 0) {
            close(s);
            if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
                handle_control_message((char*) msg.name + 4, (char*) msg.value);
            } else {
                ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
                        msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
            }
        } else {
            //檢查客戶端進(jìn)程權(quán)限
            if (check_mac_perms(msg.name, source_ctx, &cr)) {//1
                property_set((char*) msg.name, (char*) msg.value);//2
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }
            close(s);
        }
        freecon(source_ctx);
        break;
    default:
        close(s);
        break;
    }
}

注釋1處的代碼用來(lái)檢查客戶端進(jìn)程權(quán)限,在注釋2處則調(diào)用property_set函數(shù)對(duì)屬性進(jìn)行修改,代碼如下所示。

int property_set(const char* name, const char* value) {
    int rc = property_set_impl(name, value);
    if (rc == -1) {
        ERROR("property_set(\"%s\", \"%s\") failed\n", name, value);
    }
    return rc;
}

property_set函數(shù)主要調(diào)用了property_set_impl函數(shù):

static int property_set_impl(const char* name, const char* value) {
    size_t namelen = strlen(name);
    size_t valuelen = strlen(value);
    if (!is_legal_property_name(name, namelen)) return -1;
    if (valuelen >= PROP_VALUE_MAX) return -1;
    if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) {
        if (selinux_reload_policy() != 0) {
            ERROR("Failed to reload policy\n");
        }
    } else if (strcmp("selinux.restorecon_recursive", name) == 0 && valuelen > 0) {
        if (restorecon_recursive(value) != 0) {
            ERROR("Failed to restorecon_recursive %s\n", value);
        }
    }
    //從屬性存儲(chǔ)空間查找該屬性
    prop_info* pi = (prop_info*) __system_property_find(name);
    //如果屬性存在
    if(pi != 0) {
       //如果屬性以"ro."開(kāi)頭,則表示是只讀,不能修改,直接返回
        if(!strncmp(name, "ro.", 3)) return -1;
       //更新屬性值
        __system_property_update(pi, value, valuelen);
    } else {
       //如果屬性不存在則添加該屬性
        int rc = __system_property_add(name, namelen, value, valuelen);
        if (rc < 0) {
            return rc;
        }
    }
    /* If name starts with "net." treat as a DNS property. */
    if (strncmp("net.", name, strlen("net.")) == 0)  {
        if (strcmp("net.change", name) == 0) {
            return 0;
        }
      //以net.開(kāi)頭的屬性名稱更新后,需要將屬性名稱寫(xiě)入net.change中  
        property_set("net.change", name);
    } else if (persistent_properties_loaded &&
            strncmp("persist.", name, strlen("persist.")) == 0) {
        /*
         * Don't write properties to disk until after we have read all default properties
         * to prevent them from being overwritten by default values.
         */
        write_persistent_property(name, value);
    }
    property_changed(name, value);
    return 0;
}

property_set_impl函數(shù)主要用來(lái)對(duì)屬性進(jìn)行修改,并對(duì)以ro、net和persist開(kāi)頭的屬性進(jìn)行相應(yīng)的處理。到這里,屬性服務(wù)處理請(qǐng)求的源碼就講到這。

8.init進(jìn)程總結(jié)

講到這,總結(jié)起來(lái)init進(jìn)程主要做了三件事:
1.創(chuàng)建文件并掛載
2.初始化和啟動(dòng)屬性服務(wù)
3.解析init.rc配置文件并啟動(dòng)zygote進(jìn)程

參考資料:
《深入理解Android系統(tǒng)》
《深入理解Android卷I》
Android的init過(guò)程詳解(一)
Android啟動(dòng)過(guò)程深入解析
Android7.0解析Init.rc文件
Android 7.0 init.rc的一點(diǎn)改變
Android7.0 init進(jìn)程源碼分析
Android情景分析之屬性服務(wù)


歡迎關(guān)注我的微信公眾號(hào),第一時(shí)間獲得博客更新提醒,以及更多成體系的Android相關(guān)技術(shù)干貨。
掃一掃下方二維碼即可關(guān)注:

enter image description here

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評(píng)論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,740評(píng)論 3 420
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,856評(píng)論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,175評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,931評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,321評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,533評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,082評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,891評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,067評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,319評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,732評(píng)論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,987評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,794評(píng)論 3 394
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,076評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容