鴻蒙內核源碼分析(信號生產篇) | 注意結構體的名字和作用.

信號生產

關于信號篇,本只想寫一篇,但發現把它想簡單了,內容不多,難度極大.整理了好長時間,理解了為何<<深入理解linux內核>>要單獨為它開一章,原因有二

  • 信號相關的結構體多,而且還容易搞混.所以看本篇要注意結構體的名字和作用.
  • 系統調用太多了,涉及面廣,信號的來源分硬件和軟件.相當于軟中斷和硬中斷,這就會涉及到匯編代碼,但信號的處理函數又在用戶空間,CPU是禁止內核態執行用戶態代碼的,所以運行過程需在用戶空間和內核空間來回的折騰,頻繁的切換上下文.

信號思想來自Unix,它老人家已經五十多歲了,但很有活力,許多方面幾乎沒發生大的變化.信號可以由內核產生,也可以由用戶進程產生,并由內核傳送給特定的進程或線程(組),若這個進程定義了自己的信號處理程序,則調用這個程序去處理信號,否則則執行默認的程序或者忽略.

信號為系統提供了一種進程間異步通訊的方式,一個進程不必通過任何操作來等待信號的到達。事實上,進程也不可能知道信號到底什么時候到達。一般來說,只需用戶進程提供信號處理函數,內核會想方設法調用信號處理函數,網上查閱了很多的關于信號的資料.個人想換個視角去看信號.把異步過程理解為生產者(安裝和發送信號)和消費者(捕捉和處理信號)兩個過程

信號分類

每個信號都有一個名字和編號,這些名字都以SIG開頭,例如SIGQUITSIGCHLD等等。
信號定義在signal.h頭文件中,信號名都定義為正整數。
具體的信號名稱可以使用kill -l來查看信號的名字以及序號,信號是從1開始編號的,不存在0號信號。不過kill對于信號0有特殊的應用。啥用呢? 可用來查詢進程是否還在. 敲下 kill 0 pid 就知道了.

信號分為兩大類:可靠信號與不可靠信號,前32種信號為不可靠信號,后32種為可靠信號。

  • 不可靠信號: 也稱為非實時信號,不支持排隊,信號可能會丟失, 比如發送多次相同的信號, 進程只能收到一次. 信號值取值區間為1~31;

  • 可靠信號: 也稱為實時信號,支持排隊, 信號不會丟失, 發多少次, 就可以收到多少次. 信號值取值區間為32~64

  #define SIGHUP    1   //終端掛起或者控制進程終止
  #define SIGINT    2   //鍵盤中斷(ctrl + c)
  #define SIGQUIT   3   //鍵盤的退出鍵被按下
  #define SIGILL    4   //非法指令
  #define SIGTRAP   5   //跟蹤陷阱(trace trap),啟動進程,跟蹤代碼的執行
  #define SIGABRT   6   //由abort(3)發出的退出指令
  #define SIGIOT    SIGABRT //abort發出的信號
  #define SIGBUS    7   //總線錯誤 
  #define SIGFPE    8   //浮點異常
  #define SIGKILL   9   //常用的命令 kill 9 123 | 不能被忽略、處理和阻塞
  #define SIGUSR1   10  //用戶自定義信號1 
  #define SIGSEGV   11  //無效的內存引用, 段違例(segmentation     violation),進程試圖去訪問其虛地址空間以外的位置 
  #define SIGUSR2   12  //用戶自定義信號2
  #define SIGPIPE   13  //向某個非讀管道中寫入數據 
  #define SIGALRM   14  //由alarm(2)發出的信號,默認行為為進程終止
  #define SIGTERM   15  //終止信號
  #define SIGSTKFLT 16  //棧溢出
  #define SIGCHLD   17  //子進程結束信號
  #define SIGCONT   18  //進程繼續(曾被停止的進程)
  #define SIGSTOP   19  //終止進程       | 不能被忽略、處理和阻塞
  #define SIGTSTP   20  //控制終端(tty)上 按下停止鍵
  #define SIGTTIN   21  //進程停止,后臺進程企圖從控制終端讀
  #define SIGTTOU   22  //進程停止,后臺進程企圖從控制終端寫
  #define SIGURG    23  //I/O有緊急數據到達當前進程
  #define SIGXCPU   24  //進程的CPU時間片到期
  #define SIGXFSZ   25  //文件大小的超出上限
  #define SIGVTALRM 26  //虛擬時鐘超時
  #define SIGPROF   27  //profile時鐘超時
  #define SIGWINCH  28  //窗口大小改變
  #define SIGIO     29  //I/O相關
  #define SIGPOLL   29  //
  #define SIGPWR    30  //電源故障,關機
  #define SIGSYS    31  //系統調用中參數錯,如系統調用號非法 
  #define SIGUNUSED SIGSYS//不使用

  #define _NSIG 65

