7.虚拟文件系统

虚拟文件系统

虚拟文件系统(VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。系统中所有的文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。通过虚拟文件系统,程序可以利用标准的Unix系统调用不同的文件系统,甚至在不同介质上的文件系统进行读写操作。

1.文件系统抽象层

Linux可以使用通用接口对所有类型的文件系统进行操作,是因为内核在它的文件系统接口上建立了一个抽象层,使得Linux能够支持各种文件系统,即便他们在功能和行为上存在很大差别。

VFS抽象层之所以能够衔接各种各样的文件系统,是因为它定义了所有文件系统都支持的、基本的、概念是的接口和数据结构。例如用户进程进行下面写操作:

ret = write(fd, buf, len);

该系统调用将buf指针指向长为len字节的数据写入文件描述符fd对应的文件的当前位置。这个系统调用首先被一个通用调用sys_write()处理,sys_write()函数找到fd所在的文件系统实际给出的是哪个写操作,然后再执行该操作。

write()执行过程

2.Unix文件系统

从本质上文件系统是特殊的数据分层存储结构,包括文件、目录和相关的控制信息。文件系统的通用操作包含创建、删除和安装等。

Unix使用了四种和文件系统相关的传统抽象概念:文件目录项索引节点安装点(挂载点,mount point)

  • 文件,一个有序字节串,字节串的第一个字节是文件的头,最后一个字节是文件的尾。与Unix文件形成对比的是面向记录的文件系统(如OpenVMS的File-11),面向记录的文件系统提供更丰富、更结构化的表示,而简单的面向字节流的Unix则以简单性和灵活性为优点
  • 目录,文件通过目录组织。文件目录好比一个文件夹,用来容纳相关文件,同时目录也可以层层嵌套。例如/home/file是文件路径的例子,根目录为/homefile为目录。Unix中,目录也是普通文件,可以执行文件的操作
  • 索引节点,Unix系统将文件的相关信息和文件本身分开,例如控制权限、大小、拥有者、创建时间等。文件相关信息又称文件的元数据,存储在一个单独的数据结构中,该结构称为索引节点(inode)
  • 安装点,文件系统被安装在一个特定的安装点,文件系统的安装位置

Unix文件系统在物理磁盘上,也是按照上述方式存储。Unix中文件的概念从物理上被映射到存储介质。Linux的VFS设计目标就是保证能与支持和实现这些概念的文件系统协同工作。例如像FAT或NTFS这样的非Unix风格文件系统,经过封装提供符合这些概念的界面(目的是用于支持inode)。

3.VFS对象及数据结构

VFS采用了面向对象的设计思路,因为使用的C语言,因此内核的数据结构都是用C语言的结构体,这些结构体包含数据和操作数据的函数指针,其中操作函数由具体的文件系统实现。

VFS有四个主要的对象类型,分别是:

  • 超级块对象,代表一个具体已安装的文件系统
  • 索引节点对象,代表一个具体文件
  • 目录项对象,代表一个目录项,是路径的组成部分
  • 文件对象,代表由进程打开的文件

每个主要对象都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法:

  • super_operations对象,其中包括内核对文件系统所能调用的方法,例如write_inode()sync_fs等方法
  • inode_operations对象,其中包括内核对特定文件所能调用的方法,比如create()link()等方法
  • dentry_operations对象,其中包括内核对特定目录所能调用的方法,比如d_compare()d_delete()等方法
  • file_operations对象,其中包括进程针对已打开文件所能调用的方法,比如read()write()等方法

注意,上述的操作对象均是指的结构体,上面4个对象名称就是对应结构体名称。

4.超级块对象

各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常包括对应于存放在磁盘特定扇区的文件系统超级块或文件系统控制块(所以称为超级块对象)。对于非基于磁盘的文件系统(如基于内存的sysfs),它们会在使用现场创建超级块并保存到内存中。

超级块对象由super_block结构体表示,定义在文件<linux/fs.h>,下面给出它的结构和各个域描述:

struct super_block {
        struct list_head        s_list;            /* list of all superblocks */
        dev_t                   s_dev;             /* identifier */
        unsigned long           s_blocksize;       /* block size in bytes */
        unsigned long           s_old_blocksize;   /* old block size in bytes */
        unsigned char           s_blocksize_bits;  /* block size in bits */
        unsigned char           s_dirt;            /* dirty flag */
        unsigned long long      s_maxbytes;        /* max file size */
        struct file_system_type s_type;            /* filesystem type */
        struct super_operations s_op;              /* superblock methods */
        struct dquot_operations *dq_op;            /* quota methods */
        struct quotactl_ops     *s_qcop;           /* quota control methods */
        struct export_operations *s_export_op;     /* export methods */
        unsigned long            s_flags;          /* mount flags */
        unsigned long            s_magic;          /* filesystem's magic number */
        struct dentry            *s_root;          /* directory mount point */
        struct rw_semaphore      s_umount;         /* unmount semaphore */
        struct semaphore         s_lock;           /* superblock semaphore */
        int                      s_count;          /* superblock ref count */
        int                      s_syncing;        /* filesystem syncing flag */
        int                      s_need_sync_fs;   /* not-yet-synced flag */
        atomic_t                 s_active;         /* active reference count */
        void                     *s_security;      /* security module */
        struct list_head         s_dirty;          /* list of dirty inodes */
        struct list_head         s_io;             /* list of writebacks */
        struct hlist_head        s_anon;           /* anonymous dentries */
        struct list_head         s_files;          /* list of assigned files */
        struct block_device      *s_bdev;          /* associated block device */
        struct list_head         s_instances;      /* instances of this fs */
        struct quota_info        s_dquot;          /* quota-specific options */
        char                     s_id[32];         /* text name */
        void                     *s_fs_info;       /* filesystem-specific info */
        struct semaphore         s_vfs_rename_sem; /* rename semaphore */
};

创建,管理和销毁超级块对象的代码位于文件<fs/super.c>中,超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时,内核会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。其中最重要的一个是s_op,指向超级块的操作函数表,由super_operations结构体表示,定义在<linux/fs.h>中,如下:

struct super_operations {
        struct inode *(*alloc_inode) (struct super_block *sb);
        void (*destroy_inode) (struct inode *);
        void (*read_inode) (struct inode *);
        void (*dirty_inode) (struct inode *);
        void (*write_inode) (struct inode *, int);
        void (*put_inode) (struct inode *);
        void (*drop_inode) (struct inode *);
        void (*delete_inode) (struct inode *);
        void (*put_super) (struct super_block *);
        void (*write_super) (struct super_block *);
        int (*sync_fs) (struct super_block *, int);
        void (*write_super_lockfs) (struct super_block *);
        void (*unlockfs) (struct super_block *);
        int (*statfs) (struct super_block *, struct statfs *);
        int (*remount_fs) (struct super_block *, int *, char *);
        void (*clear_inode) (struct inode *);
        void (*umount_begin) (struct super_block *);
        int (*show_options) (struct seq_file *, struct vfsmount *);
};

一个文件系统要写自己的超级块,需要调用:sb->s_op->write_super(sb)这里的sb是指向文件系统超级块的指针,沿着该指针进入超级块操作函数表,并从表中取得希望得到的write_super()函数,该函数执行写入超级块的实际操作。

5.索引节点对象

索引节点对象包含了内核在操作文件或目录(目录在Unix里也是文件啊)时需要的全部信息。对于Unix风格的文件系统来说,这些信息可以从磁盘的索引节点直接读取。另外无论什么文件系统,索引节点对象必须在内存中创建,以便于文件系统使用。

索引节点对象由inode结构体表示,定义在<linux/fs.h>中:

struct inode {
        struct hlist_node       i_hash;              /* hash list */
        struct list_head        i_list;              /* list of inodes */
        struct list_head        i_dentry;            /* list of dentries */
        unsigned long           i_ino;               /* inode number */
        atomic_t                i_count;             /* reference counter */
        umode_t                 i_mode;              /* access permissions */
        unsigned int            i_nlink;             /* number of hard links */
        uid_t                   i_uid;               /* user id of owner */
        gid_t                   i_gid;               /* group id of owner */
        kdev_t                  i_rdev;              /* real device node */
        loff_t                  i_size;              /* file size in bytes */
        struct timespec         i_atime;             /* last access time */
        struct timespec         i_mtime;             /* last modify time */
        struct timespec         i_ctime;             /* last change time */
        unsigned int            i_blkbits;           /* block size in bits */
        unsigned long           i_blksize;           /* block size in bytes */
        unsigned long           i_version;           /* version number */
        unsigned long           i_blocks;            /* file size in blocks */
        unsigned short          i_bytes;             /* bytes consumed */
        spinlock_t              i_lock;              /* spinlock */
        struct rw_semaphore     i_alloc_sem;         /* nests inside of i_sem */
        struct semaphore        i_sem;               /* inode semaphore */
        struct inode_operations *i_op;               /* inode ops table */
        struct file_operations  *i_fop;              /* default inode ops */
        struct super_block      *i_sb;               /* associated superblock */
        struct file_lock        *i_flock;            /* file lock list */
        struct address_space    *i_mapping;          /* associated mapping */
        struct address_space    i_data;              /* mapping for device */
        struct dquot            *i_dquot[MAXQUOTAS]; /* disk quotas for inode */
        struct list_head        i_devices;           /* list of block devices */
        struct pipe_inode_info  *i_pipe;             /* pipe information */
        struct block_device     *i_bdev;             /* block device driver */
        unsigned long           i_dnotify_mask;      /* directory notify mask */
        struct dnotify_struct   *i_dnotify;          /* dnotify */
        unsigned long           i_state;             /* state flags */
        unsigned long           dirtied_when;        /* first dirtying time */
        unsigned int            i_flags;             /* filesystem flags */
        unsigned char           i_sock;              /* is this a socket? */
        atomic_t                i_writecount;        /* count of writers */
        void                    *i_security;         /* security module */
        __u32                   i_generation;        /* inode version number */
        union {
                void            *generic_ip;         /* filesystem-specific info */
        } u;
};

一个索引节点代表一个文件,它也可以是设备或管道这种特殊文件。因此在索引节点对象中有一些特殊文件相关项,比如i_pipe代表有名管道数据结构,i_bdev指向块设备结构,在最新内核版本里面它们都是在一个union里表示这个索引节点只能是某一种文件。

和超级块类似,对索引节点的操作调用方式如下:

i->i_op->truncate(i)

i指向给定的索引节点。索引节点的操作结构体inode_operations()结构体定义在文件<linux/fs.h>中:

struct inode_operations {
        int (*create) (struct inode *, struct dentry *,int);
        struct dentry * (*lookup) (struct inode *, struct dentry *);
        int (*link) (struct dentry *, struct inode *, struct dentry *);
        int (*unlink) (struct inode *, struct dentry *);
        int (*symlink) (struct inode *, struct dentry *, const char *);
        int (*mkdir) (struct inode *, struct dentry *, int);
        int (*rmdir) (struct inode *, struct dentry *);
        int (*mknod) (struct inode *, struct dentry *, int, dev_t);
        int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *);
        int (*readlink) (struct dentry *, char *, int);
        int (*follow_link) (struct dentry *, struct nameidata *);
        int (*put_link) (struct dentry *, struct nameidata *);
        void (*truncate) (struct inode *);
        int (*permission) (struct inode *, int);
        int (*setattr) (struct dentry *, struct iattr *);
        int (*getattr) (struct vfsmount *, struct dentry *, struct kstat *);
        int (*setxattr) (struct dentry *, const char *,const void *, size_t, int);
        ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
        ssize_t (*listxattr) (struct dentry *, char *, size_t);
        int (*removexattr) (struct dentry *, const char *);
};

