Mit6.828 lab4 Part C:Preeptive Multitasking and Inter-Process communication

環(huán)境

deepin 20
實驗地址:mit6.828 2018 lab4 partC
很重要:不知道是不是我的代碼實現(xiàn)有些問題补箍,請把lib/syscall.c中的sys_ipc_recv()中的 return syscall()的第二個參數(shù)由1改為0。具體看文末

正文

在lab4的最后一部分衬横,你將會修改內(nèi)核的代碼來從uncooperative的進(jìn)程中搶回cpu终蒂,同時我們的內(nèi)核還將會支持讓進(jìn)程之間互相通信拇泣。

Clock Interrupts and Preemption

運(yùn)行user/spin程序。這個程序會創(chuàng)建一個子線程睁蕾,這個子線程在得到CPU后會運(yùn)行一個死循環(huán)惫霸。無論是父進(jìn)程還是子進(jìn)程都不能再次得到CPU葱弟。從保護(hù)系統(tǒng)遠(yuǎn)離bug和用戶程序中的惡意代碼來說芝加,這并不是一個好的情況射窒,因為任一一個用戶程序能夠獨占CPU脉顿。為了使得內(nèi)核能夠搶回CPU的執(zhí)行艾疟,強(qiáng)制終端當(dāng)前程序的執(zhí)行,所以我們必須讓JOS能夠支持外部中斷弟疆。

Interrupt discipline

外部中斷(也就是設(shè)備中斷盗冷,比如說鍵盤的中斷仪糖,時鐘的中斷等等) ,稱為IRQs. 一般來說呢有16個外部中斷(比如說級聯(lián)的8259A芯片)蟆湖,從0到15讼育。將IRQ映射到IDT中并不是固定的奶段。在picirq.c中的pic_init()函數(shù)我們將IRQs的0-15映射到了IRQ_OFFSET到IRQ_OFFSET+15.

在inc/trap.h中,IRQ_OFFSET就是十進(jìn)制的32呢铆。因此IDT中的32-47對應(yīng)的就是IRQ的0-15棺克。比如說线定,時鐘中斷就是IRQ 0斤讥,于是呢 IDT[IFQ_OFFSET+0]就是時鐘中斷的中斷處理程序地址。IRQ_OFFSET之所以這樣選擇是因為這樣一來和處理的異常(exceptions,比如說除0異常)之間不會存在overlap(這也就是說派草,比如說將IRQ_OFFSET設(shè)置為0近迁,這樣就會造成沖突0)鉴竭。原文說到早期的MS-DOS系統(tǒng)就是這樣做的勿侯,不知道到底咋實現(xiàn)的助琐。

在JOS中,和xv6比起來做了一點簡化蛆橡。外部中斷在內(nèi)核當(dāng)中的時候總是關(guān)閉了(也就是說在內(nèi)核中不響應(yīng)外部中斷)泰演,與xv6相似的是睦焕,在用戶程序中是開啟外部中斷的。外部中斷通過eflags中的FL_IF bit來控制猾普。當(dāng)這個bit被設(shè)置的時候初家,外部中斷開啟乌助。雖然設(shè)置這個bit有多種方法他托,簡單起見赏参,我們就通過保存以及恢復(fù)eflags的方法來設(shè)置。(eflags += FL_IF,通過或運(yùn)算可以設(shè)置某bit)
你需要確保在用戶程序當(dāng)中FL_IF的設(shè)置,這樣一來當(dāng)用戶程序運(yùn)行的時候可以通過外部中斷將CPU交給內(nèi)核纸俭。如果不這樣做的話揍很,那么中斷就被屏蔽了万伤。我們在bootloader當(dāng)中使用cli指令屏蔽了外部中斷敌买,到目前為止都沒有開啟外部中斷。

Exercise 13:

修改kern/trapentry.S和kern/trap.c的代碼來初始化IDT中的成員膘融。修改kern/env.c中的env_alloc()函數(shù)祭玉,確保用戶程序執(zhí)行的時候可以響應(yīng)外部中斷脱货。
shced_halt()中的sti語句取消注釋振峻,這樣一來其他idle的CPU也可以響應(yīng)中斷。
在完成了這些練習(xí)后多矮,如果你運(yùn)行一些測試程序(比如說spin)塔逃,你應(yīng)該可以看到內(nèi)核輸出了trap frames for hardware interrupts. 雖然現(xiàn)在中斷已經(jīng)被interrupted了湾盗,但是JOS還沒有處理他們立轧,所以會出錯氛改。

代碼實現(xiàn):
這個不難胜卤。經(jīng)過我們前面的lab,修改IDT很容易實現(xiàn)澈段。代碼如下败富,我將幾個涉及到的地方放到一起兽叮,具體看注釋的內(nèi)容。

  //trap.c中的trap_init()
    SETGATE(idt[IRQ_OFFSET + IRQ_TIMER],    0, GD_KT, timer_handler, 0);
    SETGATE(idt[IRQ_OFFSET + IRQ_KBD],      0, GD_KT, kbd_handler,     0);
    SETGATE(idt[IRQ_OFFSET + IRQ_SERIAL],   0, GD_KT, serial_handler,  0);
    SETGATE(idt[IRQ_OFFSET + IRQ_SPURIOUS], 0, GD_KT, spurious_handler, 0);
    SETGATE(idt[IRQ_OFFSET + IRQ_IDE],      0, GD_KT, ide_handler,     0);
    SETGATE(idt[IRQ_OFFSET + IRQ_ERROR],    0, GD_KT, error_handler,   0);


/*
external interrupt,trapentry.S
*/
TRAPHANDLER_NOEC(timer_handler, IRQ_OFFSET + IRQ_TIMER);
TRAPHANDLER_NOEC(kbd_handler, IRQ_OFFSET + IRQ_KBD);
TRAPHANDLER_NOEC(serial_handler, IRQ_OFFSET + IRQ_SERIAL);
TRAPHANDLER_NOEC(spurious_handler, IRQ_OFFSET + IRQ_SPURIOUS);
TRAPHANDLER_NOEC(ide_handler, IRQ_OFFSET + IRQ_IDE);
TRAPHANDLER_NOEC(error_handler, IRQ_OFFSET + IRQ_ERROR);

    // LAB 4: Your code here.在env.c中的env_alloc()
    e->env_tf.tf_eflags |= FL_IF;
        

sched.c,將sti注釋取消了,這樣就開啟了中斷

    asm volatile (
        "movl $0, %%ebp\n"
        "movl %0, %%esp\n"
        "pushl $0\n"
        "pushl $0\n"
        // Uncomment the following line after completing exercise 13
        "sti\n"
        "1:\n"
        "hlt\n"
        "jmp 1b\n"
    : : "a" (thiscpu->cpu_ts.ts_esp0));

Handling Clock Interrupts

在user/spin程序當(dāng)中,當(dāng)子程序輪到執(zhí)行的時候宰僧,陷入到了死循環(huán)當(dāng)中琴儿,于是內(nèi)核永遠(yuǎn)也得不到CPU嘁捷。我們需要program硬件讓它周期性的產(chǎn)生時鐘中斷雄嚣,使得將CPU控制權(quán)還給內(nèi)核缓升,經(jīng)調(diào)度程序之后可以在多個用戶程序之間交替港谊。

Exercise 14:

修改內(nèi)核的trap_dispatch()的代碼歧寺,當(dāng)時鐘中斷發(fā)生的時候可以讓內(nèi)核調(diào)用sched_yield()來尋找然后運(yùn)行不同的用戶程序。
現(xiàn)在你應(yīng)該可以讓user/spin正常運(yùn)行了龙致。

PS: 我們在每一次響應(yīng)時鐘中斷的時候净当,應(yīng)該主動告訴外設(shè),表示中斷結(jié)束了(End of Interrupt, e.g. EOI)潭苞。關(guān)于外設(shè)的一些IO port的意思我沒有詳細(xì)的去了解真朗,希望以后有時間補(bǔ)上這個坑吧。
關(guān)于EOI的一點資料:8259-PIC-EOI

