異構(gòu)計算關(guān)鍵技術(shù)之內(nèi)存管理與DMA(一)

異構(gòu)計算關(guān)鍵技術(shù)之內(nèi)存管理與DMA(一)

誕生伊始,計算機處理能力就處于高速發(fā)展中膨桥。及至最近十年蛮浑,隨著大數(shù)據(jù)、區(qū)塊鏈只嚣、AI 等新技術(shù)的持續(xù)火爆沮稚,人們?yōu)樘嵘嬎闾幚硭俣雀前l(fā)展了多種不同的技術(shù)思路。大數(shù)據(jù)受惠于分布式集群技術(shù)册舞,區(qū)塊鏈帶來了專用處理器(Application-Specific IC, ASIC)的春天蕴掏,AI 則讓大眾聽到了“異構(gòu)計算”這個計算機界的學(xué)術(shù)名詞抛杨。

“異構(gòu)計算”(Heterogeneous computing)休建,是指在系統(tǒng)中使用不同體系結(jié)構(gòu)的處理器的聯(lián)合計算方式。在 AI 領(lǐng)域倦零,常見的處理器包括:CPU(X86藐石,Arm即供,RISC-V 等),GPU贯钩,F(xiàn)PGA 和 ASIC募狂。(按照通用性從高到低排序)。

1705038117950.jpg

本系列文章將介紹異構(gòu)計算涉及到的內(nèi)存管理技術(shù)角雷、DMA技術(shù)祸穷。結(jié)合驅(qū)動開發(fā)、FPGA/ASIC PCIe DMA engine的實例代碼進行詳細講解多線程勺三、DMA scatter-gather list雷滚、PCIe TLP等核心技術(shù)吗坚。

本章將介紹核心的基本概念:主要包括進程空間车份、頁機制、DMA類型牡彻。

一扫沼、什么是異構(gòu)計算

同構(gòu)計算或者說通用計算性能的發(fā)展已經(jīng)遠遠跟不上應(yīng)用的需求,如近幾年的國內(nèi)的天河2A和神威超算都屬于異構(gòu)超算,接下來幾年研發(fā)的超算也都屬于異構(gòu)超算缎除,可見严就,異構(gòu)超算已經(jīng)成為中美兩國超算領(lǐng)域的趨勢。

這里我們引用網(wǎng)上的一個經(jīng)典“廚房論”異構(gòu)計算。

在飯店的廚房轰坊,通常會有一個大廚(CPU)铸董,它會做各種菜(兼容性極好),但是如果做菜之前的大量重復(fù)動作(洗菜衰倦、切菜)導(dǎo)致它一天做菜的份數(shù)明顯減少。

并且孽文,由于最近(人工智能時代到來)客人點菜要求越來越高(花樣菜式)驻襟,大廚開始不堪負重。

1705038558707.jpg
1705038338934.png

本來顧客大多要的「炒白菜」芋哭,現(xiàn)在一個個都想吃「上湯娃娃菜」沉衣。

一道是家常菜,一道是國宴菜减牺。然而后者復(fù)雜程度(大量數(shù)據(jù)復(fù)雜處理)遠遠不是前者所能比較豌习。

于是,大廚想著拔疚,一大菜我一個做著麻煩肥隆,但是我可以請個幫手(協(xié)處理器)。比如在切菜方面稚失,這個幫手可以同時處理很多菜品(并行計算)栋艳,而且很熟練,速度很快(低延時)句各。

于是吸占,一個負責切菜,一個負責做菜凿宾,分工明確矾屯。當然,大廚挑選這個幫手也是精挑細選初厚,主要體現(xiàn)在以下方面:

1. 多樣的菜品處理能力件蚕,如洗菜切菜一體化(算法性能)——協(xié)處理器需要能全面支持需要用到的場景關(guān)鍵算法。

2. 支持同時、快速加工(數(shù)據(jù)并行和低延時處理能力)——協(xié)處理器需要有大量并行通道骤坐,且每個通道支持低延時的數(shù)據(jù)處理绪杏。

3. 便于大廚操作和菜品存取(接口性能)——和主處理器很方便的數(shù)據(jù)交互

4. 學(xué)習(xí)能力強纽绍,新菜式也能學(xué)會(配置靈活)——協(xié)處理器可以針對計算需求升級迭代

5. 一天別吃太多(功耗低)——協(xié)處理器更低的功耗意味著更低的運行成本蕾久,更小的空間占用和更簡單的熱處理方案。

