Android ServiceManager

经常进程间通信、Binder、ServiceManager等等这些关键字被捆绑出现在各种场合,那从字面意思来看ServiceManager就是管理各种服务,那它究竟如何工作,又在Android中扮演什么样的角色呢?

先来回顾下Binder进程间通信:


image.png
20210604170510256.png

image.png

不难看出servicemanager是整个binder机制的守护进程,它是server和cilent之间沟通的桥梁。servicemanager,server,cilent三者运行在不同的进程,servicemanager在充当守护进程的同时,它也在充当server,为什么这样说呢,因为当server进程注册service到servicemanager时,server是客户端,servicemanager是服务端,当client从servicemanager中获取服务时,client是客户端,servicemanager是服务端,当建立好关系后,client就可以和server通信了。至于servicemanager是怎样称为守护进程的,我们接着往下分析。

这里我先将本文的内容梳理一下:

1、首先讲解一下serviceManager的启动过程
2、server进程注册service的过程
3、client进程获取service的过程
4、总结

1. serviceManager的启动过程

找到servicemanger的目录frameworks/native/cmds/servicemanager

看他的编译脚本Android.mk,有如下LOCAL_MODULE := servicemanager LOCAL_INIT_RC := servicemanager.rc

所以我们知道它编译生成的是servicemanger的执行文件,servicemanger是由init进程通过解析servicemanager.rc而创建的(当然如果是android6.0会解析init.rc创建)

/frameworks/native/cmds/servicemanager/servicemanager.rc
service servicemanager /system/bin/servicemanager
    class core
    user system
    group system readproc
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart audioserver
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart inputflinger
    onrestart restart drm
    onrestart restart cameraserver
    writepid /dev/cpuset/system-background/tasks

service后面的servicemanager是服务的名称,后面的/system/bin/servicemanager最终会执行 /frameworks/native/cmds/servicemanager/service_manager.c,onrestart代表的是servicemanager进程重启的化会重启的进程,system_server进程是在zygote进程中启动的。

init进程读取了servicemanager.rc文件后就会启动servicemanager进程。

serviceManager 启动都会有哪些准备工作呢?

servicemanager进程启动后,会执行/frameworks/native/cmds/servicemanager/service_manager.c的main方法,准备工作主要是在main方法中进行的,那来看下相关代码

int main()
{
    struct binder_state *bs;
  //打开driver,申请内存空间
    bs = binder_open(128*1024);

    省略代码...

  // 设置为contextmanager
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -;
    }

    //selinux相关代码
    selinux_enabled = is_selinux_enabled();
    sehandle = selinux_android_service_context_handle();
    selinux_status_open(true);

    if (selinux_enabled > ) {
        if (sehandle == NULL) {
            ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");
            abort();
        }

        if (getcon(&service_manager_context) != ) {
            ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");
            abort();
        }
    }

    union selinux_callback cb;
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);
    cb.func_log = selinux_log_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);

  //进入loop循环不断的取命令
    binder_loop(bs, svcmgr_handler);
    return ;
}

先来看下结构体binder_state,该结构体在frameworks/native/cmds/servicemanager/binder.c中

struct binder_state
{
    int fd;
    void *mapped;
    size_t mapsize;
};

fd是文件描述符,即表示打开的/dev/binder设备文件描述符;mapped是把设备文件/dev/binder映射到进程空间的起始地址;mapsize是内存映射空间的大小

main方法中主要做了以下几件事:

1. open driver 和 mmap

调用binder_open方法 执行了open driver和mmap(映射)工作,想要深入分析,可以了解下Binder驱动底层内存映射。

2. become context manager

become context manager的主要作用是通知driver层它是context manager,这样别的进程就能调用到ServiceManager了。

具体设置代码如下:

int binder_become_context_manager(struct binder_state *bs)
{
   return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

是不是很简单,其实就是通过ioctl发送BINDER_SET_CONTEXT_MGR的cmd给driver层,driver层的关键代码在下面:

static int binder_ioctl_set_ctx_mgr(struct file *filp)
{
  省略代码...

  binder_context_mgr_node = binder_new_node(proc, 0, 0);
  if (binder_context_mgr_node == NULL) {
    ret = -ENOMEM;
    goto out;
  }
  binder_context_mgr_node->local_weak_refs++;
  binder_context_mgr_node->local_strong_refs++;
  binder_context_mgr_node->has_strong_ref = 1;
  binder_context_mgr_node->has_weak_ref = 1;
out:
  return ret;
}

会先构造一个binder_node赋给binder_context_mgr_node(注意它是一个静态属性),到此ServiceManager就变为context manager了。

3. binder_loop

从loop这个词就能断定出肯定是起了一个循环,不断的读取别的进程的命令,那来看下相关代码:

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint_t readbuf[];

    bwr.write_size = ;
    bwr.write_consumed = ;
    bwr.write_buffer = ;

    readbuf[] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint_t));

    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = ;
        bwr.read_buffer = (uintptr_t) readbuf;

        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

        if (res < ) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }

        res = binder_parse(bs, , (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == ) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < ) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}

上面代码主要做了以下几件事:

  1. 发送BC_ENTER_LOOPER给driver层
  2. 启动一个for循环,在循环中不断重复:发送消息给driver层,和读取driver层返回的消息,调用binder_parse解析返回的消息。
binder_parse

binder_loop已经启动起来,不断的监听driver层的数据了,先来简单看下都有哪些数据:

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func)
{
    int r = ;
    uintptr_t end = ptr + (uintptr_t) size;

    while (ptr < end) {
        uint_t cmd = *(uint_t *) ptr;
        省略代码...
        switch(cmd) {
        case BR_NOOP:
            break;
        case BR_TRANSACTION_COMPLETE:
            break;
        case BR_INCREFS:
        case BR_ACQUIRE:
        case BR_RELEASE:
        case BR_DECREFS:
            省略代码...
            break;
        case BR_TRANSACTION: {
            省略代码
            break;
        }
        case BR_REPLY: {
            省略代码...
            break;
        }
        case BR_DEAD_BINDER: {
            省略代码...
            break;
        }
        case BR_FAILED_REPLY:
            r = -;
            break;
        case BR_DEAD_REPLY:
            r = -;
            break;
        default:
            ALOGE("parse: OOPS %d\n", cmd);
            return -;
        }
    }

    return r;
}

上面的代码只把cmd贴出来了,其中的cmd有BINDER_NOOP,BINDER_TRANSACTION,BINDER_TRANSACTION_COMPLETE,BINDER_REPLY等是不是是曾相识。关于binder_parse的内容先介绍到这,后面还会详细介绍。

到此ServiceManager的准备工作结束,它可以等待driver层的数据了。

2. 注册服务

ServiceManager既然有保存服务的功能,那服务是需要提前注册的,那来分析下服务的注册流程。

要想向ServiceManager添加服务,需要先获取到ServiceManager的"引用"。

2.1 获取ServiceManager的“引用”

下面来分析下这个流程,从ServiceManager.java代码分析起,先看下相关代码

ServiceManager.java
private static IServiceManager getIServiceManager() {
    if (sServiceManager != null) {
        return sServiceManager;
    }

    // Find the service manager
    sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
    return sServiceManager;
}

ServiceManager类中的静态属性sServiceManager保存了ServiceManager服务的“引用”值,若为null,则去 BinderInternal.getContextObject() 中获取,看下相关代码

BinderInternal.java

public static final native IBinder getContextObject();

getContextObject是一个native方法,那进入jni层看下它的实现,

android_util_binder.cpp

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}

最终调用了ProcessState的getContextObject方法,ProcessState是咱们的老朋友了,看下相关代码

ProcessState.cpp

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
   return getStrongProxyForHandle(0);
}

getContextObject会调用getStrongProxyForHandle(0),注意这时候的参数是0,进入这个方法看下相关代码

sp<IBinder> ProcessState::getStrongProxyForHandle(int_t handle)
{
    sp<IBinder> result;

    AutoMutex _l(mLock);
  //查找handle_entry
    handle_entry* e = lookupHandleLocked(handle);

    if (e != NULL) {
        // We need to create a new BpBinder if there isn't currently one, OR we
        // are unable to acquire a weak reference on this current one.  See comment
        // in getWeakProxyForHandle() for more info about this.
        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
      //发现handle==0,
            if (handle == 0) {
                // Special case for context manager...
                // The context manager is the only object for which we create
                // a BpBinder proxy without already holding a reference.
                // Perform a dummy transaction to ensure the context manager
                // is registered before we create the first local reference
                // to it (which will occur when creating the BpBinder).
                // If a local reference is created for the BpBinder when the
                // context manager is not present, the driver will fail to
                // provide a reference to the context manager, but the
                // driver API does not return status.
                //
                // Note that this is not race-free if the context manager
                // dies while this code runs.
                //
                // TODO: add a driver API to wait for context manager, or
                // stop special casing handle  for context manager and add
                // a driver API to get a handle to the context manager with
                // proper reference counting.

                Parcel data;
        //去ping下ServiceManager是否活着,没活着则返回null
                status_t status = IPCThreadState::self()->transact(
                        , IBinder::PING_TRANSACTION, data, NULL, );
                if (status == DEAD_OBJECT)
                   return NULL;
            }
      // 根据handle来构造BpBinder,这时候的handle是0
            b = new BpBinder(handle);
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            // This little bit of nastyness is to allow us to add a primary
            // reference to the remote proxy when this team doesn't have one
            // but another team is sending the handle to us.
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }

    return result;
}

