2019-01-01 CSAPP 第八章(二)

8.5 信號

研究一種更高層次的軟件形式的異常, 也是一種軟件中斷技即,稱為Unix信號而叼,它允許進程中斷其他進程。

一個信號就是一條小消息液荸,它通知進程系統(tǒng)中發(fā)生一個某種類型的事件脱篙。

Linux系統(tǒng)支持30多種信號伤柄。

每種信號類型對應于某種系統(tǒng)事件

底層的信號文搂。

當?shù)讓影l(fā)生硬件異常,信號通知 用戶進程 發(fā)生了這些異常笔喉。

除以0:發(fā)送SIGILL信號常挚。

非法存儲器引用:發(fā)送SIGSEGV信號

較高層次的軟件事件

鍵入ctrl+c:發(fā)送SIGINT信號

一個進程可以發(fā)送給另一個進程SIGKILL信號強制終止它稽物。

子進程終止或者停止,內核會發(fā)送一個SIGCHLD信號給父進程秧倾。


8.5.1 信號術語

傳送一個信號到目的進程有兩個步驟那先。

發(fā)送信號: 內核通過更新目的進程上下文的某個狀態(tài)赡艰,就說發(fā)送一個信號給目的進程慷垮。

發(fā)送信號有兩個原因

內核檢測到一個系統(tǒng)事件。比如被零除錯誤汤纸,或者子進程終止芹血。

一個進程調用了kill函數(shù)。顯示要求進程發(fā)送信號給目的進程啃擦。

一個進程可以發(fā)信號給它自己饿悬。

接收信號: 當目的進程 被內核強迫以某種方式對信號的發(fā)送做出反應狡恬。目的進程就接收了信號蝎宇。

進程可以忽略這個信號夫啊,終止辆憔。

或者通過一個稱為信號處理程序(signal handler)的用戶層函數(shù)捕獲這個信號虱咧。

一個只發(fā)出而沒有被接收的信號叫做待處理信號(pending signal)

一種類型至多只有一個待處理信號。

如果一個進程有一個類型為k的待處理信號玄坦。

那么接下來發(fā)送到這個進程類型為k的信號都會被簡單的丟棄绘沉。

一個進程可以有選擇性地阻塞接收某種信號

它任然可以被發(fā)送车伞。但是產生的待處理信號不會被接收。

一個待處理信號最多被接收一次困曙。內核為每個進程在pending位向量維護著待處理信號的集合谦去,而在blocked位向量維護著被阻塞的信號集合鳄哭。只要傳送一個類型為k的信號,內核就會設置pending中的第k位锄俄,而只要接收了一個類型為k的信號飘痛,內核就會清除pending中的第k位宣脉。


8.5.2 發(fā)送信號

Unix系統(tǒng) 提供大量向進程發(fā)送信號的機制剔氏。所有這些機制都是基于進程組(process group)。

進程組

每個進程都屬于一個進程組塑陵。

由一個正整數(shù)進程組ID來標示

getpgrp()函數(shù)返回當前進程的進程組ID:

#include<unistd.h>

pid_t getpgrp(void);

1

2

默認蜡励,一個子進程和它的父進程同屬于一個進程組

一個進程可以通過setpgid()來改變自己或者其他進程的進程組凉倚。

#include<unistd.h>

int setpgid(pid_t pid,pid_t pgid);

如果pid是0 ,那么使用當前進程的pid扮碧。

如果pgid是0杏糙,那么使用指定的pid作為pgid(即pgid=pid)宏侍。

例如:進程15213調用setpgid(0,0)

那么進程15213會 創(chuàng)建/加入進程組15213.

1

2

3

4

5

6

7

用/bin/kill 程序發(fā)送信號

/bin/kill可以向另外的進程發(fā)送任意的信號。

比如

unix>/bin/kill -9 15213

1

發(fā)送信號9(SIGKILL)給進程15213漫蛔。

一個為負的PID會導致信號被發(fā)送到進程組PID中的每個進程旧蛾。

unix>/bin/kill -9 -15213

1

發(fā)送信號9(SIGKILL)給進程組15213中的每個進程锨天。

用/bin/kill的原因是,有些Unix shell 有自己的kill命令

從鍵盤發(fā)送信號

作業(yè)(job) :對一個命令行求值而創(chuàng)建的進程搂赋。

在任何時候至多只有一個前臺作業(yè)和0個或多個后臺作業(yè)

前臺作業(yè)就是需要等待的

后臺作業(yè)就是不需要等待的

