摘抄writev、readv(原子操作)

read()和write()系统调用每次在文件和进程的地址空间之间传送一块连续的数据。但是,应用有时也需要将分散在内存多处地方的数据连续写到文件中,或者反之。在这种情况下,如果要从文件中读一片连续的数据至进程的不同区域,使用read()则要么一次将它们读至一个较大的缓冲区中,然后将它们分成若干部分复制到不同的区域,要么调用read()若干次分批将它们读至不同区域。同样,如果想将程序中不同区域的数据块连续地写至文件,也必须进行类似的处理。

UNIX提供了另外两个函数—readv()和writev(),它们只需一次系统调用就可以实现在文件和进程的多个缓冲区之间传送数据,免除了多次系统调用或复制数据的开销。readv()称为散布读,即将文件中若干连续的数据块读入内存分散的缓冲区中。writev()称为聚集写,即收集内存中分散的若干缓冲区中的数据写至文件的连续区域中。
#include <sys/uio.h>
ssize_t readv(int fildes, const struct iovec *iov, int iovcnt);
ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);

参数fildes是文件描述字。iov是一个结构数组,它的每个元素指明存储器中的一个缓冲区。结构类型iovec有下述成员,分别给出缓冲区的起始地址和字节数:
struct iovec {
    void   *iov_base   /* 数据区的起始地址 */
    size_t  iov_len     /* 数据区的大小 */
}

参数iovcnt指出数组iov的元素个数,元素个数至多不超过IOV_MAX。Linux中定义IOV_MAX的值为1024。

图3-4说明了参数iovcnt、iov及其所指数组与这两个函数的关系。writev()依次将iov[0]、iov[1]、...、 iov[iovcnt–1]指定的存储区中的数据写至fildes指定的文件。writev()的返回值是写出的数据总字节数,正常情况下它应当等于所有数据块长度之和。


 

readv()则将fildes指定文件中的数据按iov[0]、iov[1]、...、iov[iovcnt–1]规定的顺序和长度,分散地读到它们指定的存储地址中。readv()的返回值是读入的总字节数。如果没有数据可读和遇到了文件尾,其返回值为0。

有了这两个函数,当想要集中写出某张链表时,只需让iov数组的各个元素包含链表中各个表项的地址和其长度,然后将iov和它的元素个数作为参数传递给writev(),这些数据便可一次写出。

例如用writev把多个协议头用着一个函数写给fd。下面是我的一个例子:
#include 
#include 
#include 
#include 
#include 
#include 

const char str1[] = "hello\n";
const char str2[] = "world,fuck!!\n";
void main(void)
{
int ret, len;
char temp;
char readbuf1[30];
char readbuf2[30];
struct iovec iov[2];
iov[0].iov_base = str1;
iov[0].iov_len = strlen(str1);
iov[1].iov_base = str2;
iov[1].iov_len = strlen(str2);
len = iov[1].iov_len + iov[0].iov_len;
int fd = open("testfile", O_RDWR | O_CREAT);
if(fd < 0)
{
printf("open file failed\n");
exit(1);
}
ret = writev(fd, &iov[0], 2);
if(ret != len)
{
printf("writev failed\n");
close(fd);
exit(1);
}
fd = open("testfile", O_RDONLY);
if(fd < 0)
{
printf("open file failed\n");
exit(1);
}
printf("test writev:\n");
while(read(fd, &temp, 1) != 0)
write(STDOUT_FILENO, &temp, 1);
close(fd);
printf("sleep for a few seconds\n");
sleep(3);
printf("test readv:\n");
iov[0].iov_base = readbuf1;
 iov[0].iov_len = 30;
iov[1].iov_base = readbuf2;
iov[1].iov_len = 30;
fd = open("testfile", O_RDONLY);
if(fd < 0)
{
printf("open file failed\n");
exit(1);
}
memset(readbuf1, 0, 30);
memset(readbuf2, 0, 30);
ret = readv(fd, &iov[0], 2);
if(ret != len)
{
printf("readv failed\n");
close(fd);
exit(1);
}
close(fd);
printf("readbuf1: %s\n", readbuf1);
printf("readbuf2: %s\n", readbuf2);
exit(0);
}


上面是执行结果,这里注意从文件读出来的时候,是把第一个缓冲区填满后,才往第二个缓冲区写入。
上面的执行结果可以看出,第一个没有写满,所以第二个内容是空的,另外缓冲区的大小在读的时候是根据iov[0]和iov[1]的iov_len字段来确定的。
即使缓冲区很大,但是读的时候读多少由len确定。
如将上面代码中的
iov[0].iov_base = readbuf1;
 iov[0].iov_len = 30;
中的30改成3,则执行情况如下:

另外,apue1,389页,有下面描述:
对于少量数据,使用writev的固定开销大于得益,随着需要复制数据的增加,程序中复制数据的开销也会增多,此时,writev这种替代方法就会有更大的吸引力。
posted @ 2019-08-16 16:45  jest549  阅读(406)  评论(0编辑  收藏  举报