Linux系统启动及定制过程

1、简述linux操作系统启动流程

1.1 Centos5、Centos6系统启动过程

CentOS的启动流程总体顺序如下:

POST --> Boot Sequence --> bootloader(MBR) --> Kernel --> 加载rootfs --> switchroot --> /sbin/init --> (配置文件:/etc/inittab, /etc/init/*.conf) --> 根据init配置文件设置默认运行级别 --> 运行系统初始化脚本/etc/rc.d/rc.sysinit,完成系统初始化 --> 开启或关闭用户选定的对应运行级别下所对应的服务 --> 启动终端,打印登录提示符。

注:前面加粗部分代表内核空间的系统启动流程,后面未加粗部分代表用户空间的系统启动流程。

第一步:硬件启动过程

POST加电自检

主要实现的功能是检测各个外围硬件设备是否存在而且能够正常运行起来,实现这一自检功能的是固化在主板上的ROM(主要代表为CMOS)芯片上的BIOS(Basic Input/Output System)程序;例如BIOS会检测CPU、Memory以及I/O设备是否能够正常运行,如果是个人计算机的话可能还会检测一下显示器。只要一通电,CPU就会自动去加载ROM芯片上的BIOS程序,是这样来实现的。而检测完成之后就进行硬件设备的初始化。

选择启动设备以加载MBR

主要实现的功能是选择要启动的硬件设备,选择了之后就可以读取这个设备上位于MBR里头的bootloader了。这一步的实现是这样的:根据BIOS中对启动顺序的设定,BIOS自己会依次扫描各个引导设备,然后第一个被扫描到具有引导程序(bootloader)的设备就被作为要启动的引导设备。

MBR(Main Boot Record),是硬盘的0柱面,0磁道、1扇区(第一个扇区),称为主引导扇区,也称为主引导记录。它由三部分组 成:主引导程序(BootLoader)、硬盘分区表DPT(Disk Partition table)和硬盘有效标志(55AA)。
   注:硬盘默认一个扇区大小为512字节。
  第一部分,主引导程序(BootLoader)占446个字节,负责从活动分区中装载,并运行系统引导程序。
  第二部分,硬盘分区表DPT占64个字节,有4个分区表项,每个分区表项占16个字节,硬盘中分区有多少以及每一个分区的大小都记 录在其中。
  第三部分,硬盘有效标志,占2个字节,固定为55AA。如果这个标志位0xAA55,就认为这个是MB

第二步:GRUB引导阶段

不同的系统有不同的主引导程序(BootLoader)。Windows使用的是NTLDR(NT Loader,Windows NT系列操作系统)、Bootmgr(Boot Manager,Windows Vista,7,8,10),Linux一般使用的是grub(也叫grub legacy)和grub2。GRUB程序加载执行并引导kernel(内核)程序,其中有三个阶段,Grub引导阶段的文件都在/boot/grub/目录下。

stage1:这一阶段执行的就是系统安装时预先写入到MBR的Bootloader程序,即是存放在MBR的前446字节里的程序。它的任务仅是读取(加载)硬盘的0柱面,0磁道,2扇区的内容(/boot/grub/stage1)并执行。

stage1.5:这一阶段是Stage1阶段和Stage2阶段的桥梁,功能是加载stage2所在分区的文件系统驱动,让stage1中的bootloader能识别stage2所在分区的文件系统,此后grub程序便有能力去访问/boot/grub/stage2。

stage2:这一阶段读取并解析grub的配置文件/boot/grub/grub.cnf,根据配置文件加载内核镜像到内存中,通过initrd程序建立虚拟根文件系统,最后调用(转交)内核。

第三步:内部引导阶段

加载内核,核心开始解压,启动一些最核心的程序。为了让内核足够的轻小,硬件驱动并没放在内核文件里面。系统仅探测可识别到的所有硬件设备,加载硬件驱动程序,即加载真正的根文件系统所在设备的驱动程序(有可能会借助于ramdisk加载驱动),以只读方式挂载根文件系统,运行用户空间的第一个应用程序:/sbin/init。

第四步:init初始化阶段(系统初始化阶段)

虽然CentOS 5、CentOS 6以及CentOS 7的/etc/init配置文件内容各不相同,但总体的启动流程相同:/sbin/init --> 根据/etc/inittab配置文件设置默认运行级别 --> 运行系统初始化脚本/etc/rc.d/rc.sysinit,完成系统初始化 --> 关闭或启动用户选定的默认运行级别所对应的服务 。

对于CentOS 5来说,初始化程序init是SysV init,其配置文件为:/etc/inittab; 对于CentOS 6来说,初始化程序init是upstart,其配置文件为:/etc/inittab, /etc/init/.conf,也就是upstart将配置文件拆分成多个,在/etc/init/目录下以conf结尾的都是upstart风格的配置文件,而/etc/inittab仅用于设置默认运行级别; 对于CentOS 7来说,初始化程序init是systemd,其配置文件:/usr/lib/system/systemd/, /etc/systemd/system/

具体执行过程:/sbin/init程序会读取/etc/inittab文件确认运行级别,然后执行/etc/rc.d/rc脚本,根据确认的运行级别启动对应/etc/rc.d/rc#.d/目录下的服务(#为0~6),与此同时执行系统初始化脚本/etc/rc.sysinit(软链接,指向/etc/rc.d/rc.sysinit),还会加载/etc/rc.local(软链接,指向/etc/rc.d/rc.local文件)用户自定义服务(脚本)。

CentOS7中初始化进程变为了systemd,systemd即为system daemon,是Linux下的一种init软件,开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell系统开销的效果,最终代替现在常用的System V与BSD风格的init程序。与多数发行版使用的System V风格的init相比,systemd采用了以下的新技术:A.采用Socket激活式与总线激活式服务,以提高相互依赖的各服务的并行运行性能;B.用Cgroup代替PID来追踪进程,即使是两次fork之后生成的守护进程也不会脱离systemd的控制。

  • CentOS7首先执行默认target配置文件/etc/systemd/system/default.target(这是一个软链接,与默认运行级别有关)。然后执行sysinit.target来初始化系统和basic.target来准备操作系统。接着启动multi-user.target下的本机与服务器服务,并检查/etc/rc.d/rc.local文件是否有用户自定义脚本需要启动。最后执行multi-user下的getty.target及登录服务,检查default.target是否有其他的服务需要启动。

  • 系统初始化脚本/etc/rc.d/rc.sysinit

    运行初始化脚本是为了初始化系统环境,这一步初始化包括:

    ①设置主机名;

    ②设置欢迎信息;

    ③激活udev和selinux;

    ④挂载/etc/fstab文件中定义的所有文件系统;

    ⑤检测根文件系统,以读写方式重新挂载根文件系统;

    ⑥设置系统时钟;

    ⑦根据/etc/sysctl.conf文件来设置内核参数;

    ⑧激活lvm及软raid设备;

    ⑨激活swap设备;

    ⑩加载额外设备的驱动程序(因为内核只加载根文件系统所在分区的驱动程序);

  • 关闭或启动用户选定的默认运行级别下所对应的服务

    要实现的功能是:根据前面用户通过init配置文件对默认运行级别的设定,关闭或启动这个运行级别下的服务。(注意:关闭在前,启动在后)

    具体实现方式:运行/etc/rc.d/rc这个脚本文件,而初始化程序init根据前面获取的默认运行级别信息,将这个运行级别数字以参数方式传递给/etc/rc.d/rc脚本中的变量$runlevel;然后,这个脚本会以glob方式去把/etc/rc.d/rc$runlevel.d/S匹配到的脚本所控制的服务开启,而把/etc/rc.d/rc$runlevel.d/K匹配到的脚本所控制的服务开启。

    /etc/rc.d/rc#.d/目录下的脚本文件命名格式:

    (1)K##:表示要停止的服务;'##'表示关闭优先级,数字越小,越是优先关闭;依赖到其他服务的服
    务会优先关闭,而被依赖的服务则后关闭;
    (2)S##:表示要启动的服务;'##'表示启动优先级,数字越小,越是优先启动;被依赖的服务会优先
    启动,而依赖到其他服务的服务则后启动。

第五步:启动终端

根据前面获取的运行级别来启动终端,mingetty程序是用于启动终端的,它会调用登录程序login,这样就能显示出登录提示符了,类似mingetty这种用于打开终端的程序还有getty等。而如果默认运行级别为5,则会打开图形界面。

1.2 Centos7系统启动过程

第一步:硬件启动过程

这一步和CentOS6差不多,详细请看1.1内容。

第二步:GRUB引导阶段

从这一步开始,CentOS6和CentOS7的启动流程区别开始展现出来了。CentOS7的主引导程序使用的是grub2,执行过程是先加载boot.img、core.img两个镜像,再加载MOD模块文件,把grub2程序加载执行,接着解析配置文件/boot/grub/grub.cfg,根据配置文件加载内核镜像到内存,之后构建虚拟根文件系统,最后转到内核。

CentOS7中使用命令进行配置,而不直接去修改配置文件了。grub.cfg配置文件开头注释部分说明了由/etc/grub.d/目录下文件和/etc/default/grub文件组成。改好配置后都需要使用命令grub2-mkconfig -o /boot/grub2/grub.cfg,将配置文件重新生成。

第三步:内部引导阶段

这一步与CentOS6也差不多,加载驱动,切换到真正的根文件系统,唯一不同的是执行的初始化程序变成了/usr/lib/systemd/systemd。

第四步:init初始化阶段(系统初始化阶段)

CentOS7中我们的初始化进程变为了systemd。执行默认target配置文件/etc/systemd/system/default.target(这是一个软链接,与默认运行级别有关)。然后执行sysinit.target来初始化系统和basic.target来准备操作系统。接着启动multi-user.target下的本机与服务器服务,并检查/etc/rc.d/rc.local文件是否有用户自定义脚本需要启动。最后执行multi-user下的getty.target及登录服务,检查default.target是否有其他的服务需要启动。

注意:/etc/systemd/system/default.target指向了/lib/systemd/system/目录下的graphical.target或multiuser.target。而graphical.target依赖multiuser.target,multiuser.target依赖basic.target,basic.target依赖sysinit.target,所以倒过来执行。

unit对象:unit表示不同类型的systemd对象,通过配置文件进行标识和配置;文件中主要包含了系统服务、监听socket、保存的系统快照以及其他与init相关的信息。(也就是CentOS6中的服务器启动脚本)

第五步:启动终端

systemd执行sysinit.target
systemd启动multi-user.target下的本机与服务器服务
systemd执行multi-user.target下面的/etc/rc.d/rc.local
Systemd执行multi-user.target下的getty.target及登录服务
getty.target是启动终端的systemd对象。如果到此步骤,系统没有指定启动图形桌面,到此就可以结束了,如果需要启动图形界面,要在此基础上启动桌面程序。

2、制作一个只运行shell的linux系统

#1、分区并创建文件系统
[root@localhost ~]# echo -e 'n\np\n1\n\n+1G\nw\n' | fdisk /dev/sdb
[root@localhost ~]# echo -e 'n\np\n2\n\n\n\nw\n' | fdisk /dev/sdb
[root@localhost ~]# mkfs.ext4 /dev/sdb1
[root@localhost ~]# mkfs.ext4 /dev/sdb2
#2、挂载boot
[root@localhost ~]# mkdir /mnt/boot
[root@localhost ~]# mount /dev/sdb1 /mnt/boot/
#3、安装grub
[root@localhost ~]# grub-install --root-directory=/mnt/ /dev/sdb

#4、准备内核和initramfs文件
[root@localhost ~]# cp /boot/vmlinuz-2.6.32-754.el6.x86_64 /mnt/boot/vmlinuz
[root@localhost ~]# cp /boot/initramfs-2.6.32-754.el6.x86_64.img /mnt/boot/initramfs.img
#5、准备内核和initramfs文件
[root@localhost ~]# cat /mnt/boot/grub/grub.conf 
default=0
timeout=6
title test linux
root (hd0,0)
kernel /vmlinuz root=/dev/sda2 selinux=0 init=/bin/bash
initrd /initramfs.img

#6、准备根下面相关程序和库
[root@localhost ~]# mkdir /mnt/sysroot
[root@localhost ~]# mount /dev/sdb2 /mnt/sysroot/
[root@localhost ~]# mkdir -pv /mnt/sysroot/{boot,dev,sys,proc,etc,lib,lib64,bin,sbin,tmp,var,usr,opt,home,root,mnt,media}
[root@localhost ~]# mkdir /mnt/sysroot/{dev,proc,etc,sys,lib,home,root}


#7、准备命令及库文件脚本
[root@localhost ~]# cat comcp.sh 
ch_root="/mnt/sysroot"
[ ! -d $ch_root ] && mkdir $ch_root

bincopy() {
    if which $1 &>/dev/null; then

        local cmd_path=`which --skip-alias $1`   #bash的默认路径是/usr/bin/bash,复制/bin/bash时,此处直接赋值:local cmd_path=/bin/bash
        local bin_dir=`dirname $cmd_path` #复制/bin/bash时,此处直接赋值:local cmd_path=/bin
#        local cmd_path=/bin/bash
#        local bin_dir=/bin
        [ -d ${ch_root}${bin_dir} ] || mkdir -p ${ch_root}${bin_dir}
        [ -f ${ch_root}${cmd_path} ] || cp $cmd_path ${ch_root}${bin_dir}
        return 0
    else
        echo "Command not found."
        return 1
    fi
}

libcopy() {
    local lib_list=$(ldd `which --skip-alias $1` | grep -Eo '/[^[:space:]]+')
    for loop in $lib_list;do
        local lib_dir=`dirname $loop`
        [ -d ${ch_root}${lib_dir} ] || mkdir -p  ${ch_root}${lib_dir}
        [ -f ${ch_root}${loop} ] || cp $loop ${ch_root}${lib_dir}
    done
}

read -p "Please input a command: " command

while [ "$command" != "quit" ];do
    if bincopy $command ;then
        libcopy $command
    fi
    read -p "Please input a command or quit: " command
done  

#准备网卡驱动
[root@localhost ~]# ethtool -i eth0
driver: pcnet32
version: 1.35
firmware-version: 
bus-info: 0000:02:01.0
supports-statistics: no
supports-test: yes
supports-eeprom-access: no
supports-register-dump: yes
supports-priv-flags: no
[root@localhost ~]# modinfo -n e1000
/lib/modules/2.6.32-754.el6.x86_64/kernel/drivers/net/e1000/e1000.ko
[root@localhost boot]# cp /lib/modules/2.6.32-754.el6.x86_64/kernel/drivers/net/e1000/e1000.ko /mnt/sysroot/lib/
[root@localhost ~]# chroot /mnt/sysroot/
bash-4.1# 

#7、将前一虚拟机sdb硬盘对应的vmdk文件增加进去,删除原有磁盘,开机启动

3、总结systemctl管理命令及system unit文件格式

从 CentOS 7 版本之后,系统开始用 systemd 实现init进程,系统启动和服务器守护进程管理器功能,负责在系统启动或运行时,激活系统资源,服务器进程和其它进程。

unit表示不同类型的systemd对象,通过配置文件进行标识和配置;文件中主要包含了系统服务、监听socket、保存的系统快照以及其它与init相关的信息。

[root@centos7 ~]# systemctl -t help
Available unit types:
service
socket
busname
target
snapshot
device
mount
automount
swap
timer
path
slice
scope

  • service unit: 文件扩展名为.service, 用于定义系统服务
  • Socket unit: .socket, 定义进程间通信用的socket文件,也可在系统启动时,延迟启动服务,实现按需启动
  • Target unit: 文件扩展名为.target,用于模拟实现运行级别
  • Device unit: .device, 用于定义内核识别的设备
  • Mount unit: .mount, 定义文件系统挂载点
  • Snapshot unit: .snapshot, 管理系统快照
  • Swap unit: .swap, 用于标识swap设备
  • Automount unit: .automount,文件系统的自动挂载点
  • Path unit: .path,用于定义文件系统中的一个文件或目录使用,常用于当文件系统变化时,延迟激活服务,如:spool 目录
  • Timer unit: .timer,封装了一个由 systemd 管理的定时器, 以支持基于定时器的启动。
  • slice unit: .slice,用于封装管理一组进程资源占用的控制组的 slice 单元。 此类单元是通过在 Linux cgroup(Control Group) 树中创建一个节点实现资源控制的。 slice 单元用于包含其他管理进程的单元(一般是 scope 与 service 单元)。 对 slice 单元施加的资源限制,将会作用于此 slice 单元所包含的全部进程的集合。
  • scope unit: .scope,范围(scope)单元并不通过单元文件进行配置, 而是仅能以编程的方式通过 systemd D-Bus 接口创建。 范围单元的名称都以 ".scope" 作为后缀。 与服务(service)单元不同,范围单元用于管理 一组外部创建的进程, 它自身并不派生(fork)任何进程。
systemctl COMMAND name.service

#启动:相当于service name start
systemctl start name.service
#停止:相当于service name stop
systemctl stop name.service
#重启:相当于service name restart
systemctl restart name.service
#查看状态:相当于service name status
systemctl status name.service
#禁止自动和手动启动:
systemctl mask name.service
#取消禁止
systemctl unmask name.service
#查看某服务当前激活与否的状态:
systemctl is-active name.service
#查看所有已经激活的服务:
systemctl list-units --type|-t service
#查看所有服务:
systemctl list-units --type service --all|-a
#设定某服务开机自启,相当于chkconfig name on
systemctl enable name.service
#设定某服务开机禁止启动:相当于chkconfig name off
systemctl disable name.service
#查看所有服务的开机自启状态,相当于chkconfig --list
systemctl list-unit-files --type service
#用来列出该服务在哪些运行级别下启用和禁用:chkconfig --list name
ls /etc/systemd/system/*.wants/name.service
#查看服务是否开机自启:
systemctl is-enabled name.service
#列出失败的服务
systemctl --failed --type=service
#开机并立即启动或停止
systemctl enable --now postfix
systemctl disable --now postfix
#查看服务的依赖关系:
systemctl list-dependencies name.service
#杀掉进程:
systemctl kill unitname
#显示状态
systemctl list-unit-files --type service --all
  • loaded Unit配置文件已处理
  • active(running) 一次或多次持续处理的运行
  • active(exited) 成功完成一次性的配置
  • active(waiting) 运行中,等待一个事件
  • inactive 不运行
  • enabled 开机启动
  • disabled 开机不启动
  • static 开机不启动,但可被另一个启用的服务激活
  • indirect 重定向到别处

3.2 system unit文件格式

/usr/lib/systemd/system:发行版打包者使用,每个服务最主要的启动脚本设置,类似于之前的/etc/init.d/

/etc/systemd/system:系统管理员和用户使用,管理员建立的执行脚本,类似于/etc/rcN.d/Sxx的功能,比上面目录优先运行

/lib/systemd/system::ubutun的对应目录

/run/systemd/system:系统执行过程中所产生的服务脚本,比上面目录优先运行

unit 格式说明:

  • 以 “#” 开头的行后面的内容会被认为是注释
  • 相关布尔值,1、yes、on、true 都是开启,0、no、off、false 都是关闭
  • 时间单位默认是秒,所以要用毫秒(ms)分钟(m)等须显式说明

service unit file文件通常由三部分组成:

  • [Unit]:定义与Unit类型无关的通用选项;用于提供unit的描述信息、unit行为及依赖关系等
  • [Service]:与特定类型相关的专用选项;此处为Service类型
  • [Install]:定义由“systemctl enable”以及"systemctl disable“命令在实现服务启用或禁用时用到的一些选项

Unit段的常用选项:

  • Description:描述信息
  • After:定义unit的启动次序,表示当前unit应该晚于哪些unit启动,其功能与Before相反
  • Requires:依赖到的其它units,强依赖,被依赖的units无法激活时,当前unit也无法激活
  • Wants:依赖到的其它units,弱依赖
  • Conflicts:定义units间的冲突关系

Service段的常用选项:

  • Type:定义影响ExecStart及相关参数的功能的unit进程启动类型
    • simple:默认值,这个daemon主要由ExecStart接的指令串来启动,启动后常驻于内存中
    • forking:由ExecStart启动的程序透过spawns延伸出其他子程序来作为此daemon的主要服务。原生父程序在启动结束后就会终止
    • oneshot:与simple类似,不过这个程序在工作完毕后就结束了,不会常驻在内存中
    • dbus:与simple类似,但这个daemon必须要在取得一个D-Bus的名称后,才会继续运作。因此通常也要同时设定BusNname= 才行
    • notify:在启动完成后会发送一个通知消息。还需要配合 NotifyAccess 来让 Systemd 接收消息
    • idle:与simple类似,要执行这个daemon必须要所有的工作都顺利执行完毕后才会执行。这类的daemon通常是开机到最后才执行即可的服务
  • EnvironmentFile:环境配置文件
  • ExecStart:指明启动unit要运行命令或脚本的绝对路径
  • ExecStartPre: ExecStart前运行
  • ExecStartPost: ExecStart后运行
  • ExecStop:指明停止unit要运行的命令或脚本
  • Restart:当设定Restart=1 时,则当次daemon服务意外终止后,会再次自动启动此服务
  • PrivateTmp:设定为yes时,会在生成/tmp/systemd-private-UUID-NAME.service-XXXXX/tmp/目录

Install段的常用选项:

  • Alias:别名,可使用systemctl command Alias.service
  • RequiredBy:被哪些units所依赖,强依赖
  • WantedBy:被哪些units所依赖,弱依赖
  • Also:安装本服务的时候还要安装别的相关服务

注意:对于新创建的unit文件,或者修改了的unit文件,要通知systemd重载此配置文件,而后可以选择重启。

systemctl daemon-reload

4、破解centos7 密码

首先在启动界面按e编辑启动参数,

将ro参数更改为rw init=/sysroot/bin/sh,按ctr + x启动系统

按下图执行命令更改root密码

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

推荐阅读更多精彩内容