linux 进程通信:管道、共享内存、消息队列

文章目录

  • 先问问自己,啥是进程间通信,为什么操作系统要提供进程间通信?
  • 通信方式一:管道
      • 本质:
      • 管道特征:
    • 匿名管道
      • 原理:
      • 系统接口:
      • 练习:
    • 命名管道
      • 本质
      • 打开特性:
      • 系统接口:
      • 练习:
  • 3.共享内存

先问问自己,啥是进程间通信,为什么操作系统要提供进程间通信?

答:因为进程是具有独立性的原因是每个进程都操控自己的虚拟地址空间,无法访问其他进程的地址空间,为了让进程间可以传输数据,提供进程间通信方式,本质上就是提供了一个可以让进程间通信的介质(类似人们之间可以通过手机进行通信)

通信方式一:管道

本质:

在内核空间创建了一个有大小限制的、具有同步和互斥性质的缓冲区
管道有以下两种类型

管道特征:

  1. 管道是半双工通信,同一时间只能有一个进程对其操作。
  2. 管道中没有数据,则read()会阻塞,直到有数据被写进去。
  3. 管道中数据写满,则write()会阻塞,直到有数据被读出去。
  4. 向管道中写数据是以追加写进缓冲区。也就是提供字节流服务。
  5. 从管道中读取数据会将读取到的数据从缓冲区清理掉。
  6. 若管道所有写数据的端口被关闭,则read()在读取完数据后不会再阻塞而是返回0.(没有写端了,读就没有意义,所以不阻塞了)
  7. 若管道所有读数据的端口被关闭,则write()会触发异常,进程退出。(没有读端了,写就没有意义,所以就报错)
  8. 管道和其他几种进程通信方式不同,他的生命周期随进程,当使用管道的进程全部关闭,管道就会自动关闭。

匿名管道

原理:

因为创建的管道只能在进程内(通过管道接口)获得管道的操作句柄(在文件系统没有可见的标识符),而子进程可以通过拷贝父进程的方法获得操作句柄,因此只能在具有亲缘关系的进程间进行通信,所以称为匿名管道。

系统接口:

int pipe(int fildes[2]);
fildes:用来保存管道的操作句柄,是一个输出型参数,遵循一切皆文件的原则:对管道的操作和对文件的操作相同,区别在于其中fildes[0]是专门用来读管道的文件描述符,fildes[1]是专门用来向管道写数据的文件描述符。
返回值:成功创建管道返回0,失败返回-1。

练习:

创建子进程每隔1s向管道写入数据,父进程一直读取数据。

//本demo用来演示管道的应用实现和读写测试
#include 
#include 
#include 
#include 
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;
}


命名管道

命名管道和匿名管道的区别是,他会生成一个在文件系统可见的管道文件,存储在磁盘上。这样不同进程都可以通过这个管道文件找到对应的在内核中的管道。

本质

对命名管道的操作就像是使用文件进行通信一样,但是他底层还是使用内存的,速度比使用文件快得多。(因为使用文件操作会进行内存和磁盘之间的数据拷贝)

打开特性:

  1. 具有先写入的数据先读出的特点。和普通文件产生区别。
  2. 在使用上同样需要先打开文件。
  3. 当以只读方式打开,会阻塞打开,直到该文件被其他某个进程以写的方式打开。
  4. 当以只写方式打开,会阻塞打开,直到该文件被其他某个进程以读的方式打开。
  5. 总的来说,该文件必须同时至少有一个读端和写端,才能被打开。
  6. 和匿名管道特性相同,若所有写端被关闭,读数据会返回0;若所有读端被关闭,写数据会触发异常,进程退出。

系统接口:

  1. mkfifo命令
  2. int mkfifo(const char *pathname, mode_t mode);
    当创建时errno值被置为EEXIST是说明该文件已经存在,在使用时,判断错误的时候要设置一下。

练习:

一个进程用来读取数据,一个进程负责写数据,当只打开一个进程时,会阻塞,直到另一个进程也打开。第一个用来读,第二个用来写。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
//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 
#include 
#include 
#include 
#include 
#include 
#include 
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.共享内存

用于进程间的数据共享——最快的进程通信方式
为什么说是最快的进程间通信方式?

  • 管道通信实质上是在内核态的内存上开辟了一个缓冲区,数据执行读写的时候,需要先将数据从用户态的内存拷贝到内核态的内存中,再从内核态的内存中拷贝到用户态的内存中。拷贝会导致速度较慢。
  • . 共享内存是直接在物理内存上开辟一段空间,让这块内存空间指向映射的进程的虚拟内存上,进程直接操作该块内存,从而提高了效率,不需要拷贝数据.

共享内存使用步骤:

  1. 开辟共享内存。
  2. 和进程虚拟地址空间建立映射。
  3. 通过虚拟地址对内存进行操作。
  4. 解除映射关系。
  5. 关闭共享内存。

通过一个例子学习使用共享内存:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//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;
}

共享内存的特征:

  1. 向文件内写数据是覆盖式写入.
  2. 每次读取数据后不会清除内容,所以用于数据的共享.
  3. 要注意设置共享内存的权限,否则别的进程可能无法进行读写.
  4. 共享内存是有很多属性的,可以通过shmctl进行查看.

你可能感兴趣的:(Linux)