AliOS Things 筆記

AliOS Things 筆記

[TOC]

1技術架構總覽

1.1架構概述

從底部到頂部噪沙,AliOS Things包括:

  • 板級支持包(BSP):主要由SoC供應商開發(fā)和維護
  • 硬件抽象層(HAL):比如WiFi和UART
  • 內(nèi)核:包括Rhino實時操作系統(tǒng)內(nèi)核、Yloop, VFS, KV 存儲
  • 協(xié)議棧:包括TCP/IP協(xié)議棧(LwIP)泪喊,uMesh網(wǎng)絡協(xié)議棧
  • 安全:安全傳輸層協(xié)議(TLS),可信服務框架(TFS)凭语、可信運行環(huán)境(TEE)
  • AOS API:提供可供應用軟件和中間件使用的API
  • 中間件:包括常見的物聯(lián)網(wǎng)組件和阿里巴巴增值服務中間件
  • 示例應用:阿里自主開發(fā)的示例代碼极舔,以及通過了完備測試的應用程序(比如Alinkapp)
    所有的模組都已經(jīng)被組織成組件镊屎,且每個組件都有自己的.mk文件,用于描述它和其它組件間的依賴關系,方便應用開發(fā)者按需選用气筋。

結構框圖

ax.png

文件夾結構

文件夾名稱 內(nèi)容描述
board 評估板(如STM32L496G-Discovery)
build 編譯框架
device 連接MCU/SoC的外設拆内,比如支持使用AT命令的WiFi系列模組
example 代碼示例,通過了完備測試的應用程序(比如Alink)
framework IoT 通用組件
include 系統(tǒng)頭文件
kernel 包括Rhino和協(xié)議棧
platform 芯片架構支持的相關文件
security 包括TLS宠默,TFS麸恍, TEE在內(nèi)的安全組件
tools 命令行界面(CLI)和用于建立遠程設備中心的testbed工具
utility IoT通用軟件庫,比如 cJSON
test UT測試用例

2快速開始

2.1配置環(huán)境

在一臺 Ubuntu 14.04 LTS 64-bit PC 上

sudo apt-get install -y python
sudo apt-get install -y gcc-multilib
sudo apt-get install -y libssl-dev libssl-dev:i386
sudo apt-get install -y libncurses5-dev libncurses5-dev:i386
sudo apt-get install -y libreadline-dev libreadline-dev:i386
sudo apt-get install -y python-pip
sudo apt-get install -y minicom

2.2安裝 aos-cube

首先, 用 python 包管理器 pip 來安裝 aos-cube 和相關的依賴包在全局環(huán)境搀矫,以便于后續(xù)使用 AliOS Things Studio 進行開發(fā)抹沪。

$ pip install setuptools
$ pip install wheel
$ pip install aos-cube

請確認pip環(huán)境是基于 Python 2.7 的。如果遇到權限問題瓤球,可能需要 sudo 來執(zhí)行融欧。
如果在線安裝aos-cube失敗,可以下載源碼安裝卦羡,方法如下:

  • 先下載你要安裝的包噪馏,并解壓(aos-cube-0.2.46.tar.gz)
  • $ cd aos-cube-0.2.46
  • $ python setup.py build
  • $ python setup.py install

2.3下載代碼并編譯運行

git clone https://github.com/alibaba/AliOS-Things.git
cd AliOS-Things
aos make helloworld@linuxhost
./out/helloworld@linuxhost/binary/helloworld@linuxhost.elf

2.4效果