从上面还有可以看到很多跟linux指令相关的函数,例如mkdir

6.目录项对象

虽然VFS把目录当作文件对待,但是为了方便查找,VFS引入了目录项概念。例如/bin/vi/binvi都是目录项对象,前两个是文件,后一个是普通文件。解析一个文件路径是耗时的常规字符串比较过程,因此VFS引入目录项简化这个过程。

目录项对象由dentry结构体表示,定义在文件<linux/dcache.h>中:

struct dentry {
        atomic_t                 d_count;      /* usage count */
        unsigned long            d_vfs_flags;  /* dentry cache flags */
        spinlock_t               d_lock;       /* per-dentry lock */
        struct inode             *d_inode;     /* associated inode */
        struct list_head         d_lru;        /* unused list */
        struct list_head         d_child;      /* list of dentries within */
        struct list_head         d_subdirs;    /* subdirectories */
        struct list_head         d_alias;      /* list of alias inodes */
        unsigned long            d_time;       /* revalidate time */
        struct dentry_operations *d_op;        /* dentry operations table */
        struct super_block       *d_sb;        /* superblock of file */
        unsigned int             d_flags;      /* dentry flags */
        int                      d_mounted;    /* is this a mount point? */
        void                     *d_fsdata;    /* filesystem-specific data */
        struct rcu_head          d_rcu;        /* RCU locking */
        struct dcookie_struct    *d_cookie;    /* cookie */
        struct dentry            *d_parent;    /* dentry object of parent */
        struct qstr              d_name;       /* dentry name */
        struct hlist_node        d_hash;       /* list of hash table entries */
        struct hlist_head        *d_bucket;    /* hash bucket */
        unsigned char            d_iname[DNAME_INLINE_LEN_MIN]; /* short name */
};

