經(jīng)常說(shuō)的 CPU 上下文切換是什么意思?(上)

理解平均負(fù)載( Load Average)伤锚,并用三個(gè)案例展示了不同場(chǎng)景下平均負(fù)載升高的分析方法擅笔。這其中,多個(gè)進(jìn)程競(jìng)爭(zhēng) CPU 就是一個(gè)經(jīng)常被我們忽視的問(wèn)題屯援。
我想你一定很好奇猛们,進(jìn)程在競(jìng)爭(zhēng) CPU 的時(shí)候并沒(méi)有真正運(yùn)行,為什么還會(huì)導(dǎo)致系統(tǒng)的負(fù)載升高呢狞洋?看到今天的主題弯淘,你應(yīng)該已經(jīng)猜到了,CPU 上下文切換就是罪魁禍?zhǔn)住?/p>

我們都知道吉懊,Linux 是一個(gè)多任務(wù)操作系統(tǒng)庐橙,它支持遠(yuǎn)大于 CPU 數(shù)量的任務(wù)同時(shí)運(yùn)行。當(dāng)然借嗽,這些任務(wù)實(shí)際上并不是真的在同時(shí)運(yùn)行态鳖,而是因?yàn)橄到y(tǒng)在很短的時(shí)間內(nèi),將 CPU 輪流分配給它們恶导,造成多任務(wù)同時(shí)運(yùn)行的錯(cuò)覺(jué)浆竭。

而在每個(gè)任務(wù)運(yùn)行前,CPU 都需要知道任務(wù)從哪里加載惨寿、又從哪里開(kāi)始運(yùn)行邦泄,也就是說(shuō),需要系統(tǒng)事先幫它設(shè)置好CPU 寄存器和程序計(jì)數(shù)器(Program Counter缤沦,PC)虎韵。

CPU 寄存器,是 CPU 內(nèi)置的容量小缸废、但速度極快的內(nèi)存包蓝。而程序計(jì)數(shù)器,則是用來(lái)存儲(chǔ) CPU 正在執(zhí)行的指令位置企量、或者即將執(zhí)行的下一條指令位置测萎。它們都是 CPU 在運(yùn)行任何任務(wù)前,必須的依賴環(huán)境届巩,因此也被叫做CPU 上下文硅瞧。

image.png

知道了什么是 CPU 上下文,我想你也很容易理解 CPU 上下文切換恕汇。CPU 上下文切換腕唧,就是先把前一個(gè)任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計(jì)數(shù)器)保存起來(lái)或辖,然后加載新任務(wù)的上下文到這些寄存器和程序計(jì)數(shù)器,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置枣接,運(yùn)行新任務(wù)颂暇。

而這些保存下來(lái)的上下文,會(huì)存儲(chǔ)在系統(tǒng)內(nèi)核中但惶,并在任務(wù)重新調(diào)度執(zhí)行時(shí)再次加載進(jìn)來(lái)耳鸯。這樣就能保證任務(wù)原來(lái)的狀態(tài)不受影響,讓任務(wù)看起來(lái)還是連續(xù)運(yùn)行膀曾。

我猜肯定會(huì)有人說(shuō)县爬,CPU 上下文切換無(wú)非就是更新了 CPU 寄存器的值嘛,但這些寄存器添谊,本身就是為了快速運(yùn)行任務(wù)而設(shè)計(jì)的财喳,為什么會(huì)影響系統(tǒng)的 CPU 性能呢?

在回答這個(gè)問(wèn)題前碉钠,不知道你有沒(méi)有想過(guò)纲缓,操作系統(tǒng)管理的這些“任務(wù)”到底是什么呢?

也許你會(huì)說(shuō)喊废,任務(wù)就是進(jìn)程,或者說(shuō)任務(wù)就是線程栗弟。是的污筷,進(jìn)程和線程正是最常見(jiàn)的任務(wù)。但是除此之外乍赫,還有沒(méi)有其他的任務(wù)呢瓣蛀?

不要忘了,硬件通過(guò)觸發(fā)信號(hào)雷厂,會(huì)導(dǎo)致中斷處理程序的調(diào)用惋增,也是一種常見(jiàn)的任務(wù)。