就這樣拌夏,我們將一個復(fù)雜的工作(做菜)分解成了適合各個processor(工人師傅)任務(wù)僧著,最后我們再把他們的結(jié)果(成品)封裝一下即可(成品)。

二障簿、進程地址空間布局

在Linux中每個進程都擁有獨立的虛擬地址空間盹愚,每個虛擬地址空間都可以認為自己擁有全部內(nèi)存,比如32位系統(tǒng)中的進程就認為自己擁有4GB的內(nèi)存站故。

Linux把一個進程空間中劃分為用戶空間和內(nèi)核空間兩段皆怕。在32位系統(tǒng)中給用戶空間劃分3GB,給內(nèi)核空間劃分1GB西篓;64位系統(tǒng)中其實目前只用了48位愈腾,用戶空間和內(nèi)核空間平分256TB(從4.11內(nèi)核開始擴展到57位)。

1705040805143.png
.text段存放二進制代碼岂津;

.rodata段存放只讀數(shù)據(jù)虱黄,例如const和char *字符串;

.data段存放已初始化的全局變量吮成;

.bss段存放未初始化的全局變量橱乱;

Heap是堆內(nèi)存,向高地址生長粱甫,由malloc函數(shù)分配泳叠,free函數(shù)釋放;

*.dll魔种、*.so存放運行時的共享庫析二,例如動態(tài)編譯使用printf的程序,運行時會調(diào)用這里存放的庫文件节预;

Stack是棧內(nèi)存叶摄,向低地址生長,函數(shù)跳轉(zhuǎn)的現(xiàn)場保護安拟、局部變量蛤吓、一些中斷現(xiàn)場保護等數(shù)據(jù)存放在棧中。

想看這些段的話可以去linux中寫個C程序糠赦,編譯后再反匯編就能看見了会傲。

虛擬地址

即使是現(xiàn)代操作系統(tǒng)中锅棕,內(nèi)存依然是計算機中很寶貴的資源,看看你電腦幾個T固態(tài)硬盤淌山,再看看內(nèi)存大小就知道了裸燎。

為了充分利用和管理系統(tǒng)內(nèi)存資源,Linux采用虛擬內(nèi)存管理技術(shù)泼疑,利用虛擬內(nèi)存技術(shù)讓每個進程都有4GB互不干涉的虛擬地址空間德绿。

進程初始化分配和操作的都是基于這個「虛擬地址」,只有當進程需要實際訪問內(nèi)存資源的時候才會建立虛擬地址和物理地址的映射退渗,調(diào)入物理內(nèi)存頁移稳。

打個不是很恰當?shù)谋确剑@個原理其實和現(xiàn)在的某某網(wǎng)盤一樣会油。假如你的網(wǎng)盤空間是1TB个粱,真以為就一口氣給了你這么大空間嗎?那還是太年輕翻翩,都是在你往里面放東西的時候才給你分配空間都许,你放多少就分多少實際空間給你,但你和你朋友看起來就像大家都擁有1TB空間一樣嫂冻。

虛擬地址的好處

  • 避免用戶直接訪問物理內(nèi)存地址梭稚,防止一些破壞操作,保護系統(tǒng)絮吵;
  • 每個進程都被分配了4GB的虛擬內(nèi)存,用戶應(yīng)用程序可使用比實際物理內(nèi)存更大的地址空間忱屑;

4GB的進程虛擬地址空間被分成兩部分:“用戶空間”和“內(nèi)核空間”蹬敲。

1705044192418.png

物理地址

不管是用戶空間還是內(nèi)核空間,使用的都是虛擬地址(邏輯地址)莺戒,當需進程要實際訪存的時候伴嗡,就由內(nèi)核的“請求分頁機制”,產(chǎn)生“缺頁異炒硬”調(diào)入物理內(nèi)存頁瘪校。

把虛擬地址轉(zhuǎn)換成內(nèi)存的物理地址,這中間涉及利用MMU 內(nèi)存管理單元(Memory Management Unit)對虛擬地址分段和分頁(段頁式)地址轉(zhuǎn)換名段。

1705041044672.png

<font color=B871F78><h2>三阱扬、頁表概述</font></h2>

帶有MMU的處理器訪存過程主要是通過頁表來聯(lián)系虛擬地址和物理地址。頁表是內(nèi)存中的一片供MMU訪問的空間伸辟,每個進程都有一個自己頁表麻惶,用來把自己進程的虛擬地址映射到真正的內(nèi)存中。