目录项对象有三个有效状态:被使用未被使用负状态

  • 被使用,目录项分配了一个有效索引节点,并且该对象存在使用者,不能被丢弃
  • 未被使用,目录项分配了一个有效索引节点,但是VFS中没有使用它,该目录项指向一个有效对象,被保存在缓存中以便需要时再使用它,如果要回收内存,可以撤销未使用的目录项
  • 负状态,或者说无效目录项,没有对应有效索引节点,因为索引节点被删除了,或者路径不正确,但是目录项仍然保留,以便快速解析路径查询,如果有需要,可以撤销它,实际上负状态的目录项也很少被使用

如果VFS层遍历路径名中所有的元素并将它们逐个地解析成目录项对象,还要达到最深层目录,将是一件费力的工作,因此内核将目录项对象缓存在目录项缓存中(dcache),目录项缓存包括三个主要部分:

  • 被使用的目录项链表。该链表通过索引节点对象中的i_dentry项链接相关的索引节点,因为一个给定索引节点可能有多个链接,存在多个目录项对象
  • 最近未被使用双向链表。保存未使用和负状态的目录项对象,头部是最新加入的,删除从尾部删
  • 散列表和散列函数用于快速地将给定路径解析为相关目录项对象

散列表由数组dentry_hashtable表示,其中每个元素都是一个指向具有相同键值的目录项对象链表的指针,数组大小取决于系统中物理内存大小。

