基于Atheros无线芯片的platform总线、设备、驱动、虚拟网桥分析(1)

!Warning:请确保能够访问图床Imgur,以正常显示图片


源码基线:linux-4.0-rc1 & OpenWrt 12.09 branch (Attitude Adjustment)

硬件:Atheros AR9331

1 设备模型概述

设备模型主要完成以下工作:

  1. 设备分类,以分层的架构对设备进行描述,隐藏设备内部的连接细节,对外清晰地展示可用的设备。
  2. 创建和管理设备的生命周期。
  3. 通过sysfs虚拟文件系统,向用户空间提供对设备的读写操作,获取设备的信息、改变设备的运行状态。

设备模型的结构组成:

1.总线。

所有的设备都通过总线相连,包括内部的虚拟总线。在内核中,用struct bus_type结构体来表示总线。

struct bus_type {  
    const char      * name;  
    struct kset     drivers;  
    struct kset     devices;  
}  
  • name是总线的名字。
  • kset driverskset devices,分别代表了总线的驱动程序及插入总线的所有设备集合。

2.设备。

每个设备实例用struct device结构体来表示。

struct device {  
    struct device       *parent;  
    struct kobject kobj;  
        char bus_id[BUS_ID_SIZE];  
    struct bus_type * bus;  
    struct device_driver *driver;  
}  
  • parent指向设备的所属父设备,通常是某种总线。
  • kobj表示本设备对象。
  • bus_id是总线上标识设备的ID信息,通常由字符串"<域编号>:<总线编号>:<设备编号>:<功能编号>"定义
  • bus标识了设备连接在哪个总线上。
  • driver管理设备的驱动程序。

3.驱动程序。

设备驱动程序设备模型可以跟踪所有注册的设备,驱动程序为设备提供服务,完成设备的工作。设备的驱动程序由结构体struct device_driver定义。

struct device_driver {  
    const char      * name;  
    struct bus_type     * bus;  
    struct kobject      kobj;  
        struct klist            klist_devices;  
}  
  • name是驱动程序的名字。
  • bus是指驱动程序所操作的总线类型。
  • kobj表示驱动程序服务的设备对象。
  • klist_devices是驱动程序当前能操作的设备链表。

主要数据结构

  • struct net_device:表示网络设备实例,存储了网络设备的一些关键信息:
  • 设备名称。
  • 设备mac地址。由软件随机生成。
  • 广播地址。
  • 以太网类型、帧长度、帧头长度、mtu大小。
  • 设备模型对象。
  • struct net_bridge:网桥的默认vid,默认优先级,生成树使能状态。
  • struct net_port_vlans:网桥管理的所有vlan列表。
  • struct net_bridge_fdb_entry:网桥管理的所有mac转发表。
  • netdev_queue,netdev_rx_queue:网桥的收发包队列。
  • struct net:linux网络命名空间
struct net_port_vlans {
    u16                port_idx;
    u16                pvid;
    union {
        struct net_bridge_port        *port;
        struct net_bridge        *br; /* vlan所属网桥,即vlan对哪个网桥生效 */
    }                parent;
    struct rcu_head            rcu;
    unsigned long        vlan_bitmap[BR_VLAN_BITMAP_LEN]; /* 已创建的vlan列表 */
    unsigned long        untagged_bitmap[BR_VLAN_BITMAP_LEN]; /* 哪些vlan出口时
                                    去掉tag */
    u16                num_vlans;
};

struct net_bridge_fdb_entry
{
    struct hlist_node        hlist;
    struct net_bridge_port        *dst; /* mac地址绑定的端口 */

    unsigned long            updated;
    unsigned long            used;
    mac_addr            addr; /* mac地址 */
    __u16                vlan_id;
    unsigned char            is_local:1, /* 是否本机mac地址 */
                    is_static:1, /* 是否静态mac地址 */
                    added_by_user:1,
                    added_by_external_learn:1;
    struct rcu_head            rcu;
};

struct net_bridge
{
    spinlock_t            lock;
    struct list_head        port_list; /* 网桥的虚拟端口struct net_bridge_port */
    struct net_device        *dev; /* 网桥设备br0信息 */