信號來源

信號來源分為硬件類和軟件類:

  • 硬件類
    • 用戶輸入:比如在終端上按下組合鍵ctrl+C,產生SIGINT信號;
    • 硬件異常:CPU檢測到內存非法訪問等異常,通知內核生成相應信號,并發送給發生事件的進程;
  • 軟件類
    • 通過系統調用,發送signal信號:kill()raise()sigqueue()alarm()setitimer()abort()
      • kill 命令就是一個發送信號的工具,用于向進程或進程組發送信號.例如: kill 9 PID (SIGKILL)來殺死PID進程.
      • sigqueue():只能向一個進程發送信號,不能向進程組發送信號;主要針對實時信號提出,與sigaction()組合使用,當然也支持非實時信號的發送;
      • alarm():用于調用進程指定時間后發出SIGALARM信號;
      • setitimer():設置定時器,計時達到后給進程發送SIGALRM信號,功能比alarm更強大;
      • abort():向進程發送SIGABORT信號,默認進程會異常退出。
      • raise():用于向進程自身發送信號;

信號與進程的關系

主要是通過系統調用 sigaction將用戶態信號處理函數注冊到PCB保存.所有進程的任務都共用這個信號注冊函數sigHandler,在信號的消費階段內核用一種特殊的方式'回調'它.

typedef struct ProcessCB {//PCB中關于信號的信息
    UINTPTR              sigHandler;   /**< signal handler */   //捕捉信號后的處理函數
    sigset_t             sigShare;     /**< signal share bit */ //信號共享位,64個信號各站一位
}LosProcessCB;
typedef unsigned _Int64 sigset_t; //一個64位的變量,每個信號代表一位.

struct sigaction {//信號處理機制結構體
    union {
        void (*sa_handler)(int); //信號處理函數——普通版
        void (*sa_sigaction)(int, siginfo_t *, void *);//信號處理函數——高級版
    } __sa_handler;
    sigset_t sa_mask;//指定信號處理程序執行過程中需要阻塞的信號;
    int sa_flags;    //標示位
                     // SA_RESTART:使被信號打斷的syscall重新發起。
                     // SA_NOCLDSTOP:使父進程在它的子進程暫停或繼續運行時不會收到 SIGCHLD 信號。
                     // SA_NOCLDWAIT:使父進程在它的子進程退出時不會收到SIGCHLD信號,這時子進程如果退出也不會成為僵 尸進程。
                     // SA_NODEFER:使對信號的屏蔽無效,即在信號處理函數執行期間仍能發出這個信號。
                     // SA_RESETHAND:信號處理之后重新設置為默認的處理方式。
                     // SA_SIGINFO:使用sa_sigaction成員而不是sa_handler作為信號處理函數。
    void (*sa_restorer)(void);
};
typedef struct sigaction sigaction_t;

