『Android Q 源码分析』-Android 10.0 WatchDog源码解析

概览

(本文系统源码基于Andoroid 10.0.0-r16)
Watchdog的中文叫“看门狗”,最早引入Watchdog是在单片机系统中,由于单片机的工作环境容易受到外界磁场的干扰,导致程序“跑飞”,造成整个系统无法正常工作,因此,引入了一个“看门狗”,对单片机的运行状态进行实时监测,针对运行故障做一些保护处理,譬如让系统重启。这种Watchdog属于硬件层面,必须有硬件电路的支持。

Linux也引入了Watchdog,在Linux内核下,当Watchdog启动后,便设定了一个定时器,如果在超时时间内没有对/dev/Watchdog进行写操作,则会导致系统重启,通过定时器实现的Watchdog属于软件层面

Android设计了一个软件层面Watchdog,用于保护一些重要的系统服务,当出现故障时,通常会让Android系统重启。由于这种机制的存在,就经常会出现一些system_server进程被Watchdog杀掉而发生系统重启的问题。
原理就是每到达一定的时间,获取正在监控的锁(Monitor.monitor()),能否获得锁从而判断是否产生了死锁。

Watchdog流程

<center style="font-size:13px;color:#C0C0C0;">Watchdog流程</center>

Watchdog初始化

首先我们可以看到WatchDog是继承于Thread类,而实际上WatchDog也是单线程运行的。

public class Watchdog extends Thread {}

接下来我们可以看下Watchdog的构造函数:
frameworks/base/services/core/java/com/android/server/Watchdog.java

private Watchdog() {
    // 设置线程名字为watchdog
    super("watchdog");
    // Initialize handler checkers for each common thread we want to check.
    // 请注意,我们目前没有检查后台线程,因为它可能会持有较长时间运行的操作,
    // 而不能保证那里的操作的及时性。

    // FgThread monitor
    mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
            "foreground thread", DEFAULT_TIMEOUT);
    mHandlerCheckers.add(mMonitorChecker);
    
    // main thread monitor
    mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
            "main thread", DEFAULT_TIMEOUT));
            
    // UiThread monitor
    mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
            "ui thread", DEFAULT_TIMEOUT));
            
    // IoThread monitor
    mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
            "i/o thread", DEFAULT_TIMEOUT));
            
    // DisplayThread monitor
    mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
            "display thread", DEFAULT_TIMEOUT));
    
    // AnimationThread monitor
    mHandlerCheckers.add(new HandlerChecker(AnimationThread.getHandler(),
            "animation thread", DEFAULT_TIMEOUT));
    
    // SurfaceAnimationThread monitor
    mHandlerCheckers.add(new HandlerChecker(SurfaceAnimationThread.getHandler(),
            "surface animation thread", DEFAULT_TIMEOUT));

    // BinderThreadMonitor 主要用于检测binder线程是否达到连接上限16个
    // >= 16个则阻塞线程等待mThreadCountDecrement唤醒
    addMonitor(new BinderThreadMonitor());
    
    // 用于对fd数量进行检测 只会在userdebug或者eng版本开启
    // 值是厂商设置的 达到这个值 - 12 就会在/data/anr/ dump fd的信息
    mOpenFdMonitor = OpenFdMonitor.create();

    assert DB ||
            DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
}

可以看出我们在创建出Watchdog的时候先把一些重要的线程加到Watchdog的mHandlerCheckers里面,当Watchdog执行run()方法的时候就会依次对mHandlerCheckers里面的HandlerChecker对象进行轮询,查看是否有超时的线程。有的话一般都是重启来解决。

那我们在哪里调用这个设置为private的构造函数呢?就是在本类的getInstance()

public static Watchdog getInstance() {
    if (sWatchdog == null) {
        sWatchdog = new Watchdog();
    }

    return sWatchdog;
}

我们现在再看深一层,那究竟谁调用了getInstance()方法呢?我们动用ctrl +
鼠标点一下就弹出一系列的调用者。查看一番之后,可以观察到是SystemServer调用了我们Watchdog的getInstance()方法进行初始化。
frameworks/base/services/java/com/android/server/SystemServer.java

private void startBootstrapServices() {
    final Watchdog watchdog = Watchdog.getInstance();
    watchdog.start();
    // ...
}