    struct pcpu_sw_netstats        __percpu *stats;
    spinlock_t            hash_lock;
    struct hlist_head        hash[BR_HASH_SIZE]; /* mac地址hash表,
                                每个mac地址由struct net_bridge_fdb_entry表示 */
}

struct net_device {
    char            name[IFNAMSIZ]; /* 设备名称 */
    struct hlist_node    name_hlist; /* 设备名称链表 */
    struct netdev_hw_addr_list    dev_addrs; /* 设备mac地址 */
    struct hlist_node    index_hlist; /* ifindex链表 */

    unsigned long        mem_end; /* 设备共享内存的起始地址 */
    unsigned long        mem_start; /* 设备共享内存的结束地址 */
    unsigned long        base_addr; /* 网络设备I/O基地址 */
    int            irq; /* 设备使用的中断号 */

    const struct net_device_ops *netdev_ops;
    const struct ethtool_ops *ethtool_ops;
    const struct forwarding_accel_ops *fwd_ops;
}

主要链表结构

主要使用的链表数据结构是hlist_headhlist_node,其工作原理与list_head类似。

struct hlist_head {
    struct hlist_node *first;
};

struct hlist_node {
    struct hlist_node *next, **pprev;
};

通过hlist_add_head_rcu(struct hlist_node *n, struct hlist_head *h)函数,可以把新节点添加到链表中。

  1. 空节点

  2. 添加第一个节点

  3. 添加第二个节点

2 platform总线

struct kobject与kset结构体

kobject与kset是组成设备模型的基本结构,为来表示设备模型的设备实例与设备层次关系。这两个结构体作为面向对象概念的基类,嵌套在其他结构体里使用。在sysfs中显示的每一个实例,都对应一个kobject。

/* 设备模型的基本结构。 
 * 在sysfs中显示的每一个对象,都对应一个kobject,它被用来与内核交互并创建它的可见表述。 
 * 内核用kobject结构将各个对象连接起来组成一个分层的结构体系 */  
struct kobject {  
    const char      *name;  
    struct kref     kref; /* 对象的引用计数。一个内核对象被创建时,需要知道对象存活
                的时间,跟踪生命周期的一个方法是使用引用计数,当内核中没有该对象的引用
                时,表示对象的生命周期结束,可以被删除 */  
    struct list_head    entry; /* 连接到kset建立层次结构 */  
    struct kobject      * parent; /* parent保存了分层结构中上一层节点kobject结构
                的指针。比如一个kobject结构表示了一个USB设备,它的parent指针可能指向
                了表示USB集线器的对象,而USB设备是插在USB集线器上的。parent指针最重
                要的用途是在sysfs分层结构中定位对象 */   
    struct kset     * kset; /* 一个kset是嵌入相同类型结构的kobject集合。每个kset
                内部,包含了自己的kobject。kset总是在sysfs中出现,一旦设置了kset并
                把它添加到系统中,将在sysfs中创建一个目录。kobject不必在sysfs中表示,
                但kset中每一个kobject成员都将在sysfs中得到表述 */  
    struct kobj_type    * ktype; /* 属性结构。kset中也有一个ktype,其使用优先于
                kobject的此处ktype,因此在典型应用中,kobject中的ktype成员被设置
                为NULL */  
    struct sysfs_dirent * sd; /* 指向sysfs下以kobj.name所命名生成的目录 */  
};  
  
struct kset {  
    struct kobj_type    *ktype;  
    struct list_head    list;  
    spinlock_t      list_lock;  
    struct kobject      kobj;  
    struct kset_uevent_ops  *uevent_ops;  
};  

假设有个总线X-bus,以及3个设备驱动A-driver、B-driver、C-driver,且这3个设备都属于同一类X-bus总线。那么kobject与kset在描述设备模型时,通常这样使用。在X-bus的描述结构体中包含kset结构体作为同一类型设备的集合;在描述设备驱动A、B、C的driver结构体中包含kset指针,指向所属的类型集合。每个driver都有一个kobject来表示驱动实例自身,并把所有集合按照加入的先后顺序通过list链表(kset.list与kobject.entry)连接起来。

注册platform总线

函数入口driver_init()

void __init driver_init(void)  
{  
    devices_init();  
    buses_init();  
    platform_bus_init();  
}  

devices_init()

