久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

ARM體系架構(gòu)-MMU

 waston 2022-10-31 發(fā)布于上海

一,、前言

在 嵌入式Linux 開發(fā)中,,往往會聽到 MMU 這個詞,但大多數(shù)情況下并不會去了解它,,因為操作系統(tǒng)已經(jīng)做好了關(guān)于 MMU 的一切操作,,我們只需要在操作系統(tǒng)的框架下直接使用即可。但了解 MMU 有助于幫助我們理解操作系統(tǒng),,理解進程等,,讓我們對 嵌入式Linux 的理解上升一個層次。本文將簡單地講述一下關(guān)于 MMU 的基本信息,。

注意:本文將按照ARMv7的二級頁表映射進行講述

二,、MMU

2.1 MMU基本信息

MMU 全稱為 Memory Management Unit,即 內(nèi)存管理單元,。在 帶有MMU的嵌入式Linux 中,,CPU 訪問的地址都是 虛擬地址,而 MMU 負責將程序中 代碼或數(shù)據(jù) 的 虛擬地址 翻譯為 物理地址,,以便程序訪問內(nèi)存,。

在執(zhí)行操作時,MMU 會自動轉(zhuǎn)換 CPU發(fā)出的虛擬地址,,無法人工進行操作,,只需要配置好 MMU 相關(guān)屬性即可。

虛擬地址 是在 編譯和鏈接 時定義的,,可以簡單地理解為 由鏈接器和鏈接器腳本 指定虛擬地址,。

除了 翻譯虛擬地址,MMU 還可以配置 內(nèi)存區(qū)域 的各項配置,,如內(nèi)存區(qū)域的訪問權(quán)限,,內(nèi)存區(qū)域是否使能cache等功能,。

總結(jié) MMU 的功能,,如下:

翻譯虛擬地址

配置內(nèi)存區(qū)域的相關(guān)屬性

2.2 MMU基本概念

看到 MMU 的相關(guān)文章時,,總會提及幾個概念如 頁,頁框(頁幀),,頁表,,頁表項,TLB等等,,下面我們逐個拆分來講述,。

2.2.1 頁

MMU 管理 虛擬地址空間 時,是按照 頁 為單位來進行管理,。在 ARMv7 的 MMU ,,頁大小 一共有 16M(Super Section)、1M(Section) ,、64K(Large Page) 4K(Page),。頁大小 可以通過 協(xié)處理器CP15 進行配置,越小的頁意味著內(nèi)存的顆粒度越小,,內(nèi)存使用時的浪費會越小,,但也意味著使用的TLB行越多。越大的也內(nèi)存的顆粒度月大,,內(nèi)存的使用浪費也可能月大,,但使用的TLB行越少。比如只需要申請 7K 大小的 物理內(nèi)存,,如果使用 7K大小 的內(nèi)存,,我們可以分配 2 個 4K頁,如果分配 64K的大頁,,則浪費的空間就比較大,。

2.2.2 頁框

因為 虛擬地址空間 需要有所對應(yīng)的 物理地址,這樣才能在 虛擬地址 中存儲數(shù)據(jù),。所以 MMU 管理 物理地址空間 時,,按照 頁幀 為單位進行管理。其大小分為 64K 和 4K,。一段 虛擬地址空間 有可能存在著多個 頁,,這些 頁 對應(yīng)著多個 頁幀。

按照筆者理解,,頁 和 頁幀 是 不同地址空間下的關(guān)于內(nèi)存空間大小的概念,。

2.2.3 頁表及頁表項

MMU 在進行 地址轉(zhuǎn)換 時,需要一些信息,,存放這些信息的就是 頁表,。每個 頁表 的最小單位就是 頁表項。

頁表 存儲在 物理地址空間 中,,且一個 頁表項 對應(yīng)著一個 頁,。

在 切換頁表 時,,通過將 頁表的物理首地址 設(shè)置到 協(xié)處理器CP15 中的 TTBR寄存器(Translation Table Base Register)。此后 MMU 會通過該地址自動去 物理地址空間 中找到對應(yīng)的 頁表,,從而完成 虛擬地址到物理地址的映射,。