所以改鲫,根據(jù)任務(wù)的不同诈皿,CPU 的上下文切換就可以分為幾個(gè)不同的場(chǎng)景,也就是進(jìn)程上下文切換像棘、線程上下文切換以及中斷上下文切換稽亏。

進(jìn)程上下文切換

Linux 按照特權(quán)等級(jí),把進(jìn)程的運(yùn)行空間分為內(nèi)核空間和用戶空間缕题,分別對(duì)應(yīng)著下圖中截歉, CPU 特權(quán)等級(jí)的 Ring 0 和 Ring 3。

  • 內(nèi)核空間(Ring 0)具有最高權(quán)限烟零,可以直接訪問(wèn)所有資源瘪松;

  • 用戶空間(Ring 3)只能訪問(wèn)受限資源咸作,不能直接訪問(wèn)內(nèi)存等硬件設(shè)備,必須通過(guò)系統(tǒng)調(diào)用陷入到內(nèi)核中宵睦,才能訪問(wèn)這些特權(quán)資源记罚。

image.png

換個(gè)角度看,也就是說(shuō)状飞,進(jìn)程既可以在用戶空間運(yùn)行毫胜,又可以在內(nèi)核空間中運(yùn)行。進(jìn)程在用戶空間運(yùn)行時(shí)诬辈,被稱為進(jìn)程的用戶態(tài)酵使,而陷入內(nèi)核空間的時(shí)候,被稱為進(jìn)程的內(nèi)核態(tài)焙糟。

從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變口渔,需要通過(guò)系統(tǒng)調(diào)用來(lái)完成。比如穿撮,當(dāng)我們查看文件內(nèi)容時(shí)缺脉,就需要多次系統(tǒng)調(diào)用來(lái)完成:首先調(diào)用 open() 打開(kāi)文件,然后調(diào)用 read() 讀取文件內(nèi)容悦穿,并調(diào)用 write() 將內(nèi)容寫(xiě)到標(biāo)準(zhǔn)輸出攻礼,最后再調(diào)用 close() 關(guān)閉文件。

那么栗柒,系統(tǒng)調(diào)用的過(guò)程有沒(méi)有發(fā)生 CPU 上下文的切換呢礁扮?答案自然是肯定的。

CPU 寄存器里原來(lái)用戶態(tài)的指令位置瞬沦,需要先保存起來(lái)太伊。接著,為了執(zhí)行內(nèi)核態(tài)代碼逛钻,CPU 寄存器需要更新為內(nèi)核態(tài)指令的新位置僚焦。最后才是跳轉(zhuǎn)到內(nèi)核態(tài)運(yùn)行內(nèi)核任務(wù)。

而系統(tǒng)調(diào)用結(jié)束后曙痘,CPU 寄存器需要恢復(fù)原來(lái)保存的用戶態(tài)芳悲,然后再切換到用戶空間,繼續(xù)運(yùn)行進(jìn)程屡江。所以芭概,一次系統(tǒng)調(diào)用的過(guò)程,其實(shí)是發(fā)生了兩次 CPU 上下文切換惩嘉。

不過(guò)罢洲,需要注意的是,系統(tǒng)調(diào)用過(guò)程中,并不會(huì)涉及到虛擬內(nèi)存等進(jìn)程用戶態(tài)的資源惹苗,也不會(huì)切換進(jìn)程殿较。這跟我們通常所說(shuō)的進(jìn)程上下文切換是不一樣的:

  • 進(jìn)程上下文切換,是指從一個(gè)進(jìn)程切換到另一個(gè)進(jìn)程運(yùn)行桩蓉。

  • 而系統(tǒng)調(diào)用過(guò)程中一直是同一個(gè)進(jìn)程在運(yùn)行淋纲。

所以,系統(tǒng)調(diào)用過(guò)程通常稱為特權(quán)模式切換院究,而不是上下文切換洽瞬。但實(shí)際上,系統(tǒng)調(diào)用過(guò)程中,CPU 的上下文切換還是無(wú)法避免的。

那么抛丽,進(jìn)程上下文切換跟系統(tǒng)調(diào)用又有什么區(qū)別呢?

首先为障,你需要知道,進(jìn)程是由內(nèi)核來(lái)管理和調(diào)度的放祟,進(jìn)程的切換只能發(fā)生在內(nèi)核態(tài)鳍怨。所以,進(jìn)程的上下文不僅包括了虛擬內(nèi)存跪妥、棧鞋喇、全局變量等用戶空間的資源,還包括了內(nèi)核堆棧眉撵、寄存器等內(nèi)核空間的狀態(tài)确徙。