devices_init()创建sysfs模型如下:

/sys
├── dev
│ ├── block
│ └── char
└── devices


```c
struct kset *devices_kset; /* /sys/devices/ */
static struct kobject *dev_kobj;
struct kobject *sysfs_dev_char_kobj;
struct kobject *sysfs_dev_block_kobj;

int __init devices_init(void)
{
    devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);

    dev_kobj = kobject_create_and_add("dev", NULL);

    sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);

    sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
}

kset_create_and_add()

/* 动态创建kset结构体,在sysfs中创建以@name命名的目录,如果kset->kobj.ktype有属性值,
   则在目录下创建以属性名命名的文件 */
struct kset *kset_create_and_add(const char *name,
                 const struct kset_uevent_ops *uevent_ops,
                 struct kobject *parent_kobj)
{
    struct kset *kset;

    kset = kset_create(name, uevent_ops, parent_kobj); /* 动态创建kset结构体 */
    kset_register(kset); /* 初始化kset,并加入到sysfs */
}

/* 动态创建kset结构体 */
static struct kset *kset_create(const char *name,
                const struct kset_uevent_ops *uevent_ops,
                struct kobject *parent_kobj)
{
    struct kset *kset;
    int retval;

    kset = kzalloc(sizeof(*kset), GFP_KERNEL);
    kobject_set_name(&kset->kobj, "%s", name);
    kset->uevent_ops = uevent_ops;
    kset->kobj.parent = parent_kobj;
    kset->kobj.ktype = &kset_ktype;
    kset->kobj.kset = NULL;

    return kset;
}
  
/* 初始化kset,并加入到sysfs */
int kset_register(struct kset * k)  
{  
        kset_init(k);  
        kobject_add_internal(&k->kobj);
        kobject_uevent(&k->kobj, KOBJ_ADD);  
}          

void kset_init(struct kset * k)  
{  
    kobject_init_internal(&k->kobj);
    INIT_LIST_HEAD(&k->list);
    spin_lock_init(&k->list_lock);
}  

static void kobject_init_internal(struct kobject *kobj)
{  
        kref_init(&kobj->kref); /* 初始化kobject的引用计数为1 */  
        INIT_LIST_HEAD(&kobj->entry); /* 初始化entry链表 */  
        kobj->state_in_sysfs = 0; /* 设备注册标志,0表示未注册 */
        kobj->state_add_uevent_sent = 0;
        kobj->state_remove_uevent_sent = 0;
        kobj->state_initialized = 1;
}

/* kobject_add_internal()在sysfs下建立以kobj.name命名的目录 */
static int kobject_add_internal(struct kobject *kobj)
{
    struct kobject *parent;

    parent = kobject_get(kobj->parent);

    /* join kset if set, use it as parent if we do not already have one */
    if (kobj->kset) {
        if (!parent)
            parent = kobject_get(&kobj->kset->kobj);
        kobj_kset_join(kobj);
        kobj->parent = parent;
    }

    create_dir(kobj);
    kobj->state_in_sysfs = 1; /* 标记设备已注册 */
}

/* 创建kobject对象的目录及属性文件 */
static int create_dir(struct kobject *kobj)
{
    const struct kobj_ns_type_operations *ops;

    sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); /* 创建目录 */ 
    populate_dir(kobj); /* 创建属性文件 */

    sysfs_get(kobj->sd);

    ops = kobj_child_ns_ops(kobj);
    if (ops) {
        sysfs_enable_ns(kobj->sd);
    }
}  

/* 以kobj.name命名,在sysfs下创建一个目录 */
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
    struct kernfs_node *parent, *kn;

    /* 指定创建的kobj目录所属的的父目录,默认为sysfs根目录 */
    if (kobj->parent)
        parent = kobj->parent->sd;
    else
        parent = sysfs_root_kn;

    /* 在parent目录下,创建以kobj.name命名的目录 */
    kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
                  S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns);
    kobj->sd = kn;
}

/* 对象可能是带有属性的,在kobject目录下创建以属性名命名的文件 */
static int populate_dir(struct kobject *kobj)
{
    struct kobj_type *t = get_ktype(kobj);
    struct attribute *attr;
    int i;

    if (t && t->default_attrs) {
        for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
            sysfs_create_file(kobj, attr);
        }
    }
}

devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL)创建的模型:

kobject_create_and_add()

/* 动态创建一个kobject对象,并且加入到sysfs */
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
    struct kobject *kobj;

    kobj = kobject_create();
    kobject_add(kobj, parent, "%s", name);

    return kobj;
}

struct kobject *kobject_create(void)
{
    struct kobject *kobj;

    kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
    kobject_init(kobj, &dynamic_kobj_ktype);
    return kobj;
}

/* 初始化kobject结构体 */
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
    kobject_init_internal(kobj);
    kobj->ktype = ktype;
}

int kobject_add(struct kobject *kobj, struct kobject *parent,
        const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    kobject_add_varg(kobj, parent, fmt, args);
    va_end(args);
}

static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
                const char *fmt, va_list vargs)
{
    kobject_set_name_vargs(kobj, fmt, vargs);
    kobj->parent = parent;
    return kobject_add_internal(kobj);
}

/* 设置@kobj的name */
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
                  va_list vargs)
{
    char *s;

    kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);

    /* 名字中不能包含有'/'的字符,如果有,则直接截短 */
    while ((s = strchr(kobj->name, '/')))
        s[0] = '!';
}

dev_kobj = kobject_create_and_add("dev", NULL)创建的模型:

sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj)
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj)创建的模型

buses_init()

buses_init()创建sysfs模型如下:

/sys
├── bus
└── devices
└── system


```c
static struct kset *bus_kset;
static struct kset *system_kset; /* /sys/devices/system */

