標籤:des style blog http color java 使用 os
介紹
java 的zero copy多在網路應用程式中使用。Java的libaries在linux和unix中支援zero copy,關鍵的api是java.nio.channel.FileChannel的transferTo(),transferFrom()方法。我們可以用這兩個方法來把bytes直接從調用它的channel傳輸到另一個writable byte channel,中間不會使data經過應用程式,以便提高資料轉移的效率。
傳統的資料複製方式及涉及到的環境切換:
通過網路把一個檔案傳輸給另一個程式,在OS的內部,這個copy操作要經曆四次user mode和kernel mode之間的上下文切換,甚至連資料都被拷貝了四次,如:
具體步驟如下:
- read() 調用導致一次從user mode到kernel mode的環境切換。在內部調用了sys_read() 來從檔案中讀取data。第一次copy由DMA (direct memory access)egine完成,將檔案內容從disk讀出,儲存在kernel的buffer中。
- 然後請求的資料被copy到user buffer中,此時read()成功返回。調用的返回觸發了第二次context switch: 從kernel到user。至此,資料存放區在user的buffer中。
- send() Socket call 帶來了第三次context switch,這次是從user mode到kernel mode。同時,也發生了第三次copy:把data放到了kernel adress space中。當然,這次的kernel buffer和第一步的buffer是不同的buffer。
- 最終 send() system call 返回了,同時也造成了第四次context switch。同時第四次copy發生,DMA egine將data從kernel buffer拷貝到protocol engine中。第四次copy是獨立而且非同步。
資料轉移(data transfer): zero copy方式及涉及的上下文轉換
在linux 2.4及以上版本的核心中(如linux 6或centos 6以上的版本),開發人員修改了socket buffer descriptor,使網卡支援 gather operation,通過kernel進一步減少資料的拷貝操作。這個方法不僅減少了context switch,還消除了和CPU有關的資料拷貝。user層面的使用方法沒有變,但是內部原理卻發生了變化:
- transferTo()方法使得檔案內容被copy到了kernel buffer,這一動作由DMA engine完成。
- 沒有data被copy到socket buffer。取而代之的是socket buffer被追加了一些descriptor的資訊,包括data的位置和長度。然後DMA engine直接把data從kernel buffer傳輸到protocol engine,這樣就消除了唯一的一次需要佔用CPU的拷貝操作。
代碼範例:
展示通過網路把一個檔案從client傳到server的過程
package zerocopy;import java.io.IOException;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.ByteBuffer;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;public class TransferToServer {ServerSocketChannel listener = null;protected void mySetup() {InetSocketAddress listenAddr = new InetSocketAddress(9026);try {listener = ServerSocketChannel.open();ServerSocket ss = listener.socket();ss.setReuseAddress(true);ss.bind(listenAddr);System.out.println("監聽的連接埠:" + listenAddr.toString());} catch (IOException e) {System.out.println("連接埠綁定失敗 : "+ listenAddr.toString() + " 連接埠可能已經被使用,出錯原因: "+ e.getMessage());e.printStackTrace();}}public static void main(String[] args) {TransferToServer dns = new TransferToServer();dns.mySetup();dns.readData();}private void readData() {ByteBuffer dst = ByteBuffer.allocate(4096);try {while (true) {SocketChannel conn = listener.accept();System.out.println("建立的串連: " + conn);conn.configureBlocking(true);int nread = 0;while (nread != -1) {try {nread = conn.read(dst);} catch (IOException e) {e.printStackTrace();nread = -1;}dst.rewind();}}} catch (IOException e) {e.printStackTrace();}}}
package zerocopy;import java.io.FileInputStream;import java.io.IOException;import java.net.InetSocketAddress;import java.net.SocketAddress;import java.nio.channels.FileChannel;import java.nio.channels.SocketChannel;public class TransferToClient {public static void main(String[] args) throws IOException {TransferToClient sfc = new TransferToClient();sfc.testSendfile();}public void testSendfile() throws IOException {String host = "localhost";int port = 9026;SocketAddress sad = new InetSocketAddress(host, port);SocketChannel sc = SocketChannel.open();sc.connect(sad);sc.configureBlocking(true);String fname = "src/main/java/zerocopy/test.data";FileChannel fc = new FileInputStream(fname).getChannel();long start = System.nanoTime();long nsent = 0, curnset = 0;curnset = fc.transferTo(0, fc.size(), sc);System.out.println("發送的總位元組數:" + curnset+ " 耗時(ns):"+ (System.nanoTime() - start));try {sc.close();fc.close();} catch (IOException e) {System.out.println(e);}}}
其它zero copy的用法
package zerocopy;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.channels.FileChannel;public class ZerocopyDemo {@SuppressWarnings("resource")public static void transferToDemo(String from, String to) throws IOException {FileChannel fromChannel = new RandomAccessFile(from, "rw").getChannel();FileChannel toChannel = new RandomAccessFile(to, "rw").getChannel();long position = 0;long count = fromChannel.size();fromChannel.transferTo(position, count, toChannel);fromChannel.close();toChannel.close();}@SuppressWarnings("resource")public static void transferFromDemo(String from, String to)throws IOException {FileChannel fromChannel = new FileInputStream(from).getChannel();FileChannel toChannel = new FileOutputStream(to).getChannel();long position = 0;long count = fromChannel.size();toChannel.transferFrom(fromChannel, position, count);fromChannel.close();toChannel.close();}public static void main(String[] args) throws IOException {String from="src/main/java/zerocopy/1.data";String to="src/main/java/zerocopy/2.data";//transferToDemo(from,to);transferFromDemo(from,to);}} 參考
https://www.ibm.com/developerworks/linux/library/j-zerocopy/
http://blog.csdn.net/flyingqr/article/details/6942645