实际的散列值由d_hash()函数计算,它是内核提供给文件系统的唯一一个散列函数。通过d_lookup()函数查找散列表。

VFS会优先从目录项缓存中搜索路径名,这样加快了检索速度。

目录项操作dentry_operation结构体指明了VFS操作目录项的所有方法,定义在<linux/dcache.h>

struct dentry_operations {
        int (*d_revalidate) (struct dentry *, int);
        int (*d_hash) (struct dentry *, struct qstr *);
        int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
        int (*d_delete) (struct dentry *);
        void (*d_release) (struct dentry *);
        void (*d_iput) (struct dentry *, struct inode *);
};

7.文件对象

文件对象是已经打开的文件在内存中的表示,包含我们非常熟悉的信息(如访问模式,当前偏移量等)。文件对象仅仅在进程观点上表示已打开文件,它反过来指向目录项对象(再反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象无疑是唯一的。

文件对象由file结构体表示,定义在<linux/fs.h>中:

struct file {
        struct list_head       f_list;        /* list of file objects */
        struct dentry          *f_dentry;     /* associated dentry object */
        struct vfsmount        *f_vfsmnt;     /* associated mounted fs */
        struct file_operations *f_op;         /* file operations table */
        atomic_t               f_count;       /* file object's usage count */
        unsigned int           f_flags;       /* flags specified on open */
        mode_t                 f_mode;        /* file access mode */
        loff_t                 f_pos;         /* file offset (file pointer) */
        struct fown_struct     f_owner;       /* owner data for signals */
        unsigned int           f_uid;         /* user's UID */
        unsigned int           f_gid;         /* user's GID */
        int                    f_error;       /* error code */
        struct file_ra_state   f_ra;          /* read-ahead state */
        unsigned long          f_version;     /* version number */
        void                   *f_security;   /* security module */
        void                   *private_data; /* tty driver hook */
        struct list_head       f_ep_links;    /* list of eventpoll links */
        spinlock_t             f_ep_lock;     /* eventpoll lock */
        struct address_space   *f_mapping;    /* page cache mapping */
};

类似于目录项对象,文件对象实际上没有对应的磁盘数据。所以在结构体中没有代表其对象是否为脏,是否需要写回磁盘的标志。文件对象通过f_dentry指针指向相关的目录项对象,目录项会指向相关索引节点,索引节点会记录是否是脏的。
文件操作的结构体file_operations,定义在<linux/fs.h>中:

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, char *, size_t, loff_t);
        ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
        ssize_t (*aio_write) (struct kiocb *, const char *, size_t, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int);
        int (*aio_fsync) (struct kiocb *, int);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*readv) (struct file *, const struct iovec *,
                          unsigned long, loff_t *);
        ssize_t (*writev) (struct file *, const struct iovec *,
                           unsigned long, loff_t *);
        ssize_t (*sendfile) (struct file *, loff_t *, size_t,
                             read_actor_t, void *);
        ssize_t (*sendpage) (struct file *, struct page *, int,
                             size_t, loff_t *, int);
        unsigned long (*get_unmapped_area) (struct file *, unsigned long,
                                            unsigned long, unsigned long,
                                            unsigned long);
        int (*check_flags) (int flags);
        int (*dir_notify) (struct file *filp, unsigned long arg);
        int (*flock) (struct file *filp, int cmd, struct file_lock *fl);
};