int __init buses_init(void)
{
    bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
    system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
}

bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL)创建的模型:

system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj)创建的模型:

platform_bus_init()

platform_bus_init()完成虚拟设备platform,及虚拟总线platform的创建。

/sys
└── devices
└── platform
└── uevent /* -rw-r--r-- */


```c
/* platform设备 */
struct device platform_bus = {
    .init_name    = "platform",
};

/* platform总线 */
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_groups    = platform_dev_groups,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .pm        = &platform_dev_pm_ops,
};

int __init platform_bus_init(void)
{
    early_platform_cleanup();
    device_register(&platform_bus); /* 注册到devices目录中 */  
    bus_register(&platform_bus_type); /* 注册到bus目录中 */  
    of_platform_register_reconfig_notifier();
}

device_register()

/* 注册设备:初始化设备的数据结构,将其加入到数据结构的网络中。 
   完成设备注册后,可以在/sys/devices目录中看到 */  
int device_register(struct device *dev)  
{  
    device_initialize(dev); /* 初始化dev结构 */  
    device_add(dev); /* 添加dev至sysfs */  
}  
  
void device_initialize(struct device *dev)  
{  
    /* 将设备kobject的kset集合指向devices_kset */  
    dev->kobj.kset = devices_kset; 
    kobject_init(&dev->kobj, &device_ktype);
}  

#define DEVICE_ATTR_RW(_name) \
    struct device_attribute dev_attr_##_name = __ATTR_RW(_name)

#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO),        \
             _name##_show, _name##_store)

#define __ATTR(_name, _mode, _show, _store) {                \
    .attr = {.name = __stringify(_name),                \
         .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },        \
    .show    = _show,                        \
    .store    = _store,                        \
}

static DEVICE_ATTR_RW(uevent); /* 定义dev_attr_uevent,其展开宏如下:
    struct device_attribute dev_attr_uevent {
        .attr = {
            .name = "uevent",
            .mode = (S_IWUSR | S_IRUGO),
        },
        .show = uevent_show,
        .store = uevent_store,
    }; */
  
int device_add(struct device *dev)  
{  
    struct device *parent = NULL;  
    struct kobject *kobj;
    struct class_interface *class_intf;  
    /* 将设备的引用计数加1 */
    dev = get_device(dev);
    /* 创建私有数据结构体,并与主结构体进行关联 */
    if (!dev->p) {
        device_private_init(dev);
    }

    /* 使用dev.init_name设置dev.kobj.name,并置空dev.init_name */
    if (dev->init_name) {
        dev_set_name(dev, "%s", dev->init_name);
        dev->init_name = NULL;
    }
  
    /* 如果kobject_add()的第二个参数parent为空,则以dev->kobj.kset.kobj作为父目录 */  
    kobject_add(&dev->kobj, dev->kobj.parent, NULL);  

    /* dev_attr_uevent由宏DEVICE_ATTR_RW()定义,在dev.kobj目录下创建以
       dev_attr_uevent.attr.name命名,权限为dev_attr_uevent.attr.mode的文件 */
    device_create_file(dev, &dev_attr_uevent); 
    /* 创建设备类型软链接 */
    device_add_class_symlinks(dev);
    bus_add_device(dev);
}

int bus_add_device(struct device *dev)
{
    struct bus_type *bus = bus_get(dev->bus);

    if (bus) {
        device_add_attrs(bus, dev);
        device_add_groups(dev, bus->dev_groups);
        sysfs_create_link(&bus->p->devices_kset->kobj,
                        &dev->kobj, dev_name(dev));
        sysfs_create_link(&dev->kobj,
                &dev->bus->p->subsys.kobj, "subsystem");

        klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
    }
}

bus_register()

bus_register(&platform_bus_type)向系统添加一个新总线。

/sys
└── bus
└── platform
├── devices
├── drivers
├── drivers_autoprobe /* -rw-r--r-- /
├── drivers_probe /
--w------- /
└── uevent /
--w------- */


```c
struct bus_type {  
    const char      * name; /* 总线的文本名称,用于在sysfs文件系统中标识总线 */  
  