頁表以頁為單位管理信夫,MMU也以頁為單位進行映射窃蹋。虛擬地址空間和內(nèi)存都是分頁的卡啰,一頁大小為4KB(4K邊界是這么來的)。以32位地址為例警没,高20位是頁號匈辱,用于索引頁面、低12位是頁內(nèi)偏移杀迹,用于找到頁內(nèi)要訪問的那個存儲單元亡脸。如下圖所示:

1705040270819.png

不過都不用上面這種頁表,算一筆賬啊佛南,一個頁表項4Byte梗掰,這個頁表有2^20項,那就是4MB嗅回,這還是一個進程的及穗,而整個物理內(nèi)存才4GB,顯然開銷太大了绵载。所以實際應(yīng)用中都采用多級頁表埂陆,比如64位的Linux用的四級頁表,多級頁表的優(yōu)點是節(jié)省空間娃豹,因為并不是所有進程的所有虛擬地址都會被用到焚虱,沒有用到的虛擬地址沒有必要建立虛擬地址到物理地址之間的映射關(guān)系;缺點是級數(shù)多懂版,查找慢鹃栽,所以才引入了TLB緩存

四躯畴、kmalloc和vmalloc

kmalloc和vmalloc是Linux內(nèi)核提供的兩個在內(nèi)核空間分配內(nèi)存的API民鼓。

kmalloc能夠分配物理地址連續(xù)的內(nèi)存空間,然后把這片內(nèi)存空間的首地址映射為虛擬地址返回給內(nèi)核空間蓬抄,不過kmalloc因為要求物理地址連續(xù)丰嘉,所以僅用于分配比較小的內(nèi)存空間,比如結(jié)構(gòu)體等嚷缭。隨著系統(tǒng)運行時間的越來越長饮亏,內(nèi)存碎片越來越多,kmalloc有分配失敗的可能阅爽。

vmalloc是分配虛擬地址連續(xù)的api路幸,但是物理地址不一定連續(xù),用于分配大的內(nèi)存空間优床。不過vmalloc分配的內(nèi)存用起來效率不高劝赔,Linux社區(qū)不太推薦用它。

五胆敞、缺頁中斷概述

我們上面提到:當需進程要實際訪存的時候着帽,就由內(nèi)核的“請求分頁機制”杂伟,產(chǎn)生“缺頁異常”調(diào)入物理內(nèi)存頁仍翰。

頁表中記錄著已經(jīng)分配空間的物理地址和虛擬地址間的映射關(guān)系赫粥,但如果虛擬地址空間中存在一個沒有被真正分配物理內(nèi)存的虛擬地址,那么訪問這個虛擬地址時MMU無法找到對應(yīng)的物理地址予借,這時MMU會向CPU拋出一個異常越平,即缺頁中斷。

比如上一小節(jié)說的kmalloc函數(shù)灵迫,在調(diào)用kmalloc時秦叛,系統(tǒng)首先會在虛擬地址空間中分配一段虛擬空間,此時還沒有分配對應(yīng)的物理空間瀑粥,然后訪問這個虛擬空間挣跋,此時MMU找不到對應(yīng)的物理地址,向CPU產(chǎn)生一個缺頁中斷狞换,然后在缺頁中斷里才真正地分配物理內(nèi)存并建立映射關(guān)系避咆。

SGDMA是分配多個連續(xù)的物理內(nèi)存塊,用鏈表聯(lián)系起來修噪,把它們當成一塊內(nèi)存用查库,優(yōu)點是不用要求大片物理地址連續(xù),故能進行大批量數(shù)據(jù)傳輸黄琼;而且SGDMA因為有鏈表樊销,所以當鏈表所表述的所有內(nèi)存塊全傳輸完了才會發(fā)中斷,一批傳輸只要中斷一次(指的是數(shù)據(jù)傳輸完成中斷)脏款。

SGDMA雖然牛现柠,但Block DMA也不是完全沒用,比如PCIe的SGDMA應(yīng)用中弛矛,傳輸SG鏈表的緩沖區(qū)用的就是Block DMA(因為SG鏈表用的空間不大),而傳輸數(shù)據(jù)的那部分緩沖區(qū)用的才是SGDMA比然,所以SGDMA的使用其實離不開BDMA丈氓!

六、DMA類型