鍵入unix>ls|sort

創(chuàng)建一個兩個進程組成的前臺作業(yè)脑奠。

兩個進程通過Unix管道鏈接幅慌。

shell為每個作業(yè)創(chuàng)建了一個獨立的進程組。

進程組ID取自作業(yè)中父進程中的一個齿诞。

在鍵盤輸入ctrl-c 會發(fā)送一個SIGINT信號到外殼祷杈。外殼捕獲該信號。然后發(fā)送SIGINT信號到這個前臺進程組的每個進程宿刮。在默認情況下私蕾,結果是終止前臺作業(yè)

類似是目,輸入ctrl-z會發(fā)送一個SIGTSTP信號到外殼,外殼捕獲這個信號揉抵,并發(fā)送SIGTSTP信號給前臺進程組的每個進程嗤疯,在默認情況茂缚,結果是停止(掛起)前臺作業(yè)(還是僵死的)

用kill函數(shù)發(fā)送信號

進程通過調用kill函數(shù)發(fā)送信號給其他進程,類似于bin/kill

int kill(pid_t pid, int sig);

1

pid>0,發(fā)送信號sig給進程pid

pid<0,發(fā)送信號sig給進程組abs(pid)

事例:kill(pid,SIGKILL)

用alarm函數(shù)發(fā)送信號

進程可以通過調用alarm函數(shù)向它自己SIGALRM信號龟糕。

#include<unistd.h>

unsigned int alarm(unsigned int secs);

返回:前一次鬧鐘剩余的秒數(shù)讲岁。

1

2

3

4

5

alarm函數(shù)安排內核在secs秒內發(fā)送一個SIGALRM信號給調用進程

如果secs=0 那么不會調度鬧鐘衬以,當然不會發(fā)送SIGALRM信號看峻。

在任何情況,對alarm的調用會取消待處理(pending)的鬧鐘溪窒,并且會返回被取消的鬧鐘還剩余多少秒結束。如果沒有pending的話,返回0

一個例子:

輸出

unix> ./alarm

BEEP

BEEP

BEEP

BEEP

BEEP

BOOM!

//handler是一個自己定義的信號處理程序惜浅,通過signal函數(shù)捆綁坛悉。

1

2

3

4

5

6

7

8


8.5.3 接收信號

信號的處理時機是在從內核態(tài)切換到用戶態(tài)時裸影,會執(zhí)行do_signal()函數(shù)來處理信號

當內核從一個異常處理程序返回军熏,準備將控制傳遞給進程p時,它會檢查進程p的未被阻塞的待處理信號的集合(pening&~blocked)均践。

如果這個集合為空彤委,內核將控制傳遞到p的邏輯控制流的下一條指令或衡。

如果非空封断,內核選擇集合中某個信號k(通常是最小的k),并且強制p接收k椒涯。收到這個信號會觸發(fā)進程某些行為回梧。一旦進程完成行為狱意,傳遞到p的邏輯控制流的下一條指令。

每個信號類型都有一個預定義的默認類型财骨,以下幾種.

進程終止

進程終止并轉儲存器(dump core)

進程停止直到被SIGCONT信號重啟

進程忽略該信號

進程可以通過使用signal函數(shù)修改和信號相關聯(lián)的默認行為隆箩。

SIGSTOP,SIGKILL是不能被修改的例外。

#include<signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum,sighandler_t handler);

1

2

3

4

signal函數(shù)通過下列三種方式之一改變和信號signum相關聯(lián)的行為杨蛋。

如果handler是SIG_IGN,那么忽略類型為signum的信號

如果handler是SIG_DFL,那么類型為signum的信號恢復為默認行為逞力。

否則糠爬,handler就是用戶定義的函數(shù)地址执隧,這個函數(shù)稱為信號處理程序

只要進程接收到一個類型為signum的信號,就會調用handler捅膘。

設置信號處理程序:把函數(shù)傳遞給signal改變信號的默認行為滚粟。

調用信號處理程序凡壤,叫捕獲信號

執(zhí)行信號處理程序亚侠,叫處理信號

當處理程序執(zhí)行它的return語句后,控制通常傳遞回控制流中進程被信號接收中斷位置處的指令箕别。

信號處理程序是計算機并發(fā)的又一個示例滞谢。信號處理程序的執(zhí)行中斷狮杨,類似于底層異常處理程序中斷當前應用程序的控制流的方式。因為信號處理程序的邏輯控制流與主函數(shù)的邏輯控制流重疊清寇,信號處理程序和主函數(shù)并發(fā)地運行华烟。

