Linux 提供的高级 IO 函数,虽然不常用,但能提供优异的性能。
pipe
管道具有如下特点: 1. 半双工通信,数据流是单向的 2. 管道只能用于两个有亲缘关系进程间通信,一般是父子进程间通信
FIFO 突破了第二点限制,socket 突破了以上两点限制。
shell 用经常使用管道来将一个命令的输出作为下一个命令的输入:
1
cat abc.txt | grep "123"
数据流方式
pipe 有两种数据流方式: 1. 数据在一个进程内在用户空间交互 2. 数据经过了内核 pipe 进行交互
在实际使用中,用得最多的是父子进程间的通信
- 当写端的被关闭时,读端读取时将会返回0 -
当读端被关闭时,写端写入时将会返回 -1 ,此时 errno
的值为
EPIPE
,还会接受到信号 SIGPIPE
- 宏
PIPE_BUF
指定了内核 pipe 的大小(字节),可以通过
pathconf
或 fpathconf
来设置此值
其 API 如下:
1 |
|
示例
1 |
|
socketpair
对于 socket 而言,创建 pipe
最合理的方式是使用socketpair()
:
1 |
|
socketpair()
参数列表前 3 个与socket()
一样,只是其domain
只能是AF_UNIX
socketpair()
创建的描述符都是双向的
dup
1 |
|
dup
得到文件描述符副本,副本和原文件描述符指向同一个文件。
dup 的关键在于: 返回当前可用的最小描述符
比如先关闭标准输出,然后立即调用 dup,此时 dup 返回最小描述符则是 1。
也就是说 1 和当前文件描述符指向同一个文件,那么调用 printf
时,内容也就写入文件了。
看下面示例: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char *argv[]) {
int fd = open("./output", O_CREAT | O_RDWR);
if (fd < 0) {
perror("create file failed:");
return -1;
}
printf("This message is before dup\n");
close(STDOUT_FILENO);
int ret_fd = dup(fd);
printf("This message is after dup\n");
printf("dup return fd is %d\n", ret_fd);
return 0;
}dup()
后的printf
内容,输出到 output
文件中。
readv 和 writev
1 |
|
与recvmsg(),sendmsg()
类似。
sendfile
1 |
|
sendfile 是零拷贝函数,因为是在内核中完成文件内容的复制,就没有用户空间到内核空间这一层的拷贝了。 - 显然这样的操作效率更高
下面验证服务端将一个文件发送给客户端,服务端代码: 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
60
61
62
63
64
65
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("usage: %s <port> <filepath>\n", argv[0]);
return -1;
}
int port = atoi(argv[1]);
//addr
struct sockaddr_in socket_addr;
memset(&socket_addr, 0, sizeof(socket_addr));
socket_addr.sin_family = AF_INET;
socket_addr.sin_port = htons(port);
socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
perror("can't create socket:");
return -1;
}
//bind
if (bind(socket_fd, (const struct sockaddr *)&socket_addr, sizeof(socket_addr)) < 0) {
perror("bind socket and address failed:");
return -1;
}
//listen
if (listen(socket_fd, 5) < 0) {
perror("listen failed:");
return -1;
}
printf("I'm waiting for client...\n");
//accept
int client_fd = 0;
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
if ((client_fd = accept(socket_fd, (struct sockaddr *)&client_addr, &addr_len)) < 0) {
perror("accept failed!\n");
return -1;
}
printf("connected to client ip: %s, port: %d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
printf("send file %s to client\n", argv[2]);
int file_fd = open(argv[2], O_RDONLY);
struct stat file_stat;
fstat(file_fd, &file_stat);
if (sendfile(client_fd, file_fd, NULL, file_stat.st_size) < 0) {
perror("sendfile failed:");
}
close(client_fd);
close(socket_fd);
return 0;
}
通过 telnet
连接服务端后便可以获取到该文件了
mmap 和 munmap
1 |
|
prot 设置内存段的访问权限: - PROT_READ : 可读 - PROT_WRITE: 可写 - PROT_EXEC: 可执行 - PROT_NONE: 不能被访问
flags 控制内存段内容被修改后程序的行为: - MAP_SHARED: 共享内存,对内存的修改被映射到文件中 - MAP_PRIVATE: 私有内存,对内存的修改不会被映射到文件中 - MAP_ANONYMOUS: 这段内存不是从文件映射来的,内容被初始化为全 0 - MAP_FIXED: 内存段必须位于 addr 参数指定的地址处,start 必须与内存页对齐 - MAP_HUGETLB: 按照大内存页面来分配内存空间
splice
1 |
|
此函数也是直接在内核操作,属于零拷贝高效率操作。
flags 控制数据如何移动: - SPLICE_F_MOVE : 内核尝试按整页移动数据 - SPLICE_F_NONBLOCK : 以非阻塞的形式操作 - SPLICE_F_MORE: 提示内核后续还会读取更多数据
tee
1 |
|
fcntl
1 |
|