在不考慮 TLB 和 多級頁表 的情況下,可以簡單地如下圖所示:

頁表及頁表項

2.2.4 TLB

TLB 全程為 Translation Lookaside Buffer,,即 旁路轉(zhuǎn)換緩沖,。它是 MMU 的專屬 全相聯(lián)cache,用于臨時存放 虛擬地址到物理地址映射 所需要的信息,。

下面按照步驟說明 TLB 的作用:

CPU 訪問 虛擬地址 到 MMU,。

MMU 根據(jù)規(guī)則(規(guī)則在下文講述)查看 虛擬地址 是否在 TLB 中。

如果在 TLB 中,,則稱為 TLB命中,。從 TLB 中直接獲取 物理地址 對內(nèi)存進行訪問

如果不在 TLB 中,則稱為 TLB失效,。此時 MMU 將進行 translation table walking,,即通過 訪問頁表來獲取 物理地址。并將該 虛擬地址 的信息存入 TLB,,以便下次使用,。

值得注意的是:ARM架構(gòu)的TLB只存儲有效的頁表項,對于無效的頁表項TLB并不會存儲

TLB 由許多 TLB行 組成,,如下圖所示:

TLB

TLB行 由 3個 部分組成,,分別為 標簽、ASID 和 描述符

標簽:該部分由 虛擬地址的一部分bit 組成,,MMU 通過將 虛擬地址的一部分bit 和 TLB 的所有標簽對比進行搜索,。

ASID:全稱為 Address Space ID,一般用于 多進程系統(tǒng),,下文會詳細講述,。

描述符:由 2個 部分組成,分別為 物理地址(一部分bit) 和 內(nèi)存區(qū)域?qū)傩?組成,??梢岳斫鉃?cache 中的數(shù)據(jù)。

一般情況下,,切換 進程 時會切換 頁表,,因為隨著進程的切換, 虛擬地址 到 物理地址 的映射已經(jīng)改變,。此時需要 清理TLB(即無效化TLB中的數(shù)據(jù)) 來保持 TLB一致性,。清理TLB 一般通過 協(xié)處理器CP15 來完成,在 Linux內(nèi)核 中,,有 flush_tlb_all() 和 flush_tlb_range() 函數(shù)來完成該工作,。

2.3 MMU組成

如下圖所示:

MMU組成

MMU 的工作流程可以總結(jié)為下面 2 種情況:

訪問 虛擬地址 時,,MMU 通過查找 TLB 來找出對應(yīng)的 頁幀,從而訪問 物理地址,,如圖中的 頁1、頁2 和 頁3,。

如果 MMU 在 TLB 中沒找到對應(yīng)的 TLB行 時,,將進行 traslation table working。即從 物理地址空間 的 頁表中 找出對應(yīng)的 頁表項,,并根據(jù) 頁表項 找到對應(yīng)的 物理地址,。并將該 頁表項 更新到 TLB 中,以備下次使用,。

2.4 MMU工作過程

ARMv7 下的 MMU 具有 2級頁表,,分為 1級頁表 和 2級頁表。

2.4.1 1級頁表

1級頁表 也稱 主頁表 和 段頁表,,下面簡稱 L1頁表,。它將 4GB 的地址空間劃分為 4096 個 1MB 大小的 段,每個段的地址為 32bit,。所以 1級頁表 擁有 4096 個 32bit 的 頁表項,。

2.4.1.1 一級頁表項

L1頁表 使用了 短描述符頁表(Short-descriptor translation table),其 頁表項 具有以下特征:

32bit 的頁描述符

具有 2級 以上的 頁表

支持 32bit 的 物理地址

支持 4種 內(nèi)存大?。?/p>

16MB/1M,,稱為 段

64KB/4KB,稱為 頁

在前面說了 TTBR寄存器 是存放 頁表物理地址 的寄存器,,需要注意的是:存放在TTBR寄存器的地址需要16KB對齊

一級頁表項 一共有 4種 格式,,如下圖所示:

一級頁表項格式

每種格式都由 物理地址部分+屬性部分 組成,可以直接在圖中看出 物理地址部分 的示意,,這里不多贅述,。各種格式的含義如下:

1MB段轉(zhuǎn)換頁表項(Section) ,映射到 1MB 的物理地址范圍,。其 物理地址部分 即為所需要映射的 物理基地址,。

物理地址部分 指向 2級頁表 的 物理基地址。

16MB段(SuperSection) 轉(zhuǎn)換頁表項,,是一種特殊的 1MB段轉(zhuǎn)換頁表項,。其 物理地址部分 即為所需要映射的 物理基地址。

無效頁表項,,當訪問該頁表項時,,將觸發(fā) 指令取指異常 或 取數(shù)據(jù)異常

下面簡單說下各個字段的含義:

Ignored:忽略

Level 2 Descriptor Base Address:二級頁表物理基地址

Section Base Address:1MB段基地址

Supersection Base Address:16MB段基地址

SBZ:全稱 should be zero,無效屬性字段

AP:全稱 Access Permissions,,內(nèi)存區(qū)域訪問權(quán)限

Domain:用于權(quán)限控制,,下文講述,。

TEX:全稱 Type extension,設(shè)置內(nèi)存區(qū)域類型

B:全稱 Bufferable,,是否設(shè)置 寫緩沖,。

C:全稱 Cacheable,是否設(shè)置 cache,。

nG:全稱 non-Global,。如果 頁表項的nGbit 被設(shè)置,那么該 頁表項 對應(yīng)的 內(nèi)存區(qū)域 將只能被 特定的進程 使用,。當MMU 使用該 頁表項 進行映射時,,也需要使用到 ASID。

S:全稱 Shareable,,共享設(shè)置項,。

bit[18]:該 bit 決定 段頁表項 是 1MB頁表項 還是 16MB頁表項。

bit[1:0]:這 2個bit 決定頁表項的類型,,如下:

00:無效頁表項

01:轉(zhuǎn)換表頁表項

10:段頁表項

2.4.1.2 一級頁轉(zhuǎn)換

以 1MB段 舉例,,假設(shè) L1頁表 的物理地址為 0x12300000,現(xiàn)在有一個虛擬地址 0x00100000,。其轉(zhuǎn)換過程如圖所示:

查表過程

轉(zhuǎn)換過程

查表過程:將 虛擬地址高12bit,,即0x001 乘以 4 得到 0x004。0x004 即為 該虛擬地址所在段的頁表項在頁表中的偏移,,所以 該虛擬地址對應(yīng)的頁表項的物理地址為0x12300000+0x004=0x12300004,。

根據(jù)查到的 頁表項,將 頁表項高12bit 和 虛擬地址低30bit 結(jié)合,,即為 該虛擬地址在該1MB段內(nèi)的物理地址

值得注意的是:例子中,,高12位一共是4096個頁表項,那么4096x4一共是16384字節(jié)的大小,,因為每個頁表項是32位,。所以4096個頁表項需要16K大小的內(nèi)存來存儲頁表。也是因為如此,,每個虛擬地址的高12bit都需要乘以4.

下圖為例子的完整轉(zhuǎn)換過程,,其余類型的 頁表項 轉(zhuǎn)換過程類似、

L1轉(zhuǎn)換完整過程

2.4.2 二級頁表

2級頁表 一共有 256 個 4字節(jié)大小 的 頁表項,,總共占據(jù) 1KB大小 的內(nèi)存空間,。L2頁表 的大部分內(nèi)容與 L1頁表 類似,相同部分下文將不再贅述

2.4.2 二級頁表項

二級頁表項 一共有 3種 格式,,如下圖所示:

二級頁表項

每種格式與 L1頁表項 一樣由 物理地址部分+屬性部分 組成,,可以直接在圖中看出 物理地址部分 的示意,格式如下:

2級頁表項 具有以下特征:

粗頁表項: 其 物理地址部分指向 64KB大小 的 物理基地址。

細頁表項: 其 物理地址部分指向 4KB大小 的 物理基地址

無效頁表項,,當訪問該頁表項時,,將觸發(fā) 指令取指異常 或 取數(shù)據(jù)異常

屬性字段 的含義請參考 1級頁表 章節(jié)。

2.4.2 二級頁表轉(zhuǎn)換