若ServiceManager没有死掉,会返回一个handle为0的BpBinder,BinderInternal.getContextObject() 方法会获得一个BinderProxy(它的mObject指向handle为0的BpBinder)

到此ServiceManager的“引用”获取成功,可以与ServiceManager通信了。

2.2 注册服务

注册服务的起始方法是在ServiceManager.java的addService方法中,看下它的代码

 public static void addService(String name, IBinder service) {
        try {
            getIServiceManager().addService(name, service, false);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
  }

上面的方法其实就是binder进程通信的过程.
最终的请求最终到达上章节中的binder_parse方法中,来看下相关代码

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func)
{
    int r = ;
    uintptr_t end = ptr + (uintptr_t) size;

    while (ptr < end) {
        uint_t cmd = *(uint_t *) ptr;
        省略代码...
        switch(cmd) {
        省略代码...
        case BR_TRANSACTION: {
            struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
            if ((end - ptr) < sizeof(*txn)) {
                ALOGE("parse: txn too small!\n");
                return -;
            }
            binder_dump_txn(txn);
            if (func) {
                unsigned rdata[/];
                struct binder_io msg;
                struct binder_io reply;
                int res;

                bio_init(&reply, rdata, sizeof(rdata), );
                bio_init_from_txn(&msg, txn);
                //调用func进行具体消息处理
                res = func(bs, txn, &msg, &reply);
                //异步则告知driver层释放buffer
                if (txn->flags & TF_ONE_WAY) {
                    binder_free_buffer(bs, txn->data.ptr.buffer);
                } else {
                    //同步则发送回复消息,回复消息在reply中
                    binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
                }
            }
            ptr += sizeof(*txn);
            break;
        }
        省略代码...
        }
    }

    return r;
}

上面代码会调用func(bs,txn,&msg,&reply)方法,func是service_manager.c中的svcmgr_handler方法,来看下这方法(只展示了与注册服务相关代码)

service_manager.c
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint_t *s;
    size_t len;
    uint_t handle;
    uint_t strict_policy;
    int allow_isolated;


    if (txn->target.ptr != BINDER_SERVICE_MANAGER)
        return -;

    if (txn->code == PING_TRANSACTION)
        return ;

    // Equivalent to Parcel::enforceInterface(), reading the RPC
    // header with the strict mode policy mask and the interface name.
    // Note that we ignore the strict_policy and don't propagate it
    // further (since we do no outbound RPCs anyway).
    strict_policy = bio_get_uint(msg);
    s = bio_get_string(msg, &len);
    if (s == NULL) {
        return -;
    }

    if ((len != (sizeof(svcmgr_id) / )) ||
        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
        fprintf(stderr,"invalid id %s\n", str(s, len));
        return -;
    }

    if (sehandle && selinux_status_updated() > ) {
        struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
        if (tmp_sehandle) {
            selabel_close(sehandle);
            sehandle = tmp_sehandle;
        }
    }

    switch(txn->code) {
    省略代码...
    case SVC_MGR_ADD_SERVICE:
        s = bio_get_string(msg, &len);
        if (s == NULL) {
            return -;
        }
        handle = bio_get_ref(msg);
        allow_isolated = bio_get_uint(msg) ?  : ;
        if (do_add_service(bs, s, len, handle, txn->sender_euid,
            allow_isolated, txn->sender_pid))
            return -;
        break;

    省略代码...
    }

    bio_put_uint(reply, );
    return ;
}

注册服务的code值是SVC_MGR_ADD_SERVICE,因此进入这个case中,从msg中解析出s和handle,进而在调用do_add_service方法,看下相关代码