解讀

  • 每個信號都對應一個位. 信號從1開始編號 [1 ~ 64] 對應 sigShare的[0 ~ 63]位,所以中間會差一個.記住這點,后續代碼會提到.
  • sigHandler信號處理函數的注冊過程,由系統調用sigaction(用戶空間) -> OsSigAction(內核空間)完成綁定動作.
    #include <signal.h>
    int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
    int OsSigAction(int sig, const sigaction_t *act, sigaction_t *oact)
    {
        UINTPTR addr;
        sigaction_t action;

        if (!GOOD_SIGNO(sig) || sig < 1 || act == NULL) {
            return -EINVAL;
        }
        //將數據從用戶空間拷貝到內核空間
        if (LOS_ArchCopyFromUser(&action, act, sizeof(sigaction_t)) != LOS_OK) {
            return -EFAULT;
        }

        if (sig == SIGSYS) {//鴻蒙此處通過錯誤的系統調用 來安裝信號處理函數,有點巧妙. 
            addr = OsGetSigHandler();//獲取進程信號處理函數
            if (addr == 0) {//進程沒有設置信號處理函數時
                OsSetSigHandler((unsigned long)(UINTPTR)action.sa_handler);//設置進程信號處理函數——普通版
                return LOS_OK;
            }
            return -EINVAL;
        }

        return LOS_OK;
    }
*   `sigaction(...)`第一個參數是要安裝的信號; 第二個參數與sigaction函數同名的結構體,這里會讓人很懵,函數名和結構體一直,沒明白為毛要這么搞? 結構體內定義了信號處理方法;第三個為輸出參數,將信號的當前的sigaction結構帶回.但鴻蒙顯然沒有認真對待第三個參數.把`musl`實現給閹割了.
*   對結構體的`sigaction`鴻蒙目前只支持信號處理函數——普通版,`sa_handler`表示自定義信號處理函數,該函數返回值為void,可以帶一個int參數,通過參數可以得知當前信號的編號,這樣就可以用同一個函數處理多種信號。
*   `sa_mask`指定信號處理程序執行過程中需要阻塞的信號。
*   `sa_flags`字段包含一些選項,具體看注釋
*   `sa_sigaction`是實時信號的處理函數,`union`二選一.鴻蒙暫時不支持這種方式.

信號與任務的關系

typedef struct {//TCB中關于信號的信息
    sig_cb          sig;    //信號控制塊,用于異步通信,類似于 linux singal模塊
} LosTaskCB;
typedef struct {//信號控制塊(描述符)
    sigset_t sigFlag;       //不屏蔽的信號標簽集
    sigset_t sigPendFlag;   //信號阻塞標簽集,記錄因哪些信號被阻塞
    sigset_t sigprocmask; /* Signals that are blocked            */ //進程屏蔽了哪些信號
    sq_queue_t sigactionq;  //信號捕捉隊列                    
    LOS_DL_LIST waitList;   //等待鏈表,上面掛的是等待信號到來的任務, 可查找 OsTaskWait(&sigcb->waitList, timeout, TRUE)  理解                      
    sigset_t sigwaitmask; /* Waiting for pending signals         */ //任務在等待阻塞信號
    siginfo_t sigunbinfo; /* Signal info when task unblocked     */ //任務解鎖時的信號信息
    sig_switch_context context; //信號切換上下文, 用于保存切換現場, 比如發生系統調用時的返回,涉及同一個任務的兩個棧進行切換                           
} sig_cb;

解讀

  • 系列篇已多次說過,進程只是管理資源的容器,真正讓cpu干活的是任務task,所以發給進程的信號最終還是需要分發給具體任務來處理.所以能想到的是關于任務部分會更復雜.
  • context信號處理很復雜的原因在于信號的發起在用戶空間,發送需要系統調用,而處理信號的函數又是用戶空間提供的, 所以需要反復的切換任務上下文.而且還有硬中斷的問題,比如 ctrl + c ,需要從硬中斷中回調用戶空間的信號處理函數,處理完了再回到內核空間,最后回到用戶空間.沒聽懂吧,我自己都說暈了,所以需要專門的一篇來說清楚信號的處理問題.本篇不展開說.
  • sig_cb結構體是任務處理信號的結構體,要響應,屏蔽哪些信號等等都由它完成,這個結構體雖不復雜,但是很繞,很難搞清楚它們之間的區別.筆者是經過一番痛苦的閱讀理解后才明白各自的含義.并想通過用打比方的例子試圖讓大家明白.
  • 以下用追女孩打比方理解.任務相當于某個男,沒錯說的就是屏幕前的你,除了苦逼的碼農誰會有耐心能堅持看到這里.64個信號對應64個女孩.允許一男同時追多個女孩,女孩也可同時被多個男追.女孩也可以主動追男的.理解如下:
  • waitList等待信號的任務鏈表,上面掛的是因等待信號而被阻塞的任務.眾男在排隊追各自心愛的女孩們,處于無所事事的掛起的狀態,等待女孩們的出現.
  • sigwaitmask任務在等待的信號集合,只有這些信號能喚醒任務.相當于列出喜歡的各位女孩,只要出現一位就能讓你滿血復活.
  • sigprocmask指任務對哪些信號不感冒.來了也不處理.相當于列出不喜歡的各位女孩,請她們別來騷擾你,嘚瑟.
  • sigPendFlag信號到達但并未喚醒任務.相當于喜歡你的女孩來追你,但她不在你喜歡的列表內,結果是不搭理人家繼續等喜歡的出現.
  • sigFlag記錄不屏蔽的信號集合,相當于你并不反感的女孩們.記錄來過的那些女孩(除掉你不喜歡的).