因此,進(jìn)程的上下文切換就比系統(tǒng)調(diào)用時(shí)多了一步:在保存當(dāng)前進(jìn)程的內(nèi)核狀態(tài)和 CPU 寄存器之前执桌,需要先把該進(jìn)程的虛擬內(nèi)存、棧等保存下來(lái)芜赌;而加載了下一進(jìn)程的內(nèi)核態(tài)后仰挣,還需要刷新進(jìn)程的虛擬內(nèi)存和用戶棧。

如下圖所示缠沈,保存上下文和恢復(fù)上下文的過(guò)程并不是“免費(fèi)”的膘壶,需要內(nèi)核在 CPU 上運(yùn)行才能完成。

image.png

根據(jù) Tsuna 的測(cè)試報(bào)告洲愤,每次上下文切換都需要幾十納秒到數(shù)微秒的 CPU 時(shí)間颓芭。這個(gè)時(shí)間還是相當(dāng)可觀的,特別是在進(jìn)程上下文切換次數(shù)較多的情況下柬赐,很容易導(dǎo)致 CPU 將大量時(shí)間耗費(fèi)在寄存器亡问、內(nèi)核棧以及虛擬內(nèi)存等資源的保存和恢復(fù)上,進(jìn)而大大縮短了真正運(yùn)行進(jìn)程的時(shí)間。這也正是上一節(jié)中我們所講的州藕,導(dǎo)致平均負(fù)載升高的一個(gè)重要因素束世。

另外,我們知道床玻, Linux 通過(guò) TLB(Translation Lookaside Buffer)來(lái)管理虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系毁涉。當(dāng)虛擬內(nèi)存更新后,TLB 也需要刷新锈死,內(nèi)存的訪問(wèn)也會(huì)隨之變慢贫堰。特別是在多處理器系統(tǒng)上,緩存是被多個(gè)處理器共享的待牵,刷新緩存不僅會(huì)影響當(dāng)前處理器的進(jìn)程其屏,還會(huì)影響共享緩存的其他處理器的進(jìn)程。

知道了進(jìn)程上下文切換潛在的性能問(wèn)題后洲敢,我們?cè)賮?lái)看漫玄,究竟什么時(shí)候會(huì)切換進(jìn)程上下文。

顯然压彭,進(jìn)程切換時(shí)才需要切換上下文睦优,換句話說(shuō),只有在進(jìn)程調(diào)度的時(shí)候壮不,才需要切換上下文汗盘。Linux 為每個(gè) CPU 都維護(hù)了一個(gè)就緒隊(duì)列,將活躍進(jìn)程(即正在運(yùn)行和正在等待 CPU 的進(jìn)程)按照優(yōu)先級(jí)和等待 CPU 的時(shí)間排序询一,然后選擇最需要 CPU 的進(jìn)程隐孽,也就是優(yōu)先級(jí)最高和等待 CPU 時(shí)間最長(zhǎng)的進(jìn)程來(lái)運(yùn)行。

那么健蕊,進(jìn)程在什么時(shí)候才會(huì)被調(diào)度到 CPU 上運(yùn)行呢菱阵?

最容易想到的一個(gè)時(shí)機(jī),就是進(jìn)程執(zhí)行完終止了缩功,它之前使用的 CPU 會(huì)釋放出來(lái)晴及,這個(gè)時(shí)候再?gòu)木途w隊(duì)列里,拿一個(gè)新的進(jìn)程過(guò)來(lái)運(yùn)行嫡锌。其實(shí)還有很多其他場(chǎng)景虑稼,也會(huì)觸發(fā)進(jìn)程調(diào)度,在這里我給你逐個(gè)梳理下势木。

其一蛛倦,為了保證所有進(jìn)程可以得到公平調(diào)度,CPU 時(shí)間被劃分為一段段的時(shí)間片啦桌,這些時(shí)間片再被輪流分配給各個(gè)進(jìn)程溯壶。這樣,當(dāng)某個(gè)進(jìn)程的時(shí)間片耗盡了,就會(huì)被系統(tǒng)掛起茸塞,切換到其它正在等待 CPU 的進(jìn)程運(yùn)行躲庄。