service_manager.c
int do_add_service(struct binder_state *bs,
                   const uint16_t *s, size_t len,
                   uint32_t handle, uid_t uid, int allow_isolated,
                   pid_t spid)
{
    struct svcinfo *si;

    if (!handle || (len == 0) || (len > 127))
        return -1;

  //判断是否有权限注册,
    if (!svc_can_register(s, len, spid, uid)) {
        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
             str8(s, len), handle, uid);
        return -1;
    }

  //查找svcinfo,它是一个封装了服务信息的结构体
    si = find_svc(s, len);
  //存在,则给si的handle赋新的handle值
    if (si) {
        if (si->handle) {
            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",
                 str8(s, len), handle, uid);
            svcinfo_death(bs, si);
        }
        si->handle = handle;
    } else {
    //不存在则构建si
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if (!si) {
            ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n",
                 str8(s, len), handle, uid);
            return -1;
        }
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->next = svclist;
    //把si放入svclist链表中
        svclist = si;
    }

    binder_acquire(bs, handle);
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}

上面代码主要是做了添加当前的service信息到svclist链表中的过程:若查找到si(封装了服务信息的结构体),则修改它的handle值;否则构造一个si,放入svclist链表中。

svcinfo
来看下svcinfo包含的数据有哪些,下面是它的代码

struct svcinfo
{   
    //指向下个节点
    struct svcinfo *next;
    //handle值
    uint32_t handle;
    //死亡相关d的结构体
    struct binder_death death;
    int allow_isolated;
    //下面是服务d的名称
    size_t len;
    uint16_t name[0];
};

svclist是svcinfo的链表,注册的服务存储在svcinfo中后,最终会以链表的形式存在。

注册服务成功后,会给添加服务的进程发送reply信息,到此整个流程结束,不知大家是否发现ServiceManager中保存的服务,服务的信息主要是name和handle(BpBinder)值,并没有保存服务的真正对象引用。

3. 获取服务

有了上面注册服务的基础,在分析获取服务的过程会非常容易,获取服务也是从ServiceManager.java开始,获取ServiceManager的“引用” 4节中已经讲过,就不分析了,看下相关代码

ServiceManager.java
 
 public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

ServiceManager中会有一个静态sCache来保存所有的服务,不存在则通过binder进程通信去ServiceManager中获取,获取服务的binder请求最终会到service_manager.c中的svcmgr_handler方法,看下相关代码

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;
    uint32_t strict_policy;
    int allow_isolated;

    省略代码...

    switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
        if (!handle)
            break;
        bio_put_ref(reply, handle);
        return 0;

    省略代码...
    }

    bio_put_uint32(reply, 0);
    return 0;
}

获取服务的code值是SVC_MGR_GET_SERVICE,因此进入这个case,会调用do_find_service方法,来看下这个方法

uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{
    struct svcinfo *si = find_svc(s, len);

    if (!si || !si->handle) {
        return 0;
    }

    if (!si->allow_isolated) {
        // If this service doesn't allow access from isolated processes,
        // then check the uid to see if it is isolated.
        uid_t appid = uid % AID_USER;
        if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) {
            return 0;
        }
    }

    if (!svc_can_find(s, len, spid, uid)) {
        return 0;
    }

    return si->handle;
}

这方法很简单就是从svclist中查找到svcinfo后把svcinfo的handle返回,进而在调用 bio_put_ref(reply, handle) 把handle放入reply中,在给获取服务的进程发送reply信息。获取服务的进程最终获取到的是一个(BinderProxy或BpBinder),BpBinder中的handle在driver层是会被再次转换为当前进程的handle值的。

到此获取服务的流程分析完毕。

4. 总结

ServiceManger是binder进程通信的基石,没有它binder进程通信就不可以进行,甚至整个android系统不可以运行。在系统启动过程中,init进程启动后会启动servicemanager进程,ServiceManager执行open driver,mmap准备工作,最终进入loop状态等待driver层发送的数据。

ServiceManager提供了获取/保存服务的功能,服务在ServiceManager中是以svcinfo(主要是handle和name)的结构体存在的,并将svcinfo连接到svclist的链表上。

别的进程与ServiceManager通信需要先获取ServiceManager的“引用”(handle为0的BpBinder),java层需要通过ServiceManager.java类来获取。

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

推荐阅读更多精彩内容