什么是零拷貝
維基上是這么描述零拷貝的:零拷貝描述的是CPU不執(zhí)行拷貝數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)域到另一個(gè)存儲(chǔ)區(qū)域的任務(wù)笙瑟,這通常用于通過(guò)網(wǎng)絡(luò)傳輸一個(gè)文件時(shí)以減少CPU周期和內(nèi)存帶寬。
零拷貝給我們帶來(lái)的好處:
- 減少甚至完全避免不必要的CPU拷貝,從而讓CPU解脫出來(lái)去執(zhí)行其他的任務(wù)
- 減少內(nèi)存帶寬的占用
- 通常零拷貝技術(shù)還能夠減少用戶空間和操作系統(tǒng)內(nèi)核空間之間的上下文切換
Linux系統(tǒng)的“用戶空間”和“內(nèi)核空間”
從Linux系統(tǒng)上看颜曾,除了引導(dǎo)系統(tǒng)的BIN區(qū)褐健,整個(gè)內(nèi)存空間主要被分成兩個(gè)部分:內(nèi)核空間(Kernel space)、用戶空間(User space)物蝙⊙字停“用戶空間”和“內(nèi)核空間”的空間、操作權(quán)限以及作用都是不一樣的诬乞。內(nèi)核空間是Linux自身使用的內(nèi)存空間册赛,主要提供給程序調(diào)度、內(nèi)存分配震嫉、連接硬件資源等程序邏輯使用森瘪;用戶空間則是提供給各個(gè)進(jìn)程的主要空間。用戶空間不具有訪問(wèn)內(nèi)核空間資源的權(quán)限票堵,因此如果應(yīng)用程序需要使用到內(nèi)核空間的資源扼睬,則需要通過(guò)系統(tǒng)調(diào)用來(lái)完成:從用戶空間切換到內(nèi)核空間,然后在完成相關(guān)操作后再?gòu)膬?nèi)核空間切換回用戶空間换衬。
Linux 中零拷貝技術(shù)的實(shí)現(xiàn)方向
① 直接 I/O:對(duì)于這種數(shù)據(jù)傳輸方式來(lái)說(shuō)痰驱,應(yīng)用程序可以直接訪問(wèn)硬件存儲(chǔ)证芭,操作系統(tǒng)內(nèi)核只是輔助數(shù)據(jù)傳輸。這種方式依舊存在用戶空間和內(nèi)核空間的上下文切換担映,但是硬件上的數(shù)據(jù)不會(huì)拷貝一份到內(nèi)核空間废士,而是直接拷貝至了用戶空間,因此直接I/O不存在內(nèi)核空間緩沖區(qū)和用戶空間緩沖區(qū)之間的數(shù)據(jù)拷貝蝇完。
② 在數(shù)據(jù)傳輸過(guò)程中官硝,避免數(shù)據(jù)在用戶空間緩沖區(qū)和系統(tǒng)內(nèi)核空間緩沖區(qū)之間的CPU拷貝,以及數(shù)據(jù)在系統(tǒng)內(nèi)核空間內(nèi)的CPU拷貝短蜕。本文主要討論的就是該方式下的零拷貝機(jī)制氢架。
③ copy-on-write(寫時(shí)復(fù)制技術(shù)):在某些情況下,Linux操作系統(tǒng)的內(nèi)核空間緩沖區(qū)可能被多個(gè)應(yīng)用程序所共享朋魔,操作系統(tǒng)有可能會(huì)將用戶空間緩沖區(qū)地址映射到內(nèi)核空間緩存區(qū)中岖研。當(dāng)應(yīng)用程序需要對(duì)共享的數(shù)據(jù)進(jìn)行修改的時(shí)候,才需要真正地拷貝數(shù)據(jù)到應(yīng)用程序的用戶空間緩沖區(qū)中警检,并且對(duì)自己用戶空間的緩沖區(qū)的數(shù)據(jù)進(jìn)行修改不會(huì)影響到其他共享數(shù)據(jù)的應(yīng)用程序孙援。所以,如果應(yīng)用程序不需要對(duì)數(shù)據(jù)進(jìn)行任何修改的話扇雕,就不會(huì)存在數(shù)據(jù)從系統(tǒng)內(nèi)核空間緩沖區(qū)拷貝到用戶空間緩沖區(qū)的操作拓售。
注意,對(duì)于各種零拷貝機(jī)制是否能夠?qū)崿F(xiàn)都是依賴于操作系統(tǒng)底層是否提供相應(yīng)的支持镶奉。
零拷貝機(jī)制的原理
下面我們通過(guò)一個(gè)Java非常常見(jiàn)的應(yīng)用場(chǎng)景:將系統(tǒng)中的文件發(fā)送到遠(yuǎn)端(該流程涉及:磁盤上文件 ——> 內(nèi)存(字節(jié)數(shù)組) ——> 傳輸給用戶/網(wǎng)絡(luò))來(lái)詳細(xì)展開(kāi)傳統(tǒng)I/O操作和通過(guò)零拷貝來(lái)實(shí)現(xiàn)的I/O操作础淤。
傳統(tǒng)I/O
① 發(fā)出read系統(tǒng)調(diào)用:導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換)。通過(guò)DMA引擎將文件中的數(shù)據(jù)從磁盤上讀取到內(nèi)核空間緩沖區(qū)(第一次拷貝: hard drive ——> kernel buffer)哨苛。
② 將內(nèi)核空間緩沖區(qū)的數(shù)據(jù)拷貝到用戶空間緩沖區(qū)(第二次拷貝: kernel buffer ——> user buffer)鸽凶,然后read系統(tǒng)調(diào)用返回。而系統(tǒng)調(diào)用的返回又會(huì)導(dǎo)致一次內(nèi)核空間到用戶空間的上下文切換(第二次上下文切換)移国。
③ 發(fā)出write系統(tǒng)調(diào)用:導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第三次上下文切換)吱瘩。將用戶空間緩沖區(qū)中的數(shù)據(jù)拷貝到內(nèi)核空間中與socket相關(guān)聯(lián)的緩沖區(qū)中(即道伟,第②步中從內(nèi)核空間緩沖區(qū)拷貝而來(lái)的數(shù)據(jù)原封不動(dòng)的再次拷貝到內(nèi)核空間的socket緩沖區(qū)中迹缀。)(第三次拷貝: user buffer ——> socket buffer)。
④ write系統(tǒng)調(diào)用返回蜜徽,導(dǎo)致內(nèi)核空間到用戶空間的再次上下文切換(第四次上下文切換)祝懂。通過(guò)DMA引擎將內(nèi)核緩沖區(qū)中的數(shù)據(jù)傳遞到協(xié)議引擎(第四次拷貝: socket buffer ——> protocol engine)训枢,這次拷貝是一個(gè)獨(dú)立且異步的過(guò)程瑟蜈。
Q:你可能會(huì)問(wèn)獨(dú)立和異步這是什么意思荐绝?難道是調(diào)用會(huì)在數(shù)據(jù)被傳輸前返回故源?
A:事實(shí)上調(diào)用的返回并不保證數(shù)據(jù)被傳輸誉结;它甚至不保證傳輸?shù)拈_(kāi)始。它只是意味著將我們要發(fā)送的數(shù)據(jù)放入到了一個(gè)待發(fā)送的隊(duì)列中圣贸,在我們之前可能有許多數(shù)據(jù)包在排隊(duì)限次。除非驅(qū)動(dòng)器或硬件實(shí)現(xiàn)優(yōu)先級(jí)環(huán)或隊(duì)列,否則數(shù)據(jù)是以先進(jìn)先出的方式傳輸?shù)摹?/p>
總的來(lái)說(shuō)摩梧,傳統(tǒng)的I/O操作進(jìn)行了4次用戶空間與內(nèi)核空間的上下文切換物延,以及4次數(shù)據(jù)拷貝。其中4次數(shù)據(jù)拷貝中包括了2次DMA拷貝和2次CPU拷貝仅父。
Q: 傳統(tǒng)I/O模式為什么將數(shù)據(jù)從磁盤讀取到內(nèi)核空間緩沖區(qū)叛薯,然后再將數(shù)據(jù)從內(nèi)核空間緩沖區(qū)拷貝到用戶空間緩沖區(qū)了?為什么不直接將數(shù)據(jù)從磁盤讀取到用戶空間緩沖區(qū)就好笙纤?
A: 傳統(tǒng)I/O模式之所以將數(shù)據(jù)從磁盤讀取到內(nèi)核空間緩沖區(qū)而不是直接讀取到用戶空間緩沖區(qū)耗溜,是為了減少磁盤I/O操作以此來(lái)提高性能。因?yàn)镺S會(huì)根據(jù)局部性原理在一次read()系統(tǒng)調(diào)用的時(shí)候預(yù)讀取更多的文件數(shù)據(jù)到內(nèi)核空間緩沖區(qū)中省容,這樣當(dāng)下一次read()系統(tǒng)調(diào)用的時(shí)候發(fā)現(xiàn)要讀取的數(shù)據(jù)已經(jīng)存在于內(nèi)核空間緩沖區(qū)中的時(shí)候只要直接拷貝數(shù)據(jù)到用戶空間緩沖區(qū)中即可抖拴,無(wú)需再進(jìn)行一次低效的磁盤I/O操作(注意:磁盤I/O操作的速度比直接訪問(wèn)內(nèi)存慢了好幾個(gè)數(shù)量級(jí))。
Q: 既然系統(tǒng)內(nèi)核緩沖區(qū)能夠減少磁盤I/O操作腥椒,那么我們經(jīng)常使用的BufferedInputStream緩沖區(qū)又是用來(lái)干啥的城舞?
A: BufferedInputStream的作用是會(huì)根據(jù)情況自動(dòng)為我們預(yù)取更多的數(shù)據(jù)到它自己維護(hù)的一個(gè)內(nèi)部字節(jié)數(shù)據(jù)緩沖區(qū)中,這樣做能夠減少系統(tǒng)調(diào)用的次數(shù)以此來(lái)提供性能寞酿。
總的來(lái)說(shuō)內(nèi)核空間緩沖區(qū)的一大用處是為了減少磁盤I/O操作家夺,因?yàn)樗鼤?huì)從磁盤中預(yù)讀更多的數(shù)據(jù)到緩沖區(qū)中。而BufferedInputStream的用處是減少“系統(tǒng)調(diào)用”伐弹。
DMA
DMA(Direct Memory Access) ———— 直接內(nèi)存訪問(wèn) :DMA是允許外設(shè)組件將I/O數(shù)據(jù)直接傳送到主存儲(chǔ)器中并且傳輸不需要CPU的參與拉馋,以此將CPU解放出來(lái)去完成其他的事情。
而用戶空間與內(nèi)核空間之間的數(shù)據(jù)傳輸并沒(méi)有類似DMA這種可以不需要CPU參與的傳輸工具惨好,因此用戶空間與內(nèi)核空間之間的數(shù)據(jù)傳輸是需要CPU全程參與的煌茴。所有也就有了通過(guò)零拷貝技術(shù)來(lái)減少和避免不必要的CPU數(shù)據(jù)拷貝過(guò)程。
通過(guò)sendfile實(shí)現(xiàn)的零拷貝I/O
① 發(fā)出sendfile系統(tǒng)調(diào)用日川,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換)蔓腐。通過(guò)DMA引擎將磁盤文件中的內(nèi)容拷貝到內(nèi)核空間緩沖區(qū)中(第一次拷貝: hard drive ——> kernel buffer)。然后再將數(shù)據(jù)從內(nèi)核空間緩沖區(qū)拷貝到內(nèi)核中與socket相關(guān)的緩沖區(qū)中(第二次拷貝: kernel buffer ——> socket buffer)龄句。
② sendfile系統(tǒng)調(diào)用返回回论,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換(第二次上下文切換)。通過(guò)DMA引擎將內(nèi)核空間socket緩沖區(qū)中的數(shù)據(jù)傳遞到協(xié)議引擎(第三次拷貝: socket buffer ——> protocol engine)
總的來(lái)說(shuō)分歇,通過(guò)sendfile實(shí)現(xiàn)的零拷貝I/O只使用了2次用戶空間與內(nèi)核空間的上下文切換傀蓉,以及3次數(shù)據(jù)的拷貝。其中3次數(shù)據(jù)拷貝中包括了2次DMA拷貝和1次CPU拷貝职抡。
Q:但通過(guò)是這里還是存在著一次CPU拷貝操作葬燎,即,kernel buffer ——> socket buffer。是否有辦法將該拷貝操作也取消掉了谱净?
A:有的窑邦。但這需要底層操作系統(tǒng)的支持。從Linux 2.4版本開(kāi)始壕探,操作系統(tǒng)底層提供了scatter/gather這種DMA的方式來(lái)從內(nèi)核空間緩沖區(qū)中將數(shù)據(jù)直接讀取到協(xié)議引擎中奕翔,而無(wú)需將內(nèi)核空間緩沖區(qū)中的數(shù)據(jù)再拷貝一份到內(nèi)核空間socket相關(guān)聯(lián)的緩沖區(qū)中。
帶有DMA收集拷貝功能的sendfile實(shí)現(xiàn)的I/O
從Linux 2.4版本開(kāi)始浩蓉,操作系統(tǒng)底層提供了帶有scatter/gather的DMA來(lái)從內(nèi)核空間緩沖區(qū)中將數(shù)據(jù)讀取到協(xié)議引擎中派继。這樣一來(lái)待傳輸?shù)臄?shù)據(jù)可以分散在存儲(chǔ)的不同位置上,而不需要在連續(xù)存儲(chǔ)中存放捻艳。那么從文件中讀出的數(shù)據(jù)就根本不需要被拷貝到socket緩沖區(qū)中去驾窟,只是需要將緩沖區(qū)描述符添加到socket緩沖區(qū)中去,DMA收集操作會(huì)根據(jù)緩沖區(qū)描述符中的信息將內(nèi)核空間中的數(shù)據(jù)直接拷貝到協(xié)議引擎中认轨。
① 發(fā)出sendfile系統(tǒng)調(diào)用绅络,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換)。通過(guò)DMA引擎將磁盤文件中的內(nèi)容拷貝到內(nèi)核空間緩沖區(qū)中(第一次拷貝: hard drive ——> kernel buffer)嘁字。
② 沒(méi)有數(shù)據(jù)拷貝到socket緩沖區(qū)恩急。取而代之的是只有相應(yīng)的描述符信息會(huì)被拷貝到相應(yīng)的socket緩沖區(qū)當(dāng)中。該描述符包含了兩方面的信息:a)kernel buffer的內(nèi)存地址纪蜒;b)kernel buffer的偏移量衷恭。
③ sendfile系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換(第二次上下文切換)纯续。DMA gather copy根據(jù)socket緩沖區(qū)中描述符提供的位置和偏移量信息直接將內(nèi)核空間緩沖區(qū)中的數(shù)據(jù)拷貝到協(xié)議引擎上(第二次拷貝: kernel buffer ——> protocol engine)随珠,這樣就避免了最后一次CPU數(shù)據(jù)拷貝。
總的來(lái)說(shuō)猬错,帶有DMA收集拷貝功能的sendfile實(shí)現(xiàn)的I/O只使用了2次用戶空間與內(nèi)核空間的上下文切換窗看,以及2次數(shù)據(jù)的拷貝,而且這2次的數(shù)據(jù)拷貝都是非CPU拷貝倦炒。這樣一來(lái)我們就實(shí)現(xiàn)了最理想的零拷貝I/O傳輸了显沈,不需要任何一次的CPU拷貝,以及最少的上下文切換逢唤。
關(guān)于sendfile:
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
在linux2.6.33版本之前 sendfile指支持文件到套接字之間傳輸數(shù)據(jù)拉讯,即in_fd相當(dāng)于一個(gè)支持mmap的文件,out_fd必須是一個(gè)socket智玻。但從linux2.6.33版本開(kāi)始遂唧,out_fd可以是任意類型文件描述符芙代。所以從linux2.6.33版本開(kāi)始sendfile可以支持“文件到文件”和“文件到套接字”之間的數(shù)據(jù)傳輸吊奢。
"傳統(tǒng)I/O” VS “sendfile零拷貝I/O”
- 傳統(tǒng)I/O通過(guò)兩條系統(tǒng)指令read、write來(lái)完成數(shù)據(jù)的讀取和傳輸操作,以至于產(chǎn)生了4次用戶空間與內(nèi)核空間的上下文切換的開(kāi)銷页滚;而sendfile只使用了一條指令就完成了數(shù)據(jù)的讀寫操作召边,所以只產(chǎn)生了2次用戶空間與內(nèi)核空間的上下文切換。
- 傳統(tǒng)I/O產(chǎn)生了2次無(wú)用的CPU拷貝裹驰,即內(nèi)核空間緩存中數(shù)據(jù)與用戶空間緩沖區(qū)間數(shù)據(jù)的拷貝隧熙;而sendfile最多只產(chǎn)出了一次CPU拷貝,即內(nèi)核空間內(nèi)之間的數(shù)據(jù)拷貝幻林,甚至在底層操作體系支持的情況下贞盯,sendfile可以實(shí)現(xiàn)零CPU拷貝的I/O。
- 因傳統(tǒng)I/O用戶空間緩沖區(qū)中存有數(shù)據(jù)沪饺,因此應(yīng)用程序能夠?qū)Υ藬?shù)據(jù)進(jìn)行修改等操作躏敢;而sendfile零拷貝消除了所有內(nèi)核空間緩沖區(qū)與用戶空間緩沖區(qū)之間的數(shù)據(jù)拷貝過(guò)程,因此sendfile零拷貝I/O的實(shí)現(xiàn)是完成在內(nèi)核空間中完成的整葡,這對(duì)于應(yīng)用程序來(lái)說(shuō)就無(wú)法對(duì)數(shù)據(jù)進(jìn)行操作了件余。
Q:對(duì)于上面的第三點(diǎn),如果我們需要對(duì)數(shù)據(jù)進(jìn)行操作該怎么辦了遭居?
A:Linux提供了mmap零拷貝來(lái)實(shí)現(xiàn)我們的需求啼器。
通過(guò)mmap實(shí)現(xiàn)的零拷貝I/O
mmap(內(nèi)存映射)是一個(gè)比sendfile昂貴但優(yōu)于傳統(tǒng)I/O的方法。
① 發(fā)出mmap系統(tǒng)調(diào)用俱萍,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換)端壳。通過(guò)DMA引擎將磁盤文件中的內(nèi)容拷貝到內(nèi)核空間緩沖區(qū)中(第一次拷貝: hard drive ——> kernel buffer)。
② mmap系統(tǒng)調(diào)用返回枪蘑,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換(第二次上下文切換)更哄。接著用戶空間和內(nèi)核空間共享這個(gè)緩沖區(qū),而不需要將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間腥寇。因?yàn)橛脩艨臻g和內(nèi)核空間共享了這個(gè)緩沖區(qū)數(shù)據(jù)成翩,所以用戶空間就可以像在操作自己緩沖區(qū)中數(shù)據(jù)一般操作這個(gè)由內(nèi)核空間共享的緩沖區(qū)數(shù)據(jù)。
③ 發(fā)出write系統(tǒng)調(diào)用赦役,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第三次上下文切換)麻敌。將數(shù)據(jù)從內(nèi)核空間緩沖區(qū)拷貝到內(nèi)核空間socket相關(guān)聯(lián)的緩沖區(qū)(第二次拷貝: kernel buffer ——> socket buffer)。
④ write系統(tǒng)調(diào)用返回掂摔,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換(第四次上下文切換)术羔。通過(guò)DMA引擎將內(nèi)核空間socket緩沖區(qū)中的數(shù)據(jù)傳遞到協(xié)議引擎(第三次拷貝: socket buffer ——> protocol engine)
總的來(lái)說(shuō),通過(guò)mmap實(shí)現(xiàn)的零拷貝I/O進(jìn)行了4次用戶空間與內(nèi)核空間的上下文切換乙漓,以及3次數(shù)據(jù)拷貝级历。其中3次數(shù)據(jù)拷貝中包括了2次DMA拷貝和1次CPU拷貝。
FileChannel與零拷貝
FileChannel中大量使用了我們上面所提及的零拷貝技術(shù)叭披。
FileChannel的map方法會(huì)返回一個(gè)MappedByteBuffer寥殖。MappedByteBuffer是一個(gè)直接字節(jié)緩沖器,該緩沖器的內(nèi)存是一個(gè)文件的內(nèi)存映射區(qū)域。map方法底層是通過(guò)mmap實(shí)現(xiàn)的嚼贡,因此將文件內(nèi)存從磁盤讀取到內(nèi)核緩沖區(qū)后熏纯,用戶空間和內(nèi)核空間共享該緩沖區(qū)。
MappedByteBuffer內(nèi)存映射文件是一種允許Java程序直接從內(nèi)存訪問(wèn)的一種特殊的文件粤策。我們可以將整個(gè)文件或者整個(gè)文件的一部分映射到內(nèi)存當(dāng)中樟澜,那么接下來(lái)是由操作系統(tǒng)來(lái)進(jìn)行相關(guān)的頁(yè)面請(qǐng)求并將內(nèi)存的修改寫入到文件當(dāng)中。我們的應(yīng)用程序只需要處理內(nèi)存的數(shù)據(jù)叮盘,這樣可以實(shí)現(xiàn)非常迅速的I/O操作秩贰。
FileChannel map的三種模式
- 只讀模式
/**
* Mode for a read-only mapping.
*/
public static final MapMode READ_ONLY = new MapMode("READ_ONLY");
只讀模式來(lái)說(shuō),如果程序試圖進(jìn)行寫操作柔吼,則會(huì)拋出ReadOnlyBufferException異常
- 讀寫模式
/**
* Mode for a read/write mapping.
*/
public static final MapMode READ_WRITE = new MapMode("READ_WRITE");
讀寫模式表明萍膛,對(duì)結(jié)果對(duì)緩沖區(qū)所做的修改將最終廣播到文件。但這個(gè)修改可能會(huì)也可能不會(huì)被其他映射了相同文件程序可見(jiàn)嚷堡。
- 專用模式
/**
* Mode for a private (copy-on-write) mapping.
*/
public static final MapMode PRIVATE = new MapMode("PRIVATE");
私有模式來(lái)說(shuō)蝗罗,對(duì)結(jié)果緩沖區(qū)的修改將不會(huì)被廣播到文件并且也不會(huì)對(duì)其他映射了相同文件的程序可見(jiàn)。取而代之的是蝌戒,它將導(dǎo)致被修改部分緩沖區(qū)獨(dú)自拷貝一份到用戶空間串塑。這便是OS的“copy on write”原則。
FileChannel的transferTo北苟、transferFrom
如果操作系統(tǒng)底層支持的話transferTo桩匪、transferFrom也會(huì)使用相關(guān)的零拷貝技術(shù)來(lái)實(shí)現(xiàn)數(shù)據(jù)的傳輸。所以友鼻,這里是否使用零拷貝必須依賴于底層的系統(tǒng)實(shí)現(xiàn)傻昙。
后記
本文是通過(guò)視頻學(xué)習(xí)以及大量資料查詢后對(duì)零拷貝機(jī)制進(jìn)行的一個(gè)非常膚淺的知識(shí)梳理,至少個(gè)人是這么覺(jué)得彩扔。通過(guò)這次的學(xué)習(xí)妆档,對(duì)Linux操作系統(tǒng)又多了一丟丟的了解,也希望在之后的學(xué)習(xí)中能對(duì)Linux系統(tǒng)有更近一步的深入的理解虫碉。非常歡迎大家對(duì)文中的不足和錯(cuò)誤進(jìn)行指點(diǎn)~
參考:
It's all about buffers: zero-copy, mmap and Java NIO
Zero Copy I: User-Mode Perspective
Linux Programmer's Manual SENDFILE(2)
Linux 中的零拷貝技術(shù)贾惦,第 1 部分
Linux 中的零拷貝技術(shù),第 2 部分
圣思園《精通并發(fā)與Netty》