Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:
管道是一种最基本的IPC机制,也称匿名管道,应用于有血缘关系的进程之间,完成数据传递。调用pipe函数即可创建一个管道。
有如下特质:
函数调用成功返回读端和写端的文件描述符,其中fd[0]是读端,fd[1]是写端,向管道读写数据是通过使用这两个文件描述符进行的,读写管道的实质是操作内核缓冲区。
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?
一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在血缘关系,这里的血缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。父子进程间具有相同的文件描述符,且指向同一个管道pipe,其他没有关系的进程不能获得pipe()产生的两个文件描述符,也就不能利用同一个管道进行通信。
第一步:父进程创建管道
第二步:父进程fork出子进程
第三步:父进程关闭fd[0],子进程关闭fd[1]
创建步骤总结:
/*
* @Descripttion: 使用pipe完成ps aux | grep bash
* @version:
* @Author: Lzy
* @Date: 2021-07-21 16:50:31
* @LastEditors: Lzy
* @LastEditTime: 2021-07-21 17:52:46
*/
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd[2];
int ret = pipe(fd);
if (ret < 0)
{
perror("pipe error");
return -1;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork error");
}
else if (pid > 0)
{
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
perror("excelp error");
wait(NULL);
}
else
{
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("grep", "grep", "bash", NULL);
perror("execlp error");
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd[2];
int ret;
pid_t pid;
//创建一个管道
ret = pipe(fd);
if(ret<0)
{
perror("pipe error");
return -1;
}
int i = 0;
int n = 2;
for(i=0; i<n; i++)
{
//创建子进程
pid = fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid==0)
{
break;
}
}
if(i==n)
{
close(fd[0]);
close(fd[1]);
pid_t wpid;
int status;
while(1)
{
//等待回收子进程
wpid = waitpid(-1, &status, WNOHANG);
if(wpid==0) //没有子进程退出
{
sleep(1);
continue;
}
else if(wpid==-1) //已经没有子进程
{
printf("no child is living, wpid==[%d]\n", wpid);
exit(0);
}
else if(wpid>0)
{
if(WIFEXITED(status)) //正常退出
{
printf("child normal exited, status==[%d]\n", WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)) //被信号杀死
{
printf("child killed by signo==[%d]\n", WTERMSIG(status));
}
}
}
}
//第一个子进程
if(i==0)
{
close(fd[0]);
//将标准输出重定向到管道到写端
dup2(fd[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
perror("execlp error");
close(fd[1]);
}
//第二个子进程
if(i==1)
{
printf("child: fpid==[%d], cpid==[%d]\n", getppid(), getpid());
close(fd[1]);
//将标准输入重定向到管道到读端
dup2(fd[0], STDIN_FILENO);
execlp("grep", "grep", "--color", "bash", NULL);
perror("execlp error");
close(fd[0]);
}
return 0;
}
默认情况下,管道的读写两端都是阻塞的,若要设置读或者写端为非阻塞,则可参考下列三个步骤进行:
第1步: int flags = fcntl(fd[0], F_GETFL, 0);
第2步: flag |= O_NONBLOCK;
第3步: fcntl(fd[0], F_SETFL, flags);
若是读端设置为非阻塞:
long fpathconf(int fd, int name);
printf("pipe size==[%ld]\n", fpathconf(fd[0], _PC_PIPE_BUF));
printf("pipe size==[%ld]\n", fpathconf(fd[1], _PC_PIPE_BUF));
FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间通信。但通过FIFO,不相关的进程也能交换数据。
FIFO是Linux基础文件类型中的一种(文件类型为p,可通过ls -l查看文件类型)。但FIFO文件在磁盘上没有数据块,文件大小为0,仅仅用来标识内核中一条通道。进程可以打开这个文件进行read/write,实际上是在读写内核缓冲区,这样就实现了进程间通信。
当创建了一个FIFO,就可以使用open函数打开它,常见的文件I/O函数都可用于FIFO。如:close、read、write、unlink等。
FIFO严格遵循先进先出(first in first out),对FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
思路:
注意:myfifo文件是在进程A中创建的,如果先启动进程B会报错。思考一下如何解决这个问题呢???
进程A写入数据
/*
* @Descripttion:
* @version:
* @Author: Lzy
* @Date: 2021-07-22 16:54:53
* @LastEditors: Lzy
* @LastEditTime: 2021-07-22 17:33:56
*/
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
// int access(const char *pathname, int mode);
int ret = access("./myfifo", F_OK);
if (ret == -1)
{
//文件不存在
// int mkfifo(const char *pathname, mode_t mode);
ret = mkfifo("./myfifo", 0777);
if (ret == -1)
{
perror("mkfifo error:");
return -1;
}
}
int fd = open("./myfifo",O_RDWR);
if (fd == -1){
perror("open error:");
return -1;
}
write(fd,"hello world!\n",sizeof("hello world"));
sleep(20);
close(fd);
return 0;
}
进程B读取数据
/*
* @Descripttion:
* @version:
* @Author: Lzy
* @Date: 2021-07-22 16:54:53
* @LastEditors: Lzy
* @LastEditTime: 2021-07-22 18:01:43
*/
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
// int access(const char *pathname, int mode);
int ret = access("./myfifo", F_OK);
if (ret == -1)
{
//文件不存在
// int mkfifo(const char *pathname, mode_t mode);
ret = mkfifo("./myfifo", 0777);
if (ret == -1)
{
perror("mkfifo error:");
return -1;
}
}
int fd = open("./myfifo",O_RDWR);
if (fd == -1){
perror("open error:");
return -1;
}
char str[20];
memset(str,0,sizeof(str));
read(fd,str,sizeof(str));
printf("read is %s",str);
close(fd);
return 0;
}
存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。从缓冲区中取数据,就相当于读文件中的相应字节;将数据写入缓冲区,则会将数据写入文件。这样,就可在不使用read和write函数的情况下,使用地址(指针)完成I/O操作。
使用存储映射这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。
/*
* @Descripttion: 使用mmap完成对文件的读写操作,传入第一个参数为文件名,若没传入则使用默认文件"test.log"。
* @version:
* @Author: Lzy
* @Date: 2021-07-22 14:36:29
* @LastEditors: Lzy
* @LastEditTime: 2021-07-22 15:02:25
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd = 0;
char *filename = NULL;
if (argc > 1)
{
//传入文件名,使用提供文件
filename = argv[1];
}
else
{
//使用默认文件名
filename = "test.log";
}
printf("%s\n", filename);
// int access(const char *pathname, int mode);
fd = open(filename, O_RDWR | O_CREAT, 0777);
if (fd == -1)
{
perror("open error:");
}
// ssize_t write(int fd, const void *buf, size_t count);
//使文件大小大于0,mmap函数要求
write(fd, "1", strlen("1"));
//获取文件大小
struct stat statbuf;
int ret = stat(filename, &statbuf);
if (ret < 0)
{
perror("stat error:");
}
// void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);
void *addr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED)
{
perror("mmap error");
return -1;
}
memcpy(addr, "hello world!", strlen("hello world!"));
char *str = (char *)addr;
printf("read is [%s]\n", str);
return 0;
}
/*
* @Descripttion: 使用mmap完成父子进程间通信,传入第一个参数为文件名,若没传入则使用默认文件"test.log"。
* @version:
* @Author: Lzy
* @Date: 2021-07-22 14:36:29
* @LastEditors: Lzy
* @LastEditTime: 2021-07-22 16:46:01
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd = 0;
char *filename = NULL;
if (argc > 1)
{
//传入文件名,使用提供文件
filename = argv[1];
}
else
{
//使用默认文件名
filename = "test.log";
}
printf("%s\n", filename);
// int access(const char *pathname, int mode);
fd = open(filename, O_RDWR | O_CREAT, 0777);
if (fd == -1)
{
perror("open error:");
}
// ssize_t write(int fd, const void *buf, size_t count);
//使文件大小大于0,mmap函数要求
write(fd, "1", strlen("1"));
//获取文件大小
struct stat statbuf;
int ret = stat(filename, &statbuf);
if (ret < 0)
{
perror("stat error:");
}
// void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);
void *addr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED)
{
perror("mmap error");
return -1;
}
pid_t pid = fork();
if(pid < 0){
perror("fork error:");
}else if(pid > 0){
memcpy(addr,"hello world!",strlen("hello world!"));
wait(NULL);
munmap(addr,statbuf.st_size);
}else if(pid == 0){
char str [20];
memset(str,0,sizeof(str));
memcpy(str,addr,strlen(addr));
printf("read is [%s]\n",str);
}
return 0;
}
进程一写文件:
/*
* @Descripttion: 使用mmap完成没有血缘关系的进程间通,传入第一个参数为文件名,若没传入则使用默认文件"test.log"。
* @version:
* @Author: Lzy
* @Date: 2021-07-22 14:36:29
* @LastEditors: Lzy
* @LastEditTime: 2021-07-22 16:52:25
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd = 0;
char *filename = NULL;
if (argc > 1)
{
//传入文件名,使用提供文件
filename = argv[1];
}
else
{
//使用默认文件名
filename = "test.log";
}
printf("%s\n", filename);
// int access(const char *pathname, int mode);
fd = open(filename, O_RDWR | O_CREAT, 0777);
if (fd == -1)
{
perror("open error:");
}
// ssize_t write(int fd, const void *buf, size_t count);
//使文件大小大于0,mmap函数要求
write(fd, "1", strlen("1"));
//获取文件大小
struct stat statbuf;
int ret = stat(filename, &statbuf);
if (ret < 0)
{
perror("stat error:");
}
// void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);
void *addr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED)
{
perror("mmap error");
return -1;
}
char str [20];
memset(str,0,sizeof(str));
memcpy(str,addr,strlen(addr));
printf("read is [%s]\n",str);
return 0;
}
进程2读
/*
* @Descripttion: 使用mmap完成没有血缘关系的进程间通,传入第一个参数为文件名,若没传入则使用默认文件"test.log"。
* @version:
* @Author: Lzy
* @Date: 2021-07-22 14:36:29
* @LastEditors: Lzy
* @LastEditTime: 2021-07-22 16:52:25
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd = 0;
char *filename = NULL;
if (argc > 1)
{
//传入文件名,使用提供文件
filename = argv[1];
}
else
{
//使用默认文件名
filename = "test.log";
}
printf("%s\n", filename);
// int access(const char *pathname, int mode);
fd = open(filename, O_RDWR | O_CREAT, 0777);
if (fd == -1)
{
perror("open error:");
}
// ssize_t write(int fd, const void *buf, size_t count);
//使文件大小大于0,mmap函数要求
write(fd, "1", strlen("1"));
//获取文件大小
struct stat statbuf;
int ret = stat(filename, &statbuf);
if (ret < 0)
{
perror("stat error:");
}
// void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);
void *addr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED)
{
perror("mmap error");
return -1;
}
char str [20];
memset(str,0,sizeof(str));
memcpy(str,addr,strlen(addr));
printf("read is [%s]\n",str);
return 0;
}
/*
* @Descripttion: 测试mmap匿名映射进行亲缘关系通信(推荐)
* @version:
* @Author: Lzy
* @Date: 2021-07-22 10:43:24
* @LastEditors: Lzy
* @LastEditTime: 2021-07-22 14:34:15
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
// void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
{
perror("mmap error");
return -1;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork error");
return -1;
}
else if (pid > 0)
{
memcpy(addr, "hello world!", strlen("hello world!"));
wait(NULL);
}
else if (pid == 0)
{
char *p = (char *)addr;
printf("%s", p);
}
return 0;
}