可以看見 app_delayed_action 在1秒時啟動,每5秒觸發(fā)一次绿饵。

 [   0.000]<V> AOS [application_start#15] : application started.
 [   1.000]<V> AOS [app_delayed_action#9] : helloworld app_delayed_action:9 app_task
 [   6.000]<V> AOS [app_delayed_action#9] : helloworld app_delayed_action:9 app_task
 [  11.000]<V> AOS [app_delayed_action#9] : helloworld app_delayed_action:9 app_task
 [  16.000]<V> AOS [app_delayed_action#9] : helloworld app_delayed_action:9 app_task
 [  21.000]<V> AOS [app_delayed_action#9] : helloworld app_delayed_action:9 app_task

3 編譯過程

3.1 aos

2.2小結安裝了aos-cube欠肾,aos-cube封裝了一系列 AliOS Things使用的 Python包。
官方注解如下:

aos command line tool for repositories version control, publishing and updating code from remotely hosted repositories, and invoking aos own build system and export functions, among other operations

aos 用法如下:

[naiquanhu@naiquanhu-VirtualBox AliOS-Things]$ aos --help
usage: aos [-h] [--version]             ...

Code management tool for aos - https://code.aliyun.com/aos/aos
version 0.2.46

Use 'aos <command> -h|--help' for detailed help.
Online manual and guide available at https://code.aliyun.com/aos/aos-cube

optional arguments:
  -h, --help   show this help message and exit
  --version    print version number and exit

Commands:
             
    new        Create new aos program or component
    ls         List cube info, default components info
    import     Import program from URL
    add        Add component from AOS_SDK_PATH or URL
    rm         Remove component
    deploy     Find and add missing components and source codes
    codes      Import the optional component from the remote repository
    publish    Publish program or component
    update     Update to branch, tag, revision or latest
    sync       Synchronize aos component references
    status     Show version control status
               
    make       Make aos program/component
    scons      Make aos program/component by scons
    makelib    Compile static library
               
    config     Tool configuration
               
    upload     Upload aos image
    monitor    Serial port monitor
               
    devices    List devices on serial ports
               
    upgrade    Upgrade aos-cube to latest
    help       This help screen

看一下aos程序內(nèi)容:

which aos

/usr/local/bin/aos
vi /usr/local/bin/aos

#!/usr/bin/python                                                                                  
# EASY-INSTALL-ENTRY-SCRIPT: 'aos-cube==0.2.46','console_scripts','aos'
__requires__ = 'aos-cube==0.2.46'
import sys 
from pkg_resources import load_entry_point

if __name__ == '__main__':
    sys.exit(
        load_entry_point('aos-cube==0.2.46', 'console_scripts', 'aos')()
    )   

/usr/local/bin/aos會調用aos-cube-0.2.46/aos/aos.py

3.2 aos調用make過程

aos make helloworld@linuxhost

該命令會首先進入aos/aos.py中的main()

def _run_make(arg_list):
    #install dependent toolchains
    _install_toolchains(sys.argv[2:])

    # check operating system
    host_os = get_host_os()
    if host_os == None:
        error('Unsupported Operating System!')

    #cd to aos root_dir
    ret, original_dir = cd_aos_root()
    if ret != 'success':
        error("not in AliOS-Things source code directory")

    make_cmds = {
        'Win32': 'cmd/win32/make.exe',
        'Linux64': 'cmd/linux64/make',
        'Linux32': 'cmd/linux32/make',
        'OSX': 'cmd/osx/make'
        }
    tools_dir = os.path.join(os.getcwd(), 'build').replace('\\', '/')
    make_cmd = os.path.join(tools_dir, make_cmds[host_os])

    # Run make command
    make_cmd_str = ' '.join([make_cmd, 'HOST_OS=' + host_os, 'TOOLS_ROOT=' + tools_dir] + list(arg_list))
    popen(make_cmd_str, shell=True, cwd=os.getcwd())
    if os.path.isdir(original_dir): os.chdir(original_dir)

def make_build(make_args):
    # aos new program
    if os.path.isfile(os.path.join(os.getcwd(), Cfg.file)):
        ...
        with cd(os_path):
            ...
            _run_make(['-e', '-f build/Makefile', make_args, app_dir, build_dir])
    else:
        # aos source code
        _run_make(['-e', '-f build/Makefile', make_args])

# Make command
@subcommand('make',
            help='Make aos program/component',
            description="Make aos program/component.")
def make():
    ...
    args = sys.argv[2:]
    ...
    make_args = ' '.join(args)
    for arg in args:
        if '@' in arg: 
            targets = arg.split('@')
            ...
            board = targets[1]
            if board in get_scons_enabled_boards():
                scons_build(args)
            else:
                make_build(make_args)

            return
    #aos make clean go here
    make_build(make_args)

def main():
    ...
    # Parse/run command
    if len(sys.argv) <= 1:
        help_()
        sys.exit(1)
    ...
    pargs, remainder = parser.parse_known_args()
    status = 1

    try:
        ...
        status = pargs.command(pargs)
    except ProcessException as e:
    ...
    sys.exit(status or 0)


if __name__ == "__main__":
    main()

簡單來說拟赊,調用關系如下:

aos make helloworld@linuxhost
    |-- make()
        |-- make_build()
            |-- _run_make(arg_list)
                |-- popen(make_cmd_str, shell=True, cwd=os.getcwd())

變量的值:
build_dir = 'BUILD_DIR=./out/'
app_dir = 'APPDIR=./'
make_args = helloworld@linuxhost
arg_list = ['-e', '-f build/Makefile', make_args, app_dir, build_dir]
make_cmd = ./build/cmd/linux64/make
make_cmd_str = make_cmd + 'HOST_OS=Linux64' + 'TOOLS_ROOT=./build' + + list(arg_list)

其中董济,popen是Python的標準庫中的subprocess包的類,用來fork一個子進程并運行一個外部程序要门,進入 ./build/Makefile

3.3 make入口

build/Makefile是make的主入口,傳入的參數(shù)有:
HOST_OS=Linux64
TOOLS_ROOT=./build
BUILD_DIR=./out/
APPDIR=./
make_args = helloworld@linuxhost

我們先來看看build/Makefile中的一些關鍵內(nèi)容:

export SOURCE_ROOT ?= ./  #AliOS-Things 的根目錄
export MAKEFILES_PATH := $(SOURCE_ROOT)/build   #MAKEFILES_PATH = ./build
export SCRIPTS_PATH := $(SOURCE_ROOT)/build/scripts  #SCRIPTS_PATH = ./build/scripts
MAKEFILE_TARGETS := clean

#define BUILD_STRING, AOS toolchain commands on different hosts
include $(MAKEFILES_PATH)/aos_host_cmd.mk #./build/aos_host_cmd.mk

接著看一下文件./build/aos_host_cmd.mk:

TOOLS_ROOT ?= $(SOURCE_ROOT)build #TOOLS_ROOT = ./build
COMPILER_ROOT ?=$(TOOLS_ROOT)/compiler #COMPILER_ROOT = ./build/compiler
OPENOCD_PATH      := $(TOOLS_ROOT)/OpenOCD/${HOST_OS}/ #OPENOCD_PATH = ./build/OpenOCD/Linux64/
OPENOCD_CFG_PATH  := $(MAKEFILES_PATH)/OpenOCD/${HOST_OS}/ #OPENOCD_CFG_PATH = ./build/OpenOCD/Linux64/
JTAG         ?= jlink_swd
BUILD_DIR    ?= out
...
COMMON_TOOLS_PATH := $(TOOLS_ROOT)/cmd/linux64/
#COMMON_TOOLS_PATH = ./build/cmd/linux64/
export SHELL       = $(COMMON_TOOLS_PATH)dash #SHELL = ./build/cmd/linux64/dash
OPENOCD_FULL_NAME := "$(OPENOCD_PATH)bin/openocd"
#OPENOCD_FULL_NAME = ./build/OpenOCD/Linux64/bin/openocd
ECHO              := echo
PERL              := $(shell which perl)
PYTHON            := $(shell which python)
CLEAN_COMMAND     := "$(COMMON_TOOLS_PATH)rm" -rf $(BUILD_DIR)
#CLEAN_COMMAND = "./build/cmd/linux64/rm" -rf out
MKDIR              = "$(COMMON_TOOLS_PATH)mkdir" -p $1
#MKDIR = "./build/cmd/linux64/mkdir" -p $1
RMDIR              = "$(COMMON_TOOLS_PATH)rm" -rf $1
#RMDIR = "./build/cmd/linux64/rm" -rf $1
CPDIR              = "$(COMMON_TOOLS_PATH)cp" -rf $1 $2
#CPDIR = "./build/cmd/linux64/cp" -rf $1 $2
TRUE_CMD          := true
FALSE_CMD         := false
RM      := "$(COMMON_TOOLS_PATH)rm$(EXECUTABLE_SUFFIX)" -f
#RM = "./build/cmd/linux64/rm" -f
CP      := "$(COMMON_TOOLS_PATH)cp$(EXECUTABLE_SUFFIX)" -f
#CP = "./build/cmd/linux64/cp" -f
MAKE    := "$(COMMON_TOOLS_PATH)make$(EXECUTABLE_SUFFIX)"
#MAKE = "./build/cmd/linux64/make"
BIN2C   := "$(COMMON_TOOLS_PATH)bin2c$(EXECUTABLE_SUFFIX)"
#BIN2C = "./build/cmd/linux64/bin2c"
CURRENT_TIME = $(shell $(DATE) +%Y%m%d.%H%M)
SHOULD_I_WAIT_FOR_DOWNLOAD := $(filter download, $(MAKECMDGOALS))
#因為MAKECMDGOALS = helloworld@linuxhost廓啊,所以 SHOULD_I_WAIT_FOR_DOWNLOAD = null
BUILD_STRING ?= $(strip $(filter-out $(MAKEFILE_TARGETS) download run total, $(MAKECMDGOALS)))
#BUILD_STRING = "helloworld@linuxhost"
BUILD_STRING_TO_DIR = $(subst .,/,$(1))
#BUILD_STRING_TO_DIR = null
DIR_TO_BUILD_STRING = $(subst /,.,$(1))
#DIR_TO_BUILD_STRING = null
CLEANED_BUILD_STRING := $(BUILD_STRING)
#CLEANED_BUILD_STRING = "helloworld@linuxhost"
OUTPUT_DIR   := $(BUILD_DIR)/$(CLEANED_BUILD_STRING)$(MBINS)
#OUTPUT_DIR = "out/helloworld@linuxhost"
AUTO_COMPONENT_DIR := $(OUTPUT_DIR)/auto_component
#AUTO_COMPONENT_DIR = "out/helloworld@linuxhost/auto_component"

主要設置了一些和host os相關的參數(shù)欢搜。
接著build/Makefile文件繼續(xù)看,由于IDE = null谴轮,則COMPILER = null

include $(MAKEFILES_PATH)/aos_toolchain_gcc.mk
AUTO_COMPONENT = $(AUTO_COMPONENT_DIR)/auto_component.mk
#AUTO_COMPONENT = "out/helloworld@linuxhost/auto_component/auto_component.mk"
TEST_COMPONENT_COLLECTION = $(AUTO_COMPONENT_DIR)/test_collection.default
#TEST_COMPONENT_COLLECTION = "out/helloworld@linuxhost/auto_component/test_collection.default"

$(AUTO_COMPONENT): $(TEST_COMPONENT_COLLECTION)
    $(QUIET)$(PYTHON) $(MAKEFILES_PATH)/scripts/auto_component.py $(AUTO_COMPONENT_DIR)

$(TEST_COMPONENT_COLLECTION):
    $(QUIET)$(PYTHON) $(MAKEFILES_PATH)/scripts/gen_test_collection.py $(AUTO_COMPONENT_DIR) $(TEST_COMPONENT_COLLECTION)

設置完一些變量的值后炒瘟,執(zhí)行2條python指令:

python ./build/scripts/gen_test_collection.py ./out/helloworld@linuxhost/auto_component ./out/helloworld@linuxhost/auto_component/test_collection.default

python ./build/scripts/auto_component.py ./out/helloworld@linuxhost/auto_component

命令:

aos make helloworld@linuxhost

該命令最先調用的targe是main_app:

main_app: $(OUTPUT_DIR)/config.mk $(YOS_SDK_PRE_APP_BUILDS) $(MAKEFILES_PATH)/aos_target_build.mk
    $(if $(BINS_ERROR), $(call BINS_EXIT))
    $(if $(MBINS_ERROR), $(call MBINS_EXIT))
    $(QUIET)$(ECHO) Build AOS Now
    $(QUIET)$(ECHO) TOOLCHAIN_PATH=$(TOOLCHAIN_PATH)
    $(QUIET)$(call MKDIR, $(OUTPUT_DIR)/binary)
    $(QUIET)$(call MKDIR, $(OUTPUT_DIR)/modules)
    $(QUIET)$(call MKDIR, $(OUTPUT_DIR)/libraries)
    $(QUIET)$(call MKDIR, $(OUTPUT_DIR)/resources)
    $(QUIET)$(PYTHON) $(MAKEFILES_PATH)/scripts/gen_auto_code.py $(OUTPUT_DIR)/config.mk $(AUTO_COMPONENT_DIR)
    $(QUIET)$(MAKE) -r $(JOBSNO) $(SILENT) -f $(MAKEFILES_PATH)/aos_target_build.mk $(CLEANED_BUILD_STRING) $(PASSDOWN_TARGETS)
    $(QUIET)$(ECHO) Build complete

main_app又依賴$(OUTPUT_DIR)/config.mk,即out/helloworld@linuxhost/config.mk第步,所以先調用build/aos_target_config.mk生成out/helloworld@linuxhost/config.mk疮装,完成后,主要做了兩件事:

  • /usr/bin/python .//build/scripts/gen_auto_code.py out/helloworld@linuxhost/config.mk out/helloworld@linuxhost/auto_component
    根據(jù)out/helloworld@linuxhost/config.mk生成out/helloworld@linuxhost/auto_component下的文件粘都。
  • ./build/cmd/linux64/make -r -j4 -f .//build/aos_target_build.mk helloworld@linuxhost
    進入./build/aos_target_build.mk 繼續(xù)編譯廓推。

4 第一個應用Hello World

4.1 創(chuàng)建工程目錄及源文件

$ mkdir ./example/helloworld
$ cd ./example/helloworld

創(chuàng)建文件helloworld.c helloworld.mk

#helloworld.mk
NAME := helloworld              ## 指定應用名稱
$(NAME)_SOURCES := helloworld.c ## 指定使用的源文件
#$(NAME)_DEFINES += LOCAL_MACRO ## 定義局部符號
GLOBAL_DEFINES += AOS_NO_WIFI   ## 定義全局符號
$(NAME)_COMPONENTS := yloop cli  ## 指定依賴的組件,本例使用yloop cli組件
ifeq ($(BENCHMARKS),1)
$(NAME)_COMPONENTS  += benchmarks
GLOBAL_DEFINES      += CONFIG_CMD_BENCHMARKS 
endif

helloworld.c

static void app_delayed_action(void *arg)
{
    LOG("helloworld %s:%d %s\r\n", __func__, __LINE__, aos_task_name());
    aos_post_delayed_action(5000, app_delayed_action, NULL);
}

int application_start(int argc, char *argv[])
{
    LOG("application started.");
    aos_post_delayed_action(1000, app_delayed_action, NULL);
    aos_loop_run();
    return 0;
}

編譯運行參見2.3節(jié)翩隧。

5 Ubuntu下環(huán)境配置

5.1 下載安裝Jlink

SEGGER官網(wǎng)下載最新jlink驅動樊展,網(wǎng)址 https://www.segger.com/downloads/jlink
Select J-Link Software and Documentation Pack for Linux.

$ sudo dpkg -i JLink_Linux_V634_x86_64.deb
$ ls /opt/SEGGER/
JLink  JLink_V634

設置環(huán)境JLink的環(huán)境變量:

vi ~/.bashrc
export JLINK_HOME=/opt/SEGGER/JLink
export PATH=${PATH}:${JLINK_HOME}

5.2 下載安裝GNU ARM Embedded Toolchain

download toolchain for Linux, URL: https://launchpad.net/gcc-arm-embedded/+download
解壓后放入目錄:/opt/gcc-arm-none-eabi-5_4-2016q3
設置環(huán)境Toolchain的環(huán)境變量:

vi ~/.bashrc
export ARM_TOOLCHAIN_HOME=/opt/gcc-arm-none-eabi-5_4-2016q3
export PATH=${PATH}:${ARM_TOOLCHAIN_HOME}/bin/:${JLINK_HOME}

5.3 連接開發(fā)板

$ JLinkExe
J-Link>connect
Device>CORTEX-M4
TIF>S
Speed>20
JLink.png

5.4 下載代碼

下載代碼到RAM中運行:

J-Link>loadbin firmware.bin 0x1fff0000
J-Link>setpc 0x1fff01fd
J-Link>g

可以整理為腳本:
xxx.jlink

loadbin firmware.bin 0x1fff0000
setpc 0x1fff01fd
g
#q  #退出jlink

在控制臺窗口中輸入 JLink xxx.jlink 即可。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市专缠,隨后出現(xiàn)的幾起案子雷酪,更是在濱河造成了極大的恐慌,老刑警劉巖涝婉,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哥力,死亡現(xiàn)場離奇詭異,居然都是意外死亡墩弯,警方通過查閱死者的電腦和手機吩跋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來最住,“玉大人钞澳,你說我怎么就攤上這事≌歉浚” “怎么了轧粟?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脓魏。 經(jīng)常有香客問我兰吟,道長,這世上最難降的妖魔是什么茂翔? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任混蔼,我火速辦了婚禮,結果婚禮上珊燎,老公的妹妹穿的比我還像新娘惭嚣。我一直安慰自己,他們只是感情好悔政,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布晚吞。 她就那樣靜靜地躺著,像睡著了一般谋国。 火紅的嫁衣襯著肌膚如雪槽地。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天芦瘾,我揣著相機與錄音捌蚊,去河邊找鬼。 笑死近弟,一個胖子當著我的面吹牛缅糟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播藐吮,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼溺拱,長吁一口氣:“原來是場噩夢啊……” “哼逃贝!你這毒婦竟也來了?” 一聲冷哼從身側響起迫摔,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沐扳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后句占,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沪摄,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年纱烘,在試婚紗的時候發(fā)現(xiàn)自己被綠了杨拐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡擂啥,死狀恐怖哄陶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哺壶,我是刑警寧澤屋吨,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站山宾,受9級特大地震影響至扰,放射性物質發(fā)生泄漏。R本人自食惡果不足惜资锰,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一敢课、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绷杜,春花似錦直秆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至懊缺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間培他,已是汗流浹背鹃两。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留舀凛,地道東北人俊扳。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像猛遍,于是被迫代替她去往敵國和親馋记。 傳聞我的和親對象是個殘疾皇子号坡,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容