    /* 试图查找与给定设备匹配的驱动程序 */  
    int     (*match)(struct device * dev, struct device_driver * drv);  
    int     (*uevent)(struct device *dev, struct kobj_uevent_env *env);  
    /* 在有必要将驱动程序关联到设备时,会调用probe。该函数检测设备在系统中是否 
       真正存在 */  
    int     (*probe)(struct device * dev);  
    /* 删除驱动程序和设备之间的关联。例如,在将可热挺拔的设备从系统中移除时,会 
       调用该函数 */  
    int     (*remove)(struct device * dev);  

    struct subsys_private *p;
};  
  
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_groups    = platform_dev_groups,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .pm        = &platform_dev_pm_ops,
};

static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store); /* 定义bus_attr_uevent
    ,其展开宏如下:
    struct bus_attribute bus_attr_uevent {
        .attr = {
            .name = "uevent",
            .mode = S_IWUSR,
        },
        .show = NULL,
        .store = bus_uevent_store,
    }; */
  
/* 注册新的总线 */  
int bus_register(struct bus_type *bus)
{
    int retval;
    struct subsys_private *priv;
    struct lock_class_key *key = &bus->lock_key;
  
    /* 框架设计机制:虽然可以直接用单个结构体来表示一种设备,但是更好的做法是把设备的私
       有数据分离出来,每个设备用两个结构体表示,主结构体表示大家都存在的公共属性,并把
       成员指针指向另一个表示私有属性的结构体。这样分离的另一个好处是,代码函数设计上更
       加高内聚、低耦合。 */
    priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
    priv->bus = bus;
    bus->p = priv;

    /*  */
    kobject_set_name(&priv->subsys.kobj, "%s", bus->name);  

    priv->subsys.kobj.kset = bus_kset; /* 父目录指向bus_kset.kobj */
    priv->subsys.kobj.ktype = &bus_ktype;
    priv->drivers_autoprobe = 1;

    /* 初始化&priv->subsys,并加入到sysfs */
    kset_register(&priv->subsys);

    /* bus_attr_uevent由宏BUS_ATTR()定义,在priv.subsys.kobj目录下创建以
       bus_attr_uevent.attr.name命名,权限为bus_attr_uevent.attr.mode的文件 */
    bus_create_file(bus, &bus_attr_uevent);

    priv->devices_kset = kset_create_and_add("devices", NULL,
                         &priv->subsys.kobj);

    priv->drivers_kset = kset_create_and_add("drivers", NULL,
                         &priv->subsys.kobj);

    add_probe_files(bus); /* 创建drivers_probe与drivers_autoprobe文件 */

    bus_add_groups(bus, bus->bus_groups);
}  

内核设备模型框架

driver_init()最终形成的模型框架,如下所示:

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

推荐阅读更多精彩内容