Android Low Memory Killer原理分析

Android 的設計理念之一抱冷,便是應用程序退出,但進程還會繼續(xù)存在系統(tǒng)以便再次啟動時提高響應時間. 這樣的設計會帶來一個問題, 每個進程都有自己獨立的內(nèi)存地址空間,隨著應用打開數(shù)量的增多,系統(tǒng)已使用的內(nèi)存越來越大添诉,就很有可能導致系統(tǒng)內(nèi)存不足, 那么需要一個能管理所有進程屁桑,根據(jù)一定策略來釋放進程的策略,這便有了lmk栏赴,全稱為LowMemoryKiller(低內(nèi)存殺手)蘑斧,lmkd來決定什么 時間殺掉什么進程.

Android基于Linux的系統(tǒng),其實Linux有類似的內(nèi)存管理策略——OOM killer须眷,全稱(Out Of Memory Killer), OOM的策略更多的是用于分配內(nèi)存不足時觸發(fā)竖瘾,將得分最高的進程殺掉。而lmk則會每隔一段時間檢查一次花颗,當系統(tǒng)剩余可用內(nèi)存較低時捕传,便會觸發(fā)殺進程的策 略,根據(jù)不同的剩余內(nèi)存檔位來來選擇殺不同優(yōu)先級的進程扩劝,而不是等到OOM時再來殺進程庸论,真正OOM時系統(tǒng)可能已經(jīng)處于異常狀態(tài),系統(tǒng)更希望的是未雨綢 繆棒呛,在內(nèi)存很低時來殺掉一些優(yōu)先級較低的進程來保障后續(xù)操作的順利進行聂示。

二. framework層

位于ProcessList.java中定義了3種命令類型,這些文件的定義必須跟lmkd.c定義完全一致簇秒,格式分別如下:

LMK_TARGET...(upto6pairs)

LMK_PROCPRIO

LMK_PROCREMOVE

功能

命令

對應方法

觸發(fā)時機

更新oom_adjLMK_TARGETupdateOomLevelsAMS.updateConfiguration

設置進程adjLMK_PROCPRIOsetOomAdjAMS.applyOomAdjLocked

移除進程LMK_PROCREMOVEremoveAMS.handleAppDiedLocked/cleanUpApplicationRecordLocked

在前面文章Android進程調(diào)度之a(chǎn)dj算法中有講到AMS.applyOomAdjLocked鱼喉,接下來以這個過程為主線開始分析。

2.1 AMS.applyOomAdjLocked

privatefinalbooleanapplyOomAdjLocked(ProcessRecordapp,booleandoingAll,longnow,

longnowElapsed){

...

if(app.curAdj!=app.setAdj){

//【見小節(jié)2.2】

ProcessList.setOomAdj(app.pid,app.info.uid,app.curAdj);

app.setAdj=app.curAdj;

}

...

}

2.2 PL.setOomAdj

publicstaticfinalvoidsetOomAdj(intpid,intuid,intamt){

//當adj=16宰睡,則直接返回

if(amt==UNKNOWN_ADJ)

return;

longstart=SystemClock.elapsedRealtime();

ByteBufferbuf=ByteBuffer.allocate(4*4);

buf.putInt(LMK_PROCPRIO);

buf.putInt(pid);

buf.putInt(uid);

buf.putInt(amt);

//將16Byte字節(jié)寫入socket【見小節(jié)2.3】

writeLmkd(buf);

longnow=SystemClock.elapsedRealtime();

if((now-start)>250){

Slog.w("ActivityManager","SLOW OOM ADJ: "+(now-start)+"ms for pid "+pid

+" = "+amt);

}

}

buf大小為16個字節(jié)蒲凶,依次寫入LMK_PROCPRIO(命令類型), pid(進程pid), uid(進程uid), amt(目標adj),將這些字節(jié)通過socket發(fā)送給lmkd.

2.3 PL.writeLmkd

privatestaticvoidwriteLmkd(ByteBufferbuf){

//當socket打開失敗會嘗試3次

for(inti=0;i<3;i++){

if(sLmkdSocket==null){

//打開socket 【見小節(jié)2.4】

if(openLmkdSocket()==false){

try{

Thread.sleep(1000);

}catch(InterruptedExceptionie){

}

continue;

}

}

try{

//將buf信息寫入lmkd socket

sLmkdOutputStream.write(buf.array(),0,buf.position());

return;

}catch(IOExceptionex){

try{

sLmkdSocket.close();

}catch(IOExceptionex2){

}

sLmkdSocket=null;

}

}

}