DMA分為塊DMA(Block DMA)和分散/聚集式DMA(Scatter-Gather DMA)强法。

Block DMA就是分配一個連續(xù)的物理內(nèi)存塊万俗,然后進行DMA,缺點是要求物理地址連續(xù)饮怯,很難分出來較大的塊闰歪;而且如果想要傳輸多個內(nèi)存塊的數(shù)據(jù),需要每DMA完一個塊處理一次中斷蓖墅,響應(yīng)中斷可是很麻煩的库倘!

七临扮、一致性DMA和流式DMA

DMA是直接內(nèi)存存取,也就是DMA控制器是直接操作內(nèi)存的教翩,而CPU直接可見的存儲器是Cache杆勇,若DMA控制器改寫完內(nèi)存數(shù)據(jù)后CPU直接去讀那片內(nèi)存,其實讀到的是Cache中的老數(shù)據(jù)饱亿,新數(shù)據(jù)已經(jīng)被DMA改了蚜退,這個稱為DMA一致性問題,解決問題的方法有兩種:

關(guān)了Cache直接讀內(nèi)存彪笼;

DMA結(jié)束后刷新Cache钻注。

一致性DMA用的是第一種---關(guān)了cache,直接讀內(nèi)存配猫,為一致性DMA分配緩沖區(qū)時幅恋,這片緩沖區(qū)會在頁表中被標記上不帶Cache,CPU訪問時就從主存中去讀取了章姓。上節(jié)說的BDMA就是一致性DMA佳遣。

流式DMA用的是第二種,有兩種情況:

1. 從內(nèi)存向外設(shè)DMA傳輸:首先CPU將要傳的數(shù)據(jù)寫入Cache凡伊,然后Cache把數(shù)據(jù)刷新進內(nèi)存零渐,再用內(nèi)存進行DMA。

2. 從外設(shè)向內(nèi)存DMA傳輸:CPU設(shè)置目標內(nèi)存緩沖區(qū)對應(yīng)的Cache line為臟數(shù)據(jù)(設(shè)置Cache上的dirty bit)系忙,就是無效數(shù)據(jù)诵盼,CPU下次訪問這個Cache line時會從主存中重新刷進來。

上一小節(jié)里的SGDMA就屬于流式DMA银还。

那么一致性DMA和流式DMA都在什么情景下用呢风宁?

為什么實驗室端系統(tǒng)的PCIe使用一致性DMA來緩存SG鏈表呢而不把它也換成SGDMA呢?

如果CPU和DMA要頻繁地操作一塊固定的內(nèi)存區(qū)域蛹疯,那么這個區(qū)域用一致性DMA比較好戒财,因為這個內(nèi)存塊老進行DMA,如果用流式DMA的話每進行一次DMA就得刷一次Cache捺弦,刷新Cache很耗時間的饮寞,尤其是大片地刷

八列吼、Linux中建立一致性DMA

物理地址幽崩、虛擬地址和總線地址的關(guān)系:

物理地址就是內(nèi)存的物理地址;

虛擬地址就是虛擬空間的地址寞钥;

總線地址指的是總線內(nèi)部的地址慌申,就像PCIe有它自己的總線地址,PCIe發(fā)起TLP請求時目的地址都是總線地址理郑,是由**RC**映射成**物理地址**后才能寫進內(nèi)存的蹄溉。

Linux提供了建立一致性DMA的API咨油,dma_alloc_coherent,其聲明如下:

    /**
     * Allocate DMA-coherent memory space and return both the kernel remapped
     * virtual and bus address for that space.
     */
    
    void *dma_alloc_coherent(struct device *dev, size_t size,
             dma_addr_t *handle, gfp_t gfp)
    {
        ...
    }

這個API有兩個返回地址:

1. 一個是void*型的返回值类缤,它返回的是一致性DMA緩沖區(qū)映射到內(nèi)核空間的虛擬首地址臼勉,方便驅(qū)動程序向緩沖區(qū)里寫描述符;

2. 另一個返回地址就是dma_addr_t*類型的指針餐弱,dma_addr_t是Linux中定義的總線地址類型(注意是總線地址不是物理地址)宴霸,這個地址是一致性緩沖區(qū)在總線上的首地址,這是給外面的設(shè)備用的膏蚓,萬不能在驅(qū)動中訪問這個地址

參數(shù)*dev是4.6節(jié)中說的device結(jié)構(gòu)體指針瓢谢,指向設(shè)備;

