pipe
1 2 3 |
#include <unistd.h> int pipe(int fd[2]); |
- pipe用于创建一个管道,以实现进程间通信。
- pipe函数的参数是一个包含两个int的数组指针。
该函数成功时返回0,并将一对打开的文件描述符值填入其参数指向的数组。
如果失败,返回-1并设置errno。 - 通过pipe创建的这两个文件描述符
fd[0]
和fd[1]
分别构成管道的两端。
fd[1]
往写入的数据可以从fd[0]
里读出。并且,fd[0]
只能用于从关掉读出数据,fd[1]
则只能用于往管道写入数据。不能反过来使用。 - 如果要实现双向的数据传输,就应该使用两个管道。
- 默认情况下,这一对文件描述符都是阻塞的。
此时如果用read系统调用来读取一个空的管道,read将被阻塞,直到管道内有数据可读。
如果用write系统调用往一个满的管道中写入数据,write将被阻塞,直到管道有足够多的空闲空间可用。
但如果应用程序将fd[0]
和fd[1]
都设置为非阻塞的,则read和write会有不同的行为。 - 另外,如果写端文件描述符
fd[1]
的引用计数减少至0,即没有任何进程需要往管道中写入数据,则针对该管道的读端文件描述符fd[0]
的read操作将返回0,即读到了文件结束标记(EOF)。
反之,如果管道的读端文件描述符fd[0]
的引用计数减少至0,即没有任何进程需要从管道读取数据,则针对该管道的写端文件描述符fd[1]
的write函数操作将会失败,并引发SIGPIPE信号。 - 管道内部传输的数据是字节流,这和TCP字节流的概念相同。
但二者又有细微的区别:应用程序能往一个TCP连接中写入多少个字节的数据,取决于对方的接收通告口的大小和本端的拥塞窗口的大小。而管道的话,它本身拥有一个容量限制,它规定如果应用程序不将数据从管道读走的话,该管道最多能被写入多少字节的数据。
自Linux2.6.11内核起,管道容量的大小默认是65536个字节。但是可以使用fcntl函数修改管道的容量。
socketpair
- 能够方便地创建双向管道。
- 需要注意的是,domain只能使用UNIX本地域协议族AF_UNIX,因为我们仅能在本地使用这个双向管道。
1 2 3 4 |
#include <sys/types.h> #include <sys/socket.h> int socketpair(int domain, int type, int protocol, int fd[2]); |
dup和dup2
- 用于复制文件描述符。
- 可以用来把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接。
- dup创建一个新的文件描述符,该文件描述符和原有文件描述符file_descriptor指向的相同的文件、管道或者网络连接。并且dup返回的文件描述符总是取系统当前可用的最小整数值。
- dup2和dup类似,不过它将返回第一个不小于file_descriptor_two的整数值。
- dup和dup2调用失败时返回-1并设置errno。
1 2 3 4 |
#include <unistd.h> int dup(int file_descriptor); int dup2(int file_descriptor_one, int file_descriptor_two); |
readv和writev
- readv将数据从文件描述符读到分散的内存块中,即分散读。
writev则将多块分散的内存数据一并写入文件描述符中,即集中写。
1 2 3 4 |
#include <sys/uio.h> ssize_t readv(int fd, const struct iovec* vector, int count); ssize_t writev(int fd, const struct iovec* vector, int count); |
sendfile
- 可以在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这称为零拷贝。
- in_fd是待读出内容的文件描述符,out_fd是待写入内容的文件描述符。
- offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置。count参数指定文件描述符in_fd和out_fd之间传输的字节数。
- sendfile成功时返回传输的字节数,失败返回-1并设置errno。
- in_fd必须是一个支持雷士mmap函数的文件描述符,也就是它必须指向真实的文件,而不是socket和管道。而out_fd必须是一个socket。
- 综上,sendfile几乎是专门为网络上传输文件而设计的。
1 2 3 |
#include <sys/sendfile.h> ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count); |
mmap和munmap
- mmap函数用于申请一段内存空间。可以将这段内存作为进程间通信的共享内存,也可以将文件直接映射到其中。
- munmap则释放由mmap创建的这段内存空间。
- start允许用户使用某个特定的地址作为这段内存的起始地址。如果它被设置成了NULL,系统就会自动分配一个地址。
- length指定内存段的长度。prot参数用来设置内存段的访问权限,值可以是下面选项的按位或:
- PROT_READ:内存段可读
- PROT_WRITE:内存段可写
- PROT_EXEC:内存段可执行
- PROT_NONE:内存段不能被访问
- flags参数控制了内存段内容被修改后程序的行为(下图)。
- fd参数是被映射文件对应的文件描述符。一般通告open系统调用获得。offset设置从文件的何处开始映射(这种是对于不需要读入整个文件的情况)。
- mmap成功时返回执向目标内存区域的指针,失败则返回
MAP_FAILED((void*)-1)
并设置errno。
munmap成功时返回0,失败则返回-1并设置errno。
1 2 3 4 5 |
#include <sys/mman.h> void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void* start, size_t length); |
splice
- splice用于在两个文件描述符之间移动数据,也是零拷贝操作。
- fd_in是待输入数据的文件描述符。
如果fd_in是一个管道文件描述符,那么off_in必须被设置为NULL。
如果fd_in不是一个管道文件描述符(比如socket),那么off_in表示从输入数据流的何处开始读取数据。此时,如果off_in被设置为NULL,则表示从输入数据流的当前偏移位置读入。如果off_in不为NULL,则它将指出具体的偏移位置。
fd_out和off_out参数含义与fd_in和off_in相同,不过它是用于输出数据流。 - len指定了移动数据的长度,flags参数则控制数据如何移动。
-
使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。
-
splice函数调用成功时返回移动字节的数量,可能返回0(表示没有数据需要移动,这发生在从管道中读取数据而该管道没有被写入任何数据时),调用失败时返回-1并设置了errno。
1 2 3 4 |
#include <fcntl.h> sszie_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags); |
tee
- tee在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作。
- 这个函数的参数的含义和splice相同,但需要注意的是,fd_in和fd_out必须都是管道文件描述符。
- tee成功时返回在两个文件描述符之间复制的数据数量(字节数)。返回0表示没有复制任何数据。
tee失败时返回-1并设置errno。
1 2 3 |
#include <fcntl.h> ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags); |
fcntl
- 提供了对文件描述符的各种控制操作。
另一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制。
但是,对于控制文件描述符常用的属性和行为,fcntl是由POSIX规范指定的首选方法。 - fd是被操作的文件描述符,cmd参数指定了执行何种类型的操作。
- fcntl成功时返回值见下图,失败时返回-1并设置errno。
- 在网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的。
1 2 3 |
#include <fcntl.h> int fcntl(int fd, int cmd, ...); |
1 2 3 4 5 6 |
int setnonblocking(int fd) { int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option; } |
信号
- SIGIO和SIGURG这两个信号与其他Linux信号不同,它们必须与某个文件描述符相关联方可使用。
- 当被关联的文件描述符可读或可写时,系统将触发SIGIO信号。
- 当被关联的文件描述符(而且必须是一个socket)上有带外数据可读时,系统将触发SIGURG信号。
- 将信号和文件描述符关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组,那么被指定的宿主进程或进程组将捕获这两个信号。
- 使用SIGIO时,还需要利用fcntl设置其O_ASYNC标志(也就是异步I/O标志),不过SIGIO信号模型并非真正意义上的异步I/O模型。
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Windows_API_Apply_112/31
- ♥ Linux目录的作用03/16
- ♥ Linux 内核空间&&用户空间03/30
- ♥ Shell 语法记述 第三篇09/05
- ♥ X86_64汇编学习记述三08/08
- ♥ x86_64汇编学习记述二08/07