圖解 | 不得錯(cuò)過的Binder淺析(二)

本文主要分析ServiceManager系統(tǒng)服務(wù)管理進(jìn)程對binder的管理流程。

大綱:

  • 揭開Binder面紗
  • Binder的管理
    • 1 打開binder驅(qū)動
    • 2 成為系統(tǒng)唯一的上下文
    • 3 進(jìn)入binder循環(huán)
    • 4 系統(tǒng)服務(wù)的注冊和獲取
  • 總結(jié)
  • 參考資料

本文約3.7k字,閱讀大約15分鐘已添。

Android源碼基于8.0规婆。

揭開Binder面紗

Binder跟鍵盤纬朝、顯示器一樣屬于一種外設(shè)(沒有實(shí)體的外設(shè))苹熏。由于外設(shè)種類繁多挽霉,操作系統(tǒng)如Linux抽象出文件視圖來方便用戶使用外設(shè)翩蘸。即對用戶來說所意,通過讀寫外設(shè)文件,讓操作系統(tǒng)將指令發(fā)送給外設(shè)控制器,來實(shí)現(xiàn)對外設(shè)的操作扶踊。

image

在Linux中泄鹏,各種外設(shè)文件放在/dev目錄下:

image

不過這些文件并不是像Windows上的那些外設(shè)驅(qū)動程序,而是提供給用戶去訪問外設(shè)的一個(gè)端口(就跟文件訪問一樣)秧耗,如:

  • /dev/console:系統(tǒng)控制臺
  • /dev/mem:物理內(nèi)存的全鏡像命满。可以用來直接存取物理內(nèi)存绣版。
  • /dev/kmem:內(nèi)核看到的虛擬內(nèi)存的全鏡像胶台。可以用來訪問內(nèi)核中的內(nèi)容杂抽。
  • /dev/tty0:虛擬終端
  • ...

Linux抽象出文件視圖诈唬,為用戶提供統(tǒng)一接口,一段簡單的操作外設(shè)的程序如下:

//打開 /dev 下的外設(shè)文件
int fd = open(“/dev/xxx”);
for (int i = 0; i < 10; i++) {
    //進(jìn)行讀寫操作
    write(fd,i,sizeof(int));
}
//關(guān)閉文件
close(fd);

用戶讀寫外設(shè)文件缩麸,Linux會通過外設(shè)文件找到外設(shè)控制器的地址铸磅、內(nèi)容格式等信息,向他發(fā)送合適的指令來操作外設(shè)杭朱。

現(xiàn)在我們通過adb shell進(jìn)入Android設(shè)備阅仔,看下他的/dev目錄長啥樣:

image

可以看到有binder,標(biāo)黃部分的3個(gè)分別是binder弧械、hwbinder八酒、vndbinder,我們只關(guān)注binder就行了刃唐。

從「一圖摸清Android應(yīng)用進(jìn)程的啟動」一文可知羞迷,在應(yīng)用程序啟動binder線程池時(shí),ProcessState.cpp有這么一段代碼画饥,

//ProcessState.cpp

sp<ProcessState> ProcessState::self(){
    //傳入 binder 外設(shè)文件路徑
    gProcess = new ProcessState("/dev/binder");
    return gProcess;
}

//ProcessState構(gòu)造函數(shù)
ProcessState::ProcessState(const char *driver)
    //路徑賦給 mDriverName
    : mDriverName(String8(driver))
        //1. 打開 binder 驅(qū)動
        , mDriverFD(open_driver(driver))
        ,//...
{
    //2. 映射內(nèi)存
    mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
}

我們看下打開binder驅(qū)動的open_driver函數(shù)衔瓮,

//ProcessState.cpp

static int open_driver(const char *driver){
    //打開外設(shè)文件 /dev/binder
    int fd = open(driver, O_RDWR | O_CLOEXEC);
    int vers = 0;
    //獲取 binder 版本進(jìn)行檢查
    status_t result = ioctl(fd, BINDER_VERSION, &vers);
    size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
    //設(shè)置 binder 最大線程數(shù)為 15
    result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
    //返回 int 類型的 fd 給 mDriverFD
    return fd;
}

看起來是不是跟Linux操作外設(shè)的那段程序很像,只不過這里的讀寫操作由write換成了ioctl抖甘,

