这篇文章总结《Linux高性能服务器编程》第6章 - 高级I/O函数介绍的几个函数。之前在Linux下编程少,因此大部分函数都不清楚是干什么的,通过这章正好学习这几个函数,了解如何使用。
pipe
pipe()
函数主要用于进程间通信,其函数原型如下:
1 | int pipe(int pipefd[2]); |
该函数的参数是一对fd
,它们是单向的,其中fd[0]
只能读,fd[1]
只能写,默认情况下它们都是阻塞的。和socket有关的管道函数是socketpair()
,它的函数原型是:
1 | int socketpair(int domain, int type, int protocol, int sv[2]); |
该函数的前3个参数和socket的参数一样,只是在Linux下domain
只能是AF_UNIX
(只能在本地使用)或者AF_TIPC
;最后一个参数也是一对fd
,它们是双向的,既可以读也可以写。
dup/dup2
dup()
函数可以将指定的fd
复制给新的fd
,这个函数返回值就是新的fd
,并且该fd
是当前系统可用的最小fd
。dup2()
和dup()
函数类似,只是我们可以指定复制fd
的编号,这两个函数原型如下:
1 | // 发生错误,返回-1;否则返回新的fd |
dup2()
函数处理工作时:
如果
newfd
是已经被打开的,那么它会被原子的复制;如果
oldfd
无效,那么newfd
是不会被关掉的;如果
oldfd
和newfd
相同,那么该函数什么都不做,最后返回newfd
。
我们可以用这个函数将STDOUT_FILENO
复制到用户定义的的fd
中,那么之后输出到STDOUT_FILENO
的函数都将输出到该fd
。
readv/writev
这两个函数可以读取/写入不连续的内存空间,它们最大的特点是读/写操作都是原子的。如果使用普通read()/write()
函数,每个buffer都需要进行一次读/写操作,因此容易和其他进程产生竞争。这两函数原型如下:
1 | size_t readv(int fd, const struct iovec *iov, int iovcnt); |
其中struct iovec
的结构如下:
1 | struct iovec { |
我们可以设置读写iovcnt
个该结构体,每个结构体指定了buffer的地址和大小。现在的Linux支持最大1024
个buffer,这个值是由IOV_MAX
决定的。Linux: When to use scatter/gather IO (readv, writev) vs a large buffer with fread对该函数的用处做了基本的说明。
sendfile
sendfile()
是一个零拷贝函数,也就是不需要数据在用户buffer和内核buffer之间进行相互拷贝,因此效率很高。该函数的原型如下:
1 | // 返回拷贝成功的字节数或者-1 |
其中out_fd
可以是任意的文件描述符(Linux 2.6.33后),而in_fd
必须是支持mmap的文件描述符(不能是socket)。offset
是指向存储in_fd
偏移量变量的指针,count
是想复制的字节数。我们可以很容易的用这个函数将读取的文件描述内容直接输出到socket。
splice
splice()
函数也是一个零拷贝函数,但它必须在管道和其他文件描述符中进行数据的传输。其函数原型如下:
1 | // 返回拷贝成功的字节数或者-1 |
如果fd_in
是管道,那么off_in
必须NULL
,fd_out
也同样如此;len
是需要拷贝的字节数,flags
用于控制数据如何移动。下面这个例子是用splice()
实现简单的回射服务:
1 | int pipefd[2]; |
connfd
接收到数据后将数据拷贝到pipefd[1]
,然后数据通过pipefd[1]
流到pipefd[0]
,最后再写入connfd
。SPLICE_F_MORE
告诉内核之后调用splice()
会读取更多的数据。
tee
tee()
函数和splice()
函数类似,都是零拷贝函数。不过tee()
用于复制管道,复制后原管道还留有数据(和splice()
和sendfile()
不同)。该函数的原型如下:
1 | // 返回复制数据的字节数(0表示没有复制)或者-1 |
fd_in
和fd_out
必须是管道,其他文件描述符不支持。
fcntl
fcntl()
函数实现对文件描述符的控制,可以修改文件描述符的各种属性和行为。函数原型如下:
1 | // 根据不同的cmd会有不同的返回,-1表示错误 |
这个函数支持的命令很多,这里给一个简单的例子,其他命令可以用man
查询。这个例子修改文件描述符为非阻塞:
1 | int setnonblocking(int fd) { |
mmap/munmap
mmap()
用于申请一块内存空间用于进程间的通信,同时我们也可以把文件映射到这块空间;munmap()
用于释放这块空间。这里我只是简单介绍下这两个函数,之后会在多进程的总结中详细说明这两个函数。
1 | // 成功的话,返回该空间的地址;失败返回(void *)-1 |
addr
是分配内存的起始地址,如果是NULL
那么就内核分配一个地址;length
是需要空间的大小;prot
是设置读写权限;flags
用于设置分配的内存被使用后程序的行为。fd
是需要映射的文件描述符;offset
是文件映射地址的偏移量。