linux+不同分区mv,mv操作深入浅出
業(yè)務(wù)背景:存在兩個進程,進程A生成文件file,然后將其mv到一個新的文件fileA,進程B在需要的時候會取讀取文件fileA。如果在進程B讀取文件的時候,進程A在進行mv的操作,那么這個操作是否對進程B讀取文件生成影響呢?會產(chǎn)生什么結(jié)果?
如下圖所示:
在闡述該問題之前,首先講解下mv的操作和fopen()函數(shù)等對文件的操作的基本原理。那么首先需要初步的了解下Linux的文件系統(tǒng)中所涉及到的一些基礎(chǔ)知識,比如說Linux文件系統(tǒng)組件的體系結(jié)構(gòu),VFS, i節(jié)點,元數(shù)組等概念。
1.Linux文件系統(tǒng)體系結(jié)構(gòu)
如圖1為Linux文件系統(tǒng)組件的體系結(jié)構(gòu):
其中用戶空間包含一些應(yīng)用程序(例如,文件系統(tǒng)的使用者)和 GNU C 庫(glibc),它們?yōu)槲募到y(tǒng)調(diào)用(打開、讀取、寫和關(guān)閉)提供用戶接口。系統(tǒng)調(diào)用接口的作用就像是交換器,它將系統(tǒng)調(diào)用從用戶空間發(fā)送到內(nèi)核空間中的適當(dāng)端點。
VFS 是底層文件系統(tǒng)的主要接口。這個組件導(dǎo)出一組接口,然后將它們抽象到各個文件系統(tǒng),各個文件系統(tǒng)的行為可能差異很大。有兩個針對文件系統(tǒng)對象的緩存(inode 和 dentry)。它們緩存最近使用過的文件系統(tǒng)對象。文件系統(tǒng)實現(xiàn)(比如 ext2、JFS 等等)導(dǎo)出一組通用接口,供 VFS 使用。緩沖區(qū)緩存會緩存文件系統(tǒng)和相關(guān)塊設(shè)備之間的請求。例如,對底層設(shè)備驅(qū)動程序的讀寫請求會通過緩沖區(qū)緩存來傳遞。這就允許在其中緩存請求,減少訪問物理設(shè)備的次數(shù),加快訪問速度(緩存的目的)。以最近使用(LRU)列表的形式管理緩沖區(qū)緩存。注意,可以使用 sync 命令將緩沖區(qū)緩存中的請求發(fā)送到存儲媒體(迫使所有未寫的數(shù)據(jù)發(fā)送到設(shè)備驅(qū)動程序,進而發(fā)送到存儲設(shè)備)。具體的信息可以參考IBM develop的《Linux 文件系統(tǒng)剖析》,鏈接為http://www.ibm.com/developerworks/cn/linux/l-linux-filesystem/
Linux文件系統(tǒng)下如何讀取文件:VFS采用了一組數(shù)據(jù)結(jié)構(gòu)來描述文件系統(tǒng),這些數(shù)據(jù)有超級塊、inode、dentry和數(shù)據(jù)塊。Linux版本較多,文件系統(tǒng)也不同,但是對于linux系統(tǒng),其基本結(jié)構(gòu)還是一致的,都會包含引導(dǎo)塊、超級快,目錄項i節(jié)點表、數(shù)據(jù)區(qū)等幾個部分。
1) 引導(dǎo)塊:位于文件卷最開始的第一扇區(qū),這512字節(jié)是文件系統(tǒng)的引導(dǎo)代碼,為根文件系統(tǒng)所特有,其他文件系統(tǒng)這512字節(jié)為空。
2) 超級塊:位于文件系統(tǒng)第二扇區(qū),緊跟引導(dǎo)塊之后,用于描述本文件系統(tǒng)的結(jié)構(gòu)。如i節(jié)點長度、文件系統(tǒng)大小等。
3) 目錄項:Unix所有文件均存放于目錄中,目錄本身也是一個文件。目錄存放文件的機制如下: 首先,目錄文件本身也象普通文件一樣,占用一個索引節(jié)點; 其次,由這個索引節(jié)點得到目錄內(nèi)容的存放位置; 再次,從其內(nèi)容中取出一個個的文件名和它對應(yīng)的節(jié)點號,從而訪問一個文件。
4) i節(jié)點:i節(jié)點表存放在超級塊之后,其長度是由超級塊中的s_isize字段決定的,其作用是用來描述文件的屬性、長度、屬主、屬組、數(shù)據(jù)塊表等
Linux會為每一個文件分配一個唯一的inode節(jié)點。而dentry是實現(xiàn)了文件名和inode編號的映射,當(dāng)然還有其他的功能。在linux中,文件的文件名、文件屬性、文件內(nèi)容是分別存儲的:文件名存放在目錄項(即dentry)中,文件屬性存放在inode中,文件內(nèi)容存放在數(shù)據(jù)塊中。Linux在查找操作文件系統(tǒng)中的文件時,首先先讀取超級塊信息,找到文件名對應(yīng)的inode,然后根據(jù)inode找到磁盤中的文件,進而根據(jù)inode中的信息來完成文件的各種操作。也就是說,Linux通過inode來尋找磁盤中的文件,而不是通過文件名來尋找的。這就是Linux操作文件時的一個大致過程,當(dāng)然具體情況要比這復(fù)雜。
inode的結(jié)構(gòu):? inode 編號
? 用來識別文件類型,以及用于 stat C 函數(shù)的模式信息
? 文件的鏈接數(shù)目
? 屬主的 UID
? 屬主的組 ID (GID)
? 文件的大小
? 文件所使用的磁盤塊的實際數(shù)目
? 最近一次修改的時間
? 最近一次訪問的時間
? 最近一次更改的時間
需要注意的是:inode本身并不記錄文件名,而是記錄文件的相關(guān)的屬性(在上文提到過的那些屬性),文件名則記錄在目錄所屬的塊區(qū)域。正因為這個原因,使得如果Linux讀取一個文件的內(nèi)容,就要先由根目錄/獲取該文件的上層目錄所在的inode,再由該目錄所記錄的的文件關(guān)聯(lián)性獲取該文件的inode,最后通過inode內(nèi)提供的塊指針來獲取最終的文件內(nèi)容。
可以看到i節(jié)點中包含了大多數(shù)于文件有關(guān)的信息:文件的類型,文件的訪問權(quán)限,文件所占用的數(shù)據(jù)塊的指針等。接下來我們可以認(rèn)識下這個常聽說的文件的inode節(jié)點,并且闡述mv操作對文件的inode影響。
如圖2為磁盤、分區(qū)和文件系統(tǒng)的結(jié)構(gòu)圖
通用文件模型由下列對象類型組成:
? 超級塊(superblock)對象: 存放系統(tǒng)中已安裝文件系統(tǒng)的有關(guān)信息。對于基于磁盤的文件系統(tǒng),這類對象通常對應(yīng)于存放在磁盤上的文件系統(tǒng)控制塊,也就是說,每個文件系統(tǒng)都有一個超級塊對象.
? 索引節(jié)點(inode)對象: 存放關(guān)于具體文件的一般信息。對于基于磁盤的文件系統(tǒng),這類對象通常對應(yīng)于存放在磁盤上的文件控制塊(FCB),也就是說,每個文件都有一個索引節(jié)點對象。每個索引節(jié)點對象都有一個索引節(jié)點號,這個號唯一地標(biāo)識某個文件系統(tǒng)中的指定文件。
? 目錄項(dentry)對象: 存放目錄項與對應(yīng)文件進行鏈接的信息。VFS把每個目錄看作一個由若干子目錄和文件組成的常規(guī)文件。例如,在查找 路徑名/tmp/test時 , 內(nèi)核為 根目錄“/ ”創(chuàng)建一個目錄項對象, 為根目錄下的 tmp項創(chuàng)建一個第二級目錄項對象,為 /tmp 目錄下的test項創(chuàng)建一個第三級目錄項對象。
? 文件(file)對象: 存放打開文件與進程之間進行交互的有關(guān)信息。這類信息僅當(dāng)進程訪問文件期間存在于內(nèi)存中詳細(xì)信息可以參考《UNIX環(huán)境高級編程》的第四章《文件和目錄》和《深入分析Linux內(nèi)核源碼》。
2. mv在同一個分區(qū)之內(nèi)是執(zhí)行的rename的操作,不會更改i節(jié)點的信息。
首先可以通過常用的命令,如ls -li,stat等命令來認(rèn)識下inode節(jié)點。
2.1)首先可以通過,df命令來查看磁盤的分區(qū),如下操作:
//用df -i來查一下磁盤空間
# df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 7913472 126186 7787286 2% /
2.2)然后可以通過ls -li查看i節(jié)點的信息,如以下操作中會在第一列中顯示文件的inode的編號。
//ls -li來查一個文件的inumber
#ls -i /bin/ping
1032194 -rwsr-xr-x 1 root root 33272 Apr 14 2006 /bin/ping
2.3)進一步,可以通過stat來查文件的信息。該顯示的信息會比較多,其中stat結(jié)構(gòu)中大多數(shù)的信息都是來自i節(jié)點,只有兩項數(shù)據(jù)是存放在目錄項當(dāng)中:文件名和i節(jié)點的編號。
# stat /bin/ping
File: `/bin/ping'
Size: 33272 Blocks: 80 IO Block: 4096 regular file
Device: 801h/2049d Inode: 1032194 Links: 1
Access: (4755/-rwsr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2009-08-04 11:13:08.000000000 +0800
Modify: 2006-04-14 21:16:26.000000000 +0800
Change: 2009-07-01 14:04:44.000000000 +0800
2.4)mv對inode的影響:
如果mv命令的目標(biāo)和源文件所在的分區(qū)相同:
1)使用新文件名建立目錄項(dentry),將新文件名稱對應(yīng)到inode 編號;
2)解除與舊目錄項的鏈接;
需要注意的是:該操作對inode表沒有影響(除時間戳),對數(shù)據(jù)的位置也沒有影響,不移動任何數(shù)據(jù)。只需要構(gòu)造一個現(xiàn)有i節(jié)點的新目錄項,并解除和舊目錄項的鏈接。在實踐中也能得知:不論文件有多大,執(zhí)行mv的時間都是“瞬間”完成。
2.5)查看到i節(jié)點的信息并未改變:
通過ls –li或者stat filename來查看具體的信息,如實際的例子:如下圖3所示,通過mv操作之后,并不會改變i節(jié)點的信息。
2.6)通過strace mv a b來追蹤mv的實現(xiàn)機制。如下圖4所示,其中該例子中,原有的a,b文件都是存在。
2.7)strace mv a b,如下圖5所示,其中b文件是不存在的。
從上述圖中可以得出,mv操作并不改變i節(jié)點的編號,并且其實現(xiàn)是通過rename的機制來實現(xiàn)的。
2.8) 通過mv的源碼來查看其信息??梢圆榭?coreutils-8.9)的源碼。
mv操作是針對cp_options這個結(jié)構(gòu)體,其中該結(jié)構(gòu)體中的move_mode決定了方式。即判斷是否在一個分區(qū)內(nèi),如下示意圖為調(diào)用的信息。
在mv.c中movefile->do_move->copy,在movefile中傳入了cp_options的結(jié)構(gòu)體,并通過move_mode來決定是進行rename還是read和write的方式。
結(jié)論:由以上對inode的實踐,并通過strace來追蹤mv的實現(xiàn)機制、mv的源碼,可以得出在同一個分區(qū)中,mv實際進行的是rename操作。接下來本文將講述rename的實現(xiàn)。
3. rename是一種原子操作
“Rename是一種原子操作”,如果要將這個問題講述清楚,則需要講到的是“Linux文件系統(tǒng)中元數(shù)據(jù)的加鎖機制與組織方式”。
在Linux系統(tǒng)中,需要對元數(shù)據(jù)進行加鎖,元數(shù)據(jù)操作是一種事務(wù)操作,需要滿足原子性,一致性,獨立性和持久性。為了解決這種元數(shù)據(jù)操作帶來的一致性的問題和多個元數(shù)據(jù)操作的交互和重疊的問題,采用加鎖的方式。
如:操作1,在目錄a下創(chuàng)建了b, 遞增目錄 a 的 nlink 值,操作2刪除目錄a,如果并發(fā)的進行就會出問題,但是如果通過加鎖的方式,先對a進行加鎖,再解鎖,就是一種串行的執(zhí)行,則不會出現(xiàn)問題。
如圖6元數(shù)據(jù)操作的死鎖所示:如果此時系統(tǒng)中沒有相應(yīng)的加鎖機制對元數(shù)據(jù)操作進行互斥,那么當(dāng)操作①創(chuàng)建了對象 b 以后,接下來操作②有可能就將目錄 a 刪除了,當(dāng)操作①要遞增目錄 a 的 nlink 值時,就會發(fā)現(xiàn)沒有可操作的對象了,于是操作出錯。
加鎖能夠帶來一定的優(yōu)勢,但是會引發(fā)新的問題:即死鎖的問題。需要注明的一個問題是元數(shù)據(jù)的加鎖操作基本上都是由其虛擬文件系統(tǒng)(VFS)來規(guī)定的,這樣做的好處是可以統(tǒng)一管理所有元數(shù)據(jù)操作的加鎖機制,底層的具體文件系統(tǒng)可以不理會這些問題,只需要按照 VFS 的調(diào)用來執(zhí)行對元數(shù)據(jù)的操作。
為了防止死鎖的問題:Linux也做了一些規(guī)定:即在VFS中,對于大多數(shù)的元數(shù)據(jù)操作,可以通過制定統(tǒng)一的加鎖順序來避免死鎖的發(fā)生。這個順序是:先對父目錄加鎖,再對要操作的對象(目錄或文件)加鎖。但是rename和link是除外的。如圖7所示,仍然不能夠解決mv操作的死鎖問題。
為了解決這個問題:Linux規(guī)定同一時間只能有一個rename的操作,當(dāng)然這個是針對同一個文件系統(tǒng)內(nèi)的,該實現(xiàn)機制也是通過加鎖來實現(xiàn)的。具體信息可以參考IBM develop上的《Linux文件系統(tǒng)中元數(shù)據(jù)的加鎖機制與組織方式》,鏈接為:http://www.ibm.com/developerworks/cn/linux/l-cn-fsmeta/
繼續(xù)看mv的操作實現(xiàn)原理,如圖8所示,解釋剛才出現(xiàn)的問題:
總結(jié)mv操作的實現(xiàn)過程:
1) 如果通過stat返回與此命名文件有關(guān)的信息結(jié)構(gòu),lstat函數(shù)類似于stat,但是當(dāng)命名的文件是一個符號鏈接時,lstat返回該符號鏈接的有關(guān)信息,而不是由該符號鏈接引用文件的信息。
2) 通過access文件b(W_OK),查看文件b是否有寫入的權(quán)限。對于access,它會檢查是否可以讀/寫某一已存在的文件。參數(shù)mode有幾種情況組合, R_OK,W_OK,X_OK 和F_OK。R_OK,W_OK與X_OK用來檢查文件是否具有讀取、寫入和執(zhí)行的權(quán)限。由于access只作權(quán)限的核查,并不理會文件形態(tài)或文件內(nèi)容。
3) 進行rename的重命名操作。
注意:rename操作可以根據(jù)所操作的為文件還是目錄項,分為幾種情況,由于本文的業(yè)務(wù)背景,目前只是討論所操作的為文件的情況,所以不將詳細(xì)闡述,具體的可以參考《UNIX環(huán)境高級編程》中第四章的內(nèi)容,對于rename操作,其原型為:
int rename(const char * oldname,const char * newname)
需要說明的是:如果newname已經(jīng)存在,且為文件的,則先將該目錄項刪除,然后將oldname更名為newname,若newname不存在,則只需要進行更名即可。
4. fopen()和fclose()操作
在Linux中,進程是通過文件描述符(file descriptors,簡稱fd)而不是文件名來訪問文件的,文件描述符實際上是一個整數(shù)。文件名的信息是存在文件的目錄項當(dāng)中。其中文件位置是比較重要的一個參數(shù),每個文件都有一個32位的數(shù)字來表示下一個讀寫的字節(jié)位置,根據(jù)這個位置來決定了文件的寫入和讀出。為了描述打開的位置,Linux中專門用了一個數(shù)據(jù)結(jié)構(gòu)file來保存打開文件的文件位置,這個結(jié)構(gòu)稱為打開的文件描述(open file description)。這個數(shù)據(jù)結(jié)構(gòu)的與進程的聯(lián)系非常緊密。
file結(jié)構(gòu)中主要保存了文件位置,此外,還把指向該文件索引節(jié)點的指針也放在其中。
file結(jié)構(gòu)在include\linux\fs.h中定義如下:
struct file
{
struct list_head f_list; /*所有打開的文件形成一個鏈表*/
struct dentry *f_dentry; /*指向相關(guān)目錄項的指針*/
struct vfsmount *f_vfsmnt; /*指向VFS安裝點的指針*/
struct file_operations *f_op; /*指向文件操作表的指針*/
mode_t f_mode; /*文件的打開模式*/
loff_t f_pos; /*文件的當(dāng)前位置*/
unsigned short f_flags; /*打開文件時所指定的標(biāo)志*/
unsigned short f_count; /*使用該結(jié)構(gòu)的進程數(shù)*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/*預(yù)讀標(biāo)志、要預(yù)讀的最多頁面數(shù)、上次預(yù)讀后的文件指針、預(yù)讀的字節(jié)數(shù)以及
預(yù)讀的頁面數(shù)*/
int f_owner; /* 通過信號進行異步I/O數(shù)據(jù)的傳送*/
unsigned int f_uid, f_gid; /*用戶的UID和GID*/
int f_error; /*網(wǎng)絡(luò)寫操作的錯誤碼*/
unsigned long f_version; /*版本號*/
void *private_data; /* tty驅(qū)動程序所需 */
};
fopen在打開文件時是根據(jù)文件名,之后的fread,fwrite等操作都是根據(jù)文件描述符進行的。Linux中對文件的加鎖,一般有flock(),fcntl()等機制,單純的fopen和fread和fwrite是不會對文件進行加解鎖的。fclose函數(shù)關(guān)閉了文件的流,實際是進行了一個寫文件的操作。 文件內(nèi)容寫入失敗,這時fclose函數(shù)就會出錯,如果寫權(quán)限不存在或者是超過了大小才會失敗,所以一般情況下,fclose失敗的概率也是很小的。而在調(diào)用fclose關(guān)閉一個打開的流,在文件被關(guān)閉之前,沖洗緩沖區(qū)中的輸出的數(shù)據(jù)。丟棄緩沖區(qū)中的任何輸入數(shù)據(jù),如果標(biāo)準(zhǔn)I/O庫已經(jīng)為該流自動分配了一個緩沖區(qū),則釋放此緩沖區(qū)。
5.擴展:在業(yè)務(wù)中存在的各種操作的結(jié)果現(xiàn)象以分析
通過實驗查看一下四種情況,該實現(xiàn)是基于進程見的。
操作1
1) 進程A:fopen fileA, sleep 10
2) 進程B:mv fileB fileA
3) 進程A:fclose fileA
結(jié)果:在mv完成之后,立即可以看到fileA中的文件已經(jīng)變?yōu)榱薴ileB,fclose成功。
操作2
1)進程A:fopen fileA, sleep 10
2)進程B:mv fileA fileB
3)進程A:fclose fileA
結(jié)果:在mv完成之后,fileA文件已不存在,僅有fileB文件,且fileB文件的內(nèi)容為原來fileA文件的內(nèi)容。關(guān)閉文件fclose成功。
操作3
1)進程A:fopen fileA, 通過fwrite寫入到文件的一行之后進行sleep
2)進程B:mv fileB fileA
3)進程A:fclose fileA
結(jié)果:在mv完成之后,可以立即看到fileA中的內(nèi)容變?yōu)榱薴ileB,而且當(dāng)寫文件操作完成之后,也可以看到fileA中文件仍然為fileB,且關(guān)閉文件fileB依然是成功的。
原因: mv僅僅是rename操作。fopen寫入的是磁盤位置,所以雖然mv使得文件名變了但是磁盤位置并沒有變還是可以寫入并關(guān)閉正常。在mv fileB fileA之后。fopen還是打開的原來的fileA文件(已經(jīng)不能被文件系統(tǒng)索引到)的磁盤位置,寫入操作也還是在那塊進行,fclose由于是可以寫入文件就是成功故其返回值也是正確的。但在這之后查看fileA文件實際上是查看的原來的fileB文件,而并不是原來的fileA文件。
以上描述的原因可以參考下圖對于mv的解釋
操作4
1)fopen fileA, 寫入到文件的一行后,sleep
2)mv fileA fileB
3)fclose fileA
結(jié)果:當(dāng)mv完成之后,可以看到文件fileA已經(jīng)不存在,當(dāng)fclose之后,可以看到fileB當(dāng)中的內(nèi)容為寫入的內(nèi)容。
原因: fopen()和fclose()都是對磁盤空間做操作的,并不記錄文件名。Mv僅是rename操作,并沒有對磁盤做實際意義的操作。問題點:fopen(), fwrite(), fclose()都是帶緩存,比如在執(zhí)行文件讀操作的時候,從磁盤文件將數(shù)據(jù)先讀入內(nèi)存“緩沖區(qū)”,裝滿之后再從內(nèi)存“緩沖區(qū)”依次讀入接收的變量。執(zhí)行文件寫操作的時候,先將數(shù)據(jù)寫入到內(nèi)存的“緩沖區(qū)”中。待緩沖區(qū)裝滿之后再寫入到文件中。
于是對上述的3,4實驗,希望每次寫入文件時候,及時的刷新到磁盤上,并重新進行了實驗。沒寫入一行進行fflush(fp)進行刷新。發(fā)現(xiàn)結(jié)果和上述一致,不同之處是在于由于刷新之后會及時將文件的內(nèi)容寫入到文件中,可以看到在寫的過程當(dāng)中文件的大小是逐漸遞增的。而不帶緩沖的時候,知道flose執(zhí)行完畢才將寫入的內(nèi)容一次性寫入到磁盤當(dāng)中。
接
著,做了如下的實驗,采用open()和write()等unix標(biāo)準(zhǔn)的函數(shù)進行。
區(qū)別:fopen()和open()等函數(shù)區(qū)別,fopen等是ANSIC標(biāo)準(zhǔn)中的C語言庫函數(shù),在不同的系統(tǒng)中調(diào)用不同的內(nèi)核API, open(),close(),write(),read()是標(biāo)準(zhǔn)的UNIX函數(shù),參數(shù)不是指向文件的指針,而是指向文件的句柄,并且是不帶緩沖的。因此以上的實驗將fopen換成open,fclose換成close,在實驗中可以看到,在通過write寫入的時候,可以看到文件的大小是遞增的,說明是直接寫入到磁盤空間上的。
重新嘗試上述的3,4實驗,將fwrite()改為write(),fclose()改為close()。
操作3.1
1)open fileA, 寫入到文件的一行之后進行sleep
2)mv fileB fileA
3)close fileA
結(jié)果:執(zhí)行完mv fileB fileA之后,文件fileA的內(nèi)容立即為fileB文件的內(nèi)容,而且close文件也是成功的。
操作4.2
1)open fileA, 寫入到文件的一行之后進行sleep
2)mv fileA fileB
3)close fileA
結(jié)果:執(zhí)行完mv fileB fileA之后,由于A中已經(jīng)有了部分的內(nèi)容,可以看到fileB中文件的內(nèi)容成為fileA文件的內(nèi)容,文件關(guān)閉也是成功的。
由上述的實驗當(dāng)中可以看到:是否帶緩沖的機制不會影響上述的結(jié)果。
原因:緩沖機制的作用是提高IO的讀寫速度,但是fopen之后文件的寫入還是根據(jù)i節(jié)點的位置來實現(xiàn)的,故還是會產(chǎn)生如上的結(jié)果。
6.結(jié)論
就之前提到的業(yè)務(wù)情況,進程B在讀取文件的時候,進程A在進行mv的操作,那么這個操作不會對進程B讀取文件生成影響。由于讀取文件都是根據(jù)文件名操作,并且mv操作是原子操作,故每次mv之后,都讀得是“新的”文件。
總之,分析此類問題的關(guān)鍵在于理解mv和fopen操作的區(qū)別和本質(zhì)。mv是對文件名操作的,文件名屬性記錄在目錄項中。fopen雖然打開是關(guān)聯(lián)文件名的,單在打開之后是對真正的磁盤做操作,和文件名無關(guān)。
作者:zjchao
總結(jié)
以上是生活随笔為你收集整理的linux+不同分区mv,mv操作深入浅出的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux搭建windows无盘系统,P
- 下一篇: Linux线上环境部署zabbix,Ce