进程间通信
- 由于进程之间在执行时,是完全独立执行的,所有,进程间如果需要进行通信,就需要一块共享的区域,我们可以通过创建一个多个进程共享的区域,将所需要共享的数据放在这个共享区当中,当进程间需要通信的时候,写数据的进程将数据写入该区域,需要读数据的进程将数据从该区域中读取出来。
##进程间通信的分类- 管道
- System V进程间通信
- POSIX IPC
##管道- 管道是进程间通信的一种手段,通过在内核中创建一个共享区域,来实现进程间的通信,注意,所在区域是 内核,内核,内核
###管道应该如何理解呢 ?- 管道在逻辑结构上就是一根管道,你可以理解为水管道,这根管道有两端,一个是读端,一个是写端
- 管道的设计是半双工的,也就是,一个进程在对管道进行写操作的时候,其他进程只能读取,这个写的进程占据了这个管道的写端,除非这个进程关闭这个管道的写端,否则其他进程不能对这个管道进行写入操作,读端同理,也是只能有一个进程进行读取,直到这个进程关闭读端。那么为什么不能多个进程同时对这个管道进行读取或者写入呢? 因为如果多个进程对管道进行写入的话,那管道里存储的数据顺序就会乱,因为操作系统在进行进程调度的时候,每一个进程都可能会被随时切走和切入,这样的话,如果多个进程同时拥有同一个管道的读端或写端,就会出现读写混乱.大概可以理解为,一根管道同时只能被两个进程所使用,如果有多个进程同时使用一根管道的话,就会出现读写混乱的情况。
- 在使用管道的时候,如果进程只进行写操作,那就关闭读操作。反之亦然。
- 在linux中,一切皆文件,既然如此,那管道是就是文件,那进程在打开文件的时候,就会在PCB中的file_struct记录文件的文件描述符,后面对管道的一切操作都是对文件描述符的操作。(如果你不是很懂文件描述符是什么东西,我还有一个博客是介绍文件描述符的,可以去看看)
如何开辟一个匿名管道以及使用这个匿名管道
- 在linux中,系统已经提供了管道的系统调用 ,下面的这个是匿名管道,在这个后面还有命名管道
- 匿名管道只能进行有亲缘关系的进程进行通信,常用于父子进程间的通信
- 命名管道可以进行任意进程间的通信
#include<unistd.h>
int pipe(fd[2]);//匿名管道
- 这里面的参数 fd[0] 是管道的读端 fd[1]是管道的写端。我们等会对管道进行操作的时候,就是通过这两个文件描述符进行操作的。返回值如果是0,就表示管道申请成功。如果失败,返回失败码
- 在下面的例子中,我用两个进程的方式,进行了一个进程间管道通信的演示,父进程读取,子进程写数据,最后,父进程将数据打印在屏幕上
- 这个代码是匿名管道的创建方法,匿名管道只能进行有亲缘关系的进程进行通信,常用于父子进程
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<error.h>
int main()
{
if(pipe(fd) != 0)//创建管道
{perror("pipe create");}
int id = fork();//创建进程
if(id > 0)//父进程
{
char buf[100];
close(fd[1]);
sleep(5);
ssize_t s = read(fd[0],buf,sizeof(buf));//从读端读取数据
if(s < 0)
{perror("read");}
buf[s] = 0;//将读到的最后一个字符的后一个字符设置为 \0,方便打印
printf("%s\n",buf);
}
else if( id == 0 )//子进程
{
int i = 0;
const char *msg = "hello world\n";
close(fd[0]);
while(1)
{
write(fd[1],msg,strlen(msg));//向写端写数据
sleep(1);
i++;
if(i > 5)
{break;}
}
}
else//创建进程失败
{perror("fork create");}
return 0;
}
- 以上便是匿名管道的创建和使用了
如何开辟一个命名管道,并使用这个命名管道进行进程间的通信
- 命名管道相比于匿名管道,更像一个文件,命名管道通过在目录下创建一个管道文件,通过这个管道文件进行通信,而不是匿名管道那样,在内核中进行管道的创建。
相关函数
int fifo(const char *filename,mode_t mode);
- 如果使用命名管道,需要注意的是mode的值,权限mode决定了这个管道可以进行写和读。
- 为了验证管道可以进行不同非亲缘进程间的通信,我们可以采用两个程序,一个负责向管道里写,一个负责读,然后打印出来,这样的程序,来验证进程间的通信。
- 命名管道在打卡的时候,进程必须知道,要打开的管道文件的路径,然后才能打开这个管道文件
#include<stdio.h> //这个进程负责读取管道的数据
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define FIFO "./myfifo"
int main()
{
umask(0);
if(mkfifo(FIFO,0644) < 0)
{
perror("mkfifo");
return 1;
}
char buf[1024];
int fd = open(FIFO,O_RDONLY);
if(fd > 0)
{
while(1)
{
ssize_t s = read(fd, buf, sizeof(buf) - 1);
if(s > 0)
{
buf[s]= 0;
printf("client->server : %s",buf);
}else{
printf("read error\n");
exit(1);
}
}
}
else{
perror("open file");
return 1;
}
close(fd);
return 0;
}
#include<stdio.h>//这个进程负责往管道里面写数据
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define FIFO "./myfifo"
int main()
{
char buf[1024];
int fd = open(FIFO,O_WRONLY);
if(fd < 0)
{
perror("open file");
}
while(1)
{
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf) - 1);
if(s > 0)
{
buf[s] = 0;
write(fd,buf,s);
}
else{
break;
}
}
close(fd);
return 0;
}
管道的四种情况
- 读端不读,但是写端已经把缓冲区写满了
这种情况的话,由于写端已经把缓冲区写满了,但是读端不读,管道就会阻塞,写端不能进行写入,直到读端开始将数据读走,才能继续进行写入
- 写端不写,但是读端已经把缓冲区读完了
如果读端已经将缓冲区读完了,缓冲区里已经没有数据了,读端就会阻塞,直到写端往缓冲区里写数据,读端才能继续读
- 读端不读,同时关闭了读端
读端如果不读数据,同时关闭了读端的话,就说明,写端写入的数据不会被读端读走,那么写端所写的数据就是没有意义的,所以,操作系统对此的处理就是,结束写进程
- 写端不写,同时关闭了写端
写端不写数据,但是关闭了写端,由于已经没有人会给写端里写数据了,读端在读完缓冲区里的所有数据后,读进程就会被操作系统杀掉
共享内存
- 共享内存就和他的名字一样,是一个被多个进程所共享的一个内存区域,共享内存所在的区域是用户态,不需要像管道一样,如果需要数据的访问,就必须进行用户态和内核态的切换,提高了效率。
共享内存存在的时间,是不受进程结束限制的,即使一块共享内存没有一个进程挂接,但是依旧是存在于内存中的数据区的,所以,在使用完共享内存之后,一定要记得释放共享内存。
- 共享内存在使用的时候,需要进程显示的挂接共享内存,才能进行共享内存的使用,当共享内存的挂接数不为0时,是不能被释放的。一块共享内存可以被多个进程所挂接,一个进程也可以挂接多个共享内存
- 共享内存在开辟的时候,需要一个唯一的共享内存标识,记住,这个共享内存标识,是多少不重要,重要的是,必须是唯一的,要产生这个唯一的共享内存标识, 就需要调用一个共享内存标识生成方法 ---- ftok ,通过这个方法,可以产生一个这样的唯一的标识,然后用这个标识,去向操作系统申请共享内存
共享内存接口展示
int shmget(key_t key,size_t size,int shmflg);//申请共享内存
//key :共享内存段的名字,必须是唯一的
//size:你所要开辟的共享内存的大小
//shmflg :权限,具体是啥,可以查看man手册
void *shmat(int shmid, const *shmaddr, int shmflg);//挂接共享内存
//shmid :在进行shmget的时候,返回的值就是id
//shmaddr:连接指定的地址,想让核心自动选择的话,设置为NULL
//shmflg: 也是权限 两个值 SHM_RND和SHM_RDONLY
int shmdt(const void *shmaddr);//删除挂接共享内存
//shmaddr:由shmat所返回的指针
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//删除共享内存
//shmid:有shmget所获得的标识
//cmd:要采取的动作 三个 有:IPC_STAT IPC_SET IPC_RMID,最后一个是删除共享内存段
//指向一个保存着共享内存模式状态和访问权限的数据结构,一般是NULL
- 为了实现两个不相干的进程间进行通信,我和前面一样,通过创建两个进程,一个负责向共享内存里写数据,一个负责从共享内存里读数据,通过这种形式,实现共享内存的进行间通信
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
int main()
{
int ret;
key_t key = ftok(PATH_NAME,PROJ_ID);//ftok函数通过路径名加一个proj_id来生成一个唯一的共享内存标识符 key
if(key < 0)
{
perror("ftok");
ret = 1;
return ret;
}
int shm_id = shmget(key,4096,IPC_CREAT);//创建一个共享内存,如果这个共享内存已经存在了,就返回这个共享内存,如果不存在,就返回错误
if(shm_id == -1)
{
perror("shmget");
ret = 2;
return ret;
}
void *str = shmat(shm_id,NULL,0);//挂接共享内存
if((signed long long )str < 0)
{
perror("shmat");
ret = 3;
return ret;
}
int i;
for(i = 0;i < 26;i++)
{
((char *)str)[i] = 'A' + i;//向这个共享内存里写数据
sleep(1);
}
return 0;
}
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
int main()
{
int ret;
key_t key = ftok(PATH_NAME,PROJ_ID);
if(key < 0)
{
perror("ftok");
ret = 1;
return ret;
}
int shm_id = shmget(key,4096,IPC_CREAT|IPC_EXCL);//创建共享内存,如果这个共享内存不存在就创建它,然后返回
if(shm_id == -1)
{
perror("shmget");
ret = 2;
return ret;
}
void *str = shmat(shm_id,NULL,0);//挂接共享内存
if((signed long long )str < 0)
{
perror("shmat");
ret = 3;
return ret;
}
int i = 0;
sleep(3);
for(i = 0;i < 26; i++)
{
printf("cilent -> server:%s\n",(char *)str);//输出这个共享内存里的数据
sleep(1);
}
shmdt(str);
shmctl(shm_id,IPC_RMID,NULL);
}
以上就是进程间通信的一部分东西了,包括匿名管道,命名管道,共享内存