信號發送過程

用戶進程調用kill()的過程如下:

kill(pid_t pid, int sig) - 系統調用   
|                    用戶空間
---------------------------------------------------------------------------------------
|                    內核空間
SysKill(...)
|---> OsKillLock(...)
    |---> OsKill(.., OS_USER_KILL_PERMISSION)
        |---> OsDispatch()  //鑒權,向進程發送信號
            |---> OsSigProcessSend()    //選擇任務發送信號
                |---> OsSigProcessForeachChild(..,ForEachTaskCB handler,..)
                    |---> SigProcessKillSigHandler() //處理 SIGKILL
                        |---> OsTaskWake() //喚醒所有等待任務
                        |---> OsSigEmptySet() //清空信號等待集
                    |---> SigProcessSignalHandler()
                        |---> OsTcbDispatch() //向目標任務發送信號
                            |---> OsTaskWake() //喚醒任務
                            |---> OsSigEmptySet() //清空信號等待集

流程

  • 通過 系統調用 kill 陷入內核空間

  • 因為是用戶態進程,使用OS_USER_KILL_PERMISSION權限發送信號

    #define OS_KERNEL_KILL_PERMISSION 0U    //內核態 kill 權限
    #define OS_USER_KILL_PERMISSION   3U    //用戶態 kill 權限
    
    
  • 鑒權之后進程輪詢任務組,向目標任務發送信號.這里分三種情況:

    • SIGKILL信號,將所有等待任務喚醒,拉入就緒隊列等待被調度執行,并情況信號等待集
    • SIGKILL信號時,將通過sigwaitmasksigprocmask過濾,找到一個任務向它發送信號OsTcbDispatch.