參數(shù)size是緩沖區(qū)大小驮瞧,真正的大小是2^size字節(jié)氓扛;

flag是選擇分配空間如果不足時怎么辦,填宏GFP_ATOMIC是不阻塞等待论笔,填GFP_KERNEL是阻塞等待采郎。

Linux也為PCI設(shè)備驅(qū)動提供了分配一致性DMA的API,pci_alloc_consistent狂魔,定義如下:


static inline void *
pci_alloc_consistent(struct pci_dev *hwdev, size_t size,
             dma_addr_t *dma_handle)
{
    return dma_alloc_coherent(hwdev == NULL ? NULL : &hwdev->dev, size, dma_handle, GFP_ATOMIC);
}

可見蒜埋,pci_alloc_consistent其實就是把dma_alloc_coherent又封裝了一下,調(diào)用時填入pci_dev結(jié)構(gòu)體指針就好了最楷,flag被強制設(shè)為GFP_ATOMIC整份,即空間不足時不阻塞。

九籽孙、Linux中建立SGDMA

SG緩沖區(qū)是一個一個不連續(xù)的內(nèi)存塊烈评,但塊內(nèi)是物理地址連續(xù)的,每個內(nèi)存塊是以頁為單位的犯建,如下圖所示:

1705044192418.png

每個SG緩存塊都在其所屬的頁內(nèi)讲冠,所以想要獲得一個SG緩存塊需要知道頁號、頁內(nèi)偏移(SG塊的首地址)和SG緩存塊長度适瓦。

Linux為SG緩存塊提供了一個描述類型沟启,即scatterlist結(jié)構(gòu)體,其定義如下:


struct scatterlist {
#ifdef CONFIG_DEBUG_SG
    unsigned long   sg_magic;
#endif
    unsigned long   page_link;
    unsigned int    offset;
    unsigned int    length;
    dma_addr_t  dma_address;
#ifdef CONFIG_NEED_SG_DMA_LENGTH
    unsigned int    dma_length;
#endif
};

page_link指示該SG緩存塊所在的頁面犹菇;

offset為頁內(nèi)偏移;

length為SG緩存塊的長度芽卿;

dma_address是該內(nèi)存塊的實際起始地址(已經(jīng)映射為總線地址)揭芍;

dma_length是對應(yīng)的長度信息(也是給DMA用的)。

一個scatterlist結(jié)構(gòu)體變量只是描述了一個SG緩存塊卸例,如果想描述整個SG緩沖區(qū)称杨,需要定義一個scatterlist的結(jié)構(gòu)體數(shù)組肌毅,數(shù)組元素都是scatterlist結(jié)構(gòu)體,表示一個個緩存塊姑原。

Linux提供了包括scatterlist結(jié)構(gòu)體數(shù)組的類型悬而,即sg_table結(jié)構(gòu)體,不過RIFFA驅(qū)動沒有用它锭汛,而是直接定義的scatterlist結(jié)構(gòu)體數(shù)組笨奠。

SG緩存塊依附于頁,Linux使用struct page結(jié)構(gòu)體描述一個物理頁面唤殴,出于節(jié)省內(nèi)存的考慮般婆,struct page中使用了大量的聯(lián)合體。

十朵逝、SG緩沖區(qū)的建立流程

SG緩沖區(qū)的建立流程如下所示:

1705044419664.png

(1)首先要明白的是蔚袍,所謂SG緩沖區(qū)的建立不是說去申請SG緩沖區(qū)空間,SG緩沖區(qū)本身就在內(nèi)存中配名,為什么啤咽?

因為SG緩沖區(qū)本身就是從用戶空間傳進內(nèi)核的一片數(shù)據(jù),在物理地址上自然是不連續(xù)且分塊的渠脉,這已經(jīng)滿足SG緩沖區(qū)的特征了宇整,所以并不是去建立SG緩沖區(qū),而是將這片已經(jīng)存在的连舍、不連續(xù)的普通內(nèi)存空間變成DMA真正可用的SG緩沖區(qū)没陡。

(2) 首先需要計算這片從用戶空間傳進來的數(shù)據(jù)用了多少頁,然后定義跟頁數(shù)一樣多個數(shù)的page指針索赏,形成一個指針數(shù)組盼玄,此時這個page指針數(shù)組是個空指針數(shù)組,即不描述任何物理頁潜腻。