在调用了初始化的getInstance()之后就马上调用了watchdog的start()方法,其实也就是父类Thread的start()方法,这时候我们的“看门狗”就跑起来了(执行了run()方法),而且可以看到watchdog是在startBootstrapServices()中的第一句就进行初始化了,SystemServer中初始化服务的顺序是这样的:

  1. startBootstrapServices();
  2. startCoreServices();
  3. startOtherServices();

这说明了watchdog在众多的服务中,优先级还是很高的,那有没有同学会想到是为什么呢?答案就是很多服务的超时结束机制都是依赖watchdog提供的,所以watchdog必须要比其他服务早初始化。

我们再来看一下WatchDog的init()方法,因为依赖于ActivityManagerService,所以我们将在ActivityManagerService初始化之后再执行这个方法。

public void init(Context context, ActivityManagerService activity) {
    mActivity = activity;
    context.registerReceiver(new RebootRequestReceiver(),
            new IntentFilter(Intent.ACTION_REBOOT),
            android.Manifest.permission.REBOOT, null);
}

可以看到这里通过context注册了一个广播接收器,而这个RebootRequestReceiver用来接收重启的广播来进行手机重启的,是Watchdog中定义的一个内部类,RebootRequestReceiver的代码如下:

final class RebootRequestReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context c, Intent intent) {
        if (intent.getIntExtra("nowait", 0) != 0) {
            rebootSystem("Received ACTION_REBOOT broadcast");
            return;
        }
        Slog.w(TAG, "Unsupported ACTION_REBOOT broadcast: " + intent);
    }
}

void rebootSystem(String reason) {
    Slog.i(TAG, "Rebooting system because: " + reason);
    IPowerManager pms = (IPowerManager)ServiceManager.getService(Context.POWER_SERVICE);
    try {
        // 这里是手机重启而不是系统重启
        pms.reboot(false, reason, false);
    } catch (RemoteException ex) {
    }
}

HandlerChecker

我们再来看看在Watchdog中相对比较重要的HandlerChecker内部类是长什么样子的。

/**
 * Used for checking status of handle threads and scheduling monitor callbacks.
 */
public final class HandlerChecker implements Runnable {
    // 需要检测的线程对应的handler
    private final Handler mHandler;
    // 进行日志打印的时候知道是哪个handler出现问题
    private final String mName;
    // 超时时间
    private final long mWaitMax;
    // 检测的Monitor数组
    private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
    // 还未加入mMonitors中的Monitor 
    // 等待mCompleted为true的时候才一次性加入mMonitors中
    private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();
    // 是否检查完成的标志位 空闲的话默认为true
    private boolean mCompleted;
    // 目前正在检查的Monitor
    private Monitor mCurrentMonitor;
    // 检查开始的时间
    private long mStartTime;
    // mPauseCount > 0则说明这个HandlerChecker处于暂停状态
    private int mPauseCount;

    HandlerChecker(Handler handler, String name, long waitMaxMillis) {
        mHandler = handler;
        mName = name;
        mWaitMax = waitMaxMillis;
        mCompleted = true;
    }

    // 注释1
    void addMonitorLocked(Monitor monitor) {
        // We don't want to update mMonitors when the Handler is in the middle of checking
        // all monitors. We will update mMonitors on the next schedule if it is safe
        mMonitorQueue.add(monitor);
    }

    // 注释2
    public void scheduleCheckLocked() {
        if (mCompleted) {
            // Safe to update monitors in queue, Handler is not in the middle of work
            mMonitors.addAll(mMonitorQueue);
            mMonitorQueue.clear();
        }
        // 待检测的mMonitors为零且looper正在从队列中轮询任务 
        // 或者正在暂停的情况下 不要进行一些数据的重置
        if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
                || (mPauseCount > 0)) {
            mCompleted = true;
            return;
        }
        if (!mCompleted) {
            // we already have a check in flight, so no need
            return;
        }

