文件描述符
? ? ? ?Linux系统中一切皆文件,系统中一切都被抽象成文件。对这些文件的读写都需要通过文件描述符来完成。这些文件描述符存储在文件描述符表中,内核为每一个进程维护了一个文件描述符表。文件描述符表能够存储的打开的文件数是有限制的,默认为1024个。 当一个进程被启动之后,内核PCB的文件描述符表中就已经分配了三个文件描述符:
- STDIN_FILENO:标准输入,通过该文件描述符将数据输入到终端文件。
- STDOUT_FILENO:标准输出,通过该文件描述符将数据通过终端输出。
- STDERR_FILENO:标准错误,通过该文件描述符将错误信息通过终端输出。
管道
管道的本质是内核中的一块内存(内核缓冲区),管道数据通过循环队列来维护,管道对应的内核缓冲区大小为64KB。
- 管道分为读端和写端,管道中的数据只能从写端流向读端,要实现双向同时通信需要设置两个管道。
- 管道中的数据只能读一次,数据一旦被读出就从管道中被抛弃。
- 对管道的读写操作默认是阻塞的。
- 管道内部保证同步机制。
- 数据以字节流的形式写入管道。
读管道,需要根据写端的状态进行分析。
- 写端未关闭
A. 管道无数据 -> 阻塞。如果管道中被写入了数据,阻塞解除,继续读。 B. 管道有数据 -> 不阻塞。如果管道中的数据被读完了,再继续读,管道会阻塞。 - 写端关闭
A.管道无数据 -> 阻塞解除,read函数返回0。 B.管道有数据 -> read函数先将数据读出,数据读完之后read函数返回0,阻塞解除。
写管道,需要根据读端的状态进行分析。
- 读端未关闭
A. 管道未写满 -> 不阻塞。一直写数据。 B. 管道写满 -> 阻塞。当读端将管道数据读走后,阻塞解除,继续写。 - 读端关闭
A.管道破裂,收到信号SIGPIPE,进程异常终止。
匿名管道
匿名管道只能实现有血缘关系的进程间通信。
#include <fcntl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe");
exit(0);
}
pid_t pid = fork();
if (pid == 0)
{
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
perror("execlp");
}
else if (pid > 0)
{
close(fd[1]);
char buf[4096];
while (1)
{
memset(buf, 0, sizeof(buf));
int len = read(fd[0], buf, sizeof(buf));
if (len == 0)
{
break;
}
printf("%s\n\nlen = %d\n\n", buf, len);
}
close(fd[0]);
wait(NULL);
}
return 0;
}
有名管道
? ? ? ?有名管道既可以进行有血缘关系的进程间通信,也可以进行没有血缘关系的进程间通信。有名管道在磁盘上有实体文件,由于有名管道也是将数据存储到内核缓冲区中,所以其大小永远为0,打开这个磁盘上的管道文件就可以得到操作有名管道的文件描述符,通过文件描述符读写管道存储在内核中的数据。 ? ? ? ?有名管道操作需要通过open函数得到读写管道的文件描述符,如果只是读端打开了或者只是写端打开了,进程会阻塞在这里不会向下执行,直到在另一个进程中将管道的对端打开,当前进程的阻塞才解除。
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int ret = mkfifo("./testfifo", 0664);
if (ret == -1)
{
perror("mkfifo");
exit(0);
}
printf("管道文件创建成功!\n");
int wfd = open("./testfifo", O_WRONLY);
if (wfd == -1)
{
perror("open");
exit(0);
}
printf("以只写的方式打开管道文件成功!\n");
int i = 0;
while (i < 10)
{
char buf[1024];
sprintf(buf, "hello fifo, 我在写管道-%d\n", i);
write(wfd, buf, strlen(buf));
i++;
sleep(1);
}
close(wfd);
printf("管道的写端已经关闭\n");
return 0;
}
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int rfd = open("./testfifo", O_RDONLY);
if (rfd == -1)
{
perror("open");
exit(0);
}
printf("以只读的方式打开文件成功!\n");
while (1)
{
char buf[1024];
memset(buf, 0, sizeof(buf));
int len = read(rfd, buf, sizeof(buf));
printf("读出的数据: %s\n", buf);
if (len == 0)
{
printf("管道的写端已经关闭!\n");
break;
}
}
close(rfd);
return 0;
}
内存映射
? ? ? ?内存映射区对应的内存空间在进程的用户区(用于加载动态库的区域),进程间通信使用的内存映射区在每个进程内部都有一块。进程将各自的内存映射区和同一个磁盘文件进行映射,当进程A中的内存映射区数据被修改,数据会被自动同步到磁盘文件,同时和磁盘文件建立映射关系的其他进程内存映射区中的数据也会和磁盘文件进行数据的实时同步,保障了各个进程之间的数据共享。
- 内存映射既可以实现有血缘关系的进程间通信也可以实现没有血缘关系的进程间通信。
- 内存映射区的读写是非阻塞的。
- 进程退出后内存映射区也就没有了。
void *mmap(
void *addr,
size_t length,
int prot,
int flags,
int fd,
off_t offset
);
? ? ? ?由于创建子进程会发生虚拟地址空间的拷贝,那么在父进程中创建的内存映射区也会被拷贝到子进程中,这样子进程可以直接使用内存映射区。对于没有血缘关系的进程间通信,需要在每个进程中分别创建内存映射区,但是这些进程的内存映射区必须要关联相同的磁盘文件,这样才能实现进程间的数据同步。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
int fd = open("./english.txt", O_RDWR);
int code = ftruncate(fd, 4000);
if (code == -1)
{
perror("ftruncate");
exit(0);
}
void *ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
perror("mmap");
exit(0);
}
const char *pt = "You don't get to choose how you're going to die, or when. You can only decide how you're going to live now.\n";
memcpy(ptr, pt, strlen(pt) + 1);
munmap(ptr, 4000);
close(fd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
int fd = open("./english.txt", O_RDWR);
void *ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
perror("mmap");
exit(0);
}
printf("从映射区读出的数据: %s\n", (char *)ptr);
munmap(ptr, 4000);
close(fd);
return 0;
}
参考:https://subingwen.cn/linux/file-descriptor/ 参考:https://subingwen.cn/linux/pipe/ 参考:https://subingwen.cn/linux/mmap/
|