本问要讲的是操作系统为进程间提供的几种通信方式
1.为什么操作系统要提供进程间通信?
因为进程是具有独立性的,无法访问其他进程的地址空间,但是进程间是需要具有数据传输的功能的,因此提供进程间通信方式,本质上就是提供了一个可以让进程间通信的介质(类似人们之间可以通过电话进程通信)
2.管道
匿名管道
原理:一个进程在内核中创建了一个管道,其实就是在内核中创建了一块缓冲区,并且返回这个管道的操作句柄。但是,内核中的这块缓冲区没有其他标识符,只能通过操作句柄访问。因此该进程只能与和该进程具有亲缘关系的进程进行通信,因为创建的子进程会复制父进程对管道的操作句柄,进而也能访问这个管道。
实现:
int pipe(int fildes[2]);
fildes:文件描述符,输出型参数,其中fildes[0]是用来读管道的文件描述符,fildes[1]是用来向管道写数据的文件描述符。
返回值:成功创建管道返回0,失败返回-1。
特征:
- 管道是半双工通信,同一时间只能有一个进程对其操作。
- 管道中没有数据,则read()会阻塞,直到有数据被写进去。
- 管道中数据写满,则write()会阻塞,直到有数据被读出去。
- 向管道中写数据是追加写进缓冲区。
- 从管道中读取数据会将读取到的数据从缓冲区清理掉。
- 若管道所有写数据的端口被关闭,则read()会返回0。
- 若管道所有读数据的端口被关闭,则write()会触发异常,进程退出。
例子:创建子进程每隔1s向管道写入数据,父进程一直读取数据。
//本demo用来演示管道的应用实现和读写测试
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
int fildes[2];
int ret = pipe(fildes);
if(ret<0)
{
perror("pipe create error\n");
return -1;
}
int pid = fork();
if(pid < 0)
{
perror("fork error\n");
return -1;
}
if(pid == 0)
{
close(fildes[1]);//使用匿名管道时,哪一端不用就关闭它。
while(1)
{
char buf[1024];
read(fildes[0],buf,1024);
printf("%s\n",buf);
}
}
else
{
close(fildes[0]);//使用匿名管道时,哪一端不用就关闭它。
int i=0;
char buf[1024];
while(1)
{
sleep(1);
sprintf(buf,"这是第%d条数据\n",i++);
write(fildes[1],buf,strlen(buf));
printf("%s\n",buf);
}
}
waitpid(pid,NULL,0);
return 0;
}
命名管道
命名管道可以实现多个进程间的数据通信它的实现原理是:创建一个特殊的FIFO(first in first out)类型文件,这是一个设备文件,存储在磁盘上。
特性:
- 具有先写入的数据先读出的特点。和普通文件产生区别。
- 在使用上同样需要先打开文件。
- 当以只读方式打开,会阻塞打开,直到该文件被其他某个进程以写的方式打开。
- 当以只写方式打开,会阻塞打开,直到该文件被其他某个进程以读的方式打开。
- 总的来说,该文件必须同时至少有一个读端和写端,才能被打开。
- 和匿名管道特性相同,若所有写端被关闭,读数据会返回0;若所有读端被关闭,写数据会触发异常,进程退出。
使用:
- mkfifo命令
- int mkfifo(const char *pathname, mode_t mode);
当创建时errno值被置为EEXIST是说明该文件已经存在,在使用时,判断错误的时候要设置一下。
例子:一个进程用来读取数据,一个进程负责写数据,当只打开一个进程时,会阻塞,直到另一个进程也打开。fifo.c用来读。fifo_write.c用来写。
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
//int mkfifo(const char *pathname, mode_t mode);
//pathname:管道文件路径名称,mode:管道文件的操作权限
//成功返回0,失败返回-1
//这个demo用来演示命名管道的基本操作
int main()
{
char *file = "./test.fifo";
int ret=mkfifo(file,0664);
if(ret<0){
if(errno!=EEXIST){//说明fifo文件存在存在
perror("mkfifo error");
return -1;
}
}
int fd = open(file,O_RDONLY);
if(fd<0){
perror("open error");
return -1;
}
printf("open sucess\n");
char buf[1024];
while(1)
{
sleep(1);
read(fd,buf,1023);
printf("%s\n",buf);
}
return 0;
}
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
char *file = "./test.fifo";
int ret=mkfifo(file,0664);
if(ret<0){
if(errno!=EEXIST){
perror("mkfifo error");
return -1;
}
}
int fd = open(file,O_WRONLY);
if(fd<0){
perror("open error");
return -1;
}
printf("write open sucess\n");
char buf[1024];
int i=0;
while(1)
{
sprintf(buf,"我是第%d条数据\n",i++);
write(fd,buf,strlen(buf));
}
return 0;
}
3.共享内存
用于进程间的数据共享——最快的进程通信方式
为什么说是最快的进程间通信方式?
- 管道通信实质上是在内核态的内存上开辟了一个缓冲区,数据执行读写的时候,需要先将数据从用户态的内存拷贝到内核态的内存中,再从内核态的内存中拷贝到用户态的内存中。拷贝会导致速度较慢。
- . 共享内存是直接在物理内存上开辟一段空间,让这块内存空间指向映射的进程的虚拟内存上,进程直接操作该块内存,从而提高了效率,不需要拷贝数据.
共享内存使用步骤:
- 开辟共享内存。
- 和进程虚拟地址空间建立映射。
- 通过虚拟地址对内存进行操作。
- 解除映射关系。
- 关闭共享内存。
通过一个例子学习使用共享内存:
#include <stdio.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/wait.h>
//int shmget(key_t key, size_t size, int shmflg);
//这个demo用来演示shmget的使用
#define IPC_KEY 0x12345678//设置共享内存标识
#define SHM_SIZE 4096//设置共享内存大小
int main()
{
int shmid = shmget(IPC_KEY,SHM_SIZE,IPC_CREAT|0664);
//0664设置共享内存权限
if(shmid<0){
perror("shmget error");;
return -1;
}
//建立映射关系,第二个参时是设置共享内存映射首地址,但是我们一般设置为NULL,让操作系统设置一个合适的位置。
//第三个参数是设置进程权限,设置SHM_RDONLY的前提是该文件必须要有可读权限。设置其他表示可读可写。
//返回值:成功返回映射的首地址,失败返回(void*)-1
void *shm_start = shmat(shmid,NULL,0);
if(shm_start==(void *)-1){
perror("shmat error");
return -1;
}
int pid=fork();
if(pid<0)
{
perror("fork error\n");
return -1;
}
if(pid==0)
{
while(1)
{
shmat(shmid,NULL,0);
printf("%s\n",shm_start);
sleep(2);
}
}
else
{
int i=0;
while(1){
sprintf((char*)shm_start,"我是第%d条数据",i++);
sleep(1);
}
}
//关闭映射关系,参数是映射首地址
shmdt(shm_start);
//关闭共享内存,第二个参数设置对共享内存的操作
//第三个参数获取共享内存的一些属性
shmctl(shmid,IPC_RMID,NULL);
waitpid(pid,NULL,0);
return 0;
}
共享内存的特征:
- 向文件内写数据是覆盖式写入.
- 每次读取数据后不会清除内容,所以用于数据的共享.
- 要注意设置共享内存的权限,否则别的进程可能无法进行读写.
- 共享内存是有很多属性的,可以通过shmctl进行查看.