其二,進(jìn)程在系統(tǒng)資源不足(比如內(nèi)存不足)時(shí)钾虐,要等到資源滿足后才可以運(yùn)行噪窘,這個(gè)時(shí)候進(jìn)程也會(huì)被掛起,并由系統(tǒng)調(diào)度其他進(jìn)程運(yùn)行效扫。

其三倔监,當(dāng)進(jìn)程通過(guò)睡眠函數(shù) sleep 這樣的方法將自己主動(dòng)掛起時(shí),自然也會(huì)重新調(diào)度菌仁。

其四浩习,當(dāng)有優(yōu)先級(jí)更高的進(jìn)程運(yùn)行時(shí),為了保證高優(yōu)先級(jí)進(jìn)程的運(yùn)行济丘,當(dāng)前進(jìn)程會(huì)被掛起谱秽,由高優(yōu)先級(jí)進(jìn)程來(lái)運(yùn)行。

最后一個(gè)摹迷,發(fā)生硬件中斷時(shí)疟赊,CPU 上的進(jìn)程會(huì)被中斷掛起,轉(zhuǎn)而執(zhí)行內(nèi)核中的中斷服務(wù)程序峡碉。

了解這幾個(gè)場(chǎng)景是非常有必要的近哟,因?yàn)橐坏┏霈F(xiàn)上下文切換的性能問(wèn)題,它們就是幕后兇手鲫寄。

線程上下文切換

說(shuō)完了進(jìn)程的上下文切換吉执,我們?cè)賮?lái)看看線程相關(guān)的問(wèn)題。

線程與進(jìn)程最大的區(qū)別在于地来,線程是調(diào)度的基本單位戳玫,而進(jìn)程則是資源擁有的基本單位。說(shuō)白了未斑,所謂內(nèi)核中的任務(wù)調(diào)度量九,實(shí)際上的調(diào)度對(duì)象是線程;而進(jìn)程只是給線程提供了虛擬內(nèi)存颂碧、全局變量等資源。所以类浪,對(duì)于線程和進(jìn)程载城,我們可以這么理解:

當(dāng)進(jìn)程只有一個(gè)線程時(shí),可以認(rèn)為進(jìn)程就等于線程费就。

當(dāng)進(jìn)程擁有多個(gè)線程時(shí)诉瓦,這些線程會(huì)共享相同的虛擬內(nèi)存和全局變量等資源。這些資源在上下文切換時(shí)是不需要修改的。

另外睬澡,線程也有自己的私有數(shù)據(jù)固额,比如棧和寄存器等,這些在上下文切換時(shí)也是需要保存的煞聪。

這么一來(lái)斗躏,線程的上下文切換其實(shí)就可以分為兩種情況:

第一種, 前后兩個(gè)線程屬于不同進(jìn)程昔脯。此時(shí)啄糙,因?yàn)橘Y源不共享,所以切換過(guò)程就跟進(jìn)程上下文切換是一樣云稚。

第二種隧饼,前后兩個(gè)線程屬于同一個(gè)進(jìn)程。此時(shí)静陈,因?yàn)樘摂M內(nèi)存是共享的燕雁,所以在切換時(shí),虛擬內(nèi)存這些資源就保持不動(dòng)鲸拥,只需要切換線程的私有數(shù)據(jù)拐格、寄存器等不共享的數(shù)據(jù)。

到這里你應(yīng)該也發(fā)現(xiàn)了崩泡,雖然同為上下文切換禁荒,但同進(jìn)程內(nèi)的線程切換,要比多進(jìn)程間的切換消耗更少的資源角撞,而這呛伴,也正是多線程代替多進(jìn)程的一個(gè)優(yōu)勢(shì)。

中斷上下文切換

除了前面兩種上下文切換谒所,還有一個(gè)場(chǎng)景也會(huì)切換 CPU 上下文热康,那就是中斷。

為了快速響應(yīng)硬件的事件劣领,中斷處理會(huì)打斷進(jìn)程的正常調(diào)度和執(zhí)行姐军,轉(zhuǎn)而調(diào)用中斷處理程序,響應(yīng)設(shè)備事件尖淘。而在打斷其他進(jìn)程時(shí)奕锌,就需要將進(jìn)程當(dāng)前的狀態(tài)保存下來(lái),這樣在中斷結(jié)束后村生,進(jìn)程仍然可以從原來(lái)的狀態(tài)恢復(fù)運(yùn)行惊暴。

