- 进程间通信目的:
- 数据传输:两个进程之间及进行数据交互;
- 资源共享:两个进程之间共享同一块资源;
- 通知事件:一个进程需要向另一个进程通知某件事(比如说进程终止时要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行。
一、管道:
(一).匿名管道:没有名字的管道
-
#include <unistd.h> 功能:创建一无名管道 原型 int pipe(int fd[2]); 参数 fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 返回值:成功返回0,失败返回错误代码
- 匿名管道:内核中没有标识符的缓冲区,只用于亲缘关系的进程间通信。
- 进程创建管道,操作系统为了能让进程对管道进行读写,因此向外返回了两个操作句柄,一个用于读(pipefd[0]),一个用于写(pipefd[1])。
- 管道的使用:在进程中创建管道之后,再创建一个子进程,子进程会复制父进程中的管道,可以通过复制的两个操作句柄进行同通信。
- 从键盘读取数据,写入管道,读取管道,写到屏幕:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fds[2];
char buf[100];
int len;
if ( pipe(fds) == -1 )
perror("make pipe"),exit(1);
// 键盘中读入数据
while ( fgets(buf, 100, stdin) ) {
len = strlen(buf);
// 向管道中写入数据
if ( write(fds[1], buf, len) != len ) {
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// 从管道中读
if ( (len=read(fds[0], buf, 100)) == -1 ) {
perror("read from pipe");
break;
}
// 写到终端
if ( write(1, buf, len) != len ) {
perror("write to stdout");
break;
}
}
}
例如:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main(int argc, char *argv[])
{
int pipefd[2];
if (pipe(pipefd) == -1)
ERR_EXIT("pipe error");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0) {
close(pipefd[0]);
write(pipefd[1], "hello", 5);
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
char buf[10] = { 0 };
read(pipefd[0], buf, 10);
printf("buf=%s\n", buf);
return 0;
}
(二)命名管道:有名字的管道。
-
1.命令行创建: $ mkfifo filename 2.程序中创建: int mkfifo(const char *filename,mode_t mode); 例如: int main(int argc, char *argv[]) { mkfifo("p2", 0644); return 0; } filename: 管道名称; mode:权限
- 命名管道:内核中具有标识符的管道缓冲区,可用于同意主机上的所有进程之间的通信。
- 管道若只用读方式打开,则会堵塞,直到打开写方式为止。
- 管道若只用写方式打开,则会堵塞,直到打开读方式为止。
读取文件,写入命名管道:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
mkfifo("tp", 0644);
int infd;
infd = open("abc", O_RDONLY);
if (infd == -1) ERR_EXIT("open");
int outfd;
outfd = open("tp", O_WRONLY);
if (outfd == -1) ERR_EXIT("open");
char buf[1024];
int n;
while ((n=read(infd, buf, 1024))>0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
return 0;
}
读取管道,写入目标文件:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)
do
{
perror(m);
exit(EXIT_FAILURE);
} while(0)
int main(int argc, char *argv[])
{
int outfd;
outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd == -1) ERR_EXIT("open");
int infd;
infd = open("tp", O_RDONLY);
if (outfd == -1)
ERR_EXIT("open");
char buf[1024];
int n;
while ((n=read(infd, buf, 1024))>0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
unlink("tp");
return 0;
}
- 匿名管道与命名管道的区别:
(1).匿名管道由pipe函数创建并打开。
(2).命名管道由mkfifo函数创建,打开用open。
(3).FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。 - 管道的特征:
(1)本质:内核中的缓冲区,多个进程同时访问该缓冲区。
(2)特性:- 半双工通信;
- 生命周期随进程;
- 自带同步和互斥(读和写);
- 管道提供的是字节流传输。
二、共享内存:最快的通信方式。
- 将同一块物理内存映射到进程各自的虚拟地址空间中就可以实现数据共享,可以通过自己的虚拟地址空间进行访问。
- 最快的通信方式:直接通过虚拟地址进行访问内存,进行数据共享,而其他通信方式需要讲数据拷贝到内核,再从内核中中拷贝出来,才能实现通信,而共享内存少了这两次拷贝,所以速度最快。
- 共享内存的实现:
①创建并打开共享内存;
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
②将共享内存映射到虚拟地址空间;
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
③通过任意的内存操作,对共享内存操作;
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
④解除映射(共享内存会记录当前进程映射的链接数);
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
⑤删除共享内存。(链接数为0时删除,不为0则不会立即删除,会等到链接数等于0时在删除)。
4. 本质:进程间通过映射一块物理内存实现数据共享。
5. 特征:①最快的进程通信;②生命周期随内核;③内存操作具有二义性。
三、消息队列:
- 创建一个优先级队列,多个进程间通过向队列中添加数据块和获取数据块实现数据通信。
- 特性:
- 生命周期随内核;
- 消息队列自带同步和互斥;
- 消息队列存储的数据是有最大长度限制的。
四、信号量:
- 本身不用于用于实现通信,而是实现进程间的同步与互斥,(保护进程对灵界资源的访问,进行访问时不会出现数据二义性)。
- 本质:计数器+pcb等待队列;