8.文件系统相关的数据结构

除了上述几种VFS基础对象,内核还使用了一些标准数据结构用于管理文件系统的其他数据:file_system_typevfsmount

linux支持不同文件系统,内核需要一个特殊结构用于描述文件系统:file_system_typefile_system_type结构体定义在<linux/fs.h>中:

struct file_system_type {
        const char              *name;     /* filesystem's name */
        struct subsystem        subsys;    /* sysfs subsystem object */
        int                     fs_flags;  /* filesystem type flags */
        /* the following is used to read the superblock off the disk */
        struct super_block      *(*get_sb) (struct file_system_type *, int,char *, void *);
        /* the following is used to terminate access to the superblock */
        void                    (*kill_sb) (struct super_block *);
        struct module           *owner;    /* module owning the filesystem */
        struct file_system_type *next;     /* next file_system_type in list */
        struct list_head        fs_supers; /* list of superblock objects */
};

get_sb()函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象。每种文件系统,不管有多少实例安装到系统中,还是根本没安装,都只有一个file_system_type结构。

更有趣的是,当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建,该结构体用来代表文件系统的实例——换句话说,代表一个安装点(挂载点)。

vfsmount结构被定义在<linux/mount.h>中:

struct vfsmount {
        struct list_head   mnt_hash;        /* hash table list */
        struct vfsmount    *mnt_parent;     /* parent filesystem */
        struct dentry      *mnt_mountpoint; /* dentry of this mount point */
        struct dentry      *mnt_root;       /* dentry of root of this fs */
        struct super_block *mnt_sb;         /* superblock of this filesystem */
        struct list_head   mnt_mounts;      /* list of children */
        struct list_head   mnt_child;       /* list of children */
        atomic_t           mnt_count;       /* usage count */
        int                mnt_flags;       /* mount flags */
        char               *mnt_devname;    /* device file name */
        struct list_head   mnt_list;        /* list of descriptors */
        struct list_head   mnt_fslink;      /* fs-specific expiry list */
        struct namespace   *mnt_namespace   /* associated namespace */
};

9.与进程相关的数据结构

系统中每一个进程都有一组自己打开的文件,像根文件系统、当前工作目录、安装点等。有三个数据结构将VFS层和系统进程紧密联系在一起,它们分别是:file_structfs_structnamespace结构体。

file_struct结构体定义在文件<linux/fdtabl.h>中,该结构体由进程描述符files目录项指向。所有与单个进程相关的信息(如打开的文件及文件描述符)都包含其中,其结构和描述如下:

struct files_struct {
        atomic_t    count;              /* structure's usage count */
        spinlock_t  file_lock;          /* lock protecting this structure */
        int         max_fds;            /* maximum number of file objects */
        int         max_fdset;          /* maximum number of file descriptors */
        int         next_fd;            /* next file descriptor number */
        struct file **fd;               /* array of all file objects */
        fd_set      *close_on_exec;     /* file descriptors to close on exec() */
        fd_set      *open_fds;           /* pointer to open file descriptors */
        fd_set      close_on_exec_init; /* initial files to close on exec() */
        fd_set      open_fds_init;      /* initial set of file descriptors */
        struct file *fd_array[NR_OPEN_DEFAULT]; /* default array of file objects */
};

fd_array数组指针指向已经打开的文件对象,因为NR_OPEN_DEFAULT等于BITS_PER_LONG,所以64位机,该值就为64,表示该数组最多容纳64个文件对象,如果超出,内核就会分配一个新数组,并用fd指针指向它。为了优化性能,管理员可以调整NR_OPEN_DEFAULT的预定义值。

第二个结构体是fs_struct。该结构由检查描述符fs域指向,包含文件系统和进程相关的信息,定义在<linux/fs_struct.h>中:

 struct fs_struct {
     int users;
     spinlock_t lock;
     seqcount_t seq;
     int umask;
     int in_exec;
     struct path root, pwd;
 };

该结构包含当前进程的当前工作目录(pwd)和根目录。

最后一个是namespace结构体,由进程描述符mnt_namespace域指向,它使得每个进程在系统中都能看到唯一的安装文件系统。定义在文件<linux/mnt_namespace.h>(实际上我没找到啊!)中:

struct namespace {
        atomic_t            count; /* structure usage count */
        struct vfsmount     *root; /* mount object of root directory */
        struct list_head    list;  /* list of mount points */
        struct rw_semaphore sem;   /* semaphore protecting the namespace */
};

默认情况下,所有的进程共享同样的命名空间。

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

推荐阅读更多精彩内容