跟進(jìn)程上下文不同,中斷上下文切換并不涉及到進(jìn)程的用戶態(tài)趁桃。所以辽话,即便中斷過(guò)程打斷了一個(gè)正處在用戶態(tài)的進(jìn)程肄鸽,也不需要保存和恢復(fù)這個(gè)進(jìn)程的虛擬內(nèi)存、全局變量等用戶態(tài)資源油啤。中斷上下文典徘,其實(shí)只包括內(nèi)核態(tài)中斷服務(wù)程序執(zhí)行所必需的狀態(tài),包括 CPU 寄存器益咬、內(nèi)核堆棧逮诲、硬件中斷參數(shù)等。

對(duì)同一個(gè) CPU 來(lái)說(shuō)础废,中斷處理比進(jìn)程擁有更高的優(yōu)先級(jí)汛骂,所以中斷上下文切換并不會(huì)與進(jìn)程上下文切換同時(shí)發(fā)生。同樣道理评腺,由于中斷會(huì)打斷正常進(jìn)程的調(diào)度和執(zhí)行帘瞭,所以大部分中斷處理程序都短小精悍,以便盡可能快的執(zhí)行結(jié)束蒿讥。

另外蝶念,跟進(jìn)程上下文切換一樣,中斷上下文切換也需要消耗 CPU芋绸,切換次數(shù)過(guò)多也會(huì)耗費(fèi)大量的 CPU媒殉,甚至嚴(yán)重降低系統(tǒng)的整體性能。所以摔敛,當(dāng)你發(fā)現(xiàn)中斷次數(shù)過(guò)多時(shí)廷蓉,就需要注意去排查它是否會(huì)給你的系統(tǒng)帶來(lái)嚴(yán)重的性能問(wèn)題。

小結(jié)

總結(jié)一下马昙,不管是哪種場(chǎng)景導(dǎo)致的上下文切換桃犬,你都應(yīng)該知道:

  • CPU 上下文切換,是保證 Linux 系統(tǒng)正常工作的核心功能之一行楞,一般情況下不需要我們特別關(guān)注攒暇。

  • 但過(guò)多的上下文切換,會(huì)把 CPU 時(shí)間消耗在寄存器子房、內(nèi)核棧以及虛擬內(nèi)存等數(shù)據(jù)的保存和恢復(fù)上形用,從而縮短進(jìn)程真正運(yùn)行的時(shí)間,導(dǎo)致系統(tǒng)的整體性能大幅下降证杭。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末田度,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子解愤,更是在濱河造成了極大的恐慌每币,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琢歇,死亡現(xiàn)場(chǎng)離奇詭異兰怠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)李茫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)揭保,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人魄宏,你說(shuō)我怎么就攤上這事秸侣。” “怎么了宠互?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵味榛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我予跌,道長(zhǎng)搏色,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任券册,我火速辦了婚禮频轿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘烁焙。我一直安慰自己航邢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布骄蝇。 她就那樣靜靜地躺著膳殷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪九火。 梳的紋絲不亂的頭發(fā)上赚窃,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音吃既,去河邊找鬼考榨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鹦倚,可吹牛的內(nèi)容都是我干的河质。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼震叙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼掀鹅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起媒楼,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乐尊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后划址,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扔嵌,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡限府,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了痢缎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胁勺。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖独旷,靈堂內(nèi)的尸體忽然破棺而出署穗,到底是詐尸還是另有隱情,我是刑警寧澤嵌洼,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布案疲,位于F島的核電站,受9級(jí)特大地震影響麻养,放射性物質(zhì)發(fā)生泄漏褐啡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一回溺、第九天 我趴在偏房一處隱蔽的房頂上張望春贸。 院中可真熱鬧,春花似錦遗遵、人聲如沸萍恕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)允粤。三九已至,卻和暖如春翼岁,著一層夾襖步出監(jiān)牢的瞬間类垫,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工琅坡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悉患,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓榆俺,卻偏偏與公主長(zhǎng)得像售躁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茴晋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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