思路:
響應(yīng)時鐘中斷十分簡單湖笨,只需要新加入一個case就可以慈省。然后在代碼實現(xiàn)中眠菇,調(diào)用sys_yield()捎废,別忘了發(fā)送EOI

        case (IRQ_OFFSET + IRQ_TIMER):
            // cprintf("hello world!\n");

            lapic_eoi();
            sched_yield();

            // break;

Inter-Process communication(IPC)

(從技術(shù)來說登疗,在JOS中叫IEC更合適辐益。因為Inter-Environemnt commuications,感覺像冷笑話)荷腊。
我們過去一直專注于操作系統(tǒng)的隔離(isolation,各個進(jìn)程的執(zhí)行不會影響對方)女仰,這使得每一個程序都感覺他在獨享整個電腦疾忍。另外一個重要的東西操作系統(tǒng)需要提供的服務(wù)就是讓每一個進(jìn)程之間相互通信。Unix pipe就是一個經(jīng)典的例子杨幼。
現(xiàn)有非常多的模型用于進(jìn)程通信差购。甚至在今天還有爭論說到底哪個一個才是最好的欲逃。我們不討論這個稳析,我們只是實現(xiàn)一個最簡單的IPC。

IPC on JOS

你將會實現(xiàn)幾個JOS的系統(tǒng)調(diào)用诚纸,這幾個系統(tǒng)調(diào)用共同來實現(xiàn)進(jìn)程之間通訊(inter-process communiation).你將會實現(xiàn)兩個系統(tǒng)調(diào)用,sys_ipc_recv()sys_ipc_try_send().然后實現(xiàn)兩個wrapper,ipc_recv()ipc_send()奴潘。

Sending and Receiving Messages

為了接受一個消息画髓,一個進(jìn)程調(diào)用sys_ipc_recv()奈虾。這個系統(tǒng)調(diào)用de-schedules 當(dāng)前正在運(yùn)行的進(jìn)程并且不在運(yùn)行它直到數(shù)據(jù)已經(jīng)接收到了肉微。當(dāng)一個進(jìn)程等待接受數(shù)據(jù)的時候碉纳,任何其他的都可以向他發(fā)送數(shù)據(jù),并不是說只有某個進(jìn)程專屬的奴愉,也不需要發(fā)送者和接收者之間有某種聯(lián)系锭硼,比如說它倆是父子進(jìn)程檀头。換句話說岖沛,在part A中實現(xiàn)的權(quán)限校驗并不適用于IPC暑始,因為IPC system call 被精心設(shè)計過婴削,從而把是相當(dāng)安全的蒋荚,一個進(jìn)程不能通過發(fā)送數(shù)據(jù)使得另外一個進(jìn)程出問題。

為了發(fā)送一個數(shù)據(jù)馆蠕,一個進(jìn)程調(diào)用sys_ipc_try_send()期升,參數(shù)是接收者的ID以及所有發(fā)送的內(nèi)容。如果指定的接收者正在準(zhǔn)備接收消息(也就是說調(diào)用了sys_ipc_recv()且并未返回)互躬,那么發(fā)送數(shù)據(jù)給它并且返回0.否則的話播赁,返回-E_IPC_NOT_RECV來表明接收者現(xiàn)在并不想接收消息。這里可能比較難懂吼渡,反正我一開始的時候沒有理解其中的意思,待會到代碼實現(xiàn)的時候在解釋下寺酪。

Transferring Pages

當(dāng)一個進(jìn)程調(diào)用sys_ipc_recv()并且?guī)в袇?shù)dstva的時候坎背,這就表明這個用戶進(jìn)程可以接受一個page mapping。如果發(fā)送者發(fā)送了一個pgae,那么這個page should be mapped at dstva in receiveris address space. 如果接收者已經(jīng)在dstva已經(jīng)有一個頁映射了寄雀,那么之前的就需要unmaped.

