http://my.oschina.net/cloudcoder/blog/299944
介绍
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的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
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();
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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的用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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