        mCompleted = false;
        mCurrentMonitor = null;
        mStartTime = SystemClock.uptimeMillis();
        // 往handler前面插入任务(插队)
        mHandler.postAtFrontOfQueue(this);
    }

    // 是否超时
    boolean isOverdueLocked() {
        return (!mCompleted) && (SystemClock.uptimeMillis() > mStartTime + mWaitMax);
    }
    
    // 通过mStartTime、mWaitMax和mCompleted 进行状态的判断
    // 前 mWaitMax/2 秒为WAITING状态,后mWaitMax/2为WAITED_HALF状态
    // 大于mWaitMax为超时状态也就是OVERDUE状态
    public int getCompletionStateLocked() {
        if (mCompleted) {
            return COMPLETED;
        } else {
            long latency = SystemClock.uptimeMillis() - mStartTime;
            if (latency < mWaitMax/2) {
                return WAITING;
            } else if (latency < mWaitMax) {
                return WAITED_HALF;
            }
        }
        return OVERDUE;
    }

    public Thread getThread() {
        return mHandler.getLooper().getThread();
    }

    public String getName() {
        return mName;
    }

    // 获取Blocked状态的描述
    // 在哪个名字的HandlerChecker中 或者 当前handler执行哪个monitor 
    String describeBlockedStateLocked() {
        if (mCurrentMonitor == null) {
            return "Blocked in handler on " + mName + " (" + getThread().getName() + ")";
        } else {
            return "Blocked in monitor " + mCurrentMonitor.getClass().getName()
                    + " on " + mName + " (" + getThread().getName() + ")";
        }
    }

    @Override
    public void run() {
        // 一旦我们到达这里,我们确保即使我们调用#addMonitorLocked也不会改变mMonitors,
        // 因为我们首先将新的监视器添加到mMonitorQueue中,
        // 并在下一次mCompleted为真时将它们移到mMonitors中,此时我们已经完成了这个方法的执行。
        // 所以这里用了两个数组分别存储Monitor
        final int size = mMonitors.size();
        for (int i = 0 ; i < size ; i++) {
            synchronized (Watchdog.this) {
                mCurrentMonitor = mMonitors.get(i);
            }
            mCurrentMonitor.monitor();
        }

        synchronized (Watchdog.this) {
            mCompleted = true;
            mCurrentMonitor = null;
        }
    }

    // 暂停这个HandlerChecker
    public void pauseLocked(String reason) {
        mPauseCount++;
        // Mark as completed, because there's a chance we called this after the watchog
        // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
        // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
        mCompleted = true;
        Slog.i(TAG, "Pausing HandlerChecker: " + mName + " for reason: "
                + reason + ". Pause count: " + mPauseCount);
    }

    // 继续这个HandlerChecker
    public void resumeLocked(String reason) {
        if (mPauseCount > 0) {
            mPauseCount--;
            Slog.i(TAG, "Resuming HandlerChecker: " + mName + " for reason: "
                    + reason + ". Pause count: " + mPauseCount);
        } else {
            Slog.wtf(TAG, "Already resumed HandlerChecker: " + mName);
        }
    }
}

从注释1开始,可以看到大部分方法后缀都加上了Locked,这是因为在Watchdog调用的时候都会加上synchronize锁,保证不会发生多线程并发导致的问题。
而注释2的scheduleCheckLocked()方法在Watchdog中以间隔30s执行一次。

以下是实现了Monitor接口的类:


实现了Monitor的接口的类

<center style="font-size:13px;color:#C0C0C0;">实现了Monitor的接口的类</center>

WatchDog中比较重要的几个函数

// 设置activityController 用处在后面Watchdog的run()方法会介绍到
public void setActivityController(IActivityController controller) {
    synchronized (this) {
        mController = controller;
    }
}

// 设置watchdog超时后是否能重启手机
public void setAllowRestart(boolean allowRestart) {
    synchronized (this) {
        mAllowRestart = allowRestart;
    }
}

// 在FgThread中添加对monitor的监控
public void addMonitor(Monitor monitor) {
    synchronized (this) {
        mMonitorChecker.addMonitorLocked(monitor);
    }
}

// 添加需要监控的handler 超时时间默认为60s
public void addThread(Handler thread) {
    addThread(thread, DEFAULT_TIMEOUT);
}

// 添加需要监控的handler 可以自定义超时时间
public void addThread(Handler thread, long timeoutMillis) {
    synchronized (this) {
        final String name = thread.getLooper().getThread().getName();
        mHandlerCheckers.add(new HandlerChecker(thread, name, timeoutMillis));
    }
}