(3) 然后使用get_user_pages函數(shù)從SG緩沖區(qū)的虛擬首地址開始鎖定這些SG緩存塊所在的頁埃儿,防止頁表沖突時這些頁被系統(tǒng)換出到硬盤中,都換走了那還DMA啥啊融涣。get_user_pages函數(shù)同時能夠?qū)age數(shù)組中的指針元素指向SG緩沖區(qū)的每個物理頁童番。此時page指針數(shù)組才真正地有用了。

(4) 創(chuàng)建scatterlist結(jié)構(gòu)體數(shù)組并按需分配空間(數(shù)組長度和頁數(shù)一致)威鹿,用sg_init_table函數(shù)分配scatterlist結(jié)構(gòu)體數(shù)組的長度剃斧。注意此時scatterlist結(jié)構(gòu)體數(shù)組是空數(shù)組。

(5) 上一小節(jié)說到scatterlist結(jié)構(gòu)體描述SG緩存塊的忽你,包含頁面幼东、頁內(nèi)偏移、長度等信息,Linux提供了sg_set_page函數(shù)來把這些信息存入scatterlist結(jié)構(gòu)體中(遍歷scatterlist結(jié)構(gòu)體數(shù)組一個一個分配)根蟹。

(6) 最后調(diào)用dma_map_sg函數(shù)建立SGDMA映射脓杉,此時SGDMA的環(huán)境就建立好了。dma_map_sg函數(shù)定義在/asm/dma-mapping.h中简逮,如下所示:

    int dma_map_sg(struct device *dev, struct scatterlist *sglist,
           int nents, enum dma_data_direction dir)
    {
        ...
    }
參數(shù)*dev是設(shè)備結(jié)構(gòu)體球散;

參數(shù)*sg是scatterlist結(jié)構(gòu)體數(shù)組;

參數(shù)nents是頁數(shù)散庶,也就是SG緩存塊的個數(shù)蕉堰;

參數(shù)direction是DMA傳輸方向,它是一個枚舉類型變量;

常用的是DMA_TO_DEVICE(內(nèi)存→外設(shè))和DMA_FROM_DEVICE(外設(shè)→內(nèi)存)督赤。

DMA_TO_DEVICE用于PCIe的RxMEM傳輸嘁灯、DMA_FROM_DEVICE用于PCIe的TxMEM傳輸。

十一躲舌、未完待續(xù)

下章將繼續(xù)介紹核心的基本概念:線程/進程技術(shù)丑婿。

歡迎關(guān)注知乎:北京不北,+vbeijing_bubei没卸,dou音:near.X

獲得免費答疑羹奉,長期技術(shù)交流。

十二约计、參考文獻

https://zhuanlan.zhihu.com/p/149581303

https://www.icspec.com/news/article-details/2123995

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诀拭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子煤蚌,更是在濱河造成了極大的恐慌耕挨,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尉桩,死亡現(xiàn)場離奇詭異筒占,居然都是意外死亡,警方通過查閱死者的電腦和手機蜘犁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門翰苫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人这橙,你說我怎么就攤上這事奏窑。” “怎么了屈扎?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵埃唯,是天一觀的道長。 經(jīng)常有香客問我鹰晨,道長墨叛,這世上最難降的妖魔是什么滑沧? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮巍实,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哩牍。我一直安慰自己棚潦,他們只是感情好,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布膝昆。 她就那樣靜靜地躺著丸边,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荚孵。 梳的紋絲不亂的頭發(fā)上妹窖,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音收叶,去河邊找鬼骄呼。 笑死,一個胖子當著我的面吹牛判没,可吹牛的內(nèi)容都是我干的蜓萄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼澄峰,長吁一口氣:“原來是場噩夢啊……” “哼嫉沽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起俏竞,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绸硕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后魂毁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玻佩,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年漱牵,在試婚紗的時候發(fā)現(xiàn)自己被綠了夺蛇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡酣胀,死狀恐怖刁赦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闻镶,我是刑警寧澤甚脉,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站铆农,受9級特大地震影響牺氨,放射性物質(zhì)發(fā)生泄漏狡耻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一猴凹、第九天 我趴在偏房一處隱蔽的房頂上張望夷狰。 院中可真熱鬧,春花似錦郊霎、人聲如沸沼头。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽进倍。三九已至,卻和暖如春购对,著一層夾襖步出監(jiān)牢的瞬間猾昆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工骡苞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垂蜗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓烙如,卻偏偏與公主長得像么抗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子亚铁,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

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