docker底層之cgroup

cgroup的實現相對namespace要複雜一些,網上也有一些代碼分析,大家對代碼分析的興趣估計也不大,所以這裏就不放代碼分析了,主要對其使用進行說明,麼麼噠。

Cgroup是linux內核集成的資源控制機制,cgroup與用戶態交互通過特殊文件系統cgroup文件系統,進行交互,所有設置或者查看cgroup的動作都可以通過cgroup文件系統下的文件完成,因此除了編譯內核的時候需要打開特定的選項之外,還需在系統啓動之後掛載cgroup文件系統方能使用cgroup的控制功能。幾個重要概念:

Ø  cgroup: 一組進程的行爲控制。要對某個進程進行資源限制,就將進程添加到cgroup中,一個cgroup可以有多個進程。

Ø  hierarchy: 一組cgroup的集合,可以理解成cgroup的根,cgroup是hierarchy的結點。一個hierarchy的資源限制爲1,代表擁有系統所有資源,cgroup資源限制小於1,分配系統資源。

Ø  subsystem: cgroup可以進行多種資源限制,某種資源限制就是一個subsystem,所有的subsystem構成了資源限制的所有資源種類。

三者之間的主要關係:

Ø  創建新hierarchy時,系統中的所有任務都是那個hierarchy的根cgroup的初始成員。

Ø  一個subsystem最多隻能附加到一個hierarchy。

Ø  一個hierarchy可以附加多個subsystem。

Ø  一個進程可以是多個cgroup的成員,但是這些cgroup必須在不同的hierarchy下。

Ø  創建子進程時,子進程自動成爲父進程所在 cgroup 的成員。可根據需要將該子進程移動到不同的 cgroup 中。

Cgroup包含了11個子系統,分別是:

Ø  Blkio:控制塊設備的訪問,比如帶寬等。

Ø  cpu :控制進程佔用cpu的多少。

Ø  Cpuacct: 記錄cgroup 中進程使用的 CPU 情況。

Ø  Cpuset:爲 cgroup 中的進程分配CPU和內存節點。

Ø  Devices:控制進程對設備的訪問。

Ø  Freezer:掛起或者恢復 cgroup 中的進程。

Ø  Memory:設定 cgroup 中進程的內存限制,統計使用的內存資源。

Ø  net_cls:使用等級識別符(classid)標記網絡數據包,使得Linux 流量控制程序(tc)識別具體 cgroup 中的數據包。

Ø  Ns:命名空間子系統,默認創建cgroup的時候會創建命名空間,新的內核不支持。

Ø  Debug:用於調試。

Ø  Perf:按cgroup進行性能統計。

Cgroup與用戶態的交互通過cgroup文件系統完成,cgroup的每一控制箱在cgroup文件系統下都會對應一個文件,通過對文件的讀寫,來實現資源的控制。cgroup文件系統的定義:

static struct file_system_type cgroup_fs_type = {

.name = "cgroup",

.get_sb = cgroup_get_sb,

.kill_sb = cgroup_kill_sb,

};

定義了兩個函數指針,定義了一個文件系統必須實現了的兩個操作get_sb,kill_sb,即獲得超級塊和釋放超級塊。這兩個操作會在使用mount系統調用掛載cgroup文件系統時使用。

cgroup 超級塊的定義:

static const struct super_operations cgroup_ops = {

.statfs = simple_statfs,

.drop_inode = generic_delete_inode,

.show_options = cgroup_show_options,

.remount_fs = cgroup_remount,

};

cgroup 索引塊定義:

static const struct inode_operations cgroup_dir_inode_operations = {

.lookup = simple_lookup,

.mkdir = cgroup_mkdir,

.rmdir = cgroup_rmdir,

.rename = cgroup_rename,

};

在cgroup文件系統中,使用mkdir創建cgroup或者用rmdir刪除cgroup時,就會調用相應的函數指針指向的函數。

cgroup 文件操作定義:

static const struct file_operations cgroup_file_operations = {

.read = cgroup_file_read,

.write = cgroup_file_write,

.llseek = generic_file_llseek,

.open = cgroup_file_open,

.release = cgroup_file_release,

};