自我思考:信號是一種異常/中斷,當接收到信號的時候负饲,會停下當前進程所做的事,立馬去執(zhí)行信號處理程序衩藤。并不是多線程/并行,但確是并發(fā)的涛漂。從下面這張圖匈仗,可見一斑。


8.5.4 信號處理問題

當一個程序要捕獲多個信號時间狂,一些細微的問題就產生了鉴象。

待處理信號被阻塞

Unix 信號處理程序通常會阻塞 當前處理程序正在處理 的類型的待處理信號何鸡。

待處理信號(被拋棄了)不會排隊等待

當有兩個同類型信號都是待處理信號時骡男,有一個會被拋棄隔盛。

關鍵思想:存在一個待處理的信號k僅僅表明至少一個一個信號k到達過。

系統(tǒng)調用可以被中斷(在某些unix系統(tǒng)會出現(xiàn))

像read,wait和accept這樣的系統(tǒng)調用潛在的阻塞一段較長的時間已亥,稱為慢速系統(tǒng)調用虑椎。

當處理程序捕獲一個信號,被中斷的慢速系統(tǒng)調用在信號處理程序返回后將不在繼續(xù)传趾,而是立即返回給用戶一個錯誤條件泥技,并將errno設置為EINTR珊豹。

用一個后臺回收僵死子進程的程序,前臺讀入做例子

1.初始簡單利用接收SIGCHLD信號回收蜕便,一次調用只回收一個轿腺。

在調用的過程中丛楚,又有信號發(fā)送過來,但是被阻塞了仿荆。之后又被直接拋棄坏平。

如果不處理被阻塞和不會排隊等待的問題赖歌。會有信號被拋棄。

重要教訓:不可以用信號對其他進程中發(fā)送的事件計數(shù)

handle1-code

2.一次調用盡可能的多回收功茴,保證在回收過程中庐冯,沒有遺漏的信號。

handle2-code

3.還存在一個問題坎穿,在前臺中展父,某些unix系統(tǒng)(Solaris系統(tǒng))的read被中斷后不會自動重啟,需要手動重啟玲昧,Linux一般會自動重啟栖茉。

之前 read模塊 code

現(xiàn)在改為如果是errno==EINTR手動重啟孵延。

或者使用Signal包裝函數(shù)標準吕漂。8.5.5會提到。

8.5.5 可移植的信號處理

不同系統(tǒng)之間尘应,信號處理語義的差異(比如一個被中斷的慢速系統(tǒng)調用是重啟惶凝,還是永久放棄)是Unix信號系統(tǒng)的一個缺陷吼虎。

為了處理這個問題,Posix標準定義了sigaction函數(shù)苍鲜,它允許與Linux和Solaris這樣與Posix兼容的系統(tǒng)上的用戶思灰,明確指明他們想要的信號處理語義。

#include<signal.h>

int sigaction(int signum,stuct sigaction *act,struct sigaction *oldcat);

//若成功則為1混滔,出錯則為-1洒疚。

1

2

3

4

sigaction函數(shù)應用不廣泛,它要求用戶設置多個結構條目坯屿。

一個更簡潔的方式油湖,是定義一個包裝函數(shù),稱為Signal,它調用sigaction领跛。

它的調用方式與signal函數(shù)的調用方式一樣乏德。

Signal包裝函數(shù)設置了一個信號處理程序,其信號處理語義如下(設置標準):

只有這個處理程序當前正在處理的那種類型被阻塞隔节。

和所有信號實現(xiàn)一樣鹅经,信號不會排隊等待寂呛。

只要可能怎诫,被中斷的系統(tǒng)調用會自動重啟

一旦設置了信號處理程序,它就會一直保持贷痪,直到Signal帶著handler參數(shù)為SIG_IGN或者SIG_DFL被調用幻妓。

在某些比較老的Unix系統(tǒng),信號處理程序被使用一次后劫拢,又回到默認行為肉津。

8.5.6 顯示地阻塞和取消阻塞信號

通過sigprocmask函數(shù)來操作。

#include<signal.h>

int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);

1

2

3

sigprocmask函數(shù)改變當前已阻塞信號的集合(8.5.1節(jié)描述的blocked位向量)舱沧。

具體行為依賴how值

SIG_BLOCK:添加set中的信號到blocked中妹沙。

SIG_UNBLOCK: 從blocked刪除set中的信號。

SIG_SETMASK: blocked=set熟吏。

