1.基本介绍
进程间通信的目的:数据传输、资源共享、进程控制
如何通信的?
1.因为进程独立性,因此通信需要双方拥有公共的媒介才能通信,而这个媒介由操作系统提供;
2.因为通信场景不同,因此操作系统也提供多种不同的进程间通信方式;
2.进程间通信方式:
1>管道:匿名管道/命名管道---半双工通信1
管道原理:操纵系统在内核创建一块缓冲区,并且为用户提供管道的操作句柄(文件描述符),用户通过IO接口实现管道的操作;描述符共有两个一个用于读数据,一个用于写数据
匿名管道:匿名管道是匿名的,其他进程在内核中无法找到这个管道,因此匿名管道实现进程间通信,只能用于具有亲缘关系的进程(子进程通过复制父进程的pcb--得到管道的操作句柄(两个文件描述符))
管道读写特性:
若管道中没有数据,那么如果read读取数据,则会阻塞,直到读到数据返回
若管道中数据满了,那么如果继续write写入数据,则会阻塞,直到能够写入数据
如果所有读端关闭了,这时候write写入数据则会触发异常----SIGPIPE(导致程序退出)
如果所有写端都关闭了,这时候read读取数据,将数据读完后继续则会返回0
管道自带同步与互斥
同步:保证临界资源访问的时序可控性
互斥:保证临界资源同一时间的唯一访问性
管道符的实现:连接两个命令,将第一个命令的输出结果作为第二个命令的输入数据处理 ls | grep make
主进程创建两个子进程,在两个中分别进行程序替换让两个子进程分别运行ls程序和grep程序
ls程序是浏览目录,将结果写入到标准输出
grep程序是标准输入读取数据(循环读取数据),进行过滤
因为进程1,执行的ls程序将结果写入到了标准输出,而不是管道,并没有将数据交给其他进程因此需要对进程1,进行一个标准输出重定向,将ls的结果不是写入标准输出,而是写入到管道
因为进程2,执行的gerp程序是从标准输入读取数据处理,而不是管道,因此无法获取前边命名的结果,因此需要对进程2,进行一个标准输入重定向,为了将本身从0指向的标准输入读取数据改为从管道读取段读取数据
因为进程2不知道具体前边命令写入多少数据,因此只能循环读取,读取多少处理多少;
问题:加入管道中数据已经读取完了,读不到数据则会导致read阻塞-进程2无法退出;因此需要将所有的管道写入段关闭掉,这时候读取不到数据但会0,认为没有数据了,就退出了;
命名管道:具有名字的管道----体现在文件系统可见性
创建命名管道会在文件系统中创建一个管道文件,本机上的所有进程都可以通过打开这个管道文件进而访问管道实现进程间通信
创建命名管道的方式:1、命令行方式创建:mkfifo filename;2、从程序中创建 int mkfifo(cosnt char *fifoname, mode_t mode);
命名管道可以实现同一主机上任意进程间通信
open打开特性:
若管道文件以只读打开,但是当前这个管道文件又没有被其它进程以写的方式打开,则阻塞,阻塞到其他进程以写的方式打开这个管道文件
若管道文件以只写打开,但是当前这个管道文件又没有被其它进程以读的方式打开,则阻塞,阻塞到其他进程以读的方式打开这个管道文件
命名管道读写特性同于匿名管道
命名管道与匿名管道的区别:
命名管道可以同一主机任意进程间通信,但是匿名管道只能用于具有亲缘关系的进程间通信,----因为命名管道有名字(有文件可见于文件系统之中)
匿名管道实现代码:
/*匿名管道的使用*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(){
//int pipe(int pipefd[2]);
//pipefd:用于接受管道的操作句柄(文件描述符)
//返回值:成功:0 失败:-1
int pipefd[2];
int ret = pipe(pipefd);
if(ret < 0){
perror("pipe error");
return -1;
}
int pid = fork();
if(pid < 0){
perror("fork error");
exit(-1);
}else if(pid == 0){
close(pipefd[0]);
//子进程写数据leihoua~~
int len = 0;
while(1){
char *ptr = "leihoua~~";
printf("write len: %d\n", len);
int ret = write(pipefd[1], ptr, strlen(ptr));
len += ret;
printf("write len: %d\n", len);
}
}else{
close(pipefd[1]);
//父进程读数据
char buf[1024] = {0};
int ret = read(pipefd[0], buf ,1023);
printf("child read buf:[%d-%s]\n", ret, buf);
}
return 0;
}
命名管道代码实现:
读端:
/*************************************************************************
> File Name: fifo_read.c
> Author: ssh
> Mail: sunshihao163@163.com
> Created Time: Mon 15 Apr 2019 03:11:29 PM CST
************************************************************************/
/*命名管道的基本使用*/
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
int main(){
//int mkfifo(const char *pathname, mode_t mode);
// pathname: 命名管道文件路径名
// mode: 创建文件时的权限
// 返回值:成功:0 失败:-1
umask(0);
char *fifo = "./test.fifo";
int ret = mkfifo(fifo, 0664);
if(ret < 0){
//出现错误,如果错误原因不是因为文件已存在,则报错退出
if(errno != EEXIST){
perror("mkfifo error");
return -1;
}
}
printf("i want you, guoxin!\n");
int fd = open(fifo, O_RDONLY);
if(fd < 0){
perror("open error");
return -1;
}
printf("open fifo success!");
while(1){
char buf[1024] = {0};
int ret = read(fd, buf, 1023);
if(ret > 0){
printf("client say:[%s]\n", buf);
}else if(ret == 0){
printf("write close!\n");
return -1;
}else{
perror("read error");
return -1;
}
}
close(fd);
return 0;
}
写端:
/*************************************************************************
> File Name: fifo_write.c
> Author: ssh
> Mail: sunshihao163@163.com
> Created Time: Mon 15 Apr 2019 03:32:29 PM CST
************************************************************************/
/*命名管道的基本使用*/
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
int main(){
//int mkfifo(const char *pathname, mode_t mode);
// pathname: 命名管道文件路径名
// mode: 创建文件时的权限
// 返回值: 成功:0 失败: -1
umask(0);
char *fifo = "./test.fifo";
int ret = mkfifo(fifo, 0664);
if(ret < 0){
//出先错误,如果错误原因不是因为文件已经存在,则报错退出
if(errno != EEXIST){
perror("fifo error");
return -1;
}
}
printf("fifo success!");
int fd = open(fifo, O_WRONLY);
if(fd < 0){
perror("open error");
return -1;
}
printf("open fifo success!");
while(1){
char buf[1024] = {0};
scanf("%s", buf);
write(fd, buf, strlen(buf));
printf("write buf:[%s]\n", buf);
}
close(fd);
return 0;
}
2>共享内存
共享内存是进程通信方式中最快的一种;
共享内存通信原理:在物理内存中开辟一块空间,并且将空间映射到各个进程的虚拟地址空间;这时候进程就可以通过虚拟地址直接对内存进行操作;相较于其他通信方式(将数据拷贝内核态,用的时候再从内核态拷贝到用户态)少了两步用户态/内核态之间的数据拷贝过程,因此最快。
共享内存的操作步骤:
1.创建共享内存---指定标识符,大小,权限---句柄 shmget
2.将共享内存映射到虚拟地址空间---句柄,首地址 shmat
3.进行内存的任意操作---memcpy,printf
4.解除映射关系 shmdt
5.删除共享内存 shmctl
ipcs:查看所有的进程间通信方式
ipcs -m 查看共享内存 ipcs -s 查看信号量 ipcs -q 查看消息队列
ipcrm:删除进程间通信方式
ipcrm -m shmid(共享内存id)
代码实现共享内存:
读端:
/*************************************************************************
> File Name: shm_read.c
> Author: ssh
> Mail: sunshihao163@163.com
> Created Time: Mon 15 Apr 2019 03:51:10 PM CST
************************************************************************/
/*共享内存的基本使用*/
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <sys/shm.h>
#define IPC_KEY 0x12345678
#define PROJ_ID 0x12345678
#define SHM_SIZE 4096
int main(){
//1.创建/打开共享内存
//key_t ftok(const char *pathname, int proj_id);
// 通过文件名与一个proj_id生成一个key值
//int shmget(key_t key, size_t size, int shmflg);
// key: 共享内存标识符
// size: 共享内存大小
// shmflg:选项标志
// IPC_CREAT 存在则打开,不存在则创建
// ICP_EXCL 与IPC_CREAT同用,若存在则报错,不存在则创建
// mode_flags 权限
// 返回值:句柄:正整数 失败:-1
// key_t key = ftok(".", PROJ_ID);
int shmid = shmget(IPC_KEY, SHM_SIZE, IPC_CREAT|0664);
if(shmid < 0){
perror("shmid error");
return -1;
}
//2.将共享内存映射到虚拟地址空间
//void *shmat(int shmid, const void *shmaddr, int shmflg);
// shmid: 共享内存创建返回的句柄
// shmaddr:映射首地址-通常置NULL
// shmflg:
// 0 可读可写
// SHM_RDONLY 只读
// 返回值: 成功:映射首地址 失败:(void*)-1
char *shm_start = (char*)shmat(shmid, NULL, 0);
if(shm_start == (void*)-1){
perror("shmat erroe");
return -1;
}
//3.进行内存操作
int i = 0;
while(1){
printf("%s\n", shm_start);
sleep(1);
}
//4.解除映射关系
//int shmdt(const void *shmaddr);
// shmaddr: 映射首地址
// 返回值:成功:0 失败:-1
shmdt(shm_start);
//5.删除共享内存
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// shmid: 句柄
// cmd: 操作
// IPC_RMID 删除共享内存
// buf: 设置/获取属性信息
// 想获取传入首地址,不想获取了置NULL
// 共享内存并不会被立即删除,而是判断当前连接数,若不为0,则拒绝后
// 续连接,直到为0的时候才会删除这块共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
写端:
/*************************************************************************
> File Name: shm_read.c
> Author: ssh
> Mail: sunshihao163@163.com
> Created Time: Mon 15 Apr 2019 03:51:10 PM CST
************************************************************************/
/*共享内存的基本使用*/
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <sys/shm.h>
#define IPC_KEY 0x12345678
#define PROJ_ID 0x12345678
#define SHM_SIZE 4096
int main(){
//1.创建/打开共享内存
//key_t ftok(const char *pathname, int proj_id);
// 通过文件名与一个proj_id生成一个key值
//int shmget(key_t key, size_t size, int shmflg);
// key: 共享内存标识符
// size: 共享内存大小
// shmflg:选项标志
// IPC_CREAT 存在则打开,不存在则创建
// ICP_EXCL 与IPC_CREAT同用,若存在则报错,不存在则创建
// mode_flags 权限
// 返回值:句柄:正整数 失败:-1
// key_t key = ftok(".", PROJ_ID);
int shmid = shmget(IPC_KEY, SHM_SIZE, IPC_CREAT|0664);
if(shmid < 0){
perror("shmid error");
return -1;
}
//2.将共享内存映射到虚拟地址空间
//void *shmat(int shmid, const void *shmaddr, int shmflg);
// shmid: 共享内存创建返回的句柄
// shmaddr:映射首地址-通常置NULL
// shmflg:
// 0 可读可写
// SHM_RDONLY 只读
// 返回值: 成功:映射首地址 失败:(void*)-1
char *shm_start = (char*)shmat(shmid, NULL, 0);
if(shm_start == (void*)-1){
perror("shmat erroe");
return -1;
}
//3.进行内存操作
int i = 0;
while(1){
sprintf(shm_start, "ni hao!", i++);
sleep(1);
}
//4.解除映射关系
//int shmdt(const void *shmaddr);
// shmaddr: 映射首地址
// 返回值:成功:0 失败:-1
shmdt(shm_start);
//5.删除共享内存
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// shmid: 句柄
// cmd: 操作
// IPC_RMID 删除共享内存
// buf: 设置/获取属性信息
// 想获取传入首地址,不想获取了置NULL
// 共享内存并不会被立即删除,而是判断当前连接数,若不为0,则拒绝后
// 续连接,直到为0的时候才会删除这块共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
3.消息队列
1.概述
·消息对列提供了一个从一个进程向另一个进程发送一块数据的方法
·每个数据块都被认为是一个有类型,接受者进程接受的数据可以有不同的类型值
·消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是由上限的(MSGMNB),系统上消息队列的总数也是有一个上限(MSGMNI)
2.消息队列函数
创建和访问一个消息队列: int msgget(key_t key, int msgflg);
参数:key:某个消息队列的名字
msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功:非负整数 失败:-1
4.信号量(主要用于同步有互斥)
进程互斥
·由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
·系统中某些资源一个只允许一个进程使用,称这样的资源为临界资源或者互斥资源
·在进程中涉及互斥资源的进程段叫临界区