對cgroup目錄下的控制文件進行操作時,會調用該操作函數。而這些函數將會根據訪問的不同的文件調用其對應的操作函數。cgroup文件系統爲cgroups控制文件定義了一個cftype的結構體。cftype的定義:

struct cftype {

char name[MAX_CFTYPE_NAME];

int private; /*

mode_t mode;

size_t max_write_len;

 

int (*open)(struct inode *inode, struct file *file);

ssize_t (*read)(struct cgroup *cgrp, struct cftype *cft,

struct file *file,

char __user *buf, size_t nbytes, loff_t *ppos);

u64 (*read_u64)(struct cgroup *cgrp, struct cftype *cft);

s64 (*read_s64)(struct cgroup *cgrp, struct cftype *cft);

int (*read_map)(struct cgroup *cont, struct cftype *cft,

struct cgroup_map_cb *cb);

int (*read_seq_string)(struct cgroup *cont, struct cftype *cft,

       struct seq_file *m);

 

ssize_t (*write)(struct cgroup *cgrp, struct cftype *cft,

 struct file *file,

 const char __user *buf, size_t nbytes, loff_t *ppos);

int (*write_u64)(struct cgroup *cgrp, struct cftype *cft, u64 val);

int (*write_s64)(struct cgroup *cgrp, struct cftype *cft, s64 val);

int (*write_string)(struct cgroup *cgrp, struct cftype *cft,

    const char *buffer);

int (*trigger)(struct cgroup *cgrp, unsigned int event);

 

int (*release)(struct inode *inode, struct file *file);

int (*register_event)(struct cgroup *cgrp, struct cftype *cft,

struct eventfd_ctx *eventfd, const char *args);/*

void (*unregister_event)(struct cgroup *cgrp, struct cftype *cft,

struct eventfd_ctx *eventfd);

};

cftype中除了定義文件的名字和相關權限標記外,還定義了對文件進行操作的函數指針。不同的文件可以有不同的操作,因爲不同的文件涉及到的是內核不同的子系統不同的功能。

 

 

1.1.1.      Cpu

cpuset 子系統爲 cgroup 分配獨立 CPU 和內存節點。

typedef enum {

    CS_CPU_EXCLUSIVE,

    CS_MEM_EXCLUSIVE,

    CS_MEM_HARDWALL,

    CS_MEMORY_MIGRATE, /*進行內存遷移*/

    CS_SCHED_LOAD_BALANCE, /*該set下的cpu進行負載均衡*/

    CS_SPREAD_PAGE, /*和後面的兩個標誌共同設置平均使用允許的內存節點*/

    CS_SPREAD_SLAB,

} cpuset_flagbits_t;

如果系統支持熱插拔,那麼熱插拔之後如果cpuset的cpu集合或者內存節點集合爲空,該cpuset關聯的進程將移動到上層非空cpuset中。

同一層的task_group和進程被當成同樣的調度實體來選擇,當被選到的是task_group時,則對task_group的孩子節點重複這個過程,直到選到一個運行的進程。當設置一個cgroup的shares值時,該cgroup當作一個整體和剩下的進程或其他cgroup分享cpu時間。比如,在根cgroup下建立cgroup A,將其shares值設1024,再建立cgroup B,將其shares設爲2048,再將一些進程分別加入到這兩個cgroup中,則長期調度的結果應該是A:B:C=1:2:1(C是系統中未加入到A或B的進程)。

Cpu子系統則爲cgroup分配時間比例,cpuacct則統計cgroup中進程使用的時間。

項目

功能

說明

Cpuset.cpus

限制進程能夠使用的cpu節點

 

Cpuset.mems

限制進程能夠使用的內存節點

 

cpu.shares

假設cgroup A的tasks的cpu.shares值爲1,cgroup B的tasks的cpu.shares值爲2,則cgroup B的進程佔用的cpu時間是cgroup A上進程的2倍

 

cpu.rt_runtime_us