當sLmkdSocket為空拆内,并且打開失敗旋圆,重新執(zhí)行該操作;

當sLmkdOutputStream寫入buf信息失敗麸恍,則會關閉sLmkdSocket灵巧,重新執(zhí)行該操作搀矫;

這個重新執(zhí)行操作最多3次,如果3次后還失敗刻肄,則writeLmkd操作會直接結(jié)束瓤球。嘗試3次,則不管結(jié)果如何都將退出該操作敏弃,可見writeLmkd寫入操作還有可能失敗的卦羡。

2.4 PL.openLmkdSocket

privatestaticbooleanopenLmkdSocket(){

try{

sLmkdSocket=newLocalSocket(LocalSocket.SOCKET_SEQPACKET);

//與遠程lmkd守護進程建立socket連接

sLmkdSocket.connect(

newLocalSocketAddress("lmkd",

LocalSocketAddress.Namespace.RESERVED));

sLmkdOutputStream=sLmkdSocket.getOutputStream();

}catch(IOExceptionex){

Slog.w(TAG,"lowmemorykiller daemon socket open failed");

sLmkdSocket=null;

returnfalse;

}

returntrue;

}

sLmkdSocket 采用的是SOCK_SEQPACKET,這是類型的socket能提供順序確定的麦到,可靠的绿饵,雙向基于連接的socket endpoint,與類型SOCK_STREAM很相似瓶颠,唯一不同的是SEQPACKET保留消息的邊界拟赊,而SOCK_STREAM是基于字節(jié)流,并不會 記錄邊界粹淋。

舉例:本地通過write()系統(tǒng)調(diào)用向遠程先后發(fā)送兩組數(shù)據(jù):一組4字節(jié)吸祟,一組8字節(jié);對于 SOCK_SEQPACKET類型通過read()能獲知這是兩組數(shù)據(jù)以及大小桃移,而對于SOCK_STREAM類型屋匕,通過read()一次性讀取到12個 字節(jié),并不知道數(shù)據(jù)包的邊界情況谴轮。

常見的數(shù)據(jù)類型還有SOCK_DGRAM炒瘟,提供數(shù)據(jù)報形式吹埠,用于udp這樣不可靠的通信過程第步。

再 回到openLmkdSocket()方法,該方法是打開一個名為lmkd的socket缘琅,類型為 LocalSocket.SOCKET_SEQPACKET粘都,這只是一個封裝,真實類型就是SOCK_SEQPACKET刷袍。先跟遠程lmkd守護進程建立 連接翩隧,再向其通過write()將數(shù)據(jù)寫入該socket,再接下來進入lmkd過程呻纹。

三. lmkd

lmkd是由init進程堆生,通過解析init.rc文件來啟動的lmkd守護進程,lmkd會創(chuàng)建名為lmkd的socket雷酪,節(jié)點位于/dev/socket/lmkd淑仆,該socket用于跟上層framework交互。

servicelmkd/system/bin/lmkd

classcore

critical

socket lmkdseqpacket0660system system

writepid/dev/cpuset/system-background/tasks

lmkd啟動后哥力,接下里的操作都在platform/system/core/lmkd/lmkd.c文件蔗怠,首先進入main()方法

3.1 main

intmain(intargc__unused,char**argv__unused){

structsched_paramparam={

.sched_priority=1,

};

mlockall(MCL_FUTURE);

sched_setscheduler(0,SCHED_FIFO,?m);

//初始化【見小節(jié)3.2】

if(!init())

mainloop();//成功后進入loop [見小節(jié)3.3]

ALOGI("exiting");

return0;

}

3.2 init

