今天仔細(xì)看看分段管理機(jī)制,努力看完,為分析linux內(nèi)核做準(zhǔn)備!!!?。。。。。。。。。。。。。。。。。?!
二.分段管理機(jī)制
本文介紹保護(hù)方式下的段定義以及由段選擇子及段內(nèi)偏移構(gòu)成的二維虛擬地址如何被轉(zhuǎn)換為一維線性地址。
<一>段定義和虛擬地址到線性地址的轉(zhuǎn)換
段是實現(xiàn)虛擬地址到線性地址轉(zhuǎn)換機(jī)制的基礎(chǔ)。在保護(hù)方式下,每個段由如下三個參數(shù)進(jìn)行定義:段基地址(Base Address)、段界限(Limit)和段屬性(Attributes)。
段基地址規(guī)定線性地址空間中段的開始地址。在80386保護(hù)方式下,段基地址長32位。因為基地址長度與尋址地址的長度相同,所以任何一個段都可以從32位線性地址空間中的任何一個字節(jié)開始,而不象實方式下規(guī)定的邊界必須被16整除。
段界限規(guī)定段的大小。在80386保護(hù)模式下,段界限用20位表示,而且段界限可以是以字節(jié)為單位或以4K字節(jié)為單位。段屬性中有一位對此進(jìn)行定義,把該位成為粒度位,用符號G標(biāo)記。G=0表示段界限以字節(jié)位位單位,于是20位的界限可表示的范圍是1字節(jié)至1M字節(jié),增量為1字節(jié);G=1表示段界限以4K字節(jié)為單位,于是20位的界限可表示的范圍是4K字節(jié)至4G字節(jié),增量為4K字節(jié)。當(dāng)段界限以4K字節(jié)為單位時,實際的段界限LIMIT可通過下面的公式從20 位段界限Limit計算出來: (注意什么是粒度位)
LIMIT=limit*4K+0FFFH=(Limit SHL 12)+0FFFH
所以當(dāng)粒度為1時,段的界限實際上就擴(kuò)展成32位。由此可見,在80386保護(hù)模式下,段的長度可大大超過64K字節(jié)。
基地址和界限定義了段所映射的線性地址的范圍?;刂稡ase是線性地址對應(yīng)于段內(nèi)偏移為 0的虛擬地址,段內(nèi)偏移為X的虛擬地址對應(yīng)Base+X的線性地址。段內(nèi)從偏移0到Limit范圍內(nèi)的虛擬地址對應(yīng)于從Base到Base+Limit范圍內(nèi)的線性地址。
下圖表示一個段如何從虛擬地址空間定位到線性地址空間。圖中BaseA等代表段基地址, LimitA等代表段界限。另外,段C接在段A之后,也即BaseC=BaseA+LimitA。
例如:設(shè)段A的基地址等于00012345H,段界限等于5678H,并且段界限以字節(jié)為單位(G=0),那么段A對應(yīng)線性地址空間中從00012345H ~ 000179BDH的區(qū)域。如果段界限以4K字節(jié)為單位 (G=1),那么段A對應(yīng)線性地址空間中從00012345H ~ 0568B344H (=00012345H+5678000H+0FFFH) 的區(qū)域。
通過增加段界限,可以使段的容量得到擴(kuò)展。這對于那些要在內(nèi)存中擴(kuò)展容量的普通數(shù)據(jù)段很有效,但對堆棧段情況就不是這樣。因為堆棧底在高地址端,隨著壓棧操作的進(jìn)行,堆棧向低地址方向擴(kuò)展。為了適應(yīng)普通數(shù)據(jù)段和堆棧數(shù)據(jù)段在兩個相反方向上的擴(kuò)展,數(shù)據(jù)段的段屬性中安排了一個擴(kuò)展方向位,標(biāo)記為ED。ED=0表示向高端擴(kuò)展,ED=1表示向低端擴(kuò)展。一般只有堆棧數(shù)據(jù)段才使用向低端擴(kuò)展的屬性(堆棧段也可使用向上擴(kuò)展的段),這是因為,向下擴(kuò)展的段是為以下兩個目的而設(shè)計的:
第一,堆棧段被定義為獨特段,即DS和SS包含不同的選擇器。
第二,一個堆棧段是靠將它復(fù)制到一個更大的段來擴(kuò)充自己(而不是靠將現(xiàn)存的頁增加到它的段上)。不打算用這種方法實現(xiàn)堆棧的設(shè)計者不需要定義向下擴(kuò)展的段。
需要注意的是,只有數(shù)據(jù)段的段屬性中才有擴(kuò)展方向?qū)傩晕籈D,也就是說只有數(shù)據(jù)段(堆棧段作為特殊的數(shù)據(jù)段)才有向上擴(kuò)展和向下擴(kuò)展之分,其它段都是自然的向上擴(kuò)展。
數(shù)據(jù)段的擴(kuò)展方向和段界限一起決定了數(shù)據(jù)段內(nèi)偏移的有效范圍。當(dāng)段最大為1M字節(jié)時,在向高端擴(kuò)展的段內(nèi),從0到Limit的偏移是合法有效的偏移,而從Limit+1到1M-1的偏移是非法無效的偏移;在向低端擴(kuò)展的段內(nèi),情形剛好相反,從0到Limit的偏移是非法無效的偏移,而從Limit+1到1M-1的偏移是合法有效的偏移,注意邊界值Limit對應(yīng)地址的有效性。段最大為4G時,情形類似。由此可見,如果一個段是向下擴(kuò)展的,則所有的偏移必須大于限長,因為其限長是指下限,其基地址從高地址出開始。反之,若一個段是向上擴(kuò)展的,則所有偏移必須小于等于限長,因為其限長是指上限,基地址從低地址處開始。通過使用段環(huán)繞,可以把向下擴(kuò)展段定義到任何線性地址且可定義為任何大小。
在每次把虛擬地址轉(zhuǎn)換為線性地址的過程中,要對偏移進(jìn)行檢查。如果偏移不在有效的范圍內(nèi),那么就引起異常。
段屬性規(guī)定段的主要特性。例如上面已經(jīng)提到的段粒度G就是段屬性的一部分。在對段進(jìn)行各種訪問時,將對訪問是否合法進(jìn)行檢查,主要依據(jù)是段屬性。例如:如果向一個只讀段進(jìn)行寫入操作,那么不僅不能寫入,而且會引起異常。在下面會詳細(xì)說明各個段熟屬性位的定義和作用。
<二>存儲段描述符
用于表示上述定義段的三個參數(shù)的數(shù)據(jù)結(jié)構(gòu)稱為描述符。每個描述符長8個字節(jié)。在保護(hù)方式下,每一個段都有一個相應(yīng)的描述符來描述。按描述符所描述的對象來劃分,描述符可分為如下三類:存儲段描述符、系統(tǒng)段描述符、門描述符(控制描述符)。下面先介紹存儲段描述符。
1.存儲段描述符的格式
存儲段是存放可由程序直接進(jìn)行訪問的代碼和數(shù)據(jù)的段。存儲段描述符描述存儲段,所以存儲段描述符也被稱為代碼和數(shù)據(jù)段描述符。存儲段描述符的格式如下表所示。表中上面一排是對描述符8個字節(jié)的使用的說明,最低地址字節(jié)(假設(shè)地址為m)在最右邊,其余字節(jié)依次向左,直到最高字節(jié)(地址為m+7)。下一排是對屬性域各位的說明。
存儲段 描述符 | m+7 | m+6 | m+5 | m+4 | m+3 | m+2 | m+1 | m+0 |
Base(31...24) | Attributes | Segment Base(23...0) | Segment Limite(15...0) |
存儲段 描述符 屬 性 | Byte m+6 | Byte m+5 | ||||||||||||||
BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 | BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 | |
G | D | 0 | AVL | Limit(19...16) | P | DPL | DT1 | TYPE |
從上表可知,長32位的段基地址(段開始地址)被安排在描述符的兩個域中,其位0—位23 安排在描述符內(nèi)的第2—第4字節(jié)中,其位24—位31被安排在描述符內(nèi)的第7字節(jié)中。長20 位的段界限也被安排在描述符的兩個域中,其位0—位15被安排在描述符內(nèi)的第0—第1字節(jié)中,其位16—位19被安排在描述符內(nèi)的第6字節(jié)的低4位中。
使用兩個域存放段基地址和段界限的原因與80286有關(guān)。在80286保護(hù)方式下,段基地址只有24位長,而段界限只有16位長。80286存儲段描述符盡管也是8字節(jié)長,但實際只使用低 6字節(jié),高2字節(jié)必須置為0。80386存儲段描述符這樣的安排,可使得80286的存儲段描述符的格式在80386下繼續(xù)有效。
80386描述符中的段屬性也被安排在兩個域中。下面對其定義及意義作說明。
(1)P位稱為存在(Present)位。P=1表示描述符對地址轉(zhuǎn)換是有效的,或者說該描述符所描述的段存在,即在內(nèi)存中;P=0表示描述符對地址轉(zhuǎn)換無效,即該段不存在。使用該描述符進(jìn)行內(nèi)存訪問時會引起異常。
(2)DPL表示描述符特權(quán)級(Descriptor Privilege level),共2位。它規(guī)定了所描述段的特權(quán)級,用于特權(quán)檢查,以決定對該段能否訪問。
(3)DT位說明描述符的類型。對于存儲段描述符而言,DT=1,以區(qū)別與系統(tǒng)段描述符和門描述符(DT=0)。
(4)TYPE說明存儲段描述符所描述的存儲段的具體屬性。
其中的位0指示描述符是否被訪問過(Accessed),用符號A標(biāo)記。A=0表示尚未被訪問,A=1 表示段已被訪問。當(dāng)把描述符的相應(yīng)選擇子裝入到段寄存器時,80386把該位置為1,表明描述符已被訪問。操作系統(tǒng)可測試訪問位,已確定描述符是否被訪問過。
其中的位3指示所描述的段是代碼段還是數(shù)據(jù)段,用符號E標(biāo)記。E=0表示段為數(shù)據(jù)段,相應(yīng)的描述符也就是數(shù)據(jù)段(包括堆棧段)描述符。數(shù)據(jù)段是不可執(zhí)行的,但總是可讀的。 E=1表示段是可執(zhí)行段,即代碼段,相應(yīng)的描述符就是代碼段描述符。代碼段總是不可寫的,若需要對代碼段進(jìn)行寫入操作,則必須使用別名技術(shù),即用一個可寫的數(shù)據(jù)段描述符來描述該代碼段,然后對此數(shù)據(jù)段進(jìn)行寫入。
在數(shù)據(jù)段描述符中(E=0的情況),TYPE中的位1指示所描述的數(shù)據(jù)段是否可寫,用W標(biāo)記。 W=0表示對應(yīng)的數(shù)據(jù)段不可寫。反之,W=1表示數(shù)據(jù)段是可寫的。注意,數(shù)據(jù)段總是可讀的。TYPE中的位2是ED位,指示所描述的數(shù)據(jù)段的擴(kuò)展方向。ED=0表示數(shù)據(jù)段向高端擴(kuò)展,也即段內(nèi)偏移必須小于等于段界限。ED=1表示數(shù)據(jù)段向低擴(kuò)展,段內(nèi)偏移必須大于段界限。
在代碼段描述符中(E=1的情況),TYPE中的位1指示所描述的代碼段是否可讀,用符號R標(biāo)記。R=0表示對應(yīng)的代碼段不可讀,只能執(zhí)行。R=1表示對應(yīng)的代碼段可讀可執(zhí)行。注意代碼段總是不可寫的,若需要對代碼段進(jìn)行寫入操作,則必須使用別名技術(shù)。在代碼段中,TYPE中的位2指示所描述的代碼段是否是一致代碼段,用C標(biāo)記。C=0表示對應(yīng)的代碼段不是一致代碼段(普通代碼段),C=1表示對應(yīng)的代碼段是一致代碼段。關(guān)于一致代碼段的說明,后面的文章將會詳細(xì)介紹。
存儲段描述符中的TYPE字段所說明的屬性可歸納為下表:
|
|
(5)G為就是段界限粒度(Granularity)位。G=0表示界限粒度為字節(jié);G=1表示界限粒度為4K 字節(jié)。注意,界限粒度只對段界限有效,對段基地址無效,段基地址總是以字節(jié)為單位。
(6)D位是一個很特殊的位,在描述可執(zhí)行段、向下擴(kuò)展數(shù)據(jù)段或由SS寄存器尋址的段(通常是堆棧段)的三種描述符中的意義各不相同。
在描述可執(zhí)行段的描述符中,D位決定了指令使用的地址及操作數(shù)所默認(rèn)的大小。D=1表示默認(rèn)情況下指令使用32位地址及32位或8位操作數(shù),這樣的代碼段也稱為32位代碼段;D=0 表示默認(rèn)情況下,使用16位地址及16位或8位操作數(shù),這樣的代碼段也稱為16位代碼段,它與80286兼容??梢允褂玫刂反笮∏熬Y和操作數(shù)大小前綴分別改變默認(rèn)的地址或操作數(shù)的大小。
在向下擴(kuò)展數(shù)據(jù)段的描述符中,D位決定段的上部邊界。D=1表示段的上部界限為4G;D=0表示段的上部界限為64K,這是為了與80286兼容。
在描述由SS寄存器尋址的段描述符中,D位決定隱式的堆棧訪問指令(如PUSH和POP指令)使用何種堆棧指針寄存器。D=1表示使用32位堆棧指針寄存器ESP;D=0表示使用16位堆棧指針寄存器SP,這與80286兼容。
(7)AVL位是軟件可利用位。80386對該位的使用未左規(guī)定,Intel公司也保證今后開發(fā)生產(chǎn)的處理器只要與80386兼容,就不會對該位的使用做任何定義或規(guī)定。
此外,描述符內(nèi)第6字節(jié)中的位5必須置為0,可以理解成是為以后的處理器保留的。
2.存儲段描述符的結(jié)構(gòu)類型表示
根據(jù)存儲段描述符的結(jié)構(gòu),可定義如下的匯編語言描述符結(jié)構(gòu)類型:
DESC STRUC LIMITL DW 0 ;段界限低16位
BASEL DW 0 ;基地址低16位
BASEM DB 0 ;基地址中間8位
ATTRIB DB 0 ;段屬性
LIMITH DB 0 ;段界限的高4位(包括段屬性的高4位)
BASEH DB 0 ;基地址的高8位
DESC ENDS
利用結(jié)構(gòu)類型DESC能方便地在程序中說明存儲段描述符。例如:下面的描述符DATAS描述一個可讀寫的有效(存在的)數(shù)據(jù)段,基地址是100000H,以字節(jié)為單位的界限是0FFFFH,描述符特權(quán)級DPL=3。
DATAS DESC <0FFFFH,,10H,0F2H,,>
再如:下述描述符CODEA描述一個只可執(zhí)行的有效的32位代碼段,基地址是12345678H,以 4K字節(jié)位單位的段界限值是10H(以字節(jié)位單位的界限是10FFFH),描述符特權(quán)級DPL=0。
CODEA DESC <10H,5678H,34H,98H,0C0H,12H>
<三>全局和局部描述符表
一個任務(wù)會涉及多個段,每個任務(wù)需要一個描述符來描述,為了便于組織管理,80386把描述符組織成線性表。由描述符組成的線性表稱為描述符表。在80386中有三種類型的描述符表:全局描述符表GDT(Global Descriptor Table)、局部描述符表LDT(Local Descriptor Table)和中斷描述符表IDT(Interrupt Descriptor Table)。在整個系統(tǒng)中,全局描述符表GDT和中斷描述符表IDT只有一張,局部描述符表可以有若干張,每個任務(wù)可以有一張。
例如,下列描述符表有6個描述符構(gòu)成:
DESCTAB LABEL BYTE DESC1 DESC <1234H,5678H,34H,92H,,> DESC1 DESC <1234H,5678H,34H,93H,,> DESC1 DESC <5678H,1234H,56H,98H,,> DESC1 DESC <5678H,1234H,56H,99H,,> DESC1 DESC <0FFFFH,,10H,16H,,> DESC1 DESC <0FFFFH,,10H,90H,,>
每個描述符表本身形成一個特殊的數(shù)據(jù)段。這樣的特殊數(shù)據(jù)段最多可包含有8K(8192)個描述符.
關(guān)于中斷描述符表IDT在以后的文章中介紹。
每個任務(wù)的局部描述符表LDT含有該任務(wù)自己的代碼段、數(shù)據(jù)段和堆棧段的描述符,也包含該任務(wù)所使用的一些門描述符,如任務(wù)門和調(diào)用門描述符等。隨著任務(wù)的切換,系統(tǒng)當(dāng)前的局部描述符表LDT也隨之切換。
全局描述符表GDT含有每一個任務(wù)都可能或可以訪問的段的描述符,通常包含描述操作系統(tǒng)所使用的代碼段、數(shù)據(jù)段和堆棧段的描述符,也包含多種特殊數(shù)據(jù)段描述符,如各個用于描述任務(wù)LDT的特殊數(shù)據(jù)段等。在任務(wù)切換時,并不切換GDT。
通過LDT可以使各個任務(wù)私有的各個段與其它任務(wù)相隔離,從而達(dá)到受保護(hù)的目的。通過GDT可以使各任務(wù)都需要使用的段能夠被共享。下圖給出了任務(wù)A和任務(wù)B所涉及的有關(guān)段既隔離受保護(hù),又合用共享的情況。通過任務(wù)A的局部描述符表LDTA和任務(wù)B的局部描述符表LDTB,把任務(wù)A所私有的代碼段CodeA及數(shù)據(jù)段DataA與任務(wù)B所私有的代碼段CodeB和數(shù)據(jù)段DataB及DataB2隔離,但任務(wù)A和任務(wù)B通過全局描述符表GDT共享代碼段CodeK及CodeOS和數(shù)據(jù)段DataK及DataOS。
一個任務(wù)可使用的整個虛擬地址空間分為相等的兩半,一半空間的描述符在全局描述符表中,另一半空間的描述符在局部描述符表中。由于全局和局部描述符表都可以包含多達(dá)8192個描述符,而每個描述符所描述的段的最大值可達(dá)4G字節(jié),因此最大的虛擬地址空間可為:
4GB*8192*2=64MMB=64TB
<四>段選擇子
在實模式下,邏輯地址空間中存儲單元的地址由段值和段內(nèi)偏移兩部分組成。在保護(hù)方式下,虛擬地址空間(相當(dāng)于邏輯地址空間)中存儲單元的地址由段選擇子和段內(nèi)偏移兩部分組成。與實模式相比,段選擇子代替了段值。
段選擇子長16位,其格式如下表所示。從表中可見,段選擇子的高13位是描述符索引(Index)。所謂描述符索引是指描述符在描述符表中的序號。段選擇子的第2位是引用描述符表指示位,標(biāo)記為TI(Table Indicator),TI=0指示從全局描述符表GDT中讀取描述符;TI=1指示從局部描述符表LDT中讀取描述符。
選擇子 結(jié) 構(gòu) | BIT15 | BIT14 | BIT13 | BIT12 | BIT11 | BIT10 | BIT9 | BIT8 | BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 |
描述符索引 | TI | RPL |
選擇子確定描述符,描述符確定段基地址,段基地址與偏移之和就是線性地址。所以,虛擬地址空間中的由選擇子和偏移兩部分構(gòu)成的二維虛擬地址,就是這樣確定了線性地址空間中的一維線性地址。
選擇子的最低兩位是請求特權(quán)級RPL(Requested Privilege Level),用于特權(quán)檢查。 RPL字段的用法如下:
每當(dāng)程序試圖訪問一個段時,要把當(dāng)前特權(quán)級與所訪問段的特權(quán)級進(jìn)行比較,以確定是否允許程序?qū)υ摱蔚脑L問。使用選擇子的RPL字段,將改變特權(quán)級的測試規(guī)則。在這種情況下,與所訪問段的特權(quán)級比較的特權(quán)級不是CPL,而是CPU與RPL中更外層的特權(quán)級。 CPL存放在CS寄存器的RPL字段內(nèi),每當(dāng)一個代碼段選擇子裝入CS寄存器中時,處理器自動地把CPL存放到CS的RPL字段。
由于選擇子中的描述符索引字段用13位表示,所以可區(qū)分8192個描述符。這也就是描述符表最多包含8192個描述符的原因。由于每個描述符長8字節(jié),根據(jù)上表所示選擇子的格式,屏蔽選擇子低3位后所得的值就是選擇子所指定的描述符在描述符表中的偏移,這可認(rèn)為是安排選擇子高13位作為描述符索引的原因。
有一個特殊的選擇子稱為空(Null)選擇子,它的Index=0,TI=0,而RPL字段可以為任意值??者x擇子有特定的用途,當(dāng)用空選擇子進(jìn)行存儲訪問時會引起異常??者x擇子是特別定義的,它不對應(yīng)于全局描述符表GDT中的第0個描述符,因此處理器中的第0個描述符總不被處理器訪問,一般把它置成全0。但當(dāng)TI=1時,Index為0的選擇子不是空選擇子,它指定了當(dāng)前任務(wù)局部描述符表LDT中的第0個描述符。
<五>段描述符高速緩沖寄存器
在實模式下,段寄存器含有段值,為訪問存儲器形成物理地址時,處理器引用相應(yīng)的某個段寄存器并將其值乘以16,形成20位的段基地址。在保護(hù)模式下,段寄存器含有段選擇子,如上所述,為了訪問存儲器形成線性地址時,處理器要使用選擇子所指定的描述符中的基地址等信息。為了避免在每次存儲器訪問時,都要訪問描述符表而獲得對應(yīng)的段描述符,從80286開始每個段寄存器都配有一個高速緩沖寄存器,稱之為段描述符高速緩沖寄存器或描述符投影寄存器,對程序員而言它是不可見的。每當(dāng)把一個選擇子裝入到某個段寄存器時,處理器自動從描述符表中取出相應(yīng)的描述符,把描述符中的信息保存到對應(yīng)的高速緩沖寄存器中。此后對該段訪問時,處理器都使用對應(yīng)高速緩沖寄存器中的描述符信息,而不用再從描述符表中取描述符。
各段描述符高速緩沖寄存器之內(nèi)容如下表所示。其中,32位段基地址直接取自描述符, 32位的段界限取自描述符中20位的段界限,并根據(jù)描述符屬性中的粒度位轉(zhuǎn)換成以字節(jié)為單位。其它十個特性根據(jù)描述符中的屬性而定,“Y”表示“是”,“N”表示“否” ,“R”表示必須可讀,“W”表示必須可寫,“P”表示必須存在,“D”表示根據(jù)描述符中屬性而定。
段描 述符 高速 緩沖 寄存 器的 內(nèi)容 | 段寄存器 | 段基地址 | 段界限 | 段屬性 | |||||||||
存在性 | 特權(quán)級 | 已存取 | 粒度 | 擴(kuò)展方向 | 可讀性 | 可寫性 | 可執(zhí)行 | 堆棧大小 | 一致特權(quán) | ||||
CS | 32位基地址 | 32位段界限 | P | D | D | D | D | D | N | Y | - | D | |
SS | 32位基地址 | 32位段界限 | P | D | D | D | D | R | W | N | D | - | |
DS | 32位基地址 | 32位段界限 | P | D | D | D | D | D | D | N | - | - | |
ES | 32位基地址 | 32位段界限 | P | D | D | D | D | D | D | N | - | - | |
FS | 32位基地址 | 32位段界限 | P | D | D | D | D | D | D | N | - | - | |
GS | 32位基地址 | 32位段界限 | P | D | D | D | D | D | D | N | - | - |
段描述符高速緩沖寄存器再處理器內(nèi),所以可對其進(jìn)行快速訪問。絕大多數(shù)情況下,對存儲器的訪問是在對應(yīng)選擇子裝入到段寄存器之后進(jìn)行的,所以,使用段描述符高速緩沖寄存器可以得到很好的執(zhí)行性能。
段描述符高速緩沖寄存器之內(nèi)保存的描述符信息將一直保存到重新把選擇子裝載到段寄存器時再更新。程序員盡管不可見段描述符高速緩沖寄存器,但必須注意到它的存在和它的上述更新時機(jī)。例如,在改變了描述符表中的某個當(dāng)前段的描述符后,也要更新對應(yīng)的段描述符高速緩沖寄存器的內(nèi)容,即使段選擇子未作改變,這可通過重新裝載段寄存器實現(xiàn)。
參考資料 | 書 名 | 出 版 社 | 作 者 |
《保護(hù)方式下的80386及其編程》 | 清華大學(xué)出版社 | 周明德主編 | |
《80X86匯編語言程序設(shè)計教程》 | 清華大學(xué)出版社 | 揚(yáng)季文主編 |
聯(lián)系客服