以​​​​​​​微​​​​​​​秒​​​​​​​爲​​​​​​​單​​​​​​​位​​​​​​​指​​​​​​​定​​​​​​​在​​​​​​​某​​​​​​​個​​​​​​​時​​​​​​​間​​​​​​​段​​​​​​​中​​​​​​​ cgroup 中​​​​​​​的​​​​​​​實時任​​​​​​​務​​​​​​​對​​​​​​​ CPU 資​​​​​​​源​​​​​​​的​​​​​​​最​​​​​​​長​​​​​​​連​​​​​​​續​​​​​​​訪​​​​​​​問​​​​​​​時​​​​​​​間​​​​​​​

 

cpu.rt_period_us

以​​​​​​​微​​​​​​​秒​​​​​​​爲​​​​​​​單​​​​​​​位​​​​​​​指​​​​​​​定​​​​​​​在​​​​​​​某​​​​​​​個​​​​​​​時​​​​​​​間​​​​​​​段​​​​​​​中​​​​​​​ cgroup 對​​​​​​​ CPU 資​​​​​​​源​​​​​​​訪​​​​​​​問​​​​​​​重​​​​​​​新​​​​​​​分​​​​​​​配​​​​​​​的​​​​​​​頻​​​​​​​率​​​​​​​

 

cpuset.cpu_exclusive

是否共享該cgroup指定的cpu,設置爲1一個cpu只能出現在一個cgroup裏面

 

cpuset.mem_exclusive

是否共享該cgroup指定的memory node 

 

cpuset.sched_load_balance

是否對cgroup的所有CPU做負載平衡

 

cpuset.sched_relax_domain_level 

包​​​​​​​含​​​​​​​ -1 到​​​​​​​小​​​​​​​正​​​​​​​數​​​​​​​間​​​​​​​的​​​​​​​整​​​​​​​數​​​​​​​,它​​​​​​​代​​​​​​​表​​​​​​​內​​​​​​​核​​​​​​​應​​​​​​​嘗​​​​​​​試​​​​​​​平​​​​​​​衡​​​​​​​負​​​​​​​載​​​​​​​的​​​​​​​ CPU 寬​​​​​​​度​​​​​​​範​​​​​​​圍。

-1

爲​​​​​​​負​​​​​​​載​​​​​​​平​​​​​​​衡​​​​​​​使​​​​​​​用​​​​​​​系​​​​​​​統​​​​​​​默​​​​​​​認​​​​​​​值​​​​​​​

0

不​​​​​​​執​​​​​​​行​​​​​​​直​​​​​​​接​​​​​​​負​​​​​​​載​​​​​​​平​​​​​​​衡​​​​​​​;負​​​​​​​載​​​​​​​平​​​​​​​衡​​​​​​​只​​​​​​​是​​​​​​​階​​​​​​​段​​​​​​​性​​​​​​​的​​​​​​​

1

在​​​​​​​同​​​​​​​一​​​​​​​核​​​​​​​中​​​​​​​的​​​​​​​跨​​​​​​​線​​​​​​​程​​​​​​​直​​​​​​​接​​​​​​​負​​​​​​​載​​​​​​​平​​​​​​​衡​​​​​​​

2

在​​​​​​​同​​​​​​​一​​​​​​​軟​​​​​​​件​​​​​​​包​​​​​​​中​​​​​​​的​​​​​​​跨​​​​​​​線​​​​​​​程​​​​​​​直​​​​​​​接​​​​​​​負​​​​​​​載​​​​​​​平​​​​​​​衡​​​​​​​

3

在​​​​​​​同​​​​​​​一​​​​​​​節​​​​​​​點​​​​​​​或​​​​​​​者​​​​​​​刀​​​​​​​片​​​​​​​中​​​​​​​的​​​​​​​跨​​​​​​​線​​​​​​​程​​​​​​​直​​​​​​​接​​​​​​​負​​​​​​​載​​​​​​​平​​​​​​​衡​​​​​​​

4

在​​​​​​​不​​​​​​​使​​​​​​​用​​​​​​​統​​​​​​​一​​​​​​​內​​​​​​​存​​​​​​​訪​​​​​​​問​​​​​​​(NUMA)構​​​​​​​架​​​​​​​中​​​​​​​跨​​​​​​​多​​​​​​​個​​​​​​​ CPU 的​​​​​​​直​​​​​​​接​​​​​​​負​​​​​​​載​​​​​​​平​​​​​​​衡​​​​​​​