staticintinit(void){

structepoll_eventepev;

inti;

intret;

page_k=sysconf(_SC_PAGESIZE);

if(page_k== -1)

page_k=PAGE_SIZE;

page_k/=1024;

//創(chuàng)建epoll監(jiān)聽文件句柄

epollfd=epoll_create(MAX_EPOLL_EVENTS);

//獲取lmkd控制描述符

ctrl_lfd=android_get_control_socket("lmkd");

//監(jiān)聽lmkd socket

ret=listen(ctrl_lfd,1);

epev.events=EPOLLIN;

epev.data.ptr=(void*)ctrl_connect_handler;

//將文件句柄ctrl_lfd墩弯,加入epoll句柄

if(epoll_ctl(epollfd,EPOLL_CTL_ADD,ctrl_lfd,&epev)== -1){

return-1;

}

maxevents++;

//該路徑是否具有可寫的權(quán)限

use_inkernel_interface= !access(INKERNEL_MINFREE_PATH,W_OK);

if(use_inkernel_interface){

ALOGI("Using in-kernel low memory killer interface");

}else{

ret=init_mp(MEMPRESSURE_WATCH_LEVEL,(void*)&mp_event);

if(ret)

ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");

}

for(i=0;i<=ADJTOSLOT(OOM_SCORE_ADJ_MAX);i++){

procadjslot_list[i].next= &procadjslot_list[i];

procadjslot_list[i].prev= &procadjslot_list[i];

}

return0;

}

這 里,通過檢驗/sys/module/lowmemorykiller/parameters/minfree節(jié)點是否具有可寫權(quán)限來判斷是否使用 kernel接口來管理lmk事件寞射。默認該節(jié)點是具有系統(tǒng)可寫的權(quán)限渔工,也就意味著use_inkernel_interface=1.

3.3 mainloop

staticvoidmainloop(void){

while(1){

structepoll_eventevents[maxevents];

intnevents;

inti;

ctrl_dfd_reopened=0;

//等待epollfd上的事件

nevents=epoll_wait(epollfd,events,maxevents,-1);

if(nevents== -1){

if(errno==EINTR)

continue;

continue;

}

for(i=0;i

if(events[i].events&EPOLLERR)

ALOGD("EPOLLERR on event #%d",i);

// 當事件到來,則調(diào)用ctrl_connect_handler方法 【見小節(jié)3.4】

if(events[i].data.ptr)

(*(void(*)(uint32_t))events[i].data.ptr)(events[i].events);

}

}

}

主循環(huán)調(diào)用epoll_wait()桥温,等待epollfd上的事件引矩,當接收到中斷或者不存在事件,則執(zhí)行continue操作侵浸。當事件到來脓魏,則 調(diào)用的ctrl_connect_handler方法,該方法是由init()過程中設定的方法通惫。

3.4 ctrl_connect_handler

staticvoidctrl_connect_handler(uint32_t events__unused){

structepoll_eventepev;

if(ctrl_dfd>=0){

ctrl_data_close();

ctrl_dfd_reopened=1;

}

ctrl_dfd=accept(ctrl_lfd,NULL,NULL);

if(ctrl_dfd<0){

ALOGE("lmkd control socket accept failed; errno=%d",errno);

return;

}

ALOGI("ActivityManager connected");

maxevents++;

epev.events=EPOLLIN;

epev.data.ptr=(void*)ctrl_data_handler;

//將ctrl_lfd添加到epollfd

if(epoll_ctl(epollfd,EPOLL_CTL_ADD,ctrl_dfd,&epev)== -1){

ALOGE("epoll_ctl for data connection socket failed; errno=%d",errno);

ctrl_data_close();

return;

}

}

當事件觸發(fā)茂翔,則調(diào)用ctrl_data_handler

3.5 ctrl_data_handler

staticvoidctrl_data_handler(uint32_tevents){

if(events&EPOLLHUP){

//ActivityManager 連接已斷開

if(!ctrl_dfd_reopened)

ctrl_data_close();

}elseif(events&EPOLLIN){

//[見小節(jié)3.6]

ctrl_command_handler();

}

}

3.6 ctrl_command_handler

staticvoidctrl_command_handler(void){

intibuf[CTRL_PACKET_MAX/sizeof(int)];

intlen;

intcmd= -1;

intnargs;

inttargets;

len=ctrl_data_read((char*)ibuf,CTRL_PACKET_MAX);

if(len<=0)

return;

nargs=len/sizeof(int)-1;

if(nargs<0)

gotowronglen;

//將網(wǎng)絡字節(jié)順序轉(zhuǎn)換為主機字節(jié)順序

cmd=ntohl(ibuf[0]);

switch(cmd){

caseLMK_TARGET:

targets=nargs/2;

if(nargs&0x1||targets>(int)ARRAY_SIZE(lowmem_adj))

gotowronglen;

cmd_target(targets,&ibuf[1]);

break;

caseLMK_PROCPRIO:

if(nargs!=3)

gotowronglen;

//設置進程adj【見小節(jié)3.7】

cmd_procprio(ntohl(ibuf[1]),ntohl(ibuf[2]),ntohl(ibuf[3]));

break;

caseLMK_PROCREMOVE:

if(nargs!=1)

gotowronglen;

cmd_procremove(ntohl(ibuf[1]));

break;

default:

ALOGE("Received unknown command code %d",cmd);

return;

}

return;

wronglen:

ALOGE("Wrong control socket read length cmd=%d len=%d",cmd,len);

}