在計(jì)算機(jī)中热鞍,ioctl(input/output control)是一個(gè)專用于設(shè)備輸入輸出操作的系統(tǒng)調(diào)用,該調(diào)用傳入一個(gè)跟設(shè)備有關(guān)的請求碼,系統(tǒng)調(diào)用的功能完全取決于請求碼衔彻。舉個(gè)例子薇宠,CD-ROM驅(qū)動程序可以彈出光驅(qū),它就提供了一個(gè)對應(yīng)的Ioctl請求碼米奸。設(shè)備無關(guān)的請求碼則提供了內(nèi)核調(diào)用權(quán)限昼接。ioctl這名字第一次出現(xiàn)在Unix第七版中爽篷,他在很多類unix系統(tǒng)(比如Linux悴晰、Mac OSX等)都有提供,不過不同系統(tǒng)的請求碼對應(yīng)的設(shè)備有所不同。

-- 引用自百科 ioctl

可見ioctl是一個(gè)可以控制設(shè)備I/O通道的系統(tǒng)調(diào)用铡溪,通過它用戶空間可以跟設(shè)備驅(qū)動溝通。

至于為什么要有ioctl,主要是為非標(biāo)準(zhǔn)設(shè)備考慮的(如binder就是一種非標(biāo)準(zhǔn)外設(shè))伪很,詳見百科 ioctl 背景毛甲。

ioctl函數(shù)如下:

int ioctl(int fd, ind cmd, …);

第一個(gè)參數(shù)fd是文件描述符哈扮,如binder外設(shè)文件纬纪;

第二個(gè)參數(shù)cmd則是控制命令,如指令BINDER_SET_MAX_THREADS是“設(shè)置線程數(shù)”滑肉,最后的省略號則是各指令所需的參數(shù)包各,如maxThreads表示最大線程數(shù)為 15。

指令BINDER_SET_MAX_THREADS的定義如下:

#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32)

_IOW是一個(gè)宏靶庙,Linux內(nèi)核提供了一些宏來方便用戶定義指令(傳入各種參數(shù)進(jìn)行包裝):

// nr為序號问畅,datatype 為數(shù)據(jù)類型,如 int
_IO(type, nr ) //沒有參數(shù)的命令
_IOR(type, nr, datatype) //從驅(qū)動中讀數(shù)據(jù)
_IOW(type, nr, datatype) //寫數(shù)據(jù)到驅(qū)動
_IOWR(type,nr, datatype) //雙向傳送

名字很好理解,就是 io read write的縮寫六荒。

對binder的了解暫且到這护姆,只需知道他是一個(gè)外設(shè),以文件形式通過ioctl來操作就行了掏击。

Binder的管理

從「一圖摸清Android系統(tǒng)服務(wù)」一文可知卵皂,init進(jìn)程會啟動運(yùn)行在獨(dú)立進(jìn)程的ServiceManager服務(wù)來統(tǒng)一管理系統(tǒng)服務(wù)的注冊和獲取。

image

ServiceManager的入口函數(shù)即service_manager.c的main函數(shù)中砚亭,

//frameworks/native/cmds/servicemanager/service_manager.c

int main(int argc, char** argv){
    char *driver = "/dev/binder";
    //1. 打開 binder 驅(qū)動
    struct binder_state *bs = binder_open(driver, 128*1024);
    //2. 讓自己成為整個(gè)系統(tǒng)唯一的上下文管理器渐裂,
    //   這樣其他進(jìn)程就能找到 ServiceManager 來注冊服務(wù)了
    binder_become_context_manager(bs);
    //3. 進(jìn)入binder循環(huán),等待系統(tǒng)服務(wù)的注冊和查找請求
    binder_loop(bs, svcmgr_handler);
}

下面分析這3個(gè)步驟钠惩。

1 打開binder驅(qū)動

128 * 1024即128kb是mapsize柒凉,表示把binder驅(qū)動文件的128kb映射到內(nèi)存空間,而在「一圖摸清Android應(yīng)用進(jìn)程的啟動」一文可知應(yīng)用進(jìn)程使用的mapsize大小為BINDER_VM_SIZE1MB-8kb篓跛,可見兩者的大小是不同的膝捞,

//ProcessState.cpp
//一次Binder通信最大可以傳輸?shù)拇笮∈?1MB-4KB*2
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
//映射內(nèi)存
mmap(..., BINDER_VM_SIZE, ...);

回到ServiceManager,看binder_open()的內(nèi)部實(shí)現(xiàn)binder.c

//frameworks/native/cmds/servicemanager/binder.c