L2頁表 的轉(zhuǎn)換過程與 L1頁表 的轉(zhuǎn)換過程一脈相承,。以 4KB 為例子,,如下圖所示:

image.png

由上圖可以看出其轉(zhuǎn)換步驟如下:

通過 虛擬地址 找出 L1頁表項 并轉(zhuǎn)換為 L2頁表 的 基地址。

根據(jù) L2頁表基地址 并集合 虛擬地址的[19:12]bit 找出 虛擬地址 對應(yīng)的 L2頁表項,。

將 虛擬地址[11:0]bit 和 L2頁表項 的 物理地址部分 結(jié)合得出具體的 物理地址

結(jié)合 L1頁表 的完整轉(zhuǎn)化過程如下圖所示:

image.png

2.5 MMU內(nèi)存屬性

2.5.1 內(nèi)存區(qū)域權(quán)限

每個 內(nèi)存區(qū)域 都有自己的權(quán)限,,不符合訪問權(quán)限的 內(nèi)存訪問 都會引發(fā) 異常。如果是 數(shù)據(jù)訪問 則引發(fā) 數(shù)據(jù)異常,。如果是 指令訪問,,且該指令在執(zhí)行前沒有被 flush,,將引發(fā) 預(yù)取指異常,。

引發(fā)的 異常原因 將會被設(shè)置在 CP15 的 the fault address and fault status registers

內(nèi)存區(qū)域權(quán)限 由 AP、APX 和 Domain(域) 共同控制,,如下:

AP/APX:字段 AP 和 APX 的不同組合將形成不同的 訪問權(quán)限,,如圖所示。按照筆者理解,,

Privileged 指的是 CPU 處于 svc 等狀態(tài),,而 Unprivileged 則為 CPU 處于 user 狀態(tài)。

訪問權(quán)限組合表

Domain:這是一種 ARM架構(gòu) 不常用的 內(nèi)存全權(quán)限控制 方式,。MMU 可以將所有 內(nèi)存區(qū)域 分配到 16個域 中,,每一個 域 都有自己的 訪問權(quán)限。被分配到 域 中的 內(nèi)存區(qū)域 必須遵循該 域 的訪問權(quán)限,??梢酝ㄟ^設(shè)置 頁表項 中的 Domain字段 來實現(xiàn) 域 的分配。

在 CP15協(xié)處理器 中有一個 DACR寄存器(Domain Access Control Register),,該寄存器為 32bit,,每個 域 的 訪問權(quán)限 由 2個bit 設(shè)置,一共設(shè)置 16個域,。域 的權(quán)限設(shè)置如下:

不可訪問(no-access):對該 域 的 內(nèi)存區(qū)域 進行訪問將引發(fā) 異常,。

管理者(Manager mode):訪問不受任何控制,不產(chǎn)生 異常

用戶(Client mode):使用 頁表項 中的 AP/APX字段 進行控制

需要注意的是:內(nèi)存區(qū)域控制以域控制為主,,頁表項的AP/APX字段為次,。ARMv7不建議使用域進行控制,所以建議把DACR寄存器設(shè)置為用戶模式

2.5.2 內(nèi)存類型

ARM架構(gòu) 實現(xiàn)了 3種內(nèi)存類型,,每種類型都是 互斥的,,如下:

Strongly-ordered

Device

Normal

每種 類型 的細節(jié)如下圖所示:

image.png

需要注意的是,Device類型的Shareable內(nèi)存區(qū)域現(xiàn)在已經(jīng)被棄用

內(nèi)存區(qū)域類型 可以通過 TEX字段、C字段 和 B字段 來進行設(shè)置,,如下圖所示

image.png

值得注意的是:按照筆者理解,,inner cache是L1 cache,而outer cache是指在L1cache下面的cache,,比如L2cache

操作系統(tǒng)如何使用頁表

2.6 進程與MMU

操作系統(tǒng) 會為 每個進程 分配一個 頁表,,該 頁表 使用 物理地址 存儲。當 進程 使用類似 malloc 等需要 映射代碼或數(shù)據(jù) 的操作時,,操作系統(tǒng) 會在隨后馬上 修改頁表 以加入新的 物理內(nèi)存,。當進程完成退出時,內(nèi)核會將相關(guān)的頁表項刪除掉,,以便分配給新的進程,。