當(dāng)一個進(jìn)程調(diào)用sys_ipc_try_send()并且?guī)в袇?shù)srcva,這就意味著發(fā)送者希望將當(dāng)前地址空間srcva映射的page發(fā)送到接收者得滤,權(quán)限為perm。在IPC成功以后盒犹,發(fā)送者繼續(xù)保持srcva在自己的地址空間內(nèi)的映射關(guān)系懂更,但是接收者也獲得了這個同樣的映射關(guān)系。(這也就是說在兩個不同的進(jìn)程中srcva映射到了相同的物理頁)急膀。這樣一來結(jié)果是sender和receiver共享了一個物理頁沮协。

Implementing IPC

Exercise 15:

實現(xiàn)sys_ipc_recv 和 sys_ipc_try_send在 kern/syscall.c中。在實現(xiàn)他們之前閱讀一下注釋卓嫂。當(dāng)你調(diào)用envid2env的時候checkperm設(shè)置為0.另外實現(xiàn)ipc_recv 和 ipc_sned在lib/ipc.c中慷暂。
運(yùn)行user/pingpong和user/primes程序來測試你的IPC實現(xiàn)。

這幾個函數(shù)的邏輯是:系統(tǒng)調(diào)用sys_ipc_recv和sys_ipc_try_send分別是接收數(shù)據(jù)和發(fā)送數(shù)據(jù)晨雳。sys_ipc_send的幾個參數(shù)分別是目標(biāo)進(jìn)程ID(envid)行瑞,要發(fā)送的值(value), 要發(fā)送的頁如果需要的話(srcva),頁的權(quán)限(perm)。sys_ipc_recv的參數(shù)dstva表示當(dāng)前進(jìn)程想要接收到的數(shù)據(jù)放到dstva處悍募。

在實現(xiàn)這些代碼之前蘑辑。先來看一下Env這個結(jié)構(gòu)當(dāng)中新加入的一些結(jié)構(gòu):

  • env_ipc_recving
    當(dāng)前進(jìn)程的狀態(tài),表明當(dāng)前進(jìn)程是否處于接受狀態(tài)坠宴。
  • env_ipc_dstva
    當(dāng)前進(jìn)程要把接收到的數(shù)據(jù)放到哪兒(如果需要用頁來傳遞數(shù)據(jù))洋魂。
  • env_ipc_value
    當(dāng)前進(jìn)程接收到的數(shù)據(jù)(如果用頁來傳遞數(shù)據(jù))。
  • env_ipc_from
    當(dāng)前進(jìn)程接收到的數(shù)據(jù)來自誰喜鼓。
  • env_ipc_perm
    當(dāng)前進(jìn)程接收到的數(shù)據(jù)的頁的權(quán)限副砍。

sys_ipc_recv():
某個進(jìn)程調(diào)用這個系統(tǒng)調(diào)用可以來接收數(shù)據(jù)。所以我們在這個函數(shù)內(nèi)部必然要做的是切換當(dāng)前的接受狀態(tài)庄岖,放棄CPU(如果不放棄CPU豁翎,那么發(fā)送者永遠(yuǎn)得不到CPU了)。在結(jié)合注釋里面的信息隅忿,我們還需要校驗一下地址是否是頁對齊的(page-aligned)下面是具體的代碼實現(xiàn):

static int
sys_ipc_recv(void *dstva)
{
    // LAB 4: Your code here.
    // panic("sys_ipc_recv not implemented");
        uint32_t dst_addr = (uint32_t)dstva;
    if(dst_addr < UTOP && PGOFF(dst_addr) > 0) {
        // not page-aligned, return -E_INVAL;
        return -E_INVAL;
    }
    curenv->env_ipc_recving = 1; // 表示當(dāng)前進(jìn)程正在接受信息
    curenv->env_ipc_dstva = dstva; //表明想接收數(shù)據(jù)到dstva這個虛擬地址
    curenv->env_status = ENV_NOT_RUNNABLE; // block until a message has been received
    sched_yield();
    return 0;
}