struct binder_state *binder_open(const char* driver, size_t mapsize){
    struct binder_state *bs;
    //分配空間
    bs = malloc(sizeof(*bs));
    //打開 binder 驅(qū)動愧沟,得到int類型的文件描述符 fd
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    //記錄傳入的 128kb
    bs->mapsize = mapsize;
    //映射內(nèi)存蔬咬,記錄內(nèi)存映射區(qū)的指針
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    return bs;
}

mmap可以將一個(gè)文件或者其它對象映射進(jìn)內(nèi)存,函數(shù)原型:

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

各參數(shù)如下:

  • start:映射區(qū)的開始地址沐寺,傳 NULL 表示由系統(tǒng)決定映射區(qū)的起始地址
  • length:映射區(qū)的長度林艘,傳 128kb
  • prot:期望的內(nèi)存保護(hù)標(biāo)志,傳 PROT_READ 只讀
  • flags:指定映射對象的類型混坞,映射選項(xiàng)和映射頁是否可以共享狐援。傳 MAP_PRIVATE 建立一個(gè)寫入時(shí)拷貝的私有映射钢坦,內(nèi)存區(qū)域的寫入不會影響到原文件
  • fd:有效的文件描述符,一般是由open()函數(shù)返回
  • offset:被映射對象內(nèi)容的起點(diǎn)啥酱,傳 0
  • return:成功執(zhí)行時(shí)爹凹,mmap()返回被映射區(qū)的指針

mmap會根據(jù)入?yún)inder驅(qū)動文件的一部分映射到內(nèi)存空間,然后返回該內(nèi)存空間的指針镶殷。

最后binder_open()返回的bs結(jié)構(gòu)體如下:

//frameworks/native/cmds/servicemanager/binder.c

struct binder_state{
    // binder 驅(qū)動文件描述符
    int fd;
    //由 mmap 得到的內(nèi)存映射區(qū)的指針
    void *mapped;
    // 128kb
    size_t mapsize;
};

2 成為系統(tǒng)唯一的上下文

ServiceManager讓自己成為整個(gè)系統(tǒng)唯一的上下文管理器禾酱,這樣其他進(jìn)程就能找到ServiceManager來注冊服務(wù)了,

//frameworks/native/cmds/servicemanager/binder.c

int binder_become_context_manager(struct binder_state *bs){
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

可見就是前邊提到的ioctl調(diào)用绘趋,向binder驅(qū)動發(fā)送一個(gè)指令“我ServiceManager已成為全局上下文管理器”颤陶。

binder驅(qū)動層代碼暫不跟進(jìn),我們只需知道:

一般情況下陷遮,應(yīng)用層的每個(gè)binder實(shí)體都會在binder驅(qū)動層對應(yīng)一個(gè)binder_node節(jié)點(diǎn)指郁,然而ServiceManager的binder_context_mgr_node比較特殊,它沒有對應(yīng)的應(yīng)用層binder實(shí)體拷呆。在整個(gè)系統(tǒng)里闲坎,它是如此特殊,以至于系統(tǒng)規(guī)定茬斧,任何應(yīng)用都必須使用句柄0來跨進(jìn)程地訪問它腰懂。

-- 引用自 博客 - 紅茶一杯話Binder

3 進(jìn)入binder循環(huán)

進(jìn)入binder循環(huán),等待系統(tǒng)服務(wù)的注冊和查找請求项秉,

//frameworks/native/cmds/servicemanager/binder.c

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    //readbuf 用于跟 binder 驅(qū)動互傳數(shù)據(jù)
    uint32_t readbuf[32];
    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    //指令:binder 開始循環(huán)
    readbuf[0] = BC_ENTER_LOOPER;
    //向 binder 發(fā)送該指令
    //內(nèi)部會執(zhí)行 ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) { //進(jìn)入循環(huán)
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        //向 binder 發(fā)送 讀寫指令
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        //解析從 binder 讀來的數(shù)據(jù)绣溜,交給傳入的函數(shù) svcmgr_handler 處理
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
    }
}

其中binder_parse解析邏輯如下:

//frameworks/native/cmds/servicemanager/binder.c

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func){
    int r = 1;
    //計(jì)算能讀完最后一條指令的偏移
    uintptr_t end = ptr + (uintptr_t) size;
    while (ptr < end) {
        //不斷從 readbuf 讀取 binder 回傳的指令
        uint32_t cmd = *(uint32_t *) ptr;
        //每讀完一條,進(jìn)行偏移
        ptr += sizeof(uint32_t);
        switch(cmd) { //處理各種指令
            case BR_TRANSACTION_COMPLETE:
                break;
            case BR_TRANSACTION: 
                //轉(zhuǎn)交給傳入的 svcmgr_handler 函數(shù)處理
                res = func(bs, txn, &msg, &reply);
                //...
                break;
            case BR_REPLY: 
                //...
                break;
                //...
        }
    }
    return r;
}

然后看到傳入的處理函數(shù)svcmgr_handler娄蔼,

//frameworks/native/cmds/servicemanager/service_manager.c

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply){
    //...省略數(shù)據(jù)包裝和解析的邏輯
    switch(txn->code) {
        case SVC_MGR_GET_SERVICE:
        case SVC_MGR_CHECK_SERVICE:
            //查找系統(tǒng)服務(wù)
            handle = do_find_service(...);
            bio_put_ref(reply, handle);
        case SVC_MGR_ADD_SERVICE:
            //添加系統(tǒng)服務(wù)
            do_add_service(...);
    }
}

svcmgr_handler函數(shù)會根據(jù)不同的語義碼code來執(zhí)行相應(yīng)邏輯怖喻,如查找系統(tǒng)服務(wù)的do_find_service、添加系統(tǒng)服務(wù)的do_add_service岁诉。

至此锚沸,可以看出ServiceManager的binder啟動流程:

image

4 系統(tǒng)服務(wù)的注冊和獲取

下面簡要分析一下系統(tǒng)服務(wù)的注冊和獲取,在「一圖摸清Android系統(tǒng)服務(wù)」一文已對上層邏輯進(jìn)行介紹涕癣,這里直接看do_add_servicedo_find_service兩個(gè)方法哗蜈。

1.添加系統(tǒng)服務(wù)do_add_service

struct svcinfo *svclist 以鏈表的形式記錄了所有的系統(tǒng)服務(wù)坠韩,其結(jié)構(gòu)體如下:

//frameworks/native/cmds/servicemanager/service_manager.c

struct svcinfo{
    //下一個(gè)服務(wù)
    struct svcinfo *next;
    //服務(wù)的 binder 句柄值
    uint32_t handle;
    struct binder_death death;
    int allow_isolated;
    size_t len;
    //服務(wù)注冊時(shí)取的名字
    uint16_t name[0];
};

然后看到do_add_service距潘,

//frameworks/native/cmds/servicemanager/service_manager.c

struct svcinfo *svclist; //鏈表

int do_add_service(struct binder_state *bs,
                   const uint16_t *s, size_t len,
                   uint32_t handle, uid_t uid, int allow_isolated,
                   pid_t spid){
    if (!svc_can_register(s, len, spid, uid)) {
        //判斷是否可以注冊系統(tǒng)服務(wù)
        //只有 root 進(jìn)程、SystemServer 進(jìn)程只搁、有在 allowed[] 數(shù)組中聲明的進(jìn)程可以
        return -1;
    }
    //分配空間給新的節(jié)點(diǎn)
    struct svcinfo *si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
    //記錄服務(wù)的 binder 句柄值
    si->handle = handle;
    si->len = len;
    //記錄服務(wù)注冊時(shí)取的名字
    memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
    //追加一個(gè)結(jié)束符
    si->name[len] = '\0';
    //...還有各種賦值

    //新節(jié)點(diǎn)的 next 指向鏈表
    si->next = svclist;
    //鏈表的頭插法
    svclist = si;
}

如下音比,

image

至于系統(tǒng)服務(wù)int類型的binder句柄值handle怎么來的,是由binder驅(qū)動層為我們分配氢惋,然后包裝成特定的數(shù)據(jù)結(jié)構(gòu)回傳給我們的洞翩。

可見ServiceManager鏈表svclist管理各系統(tǒng)服務(wù)的binder句柄稽犁,結(jié)構(gòu)體是svcinfo

而對應(yīng)到binder驅(qū)動層菱农,則是用鏈表binder_procs管理的,結(jié)構(gòu)體是binder_proc柿估,在/drivers/android/binder.c中循未,

//drivers/android/binder.c

//鏈表頭結(jié)點(diǎn)
static HLIST_HEAD(binder_procs);
//結(jié)構(gòu)體
struct binder_proc {
    //鏈表普通節(jié)點(diǎn),由他的 next 和 pprev 串起鏈表
    struct hlist_node proc_node;