2.6.1 Address Space ID

在操作系統(tǒng)中, 多進程 是一種常態(tài),。那么多進程 的情況下,,每次 切換進程 都需要進行 TLB清理。這樣會導(dǎo)致切換的效率變低,。

為了解決問題,,TLB 引入了 ASID(Address Space ID) 。ASID 的范圍是 0-255,。

ASID 由操作系統(tǒng)分配,,當前進程的ASID值 被寫在 ASID寄存器(使用CP15 c3訪問)。TLB 在更新 頁表項 時也會將 ASID 寫入 TLB,。

如果設(shè)置了如果 當前進程的ASID,,那么 MMU 在查找 TLB 時, 只會查找 TLB 中具有 相同ASID值 的 TLB行,。且在切換進程是,,TLB 中被設(shè)置了 ASID 的 TLB行 不會被清理掉,當下次切換回來的時候還在,。所以ASID 的出現(xiàn)使得切換進程時不需要清理 TLB 中的所有數(shù)據(jù),,可以大大減少 切換開銷。

具體可以看參考鏈接《多核MMU和ASID管理邏輯》

2.6.2 TTBR0和TTBR1

前面講了 TTBR寄存器 是用于存放 頁表基地址,,在 ARmv7 中一共有 2個 這樣的寄存器,,分別是 TTBR0 和 TTBR1。

那么這里提出一個問題:在進行 Translation Table walking 的時候,,選擇哪個TTBR寄存器,,又如何選擇?

在 ARMv7 中,,有一個寄存器為 TTBCR(TTB Control Register),,即TTB控制寄存器,。TTBCR寄存器 可以被設(shè)置為 0-7 這幾個值。

在進行 地址映射 時,, MMU 會根據(jù) TTBCR寄存器 中的值查看 虛擬地址 是高位地址,,根據(jù) 高位地址 選擇對應(yīng)的 TTBR寄存器。

舉個例子,,假設(shè) TTBCR寄存器 被設(shè)置為 4,,則 MMU 會檢查 虛擬地址 的 高4bit,如果 高4bit 都為 0,,則此時選擇 TTBR0,。

需要注意的是:TTBCR被設(shè)置為 0 時,默認選擇 TTBR0,。

下面我們看看使用和不使用 TTBR1 帶來的影響,。

不使用:在 ARM32架構(gòu)的操作系統(tǒng)中,不使用 TTBR1寄存器,。此時,,用戶空間 和 內(nèi)核空間 共用一個 頁表。也就是說 用戶空間 和 內(nèi)核空間 都使用 TTBR0 來記錄 頁表地址,,這樣可以避免一個問題,,就是進行 用戶空間和內(nèi)核空間的切換時,,可以避免切換頁表帶來的性能損耗,。但與此同時也帶來一個問題,用戶空間 的 每個進程 都擁有 內(nèi)核頁表副本,,當 內(nèi)核空間頁表 修改時,,所有 進程 都需要同步修改其 內(nèi)核頁表副本。造成一定的性能損失

使用:ARM64架構(gòu)的操作系統(tǒng)中,,虛擬地址空間 非常大,。用戶空間 和 內(nèi)核空間 都是 256T。用戶空間地址高位為0,,內(nèi)核空間地址高位為1,。這樣的特性滿足 TTBR1寄存器 的使用條件。根據(jù) 用戶空間地址 和 內(nèi)核空間地址 的不同,,選擇對應(yīng)的 TTBR寄存器,。這樣就不需要為每個 進程 維護一份 內(nèi)核頁表副本。

2.6.3 代碼實例

本小節(jié)簡單地講述一下 Linux 進行 MMU切換 時的代碼片段,。以 ARMv7單核CPU 為例子,。

根據(jù)筆者的理解,其調(diào)用圖譜如下:

->switch_mm   ->check_and_switch_context     ->cpu_switch_mm(processor.switch_mm)       ->cpu_v7_switch_mm

筆者會將簡單的說明注釋在代碼中,,不進行另外的說明,。