5

在​​​​​​​使​​​​​​​用​​​​​​​統​​​​​​​一​​​​​​​內​​​​​​​存​​​​​​​訪​​​​​​​問​​​​​​​(NUMA)構​​​​​​​架​​​​​​​中​​​​​​​跨​​​​​​​多​​​​​​​個​​​​​​​ CPU 的​​​​​​​直​​​​​​​接​​​​​​​負​​​​​​​載​​​​​​​平​​​​​​​衡​​​​​​​

 

cpuset.memory_migrate

cpuset.mems 中​​​​​​​的​​​​​​​值​​​​​​​更​​​​​​​改​​​​​​​時​​​​​​​是​​​​​​​否​​​​​​​應​​​​​​​該​​​​​​​將​​​​​​​內​​​​​​​存​​​​​​​中​​​​​​​的​​​​​​​頁​​​​​​​遷​​​​​​​移​​​​​​​到​​​​​​​新​​​​​​​節​​​​​​​點​​​​​​​的​​​​​​​標​​​​​​​籤​​​​​​​(0 或​​​​​​​者​​​​​​​ 1)

 

cpuset.mem_hardwall

是​​​​​​​否​​​​​​​應​​​​​​​將​​​​​​​內​​​​​​​存​​​​​​​頁​​​​​​​面​​​​​​​的​​​​​​​內​​​​​​​核​​​​​​​分​​​​​​​配​​​​​​​限​​​​​​​制​​​​​​​在​​​​​​​爲​​​​​​​這​​​​​​​個​​​​​​​ cpuset 指​​​​​​​定​​​​​​​的​​​​​​​內​​​​​​​存​​​​​​​節​​​​​​​點​​​​​​​

 

cpuset.memory_pressure

cgroup產生的平均內存壓力

 

cpuset.memory_pressure_enabled

是否計算cgroup中的內存壓力

 

cpuset.memory_spread_page

是否將文件系統緩存平均分配到cgroup的內存節點上

 

cpuset.memory_spread_slab

是否將用於文件輸入輸出緩衝平均分配到cgroup的內存節點上

 

新的實現增加了對cpu上限的設置,其值設置方法和命名都和cpu.rt_runtime_us和cpu.rt_period_us差不多,這個文檔早的時候寫的,這裏偷個懶就不補充了。

1.1.2.      Mem

memory 子系統可以設定 cgroup 中進程使用的內存限制,並自動統計進程使用的內存資源。memory子系統通過linux的resource counter機制實現。

resource counter是內核爲子系統提供的一種資源管理機制。包括了用於記錄資源的數據結構和相關函數。Resource counter定義了一個res_counter的結構體來管理特定資源,定義如下:

struct res_counter {

unsigned long long usage;

unsigned long long max_usage;

unsigned long long limit;

unsigned long long soft_limit;

unsigned long long failcnt;/*

spinlock_t lock;

struct res_counter *parent;

};

Usage用於記錄當前已使用的資源,max_usage用於記錄使用過的最大資源量,limit用於設置資源的使用上限,進程組不能使用超過這個限制的資源,soft_limit用於設定一個軟上限,進程組使用的資源可以超過這個限制,failcnt用於記錄資源分配失敗的次數,管理可以根據這個記錄,調整上限值。Parent指向父節點,這個變量用於處理層次性的資源管理。

memory子系統定義了一個叫mem_cgroup的結構體來管理cgroup相關的內存使用信息,定義如下:

struct mem_cgroup {

struct cgroup_subsys_state css;

struct res_counter res;

struct res_counter memsw;

struct mem_cgroup_lru_info info;

spinlock_t reclaim_param_lock;

int prev_priority;

int last_scanned_child;

bool use_hierarchy;

atomic_t oom_lock;

atomic_t refcnt;

unsigned intswappiness;

int oom_kill_disable;

bool memsw_is_minimum;

struct mutex thresholds_lock;

struct mem_cgroup_thresholds thresholds;

struct mem_cgroup_thresholds memsw_thresholds;

struct list_head oom_notify;

unsigned long move_charge_at_immigrate;

struct mem_cgroup_stat_cpu *stat;

};

mem_cgroup中包含了兩個res_counter成員,分別用於管理memory資源和memory+swap資源,如果memsw_is_minimum爲true,則res.limit=memsw.limit,即當進程組使用的內存超過memory的限制時,不能通過swap來緩解。

Cgroup通過在分配內存,換入換出的時候進行計數來管理cgroup中內存。內核線程如果屬於某個cgroup,其使用內存同樣受到cgroup控制。頁高速緩存的內存也在cgroup控制之內。

項目

功能

說明

Memory.stat

當前cgroup中的內存實時狀況

 

Memory.usage_in_bytes

當前cgroup中所有進程使用的物理內存之和

單位bytes

Memory.memsw.usage_in_bytes

當前cgroup中所有進程使用的物理內存和swap之和

 

Memory.max.usage_in_bytes

當前cgroup使用物理內存最大值

 

Memory.memsw.max_usage_in_bytes

當前cgroup使用物理內存和swap之和的最大值

 

Memory.limit_in_bytes

設置當前cgroup的物理內存的上限值(包括頁高速緩存)

 

Memory.memsw.limit_in_bytes

對物理內存和swap之和的限制

 

Memory.failcnt

物理內存值達到所設置上限值的次數

 

Memory.memsw.failcnt

物理內存和swap之和達到所設置上限值的次數

 

Memory.force_empty

釋放cgroup使用的所有內存

只有在cgroup中沒有進程時才使用

Memory.swappines

交換值,用於計算交換傾向經驗值

 

Memory.use_hierarchy

指定內存回收是否按一個hierarchy來進行,如果設置爲1,內存子系統從其中一個超過限額的子cgroup中回收內存,如果設置爲0,則只是從當前cgroup中的進程回收

 

Memory.oom_control

內存超過限制並且回收失敗後事後通過oom_killer機制處理

 

Memory.soft_limit_in_bytes

內存軟限制

 

Memory.move_charge_at_immigrate

頁遷移計數控制

 

Memory.numa.stat

每個節點的內存控制信息

 

Memory.tcp.limit_in_bytes

當前cgroup能使用的tcp內存總大小

 

Memory.tcp.usage_in_bytes

當前cgroup所有進程使用的tcp內存量

 

Memory_kmem.tcp.filcnt

當前cgroup中所有進程tcp緩存之和超過限制的次數

 

Memory.keme.tcp.max_usage_in_bytes

當前cgroup中tcp使用緩存的峯值

 

 

1.1.3.      Device

devices只控制塊設備和字符設備的訪問。有inode的設備文件通過在open中對比權限來控制cgroup中的進程能夠訪問的devices。inode不存在的設備就必須通過mknod中對比權限來控制進程對設備的訪問。

項目

功能

說明

Devices.allow

允許cgroup中進程訪問的設備名

 

Devices.deny

禁止cgroup中進程訪問的設備名

 

Devices.list

Cgroup中進程訪問的設備

 

 

1.1.4.      Net

網絡的資源控制通過kernel的tc實現。

項目

功能

說明

Net_cls.classid

流量控制中cgroup分類器的id號

 

Net_prio.ifpriomap

設置某個設備上的優先級

 

Net_prio.prioidx

全網唯一的一個標識,只讀

 

 

1.1.5.      Blkio

cgroup的blkio子系統管理塊設備訪問,有2種限制模式:

Ø  throttle,限制每個進程能使用的iops或者bps。

Ø  weight,限制每個進程能使用的iops的比例,必須通過CFQ調度器來實現。可以通過修改/sys/block/sda/queue/scheduler文件完成。io帶寬相對控制只是針對受控對象都很繁忙的時候控制其上限速度,在某個cgroup比較閒的時候,不再滿足這個規律,其他cgroup可能消耗掉更多的帶寬。

要使用blkio的weight限制需要注意:

Ø  必須direct io, 如果buffered io因爲最終寫IO的進程不是發起IO的進程,結果會有很大的偏差。

Ø  調度器必須是CFQ。

在CFQ調度器中,所有的異步請求都屬於根cgroup,不受子cgroup的限制,因此在子cgroup中對異步io進行資源限制沒有意義。

項目

功能

說明

blkio.weight

Io訪問能力的相對權重

 

blkio.weight_device

特定設備的io帶寬的相對權重

 

blkio.throttle.read_bps_device

特定設備的讀bps上限值

 

blkio.throttle.write_bps_device

特定設備的寫bps上限值

 

blkio.throttle.read_iops_device 

特定設備的讀iops上限值

 

blkio.throttle.write_iops_device

特定設備的寫iops上限值

 

blkio.io_merged

請​​​​​​​求​​​​​​​合​​​​​​​並​​​​​​​到I/O操​​​​​​​作​​​​​​​請​​​​​​​求​​​​​​​的​​​​​​​次​​​​​​​數

 

blkio.io_queued

Cgroup I/O操​​​​​​​作​​​​​​​排​​​​​​​隊​​​​​​​的​​​​​​​請​​​​​​​求​​​​​​​次​​​​​​​數​​​​​​​

 

blkio.io_service_bytes

轉​​​​​​​換​​​​​​​到​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​或​​​​​​​者​​​​​​​由​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​中​​​​​​​轉​​​​​​​換​​​​​​​出​​​​​​​的​​​​​​​字​​​​​​​節​​​​​​​數

 

blkio.io_serviced

在​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​中​​​​​​​執​​​​​​​行​​​​​​​的I/O操​​​​​​​作​​​​​​​數

 

blkio.io_service_time

在​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​中​​​​​​​的I/O操​​​​​​​作​​​​​​​請​​​​​​​求​​​​​​​發​​​​​​​送​​​​​​​和​​​​​​​請​​​​​​​求​​​​​​​完​​​​​​​成​​​​​​​之​​​​​​​間​​​​​​​的​​​​​​​時​​​​​​​間​​​​​​​

 

blkio.io_wait_time

所有io操作在隊列中等待的時間總和

 

blkio.reset_stats

重​​​​​​​新​​​​​​​設​​​​​​​定​​​​​​​在​​​​​​​其​​​​​​​它​​​​​​​僞​​​​​​​文​​​​​​​件​​​​​​​中​​​​​​​記​​​​​​​錄​​​​​​​的​​​​​​​統​​​​​​​計​​​​​​​數​​​​​​​據

 

blkio.sectors

轉​​​​​​​換​​​​​​​到​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​或​​​​​​​者​​​​​​​由​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​轉​​​​​​​換​​​​​​​出​​​​​​​的​​​​​​​扇​​​​​​​區​​​​​​​數

 

blkio.throttle.io_service_bytes

轉​​​​​​​換​​​​​​​到​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​或​​​​​​​者​​​​​​​由​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​中​​​​​​​轉​​​​​​​換​​​​​​​出​​​​​​​的​​​​​​​字​​​​​​​節​​​​​​​數

 

blkio.throttle.io_serviced

在​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​中​​​​​​​執​​​​​​​行​​​​​​​的​​​​​​​I/O操​​​​​​​作​​​​​​​數

 

blkio.time

對​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​的​​​​​​​I/O訪​​​​​​​問​​​​​​​時​​​​​​​間​​​​​​​

 

blkio.avg_queue_size

I/O操​​​​​​​作​​​​​​​的​​​​​​​平​​​​​​​均​​​​​​​隊​​​​​​​列​​​​​​​大​​​​​​​小

 

blkio.group_wait_time

每個隊列等待獲取時間片的等待時間總和​​​​

 

blkio.empty_time

不包括等待時間在內的處理時間總和

 

blkio.idle_time

Io調度程序等待更好的請求時空轉的時間

 

blkio.dequeue

​​​​​​​I/O操​​​​​​​作​​​​​​​請​​​​​​​求​​​​​​​被​​​​​​​具​​​​​​​體​​​​​​​設​​​​​​​備​​​​​​​從​​​​​​​隊​​​​​​​列​​​​​​​中​​​​​​​移​​​​​​​除​​​​​​​的​​​​​​​次​​​​​​​數​​​​​​​

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章