代碼細節

    int OsKill(pid_t pid, int sig, int permission)
    {
        siginfo_t info;
        int ret;

        /* Make sure that the para is valid */
        if (!GOOD_SIGNO(sig) || pid < 0) {//有效信號 [0,64]
            return -EINVAL;
        }
        if (OsProcessIDUserCheckInvalid(pid)) {//檢查參數進程 
            return -ESRCH;
        }

        /* Create the siginfo structure */ //創建信號結構體
        info.si_signo = sig;    //信號編號
        info.si_code = SI_USER; //來自用戶進程信號
        info.si_value.sival_ptr = NULL;

        /* Send the signal */
        ret = OsDispatch(pid, &info, permission);//發送信號
        return ret;
    }

    //信號分發
    int OsDispatch(pid_t pid, siginfo_t *info, int permission)
    {
        LosProcessCB *spcb = OS_PCB_FROM_PID(pid);//找到這個進程
        if (OsProcessIsUnused(spcb)) {//進程是否還在使用,不一定是當前進程但必須是個有效進程
            return -ESRCH;
        }
    #ifdef LOSCFG_SECURITY_CAPABILITY   //啟用能力安全模式
        LosProcessCB *current = OsCurrProcessGet();//獲取當前進程

        /* If the process you want to kill had been inactive, but still exist. should return LOS_OK */
        if (OsProcessIsInactive(spcb)) {//如果要終止的進程處于非活動狀態,但仍然存在,應該返回OK
            return LOS_OK;
        }

        /* Kernel process always has kill permission and user process should check permission *///內核進程總是有kill權限,用戶進程需要檢查權限
        if (OsProcessIsUserMode(current) && !(current->processStatus & OS_PROCESS_FLAG_EXIT)) {//用戶進程檢查能力范圍
            if ((current != spcb) && (!IsCapPermit(CAP_KILL)) && (current->user->userID != spcb->user->userID)) {
                return -EPERM;
            }
        }
    #endif
        if ((permission == OS_USER_KILL_PERMISSION) && (OsSignalPermissionToCheck(spcb) < 0)) {
            return -EPERM;
        }
        return OsSigProcessSend(spcb, info);//給參數進程發送信號
    }

    //給參數進程發送參數信號
    int OsSigProcessSend(LosProcessCB *spcb, siginfo_t *sigInfo)
    {
        int ret;
        struct ProcessSignalInfo info = {
            .sigInfo = sigInfo, //信號內容
            .defaultTcb = NULL, //以下四個值將在OsSigProcessForeachChild中根據條件完善
            .unblockedTcb = NULL,
            .awakenedTcb = NULL,
            .receivedTcb = NULL
        };
        //總之是要從進程中找個至少一個任務來接受這個信號,優先級
        //awakenedTcb > receivedTcb > unblockedTcb > defaultTcb
        /* visit all taskcb and dispatch signal */ //訪問所有任務和分發信號
        if ((info.sigInfo != NULL) && (info.sigInfo->si_signo == SIGKILL)) {//需要干掉進程時 SIGKILL = 9, #linux kill 9 14
            (void)OsSigProcessForeachChild(spcb, SigProcessKillSigHandler, &info);//進程要被干掉了,通知所有task做善后處理
            OsSigAddSet(&spcb->sigShare, info.sigInfo->si_signo);
            OsWaitSignalToWakeProcess(spcb);//等待信號喚醒進程
            return 0;
        } else {
            ret = OsSigProcessForeachChild(spcb, SigProcessSignalHandler, &info);//進程通知所有task處理信號
        }
        if (ret < 0) {
            return ret;
        }
        SigProcessLoadTcb(&info, sigInfo);
        return 0;
    }
    //讓進程的每一個task執行參數函數
    int OsSigProcessForeachChild(LosProcessCB *spcb, ForEachTaskCB handler, void *arg)
    {
        int ret;

        /* Visit the main thread last (if present) */   
        LosTaskCB *taskCB = NULL;//遍歷進程的 threadList 鏈表,里面存放的都是task節點
        LOS_DL_LIST_FOR_EACH_ENTRY(taskCB, &(spcb->threadSiblingList), LosTaskCB, threadList) {//遍歷進程的任務列表
            ret = handler(taskCB, arg);//回調參數函數
            OS_RETURN_IF(ret != 0, ret);//這個宏的意思就是只有ret = 0時,啥也不處理.其余就返回 ret
        }
        return LOS_OK;
    }

  • 如果是 SIGKILL信號,讓spcb的所有任務執行SigProcessKillSigHandler函數,查看旗下的所有任務是否又在等待這個信號的,如果有就將任務喚醒,放在就緒隊列等待被調度執行.
    //進程收到 SIGKILL 信號后,通知任務tcb處理.
    static int SigProcessKillSigHandler(LosTaskCB *tcb, void *arg)
    {
        struct ProcessSignalInfo *info = (struct ProcessSignalInfo *)arg;//轉參

        if ((tcb != NULL) && (info != NULL) && (info->sigInfo != NULL)) {//進程有信號
            sig_cb *sigcb = &tcb->sig;
            if (!LOS_ListEmpty(&sigcb->waitList) && OsSigIsMember(&sigcb->sigwaitmask, info->sigInfo->si_signo)) {//如果任務在等待這個信號
                OsTaskWake(tcb);//喚醒這個任務,加入進程的就緒隊列,并不申請調度
                OsSigEmptySet(&sigcb->sigwaitmask);//清空信號等待位,不等任何信號了.因為這是SIGKILL信號
            }
        }
        return 0;
    }
  • SIGKILL信號,讓spcb的所有任務執行SigProcessSignalHandler函數
    static int SigProcessSignalHandler(LosTaskCB *tcb, void *arg)
    {
        struct ProcessSignalInfo *info = (struct ProcessSignalInfo *)arg;//先把參數解出來
        int ret;
        int isMember;

        if (tcb == NULL) {
            return 0;
        }

        /* If the default tcb is not setted, then set this one as default. */
        if (!info->defaultTcb) {//如果沒有默認發送方的任務,即默認參數任務.
            info->defaultTcb = tcb;
        }

        isMember = OsSigIsMember(&tcb->sig.sigwaitmask, info->sigInfo->si_signo);//任務是否在等待這個信號
        if (isMember && (!info->awakenedTcb)) {//是在等待,并尚未向該任務時發送信號時
            /* This means the task is waiting for this signal. Stop looking for it and use this tcb.
            * The requirement is: if more than one task in this task group is waiting for the signal,
            * then only one indeterminate task in the group will receive the signal.
            */
            ret = OsTcbDispatch(tcb, info->sigInfo);//發送信號,注意這是給其他任務發送信號,tcb不是當前任務
            OS_RETURN_IF(ret < 0, ret);//這種寫法很有意思

            /* set this tcb as awakenedTcb */
            info->awakenedTcb = tcb;
            OS_RETURN_IF(info->receivedTcb != NULL, SIG_STOP_VISIT); /* Stop search */
        }
        /* Is this signal unblocked on this thread? */
        isMember = OsSigIsMember(&tcb->sig.sigprocmask, info->sigInfo->si_signo);//任務是否屏蔽了這個信號
        if ((!isMember) && (!info->receivedTcb) && (tcb != info->awakenedTcb)) {//沒有屏蔽,有喚醒任務沒有接收任務.
            /* if unblockedTcb of this signal is not setted, then set it. */
            if (!info->unblockedTcb) {
                info->unblockedTcb = tcb;
            }

            ret = OsTcbDispatch(tcb, info->sigInfo);//向任務發送信號
            OS_RETURN_IF(ret < 0, ret);
            /* set this tcb as receivedTcb */
            info->receivedTcb = tcb;//設置這個任務為接收任務
            OS_RETURN_IF(info->awakenedTcb != NULL, SIG_STOP_VISIT); /* Stop search */
        }
        return 0; /* Keep searching */
    }