CTRL_PACKET_MAX 大小等于 (sizeof(int) * (MAX_TARGETS * 2 + 1))蝙场;而MAX_TARGETS=6,對于sizeof(int)=4的系統(tǒng)勇皇,則CTRL_PACKET_MAX=52谋旦。 獲取framework傳遞過來的buf數(shù)據(jù)后廉涕,根據(jù)3種不同的命令龙屉,進入不同的分支泳桦。 接下來澈侠,繼續(xù)以前面?zhèn)鬟f過來的LMK_PROCPRIO命令來往下講解桥言,進入cmd_procprio過程延旧。

3.7 cmd_procprio

staticvoidcmd_procprio(intpid,intuid,intoomadj){

structproc*procp;

charpath[80];

charval[20];

...

snprintf(path,sizeof(path),"/proc/%d/oom_score_adj",pid);

snprintf(val,sizeof(val),"%d",oomadj);

//向節(jié)點/proc//oom_score_adj寫入oomadj

writefilestring(path,val);

//當使用kernel方式則直接返回

if(use_inkernel_interface)

return;

procp=pid_lookup(pid);

if(!procp){

procp=malloc(sizeof(structproc));

if(!procp){

// Oh, the irony.??May need to rebuild our state.

return;

}

procp->pid=pid;

procp->uid=uid;

procp->oomadj=oomadj;

proc_insert(procp);

}else{

proc_unslot(procp);

procp->oomadj=oomadj;

proc_slot(procp);

}

}

向節(jié)點/proc//oom_score_adj寫入oomadj谋国。由于use_inkernel_interface=1,那么再接下里需要看看kernel的情況

3.8 小節(jié)

use_inkernel_interface該值后續(xù)應該會逐漸采用用戶空間策略迁沫。不過目前仍為 use_inkernel_interface=1則有:

LMK_PROCPRIO: 向/proc//oom_score_adj寫入oomadj芦瘾,則直接返回;

LMK_PROCREMOVE:不做任何事集畅,直接返回近弟;

LMK_TARGET:分別向/sys/module/lowmemorykiller/parameters目錄下的minfree和adj節(jié)點寫入相應信息;

四. Kernel層

lowmemorykiller driver位于 drivers/staging/Android/lowmemorykiller.c

4.1 lowmemorykiller初始化

staticstructshrinkerlowmem_shrinker={

.scan_objects=lowmem_scan,

.count_objects=lowmem_count,

.seeks=DEFAULT_SEEKS*16

};

staticint__init lowmem_init(void)

{

register_shrinker(&lowmem_shrinker);

return0;

}

staticvoid__exit lowmem_exit(void)

{

unregister_shrinker(&lowmem_shrinker);

}

module_init(lowmem_init);

module_exit(lowmem_exit);

通過register_shrinker和unregister_shrinker分別用于初始化和退出挺智。

4.2 shrinker

LMK驅(qū)動通過注冊shrinker來實現(xiàn)的祷愉,shrinker是linux kernel標準的回收內(nèi)存page的機制,由內(nèi)核線程kswapd負責監(jiān)控赦颇。

當 內(nèi)存不足時kswapd線程會遍歷一張shrinker鏈表二鳄,并回調(diào)已注冊的shrinker函數(shù)來回收內(nèi)存page,kswapd還會周期性喚醒來執(zhí)行 內(nèi)存操作媒怯。每個zone維護active_list和inactive_list鏈表订讼,內(nèi)核根據(jù)頁面活動狀態(tài)將page在這兩個鏈表之間移動,最終通過 shrink_slab和shrink_zone來回收內(nèi)存頁沪摄,有興趣想進一步了解linux內(nèi)存回收機制躯嫉,可自行研究纱烘,這里再回到 LowMemoryKiller的過程分析。

