精华内容
下载资源
问答
  • Linux 零拷贝技术

    千次阅读 2017-03-25 16:28:20
    零拷贝(zero-copy)技术可以减少数据拷贝和共享总线操作的次数,消除...采用零拷贝技术,通过减少数据拷贝次数,简化协议处理的层次,在应用和网络间提供更快的数据通路,可以有效地降低通信延迟,增加网络吞吐率。

    目录


    简介

    零拷贝(zero-copy)技术可以减少数据拷贝和共享总线操作的次数,消除通信数据在存储器之间不必要的中间拷贝过程,有效地提高通信效率,是设计高速接口通道、实现高速服务器和路由器的关键技术之一。数据拷贝受制于传统的操作系统或通信协议,限制了通信性能。采用零拷贝技术,通过减少数据拷贝次数,简化协议处理的层次,在应用和网络间提供更快的数据通路,可以有效地降低通信延迟,增加网络吞吐率。


    抛砖引玉

    为了更好地理解问题的解决方案,我们首先需要了解问题本身。 让我们看看一个网络服务器通过网络将存储在文件中的数据提供给客户端这个简单过程中涉及的内容。 以下是一些示例代码:

    read(file, tmp_buf, len);
    write(socket, tmp_buf, len);

    看起来只有两次系统操作,但数据已经被复制了4次,并且执行了多次用户/内核空间的上下文切换。如下图,我们了解一下这些系统调用所涉及的处理过程,顶部显示上下文切换,底部显示拷贝操作。

    两个系统调用中的拷贝过程

    两个系统调用中的拷贝过程

    第一步:read系统调用导致上下文从用户模式切换到内核模式。第一个副本由DMA引擎执行,DMA引擎从磁盘读取文件内容并将它们存储到内核地址空间缓冲区中。

    第二步:将数据从内核缓冲区复制到用户缓冲区,并且read系统调用返回。read调用返回导致上下文从内核切换回用户模式。现在数据存储在用户地址空间缓冲区中。

    第三步:write系统调用导致上下文从用户模式切换到内核模式。执行第三次复制,以再次将数据放入内核地址空间缓冲区。这个时候,数据被放入一个不同的缓冲区,一个与sockets相关联的缓冲区。

    第四步:write系统调用返回,执行第四个上下文切换,将数据从内核缓冲区传递到协议引擎。

    正如现在所看到的,很多数据复制操作并不是真正需要的。可以消除一些复制操作以减少开销并提高性能。需要引入具有非常先进的硬件,绕过主存储器,将数据直接传输到另一个设备,这引入了一些复杂性,同时并不是所有硬件都支持。


    mmap

    mmap:一个文件或者其它对象映射进内存。

    消除复制的一种方法是调用mmap来替代read。 例如:

    tmp_buf = mmap(file, len);
    write(socket, tmp_buf, len);

    mmap

    说明

    第一步:mmap系统调用导致DMA引擎将文件内容复制到内核缓冲区中。然后与用户进程共享缓冲区,而不在内核和用户存储器空间之间执行任何复制。

    第二步:write系统调用导致内核将数据从原始内核缓冲区复制到与套接字相关联的内核缓冲区中。

    第三步:当DMA引擎将数据从内核套接字缓冲区传递到协议引擎时,发生第三次复制。

    通过使用mmap而不是read,已经削减了一半的内核复制的数据量。当传输大量数据时,这产生相当好的结果。然而,这种改进是有代价的,使用mmap + write方法时存在隐藏的缺陷。当内存映射一个文件,然后调用write,而另一个进程截断同一个文件,你会陷入其中之一。你的写系统调用将由总线错误信号SIGBUS中断,因为你执行了错误的存储器访问。该信号的默认行为是杀死进程和转储核心,这不是网络服务器最理想的操作。有两种方法来解决这个问题。

    第一种方法是为SIGBUS信号安装一个信号处理程序,然后在处理程序中调用return。

    第二个解决方案涉及内核中的文件租赁(在Microsoft Windows中称为“机会锁定”)。


    sendfile

    在内核版本2.1中,引入了sendfile系统调用,以简化网络上和两个本地文件之间的数据传输。 sendfile的引入不仅减少了数据复制,还减少了上下文切换。 使用如下:

    sendfile(socket, file, len);

    sendfile

    说明

    第一步:sendfile系统调用使文件内容被DMA引擎复制到内核缓冲区中。 然后内核将数据复制到与套接字相关联的内核缓冲区中。

    第二步:当DMA引擎将数据从内核套接字缓冲区传递到协议引擎时,发生第三次复制。


    支持聚集操作的硬件

    到目前为止,我们已经能够避免一些内核复制操作,但是我们仍然有一次内核复制操作。 这也可以避免吗? 当然,这需要硬件的一点帮助。 为了消除内核所做的所有数据复制,我们需要一个支持聚集操作的网络接口。 这仅仅意味着等待传输的数据不需要在连续的内存空间中, 这些数据可以分散在存储器的各个位置。 在内核版本2.4中,修改了套接字缓冲区描述符以适应这些要求——在Linux下称为零拷贝。 这种方法不仅减少了多个上下文切换,还完全消除了处理器的数据复制操作。

    支持聚集操作的硬件从内存的多个位置获取数据,消除内存拷贝

    支持聚集操作的硬件从内存的多个位置获取数据,消除内存拷贝

    第一步:sendfile系统调用使文件内容被DMA引擎复制到内核缓冲区中。

    第二步:没有数据被直接复制到套接字缓冲区。相反,只有描述符(关于数据的位置和长度的信息)附加到Socket缓冲区。DMA引擎将数据直接从内核缓冲区传递到协议引擎,从而消除最后剩下的一次内存拷贝。


    在Java中的应用

    可使用Java NIO中的FileChannel.transferTo()方法实现零拷贝。

    transferTo() 方法将数据从文件通道传输到了给定的可写字节通道。在内部,它依赖底层操作系统对零拷贝的支持;在 UNIX 和各种 Linux 系统中,此调用被传递到 sendfile() 系统调用中。


    参考

    展开全文
  • 零拷贝技术

    千次阅读 2014-05-05 20:41:13
    如今几乎每个人都听说过Linux中所谓的"零拷贝"特性,然而我经常碰到没有充分理解这个问题的人们。因此,我决定写一些文章略微深入的讲述这个问题,希望能将这个有用的特性解释清楚。在本文中,将从用户空间应用程序...
    如今几乎每个人都听说过Linux中所谓的"零拷贝"特性,然而我经常碰到没有充分理解这个问题的人们。因此,我决定写一些文章略微深入的讲述这个问题,希望能将这个有用的特性解释清楚。在本文中,将从用户空间应用程序的角度来阐述这个问题,因此有意忽略了复杂的内核实现。

    什么是”零拷贝”

    为了更好的理解问题的解决法,我们首先需要理解问题本身。首先我们以一个网络服务守护进程为例,考虑它在将存储在文件中的信息通过网络传送给客户这样的简单过程中,所涉及的操作。下面是其中的部分简单代阿:

    read(file, tmp_buf, len);
    write(socket, tmp_buf, len);

    看起来不能更简单了。你也许认为执行这两个系统调用并未产生多少开销。实际上,这简直错的一塌糊涂。在执行这两个系统调用的过程中,目标数据至少被复制了4次,同时发生了同样多次数的用户/内核空间的切换(实际上该过程远比此处描述的要复杂,但是我希望以简单的方式描述之,以更好的理解本文的主题)。


    为了更好的理解这两句代码所涉及的操作,请看图1。图的上半部展示了上下文切换,而下半部展示了复制操作。
    图1. Copying in Two Sample System Calls

    步骤一:系统调用read导致了从用户空间到内核空间的上下文切换。DMA模块从磁盘中读取文件内容,并将其存储在内核空间的缓冲区内,完成了第1次复制。

    步骤二:数据从内核空间缓冲区复制到用户空间缓冲区,之后系统调用read返回,这导致了从内核空间向用户空间的上下文切换。此时,需要的数据已存放在指定的用户空间缓冲区内(参数tmp_buf),程序可以继续下面的操作。

    步骤三:系统调用write导致从用户空间到内核空间的上下文切换。数据从用户空间缓冲区被再次复制到内核空间缓冲区,完成了第3次复制。不过,这次数据存放在内核空间中与使用的socket相关的特定缓冲区中,而不是步骤一中的缓冲区。

    步骤四:系统调用返回,导致了第4次上下文切换。第4次复制在DMA模块将数据从内核空间缓冲区传递至协议引擎的时候发生,这与我们的代码的执行是独立且异步发生的。你可能会疑惑:“为何要说是独立、异步?难道不是在write系统调用返回前数据已经被传送了?write系统调用的返回,并不意味着传输成功——它甚至无法保证传输的开始。调用的返回,只是表明以太网驱动程序在其传输队列中有空位,并已经接受我们的数据用于传输。可能有众多的数据排在我们的数据之前。除非驱动程序或硬件采用优先级队列的方法,各组数据是依照FIFO的次序被传输的(图1中叉状的DMA copy表明这最后一次复制可以被延后)。

    正如你所看到的,上面的过程中存在很多的数据冗余。某些冗余可以被消除,以减少开销、提高性能。作为一名驱动程序开发人员,我的工作围绕着拥有先进特性的硬件展开。某些硬件支持完全绕开内存,将数据直接传送给其他设备的特性。这一特性消除了系统内存中的数据副本,因此是一种很好的选择,但并不是所有的硬件都支持。此外,来自于硬盘的数据必须重新打包(地址连续)才能用于网络传输,这也引入了某些复杂性。为了减少开销,我们可以从消除内核缓冲区与用户缓冲区之间的复制入手。

    消除复制的一种方法是将read系统调用,改为mmap系统调用,例如:

    tmp_buf = mmap(file, len);
    write(socket, tmp_buf, len);

    为了更好的理解这其中设计的操作,请看图2。上下文切换部分与图1保持一致。

    图2. Calling mmap

    步骤一:mmap系统调用导致文件的内容通过DMA模块被复制到内核缓冲区中,该缓冲区之后与用户进程共享,这样就内核缓冲区与用户缓冲区之间的复制就不会发生。

    步骤二:write系统调用导致内核将数据从内核缓冲区复制到与socket相关联的内核缓冲区中。

    步骤三:DMA模块将数据由socket的缓冲区传递给协议引擎时,第3次复制发生。

    通过调用mmap而不是read,我们已经将内核需要执行的复制操作减半。当有大量数据要进行传输是,这将有相当良好的效果。然而,性能的改进需要付出代价的;是用mmap与write这种组合方法,存在着一些隐藏的陷阱。例如,考虑一下在内存中对文件进行映射后调用write,与此同时另外一个进程将同一文件截断的情形。此时write系统调用会被进程接收到的SIGBUS信号中断,因为当前进程访问了非法内存地址。对SIGBUS信号的默认处理是杀死当前进程并生成dump core文件——而这对于网络服务器程序而言不是最期望的操作。

    有两种方式可用于解决该问题:

    第一种方式是为SIGBUS信号设置信号处理程序,并在处理程序中简单的执行return语句。在这样处理方式下,write系统调用返回被信号中断前已写的字节数,并将errno全局变量设置为成功。必须指出,这并不是个好的解决方式——治标不治本。由于收到SIGBUS信号意味着进程发生了严重错误,我不鼓励采取这种解决方式。

    第二种方式应用了文件租借(在Microsoft Windows系统中被称为“机会锁”)。这才是解劝前面问题的正确方式。通过对文件描述符执行租借,可以同内核就某个特定文件达成租约。从内核可以获得读/写租约。当另外一个进程试图将你正在传输的文件截断时,内核会向你的进程发送实时信号——RT_SIGNAL_LEASE。该信号通知你的进程,内核即将终止在该文件上你曾获得的租约。这样,在write调用访问非法内存地址、并被随后接收到的SIGBUS信号杀死之前,write系统调用就被RT_SIGNAL_LEASE信号中断了。write的返回值是在被中断前已写的字节数,全局变量errno设置为成功。下面是一段展示如何从内核获得租约的示例代码。

    if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
    perror("kernel lease set signal");
    return -1;
    }

    /* l_type can be F_RDLCK F_WRLCK */
    if(fcntl(fd, F_SETLEASE, l_type)){
    perror("kernel lease set type");
    return -1;
    }

    Sendfile

    sendfile系统调用在内核版本2.1中被引入,目的是简化通过网络在两个本地文件之间进行的数据传输过程。sendfile系统调用的引入,不仅减少了数据复制,还减少了上下文切换的次数。使用方法如下:

    sendfile(socket, file, len);

    为了更好的理解所涉及的操作,请看图3

    图3. Replacing Read and Write with Sendfile

    步骤一:sendfile系统调用导致文件内容通过DMA模块被复制到某个内核缓冲区,之后再被复制到与socket相关联的缓冲区内。

    步骤二:当DMA模块将位于socket相关联缓冲区中的数据传递给协议引擎时,执行第3次复制。

    你可能会在想,我们在调用sendfile发送数据的期间,如果另外一个进程将文件截断的话,会发生什么事情?如果进程没有为SIGBUS注册任何信号处理函数的话,sendfile系统调用返回被信号中断前已发送的字节数,并将全局变量errno置为成功。

    然而,如果在调用sendfile之前,从内核获得了文件租约,那么类似的,在sendfile调用返回前会收到RT_SIGNAL_LEASE。

    到此为止,我们已经能够避免内核进行多次复制,然而我们还存在一分多余的副本。这份副本也可以消除吗?当然,在硬件提供的一些帮助下是可以的。为了消除内核产生的素有数据冗余,需要网络适配器支持聚合操作特性。该特性意味着待发送的数据不要求存放在地址连续的内存空间中;相反,可以是分散在各个内存位置。在内核版本2.4中,socket缓冲区描述符结构发生了改动,以适应聚合操作的要求——这就是Linux中所谓的"零拷贝“。这种方式不仅减少了多个上下文切换,而且消除了数据冗余。从用户层应用程序的角度来开,没有发生任何改动,所有代码仍然是类似下面的形式:

    sendfile(socket, file, len);

    为了更好的理解所涉及的操作,请看图4

    Figure 4. Hardware that supports gather can assemble data from multiple memory locations, eliminating another copy.

    步骤一:sendfile系统调用导致文件内容通过DMA模块被复制到内核缓冲区中。

    步骤二:数据并未被复制到socket关联的缓冲区内。取而代之的是,只有记录数据位置和长度的描述符被加入到socket缓冲区中。DMA模块将数据直接从内核缓冲区传递给协议引擎,从而消除了遗留的最后一次复制。

    由于数据实际上仍然由磁盘复制到内存,再由内存复制到发送设备,有人可能会声称这并不是真正的"零拷贝"。然而,从操作系统的角度来看,这就是"零拷贝",因为内核空间内不存在冗余数据。应用"零拷贝"特性,出了避免复制之外,还能获得其他性能优势,例如更少的上下文切换,更少的CPU cache污染以及没有CPU必要计算校验和。

    现在我们明白了什么是"零拷贝",让我们将理论付诸实践,编写一些代码。你可以从www.xalien.org/articles/source/sfl-src.tgz处下载完整的源码。执行"tar -zxvf sfl-src.tgz"将源码解压。运行make命令,编译源码,并创建随机数据文件data.bin

    从头文件开始介绍代码:

    /* sfl.c sendfile example program
    Dragan Stancevic <
    header name function / variable
    -------------------------------------------------*/
    #include <stdio.h> /* printf, perror */
    #include <fcntl.h> /* open */
    #include <unistd.h> /* close */
    #include <errno.h> /* errno */
    #include <string.h> /* memset */
    #include <sys/socket.h> /* socket */
    #include <netinet/in.h> /* sockaddr_in */
    #include <sys/sendfile.h> /* sendfile */
    #include <arpa/inet.h> /* inet_addr */
    #define BUFF_SIZE (10*1024) /* size of the tmp buffer */

    除了基本socket操作所需要的 <sys/socket.h> 和<netinet/in.h>头文件外,我们还需要包含sendfile系统调用的原型定义,这可以在<sys/sendfile.h>头文件中找到。

    服务器标志:

    /* are we sending or receiving */
    if(argv[1][0] == 's') is_server++;
    /* open descriptors */
    sd = socket(PF_INET, SOCK_STREAM, 0);
    if(is_server) fd = open("data.bin", O_RDONLY);

    该程序既能以服务端/发送方,也能以客户端/接收方的身份运行。我们需要检查命令行参数中的一项,然后相应的设置is_server标志。程序中大开了一个地址族为PF_INET的流套接字;作为服务端运行时需要向客户发送数据,因此要打开某个数据文件。由于程序中是用sendfile系统调用来发送数据,因此不需要读取文件内容并存储在程序的缓冲区内。

    接下来是服务器地址:

    /* clear the memory */
    memset(&sa, 0, sizeof(struct sockaddr_in));
    /* initialize structure */
    sa.sin_family = PF_INET;
    sa.sin_port = htons(1033);
    sa.sin_addr.s_addr = inet_addr(argv[2]);


    将服务端地址结构清零后设置协议族、端口和IP地址。服务端的IP地址作为命令行参数传递给程序。端口号硬编码为1033,选择该端口是因为它在要求root权限的端口范围之上。

    下面是服务端的分支代码:

    if(is_server){
    int client; /* new client socket */
    printf("Server binding to [%s]\n", argv[2]);
    if(bind(sd, (struct sockaddr *)&sa,sizeof(sa)) < 0){
    perror("bind");
    exit(errno);
    }

    作为服务端,需要为socket描述符分配一个地址,这是通过系统调用bind完成的,它将服务器地址(sa)分配给socket描述符(sd).

    if(listen(sd,1) < 0){
    perror("listen");
    exit(errno);
    }

    由于使用流套接字,必须对内核声明接受外来连接请求的意愿,并设置连接队列的尺寸。此处将队列长度设为1,但是通常会将该值设的高一些,用于接受已建立的连接。在老版本的内核中,该队列被用于阻止SYN flood攻击。由于listen系统调用之改为设定已建立连接的数量,该特性已被listen调用遗弃。内核参数tcp_max_syn_backlog承担了保护系统不受SYN flood攻击的功能。

    if((client = accept(sd, NULL, NULL)) < 0){
    perror("accept");
    exit(errno);
    }

    accept系统调用从待处理的已连接队列中选取第一个连接请求,为之建立一个新的socket。accept调用的返回值是新建立连接的描述符;新的socket可以用于read、write和poll/select系统调用。

    if((cnt = sendfile(client,fd,&off, BUFF_SIZE)) < 0){
    perror("sendfile");
    exit(errno);
    }

    printf("Server sent %d bytes.\n", cnt);
    close(client);

    在客户socket描述符上已经建立好连接,因此可以开始将数据传输至远端系统——这时通过调用sendfile系统调用来完成。该调用在Linux中的原型为如下形式:

    extern ssize_t
    sendfile (int __out_fd, int __in_fd, off_t *offset, size_t __count) __THROW;

    前两个参数为文件描述符,第三个参数表示sendfile开始传输数据的偏移量。第四个参数是打算传输的字节数。为了sendfile可以使用"零拷贝“特性,网卡需要支持聚合操作,此外还应具备校验和计算能力。如果你的NIC不具备这些特性,仍可以是用sendfile来发送数据,区别是内核在传输前会将所有缓冲区的内容合并。

    移植性问题

    sendfile系统调用的问题之一,总体上来看,是缺少标准化的实现,这与open系统调用类些。sendfile在Linux、Solaris或HP-UX中的实现有很大的不同。这给希望在网络传输代码中利用"零拷贝"的开发者带来了问题。

    这些实现差异中的一点在于Linux提供的sendfile,是定义为用于两个文件描述符之间和文件到socket之间的传输接口。另一方面,HP-UX和Solaris中,sendfile只能用于文件到socket的传输。

    第二点差异,是Linux没有实现向量化传输。Solaris和HP-UX 中的sendfile系统调用包含额外的参数,用于消除为待传输数据添加头部的开销。

    展望

    Linux中“零拷贝”的实现还远未结束,并很可能在不久的未来发生变化。更多的功能将会被添加,例如,现在的sendfile不支持向量化传输,而诸如Samba和Apache这样的服务器不得不是用TCP_COKR标志来执行多个sendfile调用。该标志告知系统还有数据要在下一个sendfile调用中到达。TCP_CORK和TCP_NODELAY不兼容,后者在我们希望为数据添加头部时使用。这也正是一个完美的例子,用于说明支持向量化的sendfile将在那些情况下,消除目前实现所强制产生的多个sendfile调用和延迟。

    当前sendfile一个相当令人不愉快的限制是它无法用户传输大于2GB的文件。如此尺寸大小的文件,在今天并非十分罕见,不得不复制数据是十分令人失望的。由于这种情况下sendfile和mmap都是不可用的,在未来内核版本中提供sendfile64,将会提供很大的帮助。

    结论

    尽管有一些缺点,"零拷贝"sendfile是一个很有用的特性。我希望读者认为本文提供了足够的信息以开始在程序中使用sendfile。如果你对这个主题有更深层次的兴趣,敬请期待我的第二篇文章——"Zero Copy II: Kernel Perspective",在其中将更深一步的讲述"零拷贝"的内核内部实现。


    http://t.matong.me/2011/03/29/zero-copy-linux.html

    展开全文
  • 零拷贝

    千次阅读 2019-06-08 23:15:03
    本文就介绍一下其中一个技术——零拷贝。 到目前为止,几乎每个人都听过 Linux 中所谓的零拷贝功能,但是我经常遇到对它不完全理解的人。基于此,我决定写一些文章深入探讨这个有用的功能。在这篇文章中,我们将从...

    到目前为止,几乎每个人都听过 Linux 中所谓的零拷贝功能,但是我经常遇到对它不完全理解的人。基于此,我决定写一些文章深入探讨这个有用的功能。在这篇文章中,我们将从用户的角度讨论零拷贝,因此内核层次的细节将会省略。

    什么是零拷贝?

    为了更好地理解这个问题的解决方案,我们需要先理解这个问题本身。我们来看看网络服务器将存储在文件中的数据通过网络传输到客户端的简单过程。示例代码:

    read(file, tmp_buf, len);
    write(socket, tmp_buf, len);
    

    看起来很简单。你可能认为就两次系统调用并没有什么开销。实际上,这可能是与真相相去甚远。这两次调用的背后,数据至少拷贝了四次,用户/内核上下文转换也几乎执行了相同的次数(这个执行过程比这复杂得多,但是我想先使它简单)。为了更好的理解这个执行过程,请看图1。上面部分展示的是上下文切换,下面部分展示的是拷贝操作。两个系统调用中的拷贝过程
    图1 两个系统调用中的拷贝过程

    1. 第一步:read 系统调用导致一次从用户态到内核态的上下文切换。这第一次拷贝在 DMA 引擎中执行,它从磁盘中读取文件内容,并且存入内核地址空间缓存中。
    2. 第二步:数据从内核缓存拷贝到用户缓存,同时 read 系统调用返回。read 调用的返回导致一次从内核态到用户态的上下文切换。现在数据用户地址空间缓存中,并且再次开始向下移动。
    3. 第三步:write 系统调用导致一次从用户态到内核态的上下文切换。第三次拷贝执行是为了再次把数据放到内核地址空间缓存中。此时,数据被放到一个不同的缓存中,一个和特定的 sockets 关联的缓存。
    4. 第四步:write 系统调用返回,并且造成第四次上下文切换。第四次拷贝在独立且异步的情况下,通过 DMA 引擎将数据从内核缓存拷贝到协议引擎中。你很可能要问,“独立且异步是什么意思?调用返回之前,难道没有数据传输吗?”事实上,调用返回不能保证数据传输;它甚至不能保证数据传输的开始。它仅仅意味着以太网驱动程序的队列中有空闲的描述符,可以接收我们的数据传输。在我们之前可能有很多数据包在排队。除非驱动程序或硬件实现优先级环或队列,否则数据按先进先出原则传输。(图1 中的 DMA 拷贝说明了最后一次拷贝可以被延迟的事实)。

    如你所见,很多的数据复制并不是真正需要的。有些复制可以消除以减少开销,提升性能。作为一个驱动程序开发者,我工作中使用的硬件具有相当高级的特性。一些硬件可以完全绕过主存,直接传输数据到另一个设备。这个特性减少了一次在系统内存中的拷贝,是一个很好的特性,但是这不是所有硬件都支持的。数据从磁盘重新打包到网络中也是存在问题的,这引入了一些复杂性。为了减少开销,我们从减少内核缓存和用户缓存之间的拷贝开始。

    mmap

    一种减少拷贝的方法,是调用 mmap 代替调用 read。例如:

    tmp_buf = mmap(file, len);
    write(socket, tmp_buf, len);
    

    为了更好地理解处理过程,请看图2。上下文切换保持不变。
    在这里插入图片描述
    图2 mmap 调用

    1. 第一步:mmap 系统调用导致文件内容被 DMA 引擎拷贝到内核缓存中。这个缓存是和用户进程共享的,在内核和用户内存空间中没有执行任何拷贝。
    2. 第二步:write 系统调用导致内核从原始内核缓存拷贝数据到与 sockets 关联的内核缓存中。
    3. 第三步:当 DMA 引擎从内核 Socket 缓存中传输数据到协议引擎时,第三次拷贝发生。

    通过使用 mmap 代替 read,我们将内核必须拷贝的数据量减少了一半。当大量的数据被传输时,这能产生相当好的效果。然而,这种提升不是没有代价的;使用 mmap+write 方法有些隐藏的陷阱。当你在内存映射一个文件,然后调用 write,同时另一个进程截断相同的文件时,你将会掉入其中的一个陷阱中。你的 write 系统调用将会被总线的错误信号 SIGBUG 中断,因为你执行了一个错误的内存访问。那个信号的默认行为是杀死该进程并转存内核——不是网络服务器最理想的处理方式。有两种方式解决这个问题。

    第一种方式是为 SIGBUS 信号安装一个信号处理程序,然后在处理程序中简单地调用 return。通过这样做,write 系统调用返回在它被中断之前写的字节数,并且把 errno 置为 success。让我指出,这是一个不好的解决方案,一个解决治标不治本的方案。因为 SIGBUS 信号表示进程已经发生了非常严重的错误,我不鼓励使用这个解决方案。

    第二种方式涉及到内核中的文件租赁(在 Microsoft Windows 中叫作 “机会锁定”)。这是这个问题正确的解决方案。通过在文件描述符中使用租赁,你可以使用内核租赁一个特殊的文件。然后你可以从内核请求读/写租约。当另一个进程尝试截断你正在传输的文件时,内核会给你发送一个实时信号——RT_SIGNAL_LEASE 信号。它告诉你内核正在破坏你在文件的读/写租约。你的 write 调用在你的程序访问到一个非法地址,并被 SIGBUS 信号杀死之前被中断。write 调用的返回值是在中断之前写的字节数,并且设置 errno 为 success。下面是示例代码,表示怎么从内核回的一个租约。

    if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
        perror("kernel lease set signal");
        return -1;
    }
    /* l_type can be F_RDLCK F_WRLCK */
    if(fcntl(fd, F_SETLEASE, l_type)){
        perror("kernel lease set type");
        return -1;
    }
    

    你应该在 mmaping 文件之前得到你的租约,并且在你完成之后将租约撕毁。这是通过使用 F_UNLCK 的租约类型调用 fcntl F_SETLEASE 实现的。

    Sendfile

    在内核 2.1 版本中,sendfile 系统调用被引入,以简化网络和两个本地文件之间的数据传输。sendfile 的引入不仅减少了数据拷贝,也减少了上下文切换。它使用起来像这样:

    sendfile(socket, file, len);
    

    为了更好地理解处理过程,请看图3:
    在这里插入图片描述
    图3 使用 Sendfile 代替 read 和 write

    1. 第一步:sendfile 系统调用导致文件内容被 DMA 引擎拷贝到内核缓存中。然后数据被内核拷贝到与 sockets 关联的内核缓存中。
    2. 第二步:当 DMA 引擎将数据从内核 Socket 缓存中传输到协议引擎时,第三次拷贝发生。

    你很可能想知道当另一个进程截断我们用 sendfile 系统调用传输的文件时,发生了什么。如果我们不注册任何信号处理程序,sendfile 只返回它在中断之前传输的字节数,而且 errno 会被设置为 success。

    在我们调用 sendfile 之前,如果我们从内核获得文件租约,则行为和返回状态是完全一样的。在 sendfile 调用返回之前,我们也可以获得 RT_SIGNAL_LEASE 信号。

    到此为止,我们已经能够避免内核发生一些拷贝,但是我们仍然还有一次拷贝。我们也可以避免麽?使用一点硬件帮助,绝对可以的。为了消除内核的数据拷贝,我们需要一个支持聚集操作的网络接口。这仅仅意味着等待传输的数据不需要连续的内存空间;它可以分散不同的内存位置。在内核 2.4 版本中,socket 缓存描述符被修改以适应这些需求——在 Linux 中被称作零拷贝(Zero Copy)。这种方式不仅减少了多个上下文切换,也减少了处理器的数据拷贝。因为用户级应用程序没有改变,所以代码看起来像这样:

    sendfile(socket, file, len);
    

    为了更好地理解处理过程,请看图4:
    在这里插入图片描述
    图4 支持聚集操作的硬件从内存的多个位置获取数据,消除内存拷贝

    1. 第一步:sendfile 系统调用导致文件内容被 DMA 引擎拷贝到内核缓存中。
    2. 第二步:没有数据被拷贝到 socket 缓存中。取而代之的是,只有关于数据的位置和长度的信息的描述符附加到套接字缓冲区。DMA 引擎直接将数据从内核缓存传输到协议引擎,因此消除了最后的拷贝。

    因为数据仍然是从磁盘拷贝到内存,从内存到导线,有人可能会说这不是真正的零拷贝。站在操作系统的角度,这是零拷贝,因为在内核缓存之间没有了数据拷贝。当使用零拷贝时,不就有避免拷贝的性能收益,还有像更少的上下文切换,较少的 CPU 数据缓存污染和没有 CPU 校验和计算。

    可移植性问题

    一般来说,sendfile 系统调用的一个问题是,缺少标准的实现,像开源系统调用那样。sendfile 在 Linux、Solaris 或 HP-UX 上的实现是完全不同的。这对希望在他们的网络数据传输代码中使用零拷贝的开发者造成了困扰。

    实现的不同点之一是,Linux 提供的 sendfile 定义了在两个文件描述符(文件到文件)和(文件到套接字)之间传输数据的接口。另一方面,HP-UX 和 Solaris 仅仅能使用了文件到套接字提交。

    第二个不同点是,Linux 没有实现矢量传输。Solaris 和 HP-UX 的 sendfile 有额外的参数,可取消与将要发送的数据预置标头相关的开销。

    展望未来

    Linux 中的零拷贝的实现还远远没有完成,并且不久的将来很可能会发生改变。更多的功能会被添加。例如,sendfile 不支持矢量传输,Samba 和 Apache 这类服务器不得不使用具有 TCP_CORK 标志设置的多个 sendfile 调用。该标志告诉系统更多的数据将在下一个 sendfile 调用中通过。TCP_CORK 也和 TCP_NODELAY 不兼容,并且在我们想要给数据预置或追加头信息时使用。这是一个完美的例子,矢量调用可以消除多次 sendfile 调用的需要,以及当前实现强制要求的延迟。

    现在的 sendfile 另一个令人相对不爽的限制是,它不能被用于传输超过 2GB 的文件。这样大小的文件在今天还不常见,同时在退出的过程中不得不复制所有这些数据,这相当令人失望。因为 sendfilemmap 不能被用于这样的场景,所以 sendfile64 在将来的内核版本中使用会非常方便。

    原文地址

    展开全文
  • 点击上方“朱小厮的博客”,选择“设为星标”后台回复”加群“获取公众号专属群聊入口来源:rrd.me/ggFBd零拷贝,从字面意思理解就是数据不需要来回的拷贝,大大提升了系统的性能。我们也...

    点击上方“朱小厮的博客”,选择“设为星标”

    后台回复”加群“获取公众号专属群聊入口

    来源:rrd.me/ggFBd

    零拷贝,从字面意思理解就是数据不需要来回的拷贝,大大提升了系统的性能。我们也经常在 Java NIO,Netty,Kafka,RocketMQ 等框架中听到零拷贝,它经常作为其提升性能的一大亮点;下面从 I/O 的几个概念开始,进而再分析零拷贝。

    I/O 概念

    缓冲区

    缓冲区是所有 I/O 的基础,I/O 讲的无非就是把数据移进或移出缓冲区;进程执行 I/O 操作,就是向操作系统发出请求,让它要么把缓冲区的数据排干(写),要么填充缓冲区(读)。

    下面看一个 Java 进程发起 Read 请求加载数据大致的流程图:

    进程发起 Read 请求之后,内核接收到 Read 请求之后,会先检查内核空间中是否已经存在进程所需要的数据,如果已经存在,则直接把数据 Copy 给进程的缓冲区。

    如果没有内核随即向磁盘控制器发出命令,要求从磁盘读取数据,磁盘控制器把数据直接写入内核 Read 缓冲区,这一步通过 DMA 完成。

    接下来就是内核将数据 Copy 到进程的缓冲区;如果进程发起 Write 请求,同样需要把用户缓冲区里面的数据 Copy 到内核的 Socket 缓冲区里面,然后再通过 DMA 把数据 Copy 到网卡中,发送出去。

    你可能觉得这样挺浪费空间的,每次都需要把内核空间的数据拷贝到用户空间中,所以零拷贝的出现就是为了解决这种问题的。

    关于零拷贝提供了两种方式分别是:

    • mmap+write

    • Sendfile

    虚拟内存

    所有现代操作系统都使用虚拟内存,使用虚拟的地址取代物理地址,这样做的好处是:

    • 一个以上的虚拟地址可以指向同一个物理内存地址。

    • 虚拟内存空间可大于实际可用的物理地址。

    利用第一条特性可以把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样 DMA 就可以填充对内核和用户空间进程同时可见的缓冲区了。

    大致如下图所示:

    省去了内核与用户空间的往来拷贝,Java 也利用操作系统的此特性来提升性能,下面重点看看 Java 对零拷贝都有哪些支持。

    mmap+write 方式

    使用 mmap+write 方式代替原来的 read+write 方式,mmap 是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。

    这样就可以省掉原来内核 Read 缓冲区 Copy 数据到用户缓冲区,但是还是需要内核 Read 缓冲区将数据 Copy 到内核 Socket 缓冲区。

    大致如下图所示:

    Sendfile 方式

    Sendfile 系统调用在内核版本 2.1 中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。

    Sendfile 系统调用的引入,不仅减少了数据复制,还减少了上下文切换的次数,大致如下图所示:

    数据传送只发生在内核空间,所以减少了一次上下文切换;但是还是存在一次 Copy,能不能把这一次 Copy 也省略掉?

    Linux2.4 内核中做了改进,将 Kernel buffer 中对应的数据描述信息(内存地址,偏移量)记录到相应的 Socket 缓冲区当中,这样连内核空间中的一次 CPU Copy 也省掉了。

    Java 零拷贝

    MappedByteBuffer

    Java NIO 提供的 FileChannel 提供了 map() 方法,该方法可以在一个打开的文件和 MappedByteBuffer 之间建立一个虚拟内存映射。

    MappedByteBuffer 继承于 ByteBuffer,类似于一个基于内存的缓冲区,只不过该对象的数据元素存储在磁盘的一个文件中。

    调用 get() 方法会从磁盘中获取数据,此数据反映该文件当前的内容,调用 put() 方法会更新磁盘上的文件,并且对文件做的修改对其他阅读者也是可见的。

    下面看一个简单的读取实例,然后再对 MappedByteBuffer 进行分析:

    public class MappedByteBufferTest {
    
        public static void main(String[] args) throws Exception {
            File file = new File("D://db.txt");
            long len = file.length();
            byte[] ds = new byte[(int) len];
            MappedByteBuffer mappedByteBuffer = new FileInputStream(file).getChannel().map(FileChannel.MapMode.READ_ONLY, 0,
                    len);
            for (int offset = 0; offset < len; offset++) {
                byte b = mappedByteBuffer.get();
                ds[offset] = b;
            }
            Scanner scan = new Scanner(new ByteArrayInputStream(ds)).useDelimiter(" ");
            while (scan.hasNext()) {
                System.out.print(scan.next() + " ");
            }
        }
    }
    

    主要通过 FileChannel 提供的 map() 来实现映射,map() 方法如下:

        public abstract MappedByteBuffer map(MapMode mode,
                                             long position, long size)
            throws IOException;
    

    分别提供了三个参数,MapMode,Position 和 Size,分别表示:

    • MapMode:映射的模式,可选项包括:READ_ONLY,READ_WRITE,PRIVATE。

    • Position:从哪个位置开始映射,字节数的位置。

    • Size:从 Position 开始向后多少个字节。

    重点看一下 MapMode,前两个分别表示只读和可读可写,当然请求的映射模式受到 Filechannel 对象的访问权限限制,如果在一个没有读权限的文件上启用 READ_ONLY,将抛出 NonReadableChannelException。

    PRIVATE 模式表示写时拷贝的映射,意味着通过 put() 方法所做的任何修改都会导致产生一个私有的数据拷贝并且该拷贝中的数据只有 MappedByteBuffer 实例可以看到。

    该过程不会对底层文件做任何修改,而且一旦缓冲区被施以垃圾收集动作(garbage collected),那些修改都会丢失。

    大致浏览一下 map() 方法的源码:

        public MappedByteBuffer map(MapMode mode, long position, long size)
            throws IOException
        {
                ...省略...
                int pagePosition = (int)(position % allocationGranularity);
                long mapPosition = position - pagePosition;
                long mapSize = size + pagePosition;
                try {
                    // If no exception was thrown from map0, the address is valid
                    addr = map0(imode, mapPosition, mapSize);
                } catch (OutOfMemoryError x) {
                    // An OutOfMemoryError may indicate that we've exhausted memory
                    // so force gc and re-attempt map
                    System.gc();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException y) {
                        Thread.currentThread().interrupt();
                    }
                    try {
                        addr = map0(imode, mapPosition, mapSize);
                    } catch (OutOfMemoryError y) {
                        // After a second OOME, fail
                        throw new IOException("Map failed", y);
                    }
                }
    
                // On Windows, and potentially other platforms, we need an open
                // file descriptor for some mapping operations.
                FileDescriptor mfd;
                try {
                    mfd = nd.duplicateForMapping(fd);
                } catch (IOException ioe) {
                    unmap0(addr, mapSize);
                    throw ioe;
                }
    
                assert (IOStatus.checkAll(addr));
                assert (addr % allocationGranularity == 0);
                int isize = (int)size;
                Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
                if ((!writable) || (imode == MAP_RO)) {
                    return Util.newMappedByteBufferR(isize,
                                                     addr + pagePosition,
                                                     mfd,
                                                     um);
                } else {
                    return Util.newMappedByteBuffer(isize,
                                                    addr + pagePosition,
                                                    mfd,
                                                    um);
                }
         }
    

    大致意思就是通过 Native 方法获取内存映射的地址,如果失败,手动 GC 再次映射。

    最后通过内存映射的地址实例化出 MappedByteBuffer,MappedByteBuffer 本身是一个抽象类,其实这里真正实例化出来的是 DirectByteBuffer。

    DirectByteBuffer

    DirectByteBuffer 继承于 MappedByteBuffer,从名字就可以猜测出开辟了一段直接的内存,并不会占用 JVM 的内存空间。

    上一节中通过 Filechannel 映射出的 MappedByteBuffer 其实际也是 DirectByteBuffer,当然除了这种方式,也可以手动开辟一段空间:

    ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(100);
    

    如上开辟了 100 字节的直接内存空间。

    Channel-to-Channel 传输

    经常需要从一个位置将文件传输到另外一个位置,FileChannel 提供了 transferTo() 方法用来提高传输的效率,首先看一个简单的实例:

    public class ChannelTransfer {
        public static void main(String[] argv) throws Exception {
            String files[]=new String[1];
            files[0]="D://db.txt";
            catFiles(Channels.newChannel(System.out), files);
        }
    
        private static void catFiles(WritableByteChannel target, String[] files)
                throws Exception {
            for (int i = 0; i < files.length; i++) {
                FileInputStream fis = new FileInputStream(files[i]);
                FileChannel channel = fis.getChannel();
                channel.transferTo(0, channel.size(), target);
                channel.close();
                fis.close();
            }
        }
    }
    

    通过 FileChannel 的 transferTo() 方法将文件数据传输到 System.out 通道,接口定义如下:

        public abstract long transferTo(long position, long count,
                                        WritableByteChannel target)
            throws IOException;

    几个参数也比较好理解,分别是开始传输的位置,传输的字节数,以及目标通道;transferTo() 允许将一个通道交叉连接到另一个通道,而不需要一个中间缓冲区来传递数据。

    注:这里不需要中间缓冲区有两层意思:第一层不需要用户空间缓冲区来拷贝内核缓冲区,另外一层两个通道都有自己的内核缓冲区,两个内核缓冲区也可以做到无需拷贝数据。

    Netty 零拷贝

    Netty 提供了零拷贝的 Buffer,在传输数据时,最终处理的数据会需要对单个传输的报文,进行组合和拆分,NIO 原生的 ByteBuffer 无法做到,Netty 通过提供的 Composite(组合)和 Slice(拆分)两种 Buffer 来实现零拷贝。

    看下面一张图会比较清晰:

    TCP 层 HTTP 报文被分成了两个 ChannelBuffer,这两个 Buffer 对我们上层的逻辑(HTTP 处理)是没有意义的。

    但是两个 ChannelBuffer 被组合起来,就成为了一个有意义的 HTTP 报文,这个报文对应的 ChannelBuffer,才是能称之为“Message”的东西,这里用到了一个词“Virtual Buffer”。

    可以看一下 Netty 提供的 CompositeChannelBuffer 源码:

    public class CompositeChannelBuffer extends AbstractChannelBuffer {
    
        private final ByteOrder order;
        private ChannelBuffer[] components;
        private int[] indices;
        private int lastAccessedComponentId;
        private final boolean gathering;
    
        public byte getByte(int index) {
            int componentId = componentId(index);
            return components[componentId].getByte(index - indices[componentId]);
        }
        ...省略...
    

    Components 用来保存的就是所有接收到的 Buffer,Indices 记录每个 buffer 的起始位置,lastAccessedComponentId 记录上一次访问的 ComponentId。

    CompositeChannelBuffer 并不会开辟新的内存并直接复制所有 ChannelBuffer 内容,而是直接保存了所有 ChannelBuffer 的引用,并在子 ChannelBuffer 里进行读写,实现了零拷贝。

    其他零拷贝

    RocketMQ 的消息采用顺序写到 commitlog 文件,然后利用 consume queue 文件作为索引。

    RocketMQ 采用零拷贝 mmap+write 的方式来回应 Consumer 的请求。

    同样 Kafka 中存在大量的网络数据持久化到磁盘和磁盘文件通过网络发送的过程,Kafka使用了 Sendfile 零拷贝方式。

    总结

    零拷贝如果简单用 Java 里面对象的概率来理解的话,其实就是使用的都是对象的引用,每个引用对象的地方对其改变就都能改变此对象,永远只存在一份对象。

    想知道更多?描下面的二维码关注我

    后台回复”加群“获取公众号专属群聊入口

    【精彩推荐】

    朕已阅 

    展开全文
  • 零拷贝,从字面意思理解就是数据不需要来回的拷贝,大大提升了系统的性能。我们也经常在 Java NIO,Netty,Kafka,RocketMQ 等框架中听到零拷贝,它经常作为其提升性能的一大亮点;下面从 I/O 的几个概念开始,进而...
  • 网卡驱动内存零拷贝技术

    千次阅读 2014-08-26 16:13:50
    网卡驱动内存零拷贝技术实现思路: 主要有这么几点: 1. 在内核中为Rx分配一片连续的内存, 再分割成一个个的小buffer挂到descriptor上(由于linux内核分配大的内存块有限制,所以可以分配几大块然后分别挂在...
  • 什么是"零拷贝"技术

    千次阅读 2020-01-16 15:00:31
    下面从I/O的几个概念开始,进而在分析零拷贝。 I/O概念 1、缓冲区 缓冲区是所有I/O的基础,I/O讲的无非就是把数据移进或移出缓冲区;进程执行I/O操作,就是向操作系统发出请求,让它要么把缓冲区的数...
  • 零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间。图片来...
  • 我们也经常在 Java NIO,Netty,Kafka,RocketMQ 等框架中听到零拷贝,它经常作为其提升性能的一大亮点 下面从 I/O 的几个概念开始,进而再分析零拷贝。 I/O 概念 缓冲区 缓冲区是所有 I/O 的基础,I/O 讲的无非就是...
  • 通俗易懂的Kafka零拷贝机制

    千次阅读 2020-09-11 11:01:00
    Kafka之所以那么快,其中一个很大的原因就是零拷贝(Zero-copy)技术零拷贝不是kafka的专利,而是操作系统的升级,又比如Netty,也用到了零拷贝。下面我就画图讲解零拷贝,如果对你有帮助请点个赞支持。 传统IO ...
  • JavaNIO和零拷贝(Zero Copy)

    万次阅读 多人点赞 2018-05-10 22:32:34
    一.Linux操作系统中的零拷贝 1.1先从Linux的普通I/O过程说起 1.2内存映射方式I/O 1.3内核空间内部传输I/O 1.4理想状态下的零拷贝I/O 1.5splice()系统调用 二.JavaNIO中的零拷贝 2.1NIO中内存映射方式I/O 2.2NIO中...
  • IO零拷贝_Linux

    2018-03-15 10:05:31
    目录目录 简介 抛砖引玉 mmap sendfile 支持聚集操作的硬件 在Java中的应用 参考 简介零拷贝(zero-copy)技术可以减少数据拷贝和共享总线操作的次数,消除通信...采用零拷贝技术,通过减少数据拷贝次数,简化协议处
  • 下面从I/O的几个概念开始,进而在分析零拷贝。 I/O概念 1、缓冲区 缓冲区是所有I/O的基础,I/O讲的无非就是把数据移进或移出缓冲区;进程执行I/O操作,就是向操作系统发出请求,让它要么把缓冲区的数据排干.
  • Zero-copy(零拷贝

    2018-09-25 10:34:11
    也译零拷贝技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。 原理 操作系统某些组件(例如驱动程序、文件系统和网络协议栈...
  • 讨论:零拷贝和环形队列缓存队列问题windows下如何实现零拷贝?是不是windows下不需要考虑防止内存...相关链接:转载 零拷贝技术研究与实现http://blog.csdn.net/zhoujunyi/archive/2007/06/18/1656290.aspxwindows下
  • 通过零拷贝实现有效数据传输 Linux zero copy
  • JAVA的几种实现零拷贝方式

    千次阅读 2020-07-17 14:02:52
    下面从I/O的几个概念开始,进而在分析零拷贝。 I/O概念 1.缓冲区 缓冲区是所有I/O的基础,I/O讲的无非就是把数据移进或移出缓冲区;进程执行I/O操作,就是向操作系统发出请求,让它要么把缓冲区的数据排干(写),...
  • 实现零拷贝用到的最主要技术是 DMA 数据传输技术和内存区域映射技术零拷贝机制可以减少数据在内核缓冲区和用户进程缓冲区之间反复的 I/O 拷贝操作。 零拷贝机制可以减少用户进程地址空间和内核地址
  • NIO、BIO编程模型与零拷贝

    千次阅读 2020-02-21 11:19:53
    Java IO模型 Java共支持3种网络编程... //在linux下一个transferTo 方法就可以完成传输 //在windows 下 一次调用 transferTo 只能发送8m , 就需要分段传输文件 //transferTo 底层使用到零拷贝 long transferCount = ...
  • 使用零拷贝请求的应用程序,内核将数据直接从磁盘文件复制到套接字,而不通过应用程序。 零拷贝大大提高了应用程序的性能,并减少了内核和用户模式之间的上下文切换次数。 Java类库通过 transferTo() in方法在  ...
  • 程序员的成长之路互联网/程序员/技术/资料共享关注阅读本文大概需要 7分钟。来源:ksfzhaohui juejin.im/post/5cad6f1ef265da039f0ef5df...
  • 从这篇文章开始,作者将带着大家来学习《Windows黑客编程技术详解》,其作者是甘迪文老师,推荐大家购买来学习。作者将采用实际编程和图文结合的方式进行分享,并且会进一步补充知识点。第一篇文章主要包括两部分...
  • 作为开发Windows驱动程序的程序员,需要比普通程序员更多了解Windows内部的内存管理机制,并在驱动程序中有效地使用内存。在驱动程序的编写中,分配和管理内存不能使用熟知的Win32 API函数,取而代之的是DDK提供的...
  • Windows2000 内核级进程隐藏、侦测技术 指导老师:龙老师 
  • windows服务器下从开始用wordpress一步步建站wordpress介绍购买云服务器远程连接服务器下载安装XAMPP和wordpress搭建网站欢迎关注微信公众号:Java后台开发 wordpress介绍 WordPress是使用PHP语言开发的博客平台,...
  • 第十一讲 多媒体编程 随着多媒体技术的迅猛发展和PC性能的大... Windows 95提供了对多媒体编程的良好支持,本章将帮助读者迅速掌握一些实用的多媒体编程技术,主要的内容包括:调色板位图依赖于设备的位图(DDB)与设
  • 下面我选取了信息对抗技术的中一个很小一角关于windows内核级病毒隐藏技术和反病毒侦测技术作为议题详细讨论。 关键字: 内核, 拦截, 活动进程链表, 系统服务派遣表, 线程调度链 Abstract Nowadays, i

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,408
精华内容 6,963
关键字:

windows零拷贝技术