    //4棵紅黑樹秫舌,rb = red black
    //記錄執(zhí)行傳輸動作的線程信息 binder_thread
    struct rb_root threads;
    //記錄 binder 實(shí)體 binder_node
    struct rb_root nodes;
    //記錄 binder 代理 binder_ref
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
};

這里用了HLIST_HEAD和hlist_node來串起鏈表的妖,binder驅(qū)動層的代碼暫不展開,感興趣可以閱讀「紅茶一杯話Binder傳輸機(jī)制篇」足陨。

2.查找系統(tǒng)服務(wù)do_find_service嫂粟。

//frameworks/native/cmds/servicemanager/service_manager.c

uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid){
    //遍歷鏈表找到節(jié)點(diǎn)
    struct svcinfo *si = find_svc(s, len);
    //返回 binder 句柄值
    return si->handle;
}

struct svcinfo *find_svc(const uint16_t *s16, size_t len){
    struct svcinfo *si;
    //遍歷鏈表找到節(jié)點(diǎn)
    for (si = svclist; si; si = si->next) {
        if ((len == si->len) &&
            !memcmp(s16, si->name, len * sizeof(uint16_t))) {
            return si;
        }
    }
    return NULL;
}

綜上,查找系統(tǒng)服務(wù)的do_find_service和添加系統(tǒng)服務(wù)的do_add_service如下圖:

image

總結(jié)

ServiceManager作為管理系統(tǒng)服務(wù)的進(jìn)程墨缘,經(jīng)過打開binder驅(qū)動星虹、注冊成為系統(tǒng)唯一的上下文、進(jìn)入binder循環(huán)3個(gè)核心步驟镊讼,便開始支持系統(tǒng)服務(wù)的注冊和獲取宽涌。系統(tǒng)服務(wù)的注冊和獲取過程基于binder機(jī)制實(shí)現(xiàn)IPC通信,binder的本質(zhì)就是一個(gè)外設(shè)蝶棋,以文件形式通過ioctl系統(tǒng)調(diào)用來操作卸亮。

留下2個(gè)疑問繼續(xù)探討:

  1. binder句柄的遠(yuǎn)程轉(zhuǎn)本地
  2. one way異步模式和他的串行調(diào)用(async_todo)、同步模式的并行調(diào)用

系列文章:

補(bǔ)充

  • 系統(tǒng)服務(wù)由ServiceManager進(jìn)程管理玩裙,但用戶自定義的Service組件兼贸,bindService時(shí)的onServiceConnected回調(diào)拿到的IBinder句柄,是由SystemServer進(jìn)程的AMS管理的吃溅,后面再開篇分析了溶诞。

    這兩個(gè)進(jìn)程容易搞混,再貼出來鞏固一下...

    image

參考資料


更多性感文章决侈,關(guān)注原創(chuàng)技術(shù)公眾號:哈利迪ei

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末很澄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子颜及,更是在濱河造成了極大的恐慌甩苛,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俏站,死亡現(xiàn)場離奇詭異讯蒲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)肄扎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門墨林,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赁酝,“玉大人,你說我怎么就攤上這事旭等∽么簦” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵搔耕,是天一觀的道長隙袁。 經(jīng)常有香客問我,道長弃榨,這世上最難降的妖魔是什么菩收? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鲸睛,結(jié)果婚禮上娜饵,老公的妹妹穿的比我還像新娘。我一直安慰自己官辈,他們只是感情好箱舞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拳亿,像睡著了一般褐缠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上风瘦,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天队魏,我揣著相機(jī)與錄音,去河邊找鬼万搔。 笑死胡桨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瞬雹。 我是一名探鬼主播昧谊,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酗捌!你這毒婦竟也來了呢诬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胖缤,失蹤者是張志新(化名)和其女友劉穎尚镰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哪廓,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狗唉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涡真。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片分俯。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肾筐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缸剪,到底是詐尸還是另有隱情吗铐,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布杏节,位于F島的核電站唬渗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拢锹。R本人自食惡果不足惜谣妻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一萄喳、第九天 我趴在偏房一處隱蔽的房頂上張望卒稳。 院中可真熱鬧,春花似錦他巨、人聲如沸充坑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捻爷。三九已至,卻和暖如春份企,著一層夾襖步出監(jiān)牢的瞬間也榄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工司志, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甜紫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓骂远,卻偏偏與公主長得像囚霸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子激才,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

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