// 暂停当前正在运行的线程的监视操作。在执行可能错误触发watchdog的长时间运行的操作之前非常有用。
// 每个调用都需要一个匹配的{@link #resumeWatchingCurrentThread}调用。
public void pauseWatchingCurrentThread(String reason) {
    synchronized (this) {
        for (HandlerChecker hc : mHandlerCheckers) {
            if (Thread.currentThread().equals(hc.getThread())) {
                hc.pauseLocked(reason);
            }
        }
    }
}

// 恢复watchdog的检测状态
public void resumeWatchingCurrentThread(String reason) {
    synchronized (this) {
        for (HandlerChecker hc : mHandlerCheckers) {
            if (Thread.currentThread().equals(hc.getThread())) {
                hc.resumeLocked(reason);
            }
        }
    }
}

// 计算HandlerChecker的状态 只要一个handler中有其中一个HandlerChecker超时了 
// 那Watchdog就超时了
private int evaluateCheckerCompletionLocked() {
    int state = COMPLETED;
    for (int i=0; i<mHandlerCheckers.size(); i++) {
        HandlerChecker hc = mHandlerCheckers.get(i);
        state = Math.max(state, hc.getCompletionStateLocked());
    }
    return state;
}

// 获取哪个HandlerChecker超时,并加入数组返回
private ArrayList<HandlerChecker> getBlockedCheckersLocked() {
    ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>();
    for (int i=0; i<mHandlerCheckers.size(); i++) {
        HandlerChecker hc = mHandlerCheckers.get(i);
        if (hc.isOverdueLocked()) {
            checkers.add(hc);
        }
    }
    return checkers;
}

// 获取每个超时HandlerChecker的超时原因,并组成String返回
private String describeCheckersLocked(List<HandlerChecker> checkers) {
    StringBuilder builder = new StringBuilder(128);
    for (int i=0; i<checkers.size(); i++) {
        if (builder.length() > 0) {
            builder.append(", ");
        }
        builder.append(checkers.get(i).describeBlockedStateLocked());
    }
    return builder.toString();
}

WatchDog.run()

接下来我们就看一下Watchdog的核心代码,run()方法