解讀

  • 函數的意思是,當進程中有多個任務在等待這個信號時,發送信號給第一個等待的任務awakenedTcb.
  • 如果沒有任務在等待信號,那就從不屏蔽這個信號的任務集中隨機找一個receivedTcb接受信號.
  • 只要不屏蔽 unblockedTcb就有值,隨機的.
  • 如果上面的都不滿足,信號發送給defaultTcb.
  • 尋找發送任務的優先級是 awakenedTcb > receivedTcb > unblockedTcb > defaultTcb

信號相關函數

信號集操作函數

  • sigemptyset(sigset_t *set):信號集全部清0;
  • sigfillset(sigset_t *set): 信號集全部置1,則信號集包含linux支持的64種信號;
  • sigaddset(sigset_t *set, int signum):向信號集中加入signum信號;
  • sigdelset(sigset_t *set, int signum):向信號集中刪除signum信號;
  • sigismember(const sigset_t *set, int signum):判定信號signum是否存在信號集中。

信號阻塞函數

  • sigprocmask(int how, const sigset_t *set, sigset_t *oldset)); 不同how參數,實現不同功能
    • SIG_BLOCK:將set指向信號集中的信號,添加到進程阻塞信號集;
    • SIG_UNBLOCK:將set指向信號集中的信號,從進程阻塞信號集刪除;
    • SIG_SETMASK:將set指向信號集中的信號,設置成進程阻塞信號集;
  • sigpending(sigset_t *set)):獲取已發送到進程,卻被阻塞的所有信號;
  • sigsuspend(const sigset_t *mask)):用mask代替進程的原有掩碼,并暫停進程執行,直到收到信號再恢復原有掩碼并繼續執行進程。

寫在最后

  • 如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
  • 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
  • 關注小編,同時可以期待后續文章ing??,不定期分享原創知識。
  • 想要獲取更多完整鴻蒙最新學習知識點,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容