4.3 lowmem_count

staticunsignedlonglowmem_count(structshrinker*s,

structshrink_control*sc)

{

returnglobal_page_state(NR_ACTIVE_ANON)+

global_page_state(NR_ACTIVE_FILE)+

global_page_state(NR_INACTIVE_ANON)+

global_page_state(NR_INACTIVE_FILE);

}

ANON代表匿名映射祈餐,沒有后備存儲器擂啥;FILE代表文件映射; 內(nèi)存計算公式= 活動匿名內(nèi)存 + 活動文件內(nèi)存 + 不活動匿名內(nèi)存 + 不活動文件內(nèi)存

4.4 lowmem_scan

當觸發(fā)lmkd,則先殺oom_adj最大的進程, 當oom_adj相等時,則選擇oom_score_adj最大的進程.

staticunsignedlonglowmem_scan(structshrinker*s,structshrink_control*sc)

{

structtask_struct*tsk;

structtask_struct*selected=NULL;

unsignedlongrem=0;

inttasksize;

inti;

shortmin_score_adj=OOM_SCORE_ADJ_MAX+1;

intminfree=0;

intselected_tasksize=0;

shortselected_oom_score_adj;

intarray_size=ARRAY_SIZE(lowmem_adj);

//獲取當前剩余內(nèi)存大小

intother_free=global_page_state(NR_FREE_PAGES)-totalreserve_pages;

intother_file=global_page_state(NR_FILE_PAGES)-

global_page_state(NR_SHMEM)-

total_swapcache_pages();

//獲取數(shù)組大小

if(lowmem_adj_size

array_size=lowmem_adj_size;

if(lowmem_minfree_size

array_size=lowmem_minfree_size;

//遍歷lowmem_minfree數(shù)組找出相應的最小adj值

for(i=0;i

minfree=lowmem_minfree[i];

if(other_free

min_score_adj=lowmem_adj[i];

break;

}

}

if(min_score_adj==OOM_SCORE_ADJ_MAX+1){

return0;

}

selected_oom_score_adj=min_score_adj;

rcu_read_lock();

for_each_process(tsk){

structtask_struct*p;

shortoom_score_adj;

if(tsk->flags&PF_KTHREAD)

continue;

p=find_lock_task_mm(tsk);

if(!p)

continue;

if(test_tsk_thread_flag(p,TIF_MEMDIE)&&

time_before_eq(jiffies,lowmem_deathpending_timeout)){

task_unlock(p);

rcu_read_unlock();

return0;

}

oom_score_adj=p->signal->oom_score_adj;

//小于目標adj的進程帆阳,則忽略

if(oom_score_adj

task_unlock(p);

continue;

}

//獲取的是進程的Resident Set Size哺壶,也就是進程獨占內(nèi)存 + 共享庫大小。

tasksize=get_mm_rss(p->mm);

task_unlock(p);

if(tasksize<=0)

continue;

//算法關鍵蜒谤,選擇oom_score_adj最大的進程中山宾,并且rss內(nèi)存最大的進程.

if(selected){

if(oom_score_adj

continue;

if(oom_score_adj==selected_oom_score_adj&&

tasksize<=selected_tasksize)

continue;

}

selected=p;

selected_tasksize=tasksize;

selected_oom_score_adj=oom_score_adj;

lowmem_print(2,"select '%s' (%d), adj %hd, size %d, to kill\n",

p->comm,p->pid,oom_score_adj,tasksize);

}

if(selected){

longcache_size=other_file*(long)(PAGE_SIZE/1024);

longcache_limit=minfree*(long)(PAGE_SIZE/1024);

longfree=other_free*(long)(PAGE_SIZE/1024);

lowmem_deathpending_timeout=jiffies+HZ;

set_tsk_thread_flag(selected,TIF_MEMDIE);

//向選中的目標進程發(fā)送signal 9來殺掉目標進程

send_sig(SIGKILL,selected,0);

rem+=selected_tasksize;

}

rcu_read_unlock();

returnrem;

}

選擇oom_score_adj最大的進程中,并且rss內(nèi)存最大的進程作為選中要殺的進程鳍徽。

殺進程方式:send_sig(SIGKILL, selected, 0)向選中的目標進程發(fā)送signal 9來殺掉目標進程资锰。

另外,lowmem_minfree[]和lowmem_adj[]數(shù)組大小個數(shù)為6阶祭,通過如下兩條命令:

module_param_named(debug_level,lowmem_debug_level,uint,S_IRUGO|S_IWUSR);

module_param_array_named(adj,lowmem_adj,short,&lowmem_adj_size,S_IRUGO|S_IWUSR);

當如下節(jié)點數(shù)據(jù)發(fā)送變化時绷杜,會通過修改lowmem_minfree[]和lowmem_adj[]數(shù)組:

/sys/module/lowmemorykiller/parameters/minfree

/sys/module/lowmemorykiller/parameters/adj

五、總結(jié)

本 文主要從frameworks的ProcessList.java調(diào)整adj濒募,通過socket通信將事件發(fā)送給native的守護進程 lmkd鞭盟;lmkd再根據(jù)具體的命令來執(zhí)行相應操作,其主要功能 更新進程的oom_score_adj值以及l(fā)owmemorykiller驅(qū)動的parameters(包括minfree和adj)瑰剃;

最 后講到了lowmemorykiller驅(qū)動齿诉,通過注冊shrinker,借助linux標準的內(nèi)存回收機制晌姚,根據(jù)當前系統(tǒng)可用內(nèi)存以及 parameters配置參數(shù)(adj,minfree)來選取合適的selected_oom_score_adj粤剧,再從所有進程中選擇adj大于該目 標值的并且占用rss內(nèi)存最大的進程,將其殺掉舀凛,從而釋放出內(nèi)存俊扳。

5.1 lmkd參數(shù):

oom_adj:代表進程的優(yōu)先級, 數(shù)值越大,優(yōu)先級越低,越容易被殺. 取值范圍[-16, 15]

oom_score_adj: 取值范圍[-1000, 1000]

oom_score:lmk策略中貌似并沒有看到使用的地方,這個應該是oom才會使用猛遍。

想查看某個進程的上述3值,只需要知道pid号坡,查看以下幾個節(jié)點:

/proc//oom_adj

/proc//oom_score_adj

/proc//oom_score

對于oom_adj與oom_score_adj有一定的映射關系:

當oom_adj = 15, 則oom_score_adj=1000;

當oom_adj < 15, 則oom_score_adj= oom_adj * 1000/17;

5.2 driver參數(shù)

/sys/module/lowmemorykiller/parameters/minfree(代表page個數(shù))

/sys/module/lowmemorykiller/parameters/adj(代表oom_score_adj)

例 如:將1,6寫入節(jié)點/sys/module/lowmemorykiller/parameters/adj懊烤,將1024,8192寫入節(jié)點/sys /module/lowmemorykiller/parameters/minfree。策略:當系統(tǒng)可用內(nèi)存低于8192個pages時宽堆,則會殺掉 oom_score_adj>=6的進程腌紧;當系統(tǒng)可用內(nèi)存低于1024個pages時,則會殺掉oom_score_adj>=1的進程畜隶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壁肋,一起剝皮案震驚了整個濱河市号胚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浸遗,老刑警劉巖猫胁,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跛锌,居然都是意外死亡弃秆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門髓帽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菠赚,“玉大人,你說我怎么就攤上這事郑藏『獠椋” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵必盖,是天一觀的道長峡捡。 經(jīng)常有香客問我,道長筑悴,這世上最難降的妖魔是什么们拙? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮阁吝,結(jié)果婚禮上砚婆,老公的妹妹穿的比我還像新娘。我一直安慰自己突勇,他們只是感情好装盯,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甲馋,像睡著了一般埂奈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上定躏,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天账磺,我揣著相機與錄音,去河邊找鬼痊远。 笑死垮抗,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的碧聪。 我是一名探鬼主播冒版,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逞姿!你這毒婦竟也來了辞嗡?” 一聲冷哼從身側(cè)響起捆等,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎续室,沒想到半個月后栋烤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡猎贴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年班缎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片她渴。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡达址,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趁耗,到底是詐尸還是另有隱情沉唠,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布苛败,位于F島的核電站满葛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏罢屈。R本人自食惡果不足惜嘀韧,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缠捌。 院中可真熱鬧锄贷,春花似錦、人聲如沸曼月。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哑芹。三九已至炎辨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聪姿,已是汗流浹背碴萧。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咳燕,地道東北人勿决。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像招盲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嘉冒,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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