如果oldset非空距糖,block位向量以前的值會保存到oldset中。

還有以下函數(shù)操作set集合

#include<signal.h>

int sigemptyset(sigset_t *set);

//置空

int sigfillset(sigset_t *set);

//每個信號全部填入

int sigaddset(sigset_t *set,int signum);

//添加

int sigdelset(sigset_t *set,int signum);

//刪除

//成功輸出0牵寺,出錯輸出-1

int sigismember(const sigset_t *set,int signum);

//判斷

//若signum是set的成員悍引,輸出1,不是輸出0帽氓,出錯輸出-1趣斤。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

8.5.7 同步流以避免討厭的并發(fā)錯誤

如何編寫讀寫相同存儲位置的并發(fā)流程序的問題,困擾著數(shù)代計算機科學家黎休。

流可能交錯 的數(shù)量是與指令數(shù) 量呈指數(shù)關系

有些交錯會產生正確結果浓领,有些可能不會玉凯。

所謂同步流就是。以某種方式同步并發(fā)流镊逝,從而得到 最大的可行交錯的集合 壮啊,每個交錯集合都能得到正確的結果。

并發(fā)編程是一個很深奧撑蒜,很重要的問題歹啼。在第12章詳細討論。

現(xiàn)在我們只考慮一個并發(fā)相關的智力挑戰(zhàn)座菠。

code

如果發(fā)生以下情況狸眼,會出現(xiàn)同步錯誤。

父進程執(zhí)行fork函數(shù)浴滴,內核調度新創(chuàng)建的子進程運行拓萌,而不是父進程。

在父進程再次運行前升略,子進程已經(jīng)終止微王,變成僵死進程,需要內核一個SIGCHLD信號給父進程

父進程處理信號品嚣,調用deletejob.

調用addjob炕倘。

顯然deletejob必須在addjob之后,不然添加進去的job永久存在翰撑。這就是同步錯誤罩旋。

這是一個稱為競爭(race)的經(jīng)典同步錯誤的示例。

main中的addjob和處理程序中調用deletejob之間存在競爭眶诈。

必須addjob贏得進展涨醋,結果才是正確的,否則就是錯誤的逝撬。但是addjob不一定能贏浴骂,所以有可能錯誤。即為同步錯誤宪潮。

因為內核的調度問題溯警,這種錯誤十分難以被發(fā)現(xiàn)。難以調試坎炼。

Q:如何消除競爭愧膀?

A:可以在fork之前,阻塞SIGCHLD信號谣光,在調用addjob后取消阻塞檩淋。

注意,子進程繼承了阻塞,我們要小心地接觸子進程中的阻塞蟀悦。

消除競爭的原則就是媚朦,讓該贏得競爭的對象在任何情況下都能贏。

一個暴露你的代碼中競爭的簡便技巧

制造一個fork的包裝函數(shù)Fork日戈,通過隨機+休眠询张,在fork的那一瞬間,讓子進程浙炼,父進程都有50%機會先運行

8.6 非本地跳轉

C語言提供一種用戶級異撤菅酰控制流形式,稱為非本地跳轉(nonlocal jump)弯屈。

它將控制直接從一個函數(shù)轉移到另一個當前正在執(zhí)行的函數(shù)蜗帜。不需要經(jīng)過正常的調用-返回序列。

非本地跳轉是通過setjmp和longjmp函數(shù)來提供资厉。

#include<setjmp.h>

int setjmp(jmp_buf env);

int sigsetjmp(sigjmp_buf env,int savesigs);//信號處理程序使用

//參數(shù)savesigs若為非0則代表擱置的信號集合也會一塊保存

1

2

3

4

5

setjmp函數(shù)在env緩沖區(qū)保存當前調用環(huán)境厅缺,以供后面longjmp使用,并返回0

調用環(huán)境包括程序計數(shù)器宴偿,棧指針湘捎,通用目的寄存器。

#include

8.7 操作進程的工具

STRACE(痕跡):打印一個正在運行的程序和它的子進程調用的每個系統(tǒng)調用的軌跡窄刘。

用-static編譯窥妇,能得到一個更干凈,不帶有大量共享庫相關的輸出的軌跡都哭。

PS(Processes Status): 列出當前系統(tǒng)的進程(包括僵死進程)

TOP(因為我們關注峰值的幾個程序秩伞,所以叫TOP):打印當前進程使用的信息逞带。

PMAP(rePort Memory map of A Process): 查看進程的內存映像信息