sys_ipc_try_send():
這個系統(tǒng)調(diào)用的實現(xiàn)心剥,需要進(jìn)行一大堆的條件判斷來剔除異常情況邦尊。除此之外,值得我們關(guān)注的就是注釋里面提到的
這里的意思是:如果目標(biāo)進(jìn)程沒有被掛起(放棄CPU)优烧,即env_ipc_recving == 0的時候蝉揍,因為env_ipc_recving == 1說明進(jìn)程等待接受數(shù)據(jù)。那么應(yīng)該返回-E_IPC_NOT_RECV畦娄。此外還需要關(guān)注的就是又沾,我們往目標(biāo)進(jìn)程插入頁,接收者和發(fā)送者共享一個頁熙卡。
代碼如下:

static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
    // LAB 4: Your code here.
    int result;
    struct Env* proc; //目標(biāo)進(jìn)程
    uint32_t src_addr = (uint32_t)srcva; // the page  address that will be sent to target process
    struct PageInfo *page; //phyiscal page 
    pte_t *pg_table_entry; // page table entry
    if ((result = envid2env(envid, &proc, 0)) < 0)
        return result;  
    if(proc->env_ipc_recving == 0) {
        // sys_ipc_recv()中設(shè)置了recving=1來表明這個進(jìn)程想接受數(shù)據(jù), 如果target process的recving == 0
        // 說明target process并不想接收數(shù)據(jù)杖刷,所以return -E_IPC_NOT_RECV;
        return -E_IPC_NOT_RECV;
    }
    if(src_addr < UTOP && PGOFF(src_addr) > 0) {
        return -E_INVAL;
    }
    if (((uint32_t)srcva < UTOP) && ((perm | PTE_SYSCALL) != PTE_SYSCALL)) {
        return -E_INVAL;
    }
    page = page_lookup(curenv->env_pgdir,srcva,&pg_table_entry);
    if(src_addr < UTOP && page == NULL) {
        return -E_INVAL;
    } 
    if((perm & PTE_W) && !(*pg_table_entry & PTE_W)) {
        //(*pg_table_entry & PTE_W)如果這個條件成立,說明是writetable的
        //所以如果我們要判斷它是否是一個read-only的驳癌,只需要滑燃!即可
        return -E_INVAL;
    }

    proc->env_ipc_perm = 0;
    if ((src_addr< UTOP) && ((uint32_t) proc->env_ipc_dstva < UTOP)){
        // 如果src_addr < UTOP,才可以使用頁來傳遞數(shù)據(jù)
        result = page_insert(proc->env_pgdir,page,proc->env_ipc_dstva,perm);
        if(result < 0) {
            return -E_NO_MEM;
        }
        proc->env_ipc_perm = perm;
    }
    proc->env_ipc_recving = 0; //表示接受完畢
    proc->env_ipc_value = value;
    proc->env_ipc_from = curenv->env_id;
    proc->env_status = ENV_RUNNABLE; //接收數(shù)據(jù)完畢后設(shè)置為RUNNABLE,接受調(diào)度
    return 0;
    // panic("sys_ipc_try_send not implemented");
}

ipc_send():
相對來說ipc_send()和系統(tǒng)調(diào)用比起來就沒有那么困難了喂柒。他只是一個對于系統(tǒng)調(diào)用sys_ipc_try_send()的包裝不瓶。結(jié)合注釋,他說this function keeps trying until it succeeds 這就是意味著我們需要用一個循環(huán)來判斷目標(biāo)進(jìn)程是否準(zhǔn)備好接收消息灾杰。如果sys_ipc_try_send()的返回值不是-E_IPC_NOT_RECV那么就panic蚊丐。另外我們要發(fā)送消息,肯定要讓當(dāng)前進(jìn)程放棄CPU的艳吠,因此在最后還需要調(diào)用sys_yield()來放棄CPU麦备。其他的一些信息就卸載注釋里面了,代碼如下:

void
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{
    // LAB 4: Your code here.
    int result;
    if(pg == NULL) {
        //如果不需要以頁來發(fā)送數(shù)據(jù),那么就將pg設(shè)置為一個合適的值
        //讓sys_ipc_try_send理解這個不是一個合法的地址
        //根據(jù)sys_ipc_try_send里面的注釋,很多條件都需要 < UTOP,那就意味著說如果我們傳入UTOP
        //將會被視為不合法的地址
        pg = (void*)UTOP;
    }
    while((result = sys_ipc_try_send(to_env,val,pg,perm)) == -E_IPC_NOT_RECV);
    if(result != -E_IPC_NOT_RECV && result < 0) {
        panic("ipc_send():send message to %d failed",to_env);
    }
    sys_yield();
    
}

ipc_recv():
這個函數(shù)是對系統(tǒng)調(diào)用sys_ipc_recv()的封裝昭娩。根據(jù)注釋里面的解釋凛篙,主要是進(jìn)行返回值的判斷。不必多說栏渺,代碼如下呛梆。

int32_t
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{
    // LAB 4: Your code here.
    if(pg == NULL) {
        // 系統(tǒng)調(diào)用中的條件都是要求pg < UTOP,所以傳入UTOP就會被視為
        // no page
        pg = (void*)UTOP;
    }
    int result;
    result = sys_ipc_recv(pg);
    if(result < 0) {
        if(from_env_store != NULL) {
            *from_env_store = 0;
        }
        if(perm_store != NULL) {
            *perm_store = 0;
        }
    }

    if(from_env_store != NULL) {
        *from_env_store = thisenv->env_ipc_from;
    }
    if(perm_store != NULL) {
        *perm_store = thisenv->env_ipc_perm;
    }
    
    // return the value sent by sender
    return thisenv->env_ipc_value;

}

修改lib/syscall.c的代碼:
如果不修改,我一直無法通過pingpong和primes測試點磕诊。并且運(yùn)行的時候會報錯填物,將原來的代碼修改為:

int
sys_ipc_recv(void *dstva)
{
    return syscall(SYS_ipc_recv, 0, (uint32_t)dstva, 0, 0, 0, 0); 
        //原來為return syscall(SYS_ipc_recv, 1, (uint32_t)dstva, 0, 0, 0, 
}c

往kern/syscall.c中加入新的case:
雖然這個很簡單,但是我在這里找了很久的bug霎终。真是低級的錯誤


實驗結(jié)果

運(yùn)行make run-pingpong滞磺,出現(xiàn)以下結(jié)果:

make run-pingpong

運(yùn)行make grade,結(jié)果如下莱褒,我們通過了所有的測試點击困。

make grade

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市广凸,隨后出現(xiàn)的幾起案子阅茶,更是在濱河造成了極大的恐慌蛛枚,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脸哀,死亡現(xiàn)場離奇詭異坤候,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)企蹭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來智末,“玉大人谅摄,你說我怎么就攤上這事∠倒荩” “怎么了送漠?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長由蘑。 經(jīng)常有香客問我闽寡,道長,這世上最難降的妖魔是什么尼酿? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任爷狈,我火速辦了婚禮,結(jié)果婚禮上裳擎,老公的妹妹穿的比我還像新娘涎永。我一直安慰自己,他們只是感情好鹿响,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布羡微。 她就那樣靜靜地躺著,像睡著了一般惶我。 火紅的嫁衣襯著肌膚如雪妈倔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天绸贡,我揣著相機(jī)與錄音盯蝴,去河邊找鬼。 笑死恃轩,一個胖子當(dāng)著我的面吹牛结洼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叉跛,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼松忍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了筷厘?” 一聲冷哼從身側(cè)響起鸣峭,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宏所,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后摊溶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爬骤,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年莫换,在試婚紗的時候發(fā)現(xiàn)自己被綠了霞玄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡拉岁,死狀恐怖坷剧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喊暖,我是刑警寧澤惫企,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站陵叽,受9級特大地震影響狞尔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜巩掺,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一偏序、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锌半,春花似錦禽车、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阁谆。三九已至筛谚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留碗硬,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓瓢颅,卻偏偏與公主長得像恩尾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挽懦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

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