@Override
public void run() {
    // 是否在等待的前半段时间 true则为等待的后半段时间
    boolean waitedHalf = false;
    // 死循环
    while (true) {
        final List<HandlerChecker> blockedCheckers;
        // 超时原因 用于日志打印
        final String subject;
        // 是否允许system_server重启 默认为true
        // 可以通过watchdog.setAllowRestart()重新设置值
        final boolean allowRestart;
        // 调试进程连接数 有的话会赋值为2
        int debuggerWasConnected = 0;
        synchronized (this) {
            // CHECK_INTERVAL为30s
            long timeout = CHECK_INTERVAL;
            // 逐一执行每个HandlerChecker的scheduleCheckLocked()方法
            for (int i=0; i<mHandlerCheckers.size(); i++) {
                HandlerChecker hc = mHandlerCheckers.get(i);
                hc.scheduleCheckLocked();
            }
            
            if (debuggerWasConnected > 0) {
                debuggerWasConnected--;
            }

            
            // 这里用uptimeMillis() 是因为只会在设备唤醒的时候计算超时,
            // 设备休眠计算时间会导致错误重启
            long start = SystemClock.uptimeMillis();
            while (timeout > 0) {
                if (Debug.isDebuggerConnected()) {
                    debuggerWasConnected = 2;
                }
                try {
                    // 等待30s
                    wait(timeout);
                    // Note: mHandlerCheckers and mMonitorChecker may have changed after waiting
                } catch (InterruptedException e) {
                    Log.wtf(TAG, e);
                }
                if (Debug.isDebuggerConnected()) {
                    debuggerWasConnected = 2;
                }
                // 有可能wait到一半的时候发生了InterruptedException 导致时间没有走完
                // 只要没有消耗完timeout的值 就继续等待
                timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
            }

            // fd是否达到了限制的数量
            boolean fdLimitTriggered = false;
            if (mOpenFdMonitor != null) {
                fdLimitTriggered = mOpenFdMonitor.monitor();
            }
            
            // fd数量限制没有达到
            if (!fdLimitTriggered) {
                final int waitState = evaluateCheckerCompletionLocked();
                // 都完成了的话 继续下个循环 本次循环结束
                if (waitState == COMPLETED) {
                    waitedHalf = false;
                    continue;
                } else if (waitState == WAITING) {
                    // 在等待的前半段时间 继续下个循环 本次循环结束
                    continue;
                } else if (waitState == WAITED_HALF) {
                    if (!waitedHalf) {
                        Slog.i(TAG, "WAITED_HALF");
                        // 我们等了一半的死锁探测时间。
                        // 获取堆栈跟踪并等待另一半。
                        ArrayList<Integer> pids = new ArrayList<Integer>();
                        pids.add(Process.myPid());
                        ActivityManagerService.dumpStackTraces(pids, null, null,
                            getInterestingNativePids());
                        waitedHalf = true;
                    }
                    continue;
                }

                // 到这里的时候已经有handler超时了
                // 获取超时的HandlerChecker
                blockedCheckers = getBlockedCheckersLocked();
                // 获取超时的HandlerChecker的信息
                subject = describeCheckersLocked(blockedCheckers);
            } else {
                // fd数量限制达到了
                blockedCheckers = Collections.emptyList();
                subject = "Open FD high water mark reached";
            }
            allowRestart = mAllowRestart;
        }

        // 如果我们到这里,这意味着系统很可能挂起。
        // 首先从系统进程的所有线程收集堆栈跟踪。然后杀死这个进程,
        // 这样系统才会重新启动。
        EventLog.writeEvent(EventLogTags.WATCHDOG, subject);

        ArrayList<Integer> pids = new ArrayList<>();
        pids.add(Process.myPid());
        if (mPhonePid > 0) pids.add(mPhonePid);
        
        // 打印java线程和native线程堆栈
        final File stack = ActivityManagerService.dumpStackTraces(
                pids, null, null, getInterestingNativePids());

        // 挂起5s确保堆栈能写入到文件中
        SystemClock.sleep(5000);

        // 让kernel dump全部的blocked线程 和 cpu信息
        doSysRq('w');
        doSysRq('l');

        // 尝试把error加到dropbox下,但假设ActivityManager自己会死锁
        // 当这种情况发生时, 导致以下语句死锁,watchdog作为一个整体将会失效
        Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
                public void run() {
                    // 如果其中一条被观察的线程在Watchdog init()方法执行前被挂起
                    // 我们则没有一个有效的AMS,所以不能把log打印存储到dropbox路径下
                    if (mActivity != null) {
                        mActivity.addErrorToDropBox(
                                "watchdog", null, "system_server", null, null, null,
                                subject, null, stack, null);
                    }
                    StatsLog.write(StatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);
                }
            };
        dropboxThread.start();
        try {
            // 等待2s 让dropboxThread返回
            dropboxThread.join(2000);  
        } catch (InterruptedException ignored) {}

        IActivityController controller;
        synchronized (this) {
            controller = mController;
        }
        // 如果ActivityController不为null 
        if (controller != null) {
            Slog.i(TAG, "Reporting stuck state to activity controller");
            try {
                // 由于挂起system process而禁用dump 防止controller在报告错误的时候被挂起
                Binder.setDumpDisabled("Service dumps disabled due to hung system process.");
                // 1 = keep waiting, -1 = kill system
                int res = controller.systemNotResponding(subject);
                if (res >= 0) {
                    Slog.i(TAG, "Activity controller requested to coninue to wait");
                    waitedHalf = false;
                    continue;
                }
            } catch (RemoteException e) {
            }
        }

        // 只有在没有debugger连接的情况下才会杀死进程。
        if (Debug.isDebuggerConnected()) {
            debuggerWasConnected = 2;
        }
        if (debuggerWasConnected >= 2) {
            Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
        } else if (debuggerWasConnected > 0) {
            Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process");
        } else if (!allowRestart) {
            Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
        } else {
            Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
            WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
            Slog.w(TAG, "*** GOODBYE!");
            // 结束进程 watchdog存在于system_server进程之下 
            // 因为watchdog就是在system_server初始化的
            Process.killProcess(Process.myPid());
            System.exit(10);
        }

        waitedHalf = false;
    }
}

文章到这里就完了,基本上只是对参考文章进行代码的上更新(Android Q),结构大致一样。仅作为学习记录用途。

参考文章

Android 系统中的 WatchDog 详解
Watchdog机制以及问题分析

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容