/proc:一個虛擬文件系統(tǒng)欺矫,以ASCII文本格式輸出大量內核數(shù)據(jù)結構。

用戶程序可以讀取這些內容展氓。

比如穆趴,輸入"cat /proc/loadavg,觀察Linux系統(tǒng)上當前的平均負載遇汞。

8.8 小結

異澄疵茫控制流(ECF)發(fā)生在計算機系統(tǒng)的各個層次,是計算機系統(tǒng)中提供并發(fā)的基本機制空入。

在硬件層络它,異常是處理器中的事件出發(fā)的控制流中的突變⊥嵊控制流傳遞給一個異常處理程序化戳,該處理程序進行一些處理,然后返回控制被中斷的控制流埋凯。

有四種不同類型的異常:中斷点楼,故障扫尖,終止和陷阱。

定時器芯片或磁盤控制器掠廓,設置了處理器芯片上的中斷引腳時换怖,中斷會異步發(fā)生。返回到Inext

一條指令的執(zhí)行可能導致故障和終止同時出現(xiàn)蟀瞧。

故障可能返回調用指令沉颂。

終止不將控制返回。

陷阱用于系統(tǒng)調用悦污。結束后兆览,返回Inext

在操作系統(tǒng)層,內核用ECF提供進程的基本概念塞关。進程給應用兩個重要抽象:

邏輯控制流

私有地址空間

在操作系統(tǒng)和應用程序接口處抬探,有子進程,和信號帆赢。

最后小压,C語言的非本地跳轉 完成應用程序層面的異常處理。

至此椰于,異常貫穿了從底層硬件怠益,到抽象的軟件層次。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末瘾婿,一起剝皮案震驚了整個濱河市蜻牢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌偏陪,老刑警劉巖抢呆,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異笛谦,居然都是意外死亡抱虐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門饥脑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恳邀,“玉大人,你說我怎么就攤上這事灶轰∫シ校” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵笋颤,是天一觀的道長乳附。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么许溅? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任瓤鼻,我火速辦了婚禮,結果婚禮上贤重,老公的妹妹穿的比我還像新娘茬祷。我一直安慰自己,他們只是感情好并蝗,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布祭犯。 她就那樣靜靜地躺著,像睡著了一般滚停。 火紅的嫁衣襯著肌膚如雪沃粗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天键畴,我揣著相機與錄音最盅,去河邊找鬼。 笑死起惕,一個胖子當著我的面吹牛涡贱,可吹牛的內容都是我干的。 我是一名探鬼主播惹想,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼问词,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嘀粱?” 一聲冷哼從身側響起激挪,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锋叨,沒想到半個月后垄分,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡悲柱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年锋喜,在試婚紗的時候發(fā)現(xiàn)自己被綠了些己。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豌鸡。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖段标,靈堂內的尸體忽然破棺而出涯冠,到底是詐尸還是另有隱情,我是刑警寧澤逼庞,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布蛇更,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏派任。R本人自食惡果不足惜砸逊,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掌逛。 院中可真熱鬧师逸,春花似錦、人聲如沸豆混。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽皿伺。三九已至员辩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸵鸥,已是汗流浹背奠滑。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妒穴,地道東北人养叛。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像宰翅,于是被迫代替她去往敵國和親弃甥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內容

  • 又來到了一個老生常談的問題汁讼,應用層軟件開發(fā)的程序員要不要了解和深入學習操作系統(tǒng)呢淆攻? 今天就這個問題開始,來談談操...
    tangsl閱讀 4,127評論 0 23
  • 文/tangsl(簡書作者) 原文鏈接:http://www.reibang.com/p/2b993a4b913e...
    西葫蘆炒胖子閱讀 3,771評論 0 5
  • 一嘿架、Linux系統(tǒng)概述 不加引號可理解為宏瓶珊,直接替換,單引號中特殊字符會被解釋為普通字符耸彪,雙引號中$,,'還是特殊...
    赤果_b4a7閱讀 1,507評論 0 2
  • 信號的基本概念 信號被認為是一種軟件中斷(區(qū)別于硬件中斷),信號機制提供了一種在單進程/線程下處理異步事件的方法伞芹。...
    小葉大孟閱讀 1,918評論 0 1
  • 折柳的岸邊 誰的簫聲憂傷? 秦娥可又被 在夢魘里驚起蝉娜? 獨對這玉壺冰光 和秦樓孤寂 欄桿冷唱较,夜色沉 把柳絲在指間揉...
    懶懶的雞毛菜閱讀 155評論 0 1