进程是具有独立性的,进程之间在保持各自独立性时,可能会存在特定的协同工作的场景,比如一个进程要把数据交付给另一个进程进行处理,这种场景下就需要进程间通信。要实现进程间通信,操作系统就要设计适合系统的通信方式,然而进程是具有独立性的,要交互数据,成本非常高,因为一个进程是获取不到另一个进程的资源的,如果要实现进程间通信就必须让不同的进程可以看到同一份资源。
所以说,进程间通信的本质是由操作系统提供一份通信进程可以看到的公共资源,这份资源可能以文件方式提供,也可能队列的方式,也可能是原始的内存块。
管道
System V进程间通信
POSIX进程间通信
管道使用:
[cwx@VM-20-16-centos pipe]$ cat mytest.c | wc -l
13
[cwx@VM-20-16-centos pipe]$
父进程调用fork()创建子进程,子进程以父进程为模板创建PCB,struct files_struct结构,并将父进程的数据拷贝到自己的数据结构里,父子进程就可以通过struct files_struct指向同一个文件内核缓冲区,看到了同一份资源,就可以实现进程间通信。
接口介绍:
#include
功能:创建一个匿名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
通过代码实现父子进程间通信:
#include
#include
#include
#include
// pipefd[2]:输出型参数
// 创建成功返回0,错误返回-1
// int pipe(int pipefd[2]);
int main()
{
int pipe_fd[2] = {0};
if(pipe(pipe_fd) != 0){
perror("create pipe");
return 1;
}
// 父进程读取,子进程写入
if(fork() == 0){
// 子进程
close(pipe_fd[0]); // 关闭读端
const char* msg = "hello world ";
while(1){
write(pipe_fd[1], msg, strlen(msg));
}
exit(0);
}
// 父进程
close(pipe_fd[1]); // 关闭写端
while(1){
char buffer[64] = {0};
// read返回值为0,表示子进程关闭文件写端
ssize_t s = read(pipe_fd[0], buffer, sizeof(buffer)-1);
if(s == 0){
printf("child process quit...\n");
break;
}
else if(s > 0){
buffer[s] = 0;
printf("child process: %s\n", buffer);
}
else{
printf("read error\n");
break;
}
}
return 0;
}
运行结果:
[cwx@VM-20-16-centos pipe]$ ./pipe_process
child process: hello world
child process: hello world
child process: hello world
child process: hello world
child process: hello world
child process: hello world
child process: hello world
^C
[cwx@VM-20-16-centos pipe]$
用fork来共享管道原理:
站在文件描述符角度-深度理解管道:
1、读端暂停读数据或者读端读数据比较慢,写端等待读端(write调用阻塞)。
2、写端暂停写数据或者写端写数据比较慢,读端等待写端(read调用阻塞)。
3、读端关闭,写端收到操作系统发送的SIGPIPE信号终止进程。
4、写端关闭,读端读完匿名管道内部数据后,读到0,表示读到文件结尾。
验证读端关闭的情况:
#include
#include
#include
#include
#include
// pipefd[2]:输出型参数
// 创建成功返回0,错误返回-1
// int pipe(int pipefd[2]);
int main()
{
int pipe_fd[2] = {0};
if(pipe(pipe_fd) != 0){
perror("create pipe");
return 1;
}
// 父进程读取,子进程写入
if(fork() == 0){
// 子进程
close(pipe_fd[0]); // 关闭读端
const char* msg = "hello world ";
while(1){
write(pipe_fd[1], msg, strlen(msg));
sleep(3);
}
exit(0);
}
// 父进程
close(pipe_fd[1]); // 关闭写端
while(1){
char buffer[64] = {0};
ssize_t s = read(pipe_fd[0], buffer, sizeof(buffer)-1);
if(s == 0){
printf("child process quit...\n");
break;
}
else if(s > 0){
buffer[s] = 0;
printf("child process: %s\n", buffer);
break;
}
else{
printf("read error\n");
break;
}
}
close(pipe_fd[0]); // 关闭读端
// 进程等待获取退出信号
int status = 0;
waitpid(-1, &status, 0);
printf("exit signal: %d\n", status&0x7F);
return 0;
}
运行结果:
[cwx@VM-20-16-centos pipe]$ ./pipe_process
child process: hello world
exit signal: 13
读端在读取一条信息后,立刻退出循环,关闭读端,父进程waitpid()进程等待获取子进程退出的退出码,exit signal: 13,kill -l查看13号信号为SIGPIPE。
验证写端关闭的情况:
#include
#include
#include
#include
#include
// pipefd[2]:输出型参数
// 创建成功返回0,错误返回-1
// int pipe(int pipefd[2]);
int main()
{
int pipe_fd[2] = {0};
if(pipe(pipe_fd) != 0){
perror("create pipe");
return 1;
}
// 父进程读取,子进程写入
if(fork() == 0){
// 子进程
close(pipe_fd[0]); // 关闭读端
const char* msg = "hello world ";
while(1){
write(pipe_fd[1], msg, strlen(msg));
sleep(3);
break;
}
close(pipe_fd[1]);
exit(0);
}
// 父进程
close(pipe_fd[1]); // 关闭写端
while(1){
char buffer[64] = {0};
// read返回值为0,表示子进程关闭文件写端
ssize_t s = read(pipe_fd[0], buffer, sizeof(buffer)-1);
if(s == 0){
printf("child process quit...\n");
break;
}
else if(s > 0){
buffer[s] = 0;
printf("child process: %s\n", buffer);
}
else{
printf("read error\n");
break;
}
}
return 0;
}
运行结果:
[cwx@VM-20-16-centos pipe]$ ./pipe_process
child process: hello world
child process quit...
子进程写入字符串三秒后退出循环,关闭写端。父进程read读取子进程写入的数据,之后read返回值为0,表示子进程关闭文件写端。
子进程定义count变量,每次向管道写入一个字符,count++,父进程等待子进程写入,测试管道可以容纳多少bit的字符。
测试代码:
#include
#include
#include
int main()
{
int pipe_fd[2] = {0};
if(pipe(pipe_fd) != 0){
perror("create pipe");
return 1;
}
// 父进程读取,子进程写入
if(fork() == 0){
// 子进程
close(pipe_fd[0]); // 关闭读端
int count = 0;
while(1){
write(pipe_fd[1], "a", 1);
count++;
printf("count: %d\n", count);
}
exit(0);
}
// 父进程
close(pipe_fd[1]); // 关闭写端
while(1);
return 0;
}
运行结果:
......
count: 65530
count: 65531
count: 65532
count: 65533
count: 65534
count: 65535
count: 65536
^C
[cwx@VM-20-16-centos test_pipe]$
当count累加到65536时,子进程写端不再写入数据,通过以上代码可以测试出管道的容量为65536bytes=64kb。
通过man 7 pipe查看管道描述:
也就是说当管道满的时候,写端停止写入,当读端一次性读取的数据少于PIPE_BUF时,写端仍然暂停写入,当读端一次性读取的数据大于PIPE_BUF时,写端才会解除阻塞,继续写入。
创建命名管道:
$ mkfifo filename
#include
#include
int mkfifo(const char *filename, mode_t mode);
mkfifo() 创建一个名为 pathname 的 FIFO 特殊文件。 mode 指定 FIFO 的权限。
它由进程的umask以通常的方式修改:创建文件的权限为(mode & ~umask)。
测试代码:
umask(0);
if(mkfifo("./fifo", 0666) < 0){
perror("mkfifo");
}
运行结果:
[cwx@VM-20-16-centos fifo]$ ll
total 1
prw-rw-rw- 1 cwx cwx 0 Aug 7 17:26 fifo
通过命名管道实现server进程和client进程间通信,client进程给server进程传输数据,并结合进程替换exec函数,实现打印目录文件等操作。
[cwx@VM-20-16-centos fifo]$ ll
total 16
-rw-rw-r-- 1 cwx cwx 555 Jul 25 16:19 client.c
-rw-rw-r-- 1 cwx cwx 206 Jul 25 16:18 comm.h
-rw-rw-r-- 1 cwx cwx 139 Jul 25 15:50 Makefile
-rw-rw-r-- 1 cwx cwx 1452 Aug 7 17:23 server.c
Makefile文件:
.PHONY:all
all: client server
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -rf client server fifo
comm.h头文件:
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#define MY_FIFO "./fifo"
server.c:
#include "comm.h"
int main()
{
// 创建命名管道
umask(0);
if(mkfifo(MY_FIFO, 0666) < 0){
perror("mkfifo");
}
// 文件操作
int fd = open(MY_FIFO, O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
// 读写操作
while(1){
char buffer[64] = {0};
ssize_t s = read(fd, buffer, sizeof(buffer)-1);
if(s > 0){
// 读取成功
buffer[s] = 0;
if(strcmp(buffer, "show") == 0){
if(fork() == 0){
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
exit(1);
}
waitpid(-1, NULL, 0);
}
else if(strcmp(buffer, "run") == 0){
if(fork() == 0){
execl("/usr/bin/sl", "sl", NULL);
exit(1);
}
waitpid(-1, NULL, 0);
}
else{
printf("cilent# %s\n", buffer);
}
}
else if(s == 0){
// 读取退出
printf("client quit...\n");
break;
}
else{
// 读取错误
perror("read");
break;
}
}
close(fd);
return 0;
}
client.c:
#include "comm.h"
int main()
{
int fd = open(MY_FIFO, O_WRONLY);
if(fd < 0){
perror("open");
return 1;
}
// 读写操作
while(1){
printf("请输入# ");
fflush(stdout);
char buffer[64] = {0};
// 将数据标准输入到client内部
ssize_t s = read(0, buffer, sizeof(buffer)-1);
if(s > 0){
buffer[s-1] = 0; // 去掉回车
write(fd, buffer, strlen(buffer));
}
}
close(fd);
return 0;
}