/* arch/arm/include/asm/mmu_context.h */static inline voidswitch_mm(struct mm_struct *prev, struct mm_struct *next,       struct task_struct *tsk){#ifdef CONFIG_MMU     unsigned int cpu = smp_processor_id();     /*      * __sync_icache_dcache doesn't broadcast the I-cache invalidation,      * so check for possible thread migration and invalidate the I-cache      * if we're new to this CPU.      */     /* 這里應(yīng)該是說進程如果調(diào)度到新的CPU,則需要將該CPU的cache給清理掉 */     if (cache_ops_need_broadcast() &&         !cpumask_empty(mm_cpumask(next)) &&         !cpumask_test_cpu(cpu, mm_cpumask(next)))         __flush_icache_all();     if (!cpumask_test_and_set_cpu(cpu, mm_cpumask(next)) || prev != next) {         /* 如果調(diào)度的進程不是本進程,則執(zhí)行check_and_switch_context */         check_and_switch_context(next, tsk);         if (cache_is_vivt())             cpumask_clear_cpu(cpu, mm_cpumask(prev));     }#endif}static inline void check_and_switch_context(struct mm_struct *mm,                         struct task_struct *tsk){     if (unlikely(mm->context.vmalloc_seq != init_mm.context.vmalloc_seq))         __check_vmalloc_seq(mm);     if (irqs_disabled())         /*          * cpu_switch_mm() needs to flush the VIVT caches. To avoid          * high interrupt latencies, defer the call and continue          * running with the old mm. Since we only support UP systems          * on non-ASID CPUs, the old mm will remain valid until the          * finish_arch_post_lock_switch() call.          */         mm->context.switch_pending = 1;     else         /* 使用該函數(shù)進行MMU切換頁表 */         cpu_switch_mm(mm->pgd, mm);}/* arch/arm/include/asm/proc-fns.h *//* 根據(jù)筆者找的代碼,,cpu_switch_mm 應(yīng)該直接調(diào)用了processor.switch_mm */#define cpu_do_switch_mm        processor.switch_mm#define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm)

processor.switch_mm 是一個 回調(diào)函數(shù),,根據(jù)筆者找到的資料,應(yīng)該是指向 ** arch/arm/mm** 目錄下的一些列 MMU 操作代碼,。這里以 proc-v7-2level.S(即ARMv7 2級頁表) 進行說明

/* arch/arm/mm/proc-v7-2level.S *//* 根據(jù)APCS,,傳入的參數(shù)是存放在寄存器 r0和r1 */ENTRY(cpu_v7_switch_mm)#ifdef CONFIG_MMU         mmid    r1, r1                          @ get mm->context.id        ALT_SMP(orr     r0, r0, #TTB_FLAGS_SMP)         ALT_UP(orr      r0, r0, #TTB_FLAGS_UP)#ifdef CONFIG_PID_IN_CONTEXTIDR         mrc     p15, 0, r2, c13, c0, 1          @ read current context ID         lsr     r2, r2, #8                      @ extract the PID         bfi     r1, r2, #8, #24                 @ insert into new context ID#endif#ifdef CONFIG_ARM_ERRATA_754322         dsb#endif         mcr     p15, 0, r1, c13, c0, 1          @ set context ID         isb        /* 在這里,將r0所指向的頁表基地址設(shè)置到TTBR0中,,完成頁表的切換 */         mcr     p15, 0, r0, c2, c0, 0           @ set TTB 0         isb#endif         bx      lrENDPROC(cpu_v7_switch_mm)

三,、參考鏈接

《ARM Cortex-A Series Programmer’s Guide》

《Cortex-A7 MPCore Technical Reference Manual》

《多核MMU和ASID管理邏輯》

TLB的作用及工作過程

MMU和cache詳解(TLB機制)

inux-kernel – Linux內(nèi)核ARM轉(zhuǎn)換表庫(TTB0和TTB1)

ARM TTBR0,TTBR1寄存器與ARM32頁表復(fù)制

選擇使用TTBR0或TTBR1做為translation table base地址寄存器

TLB中ASID和nG bit的關(guān)系

ASID

Linux arm 進程切換

ARM-LINUX的進程切換

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多