linux 进程通信:管道、共享内存

本问要讲的是操作系统为进程间提供的几种通信方式

1.为什么操作系统要提供进程间通信?

因为进程是具有独立性的,无法访问其他进程的地址空间,但是进程间是需要具有数据传输的功能的,因此提供进程间通信方式,本质上就是提供了一个可以让进程间通信的介质(类似人们之间可以通过电话进程通信)

2.管道

匿名管道

原理:一个进程在内核中创建了一个管道,其实就是在内核中创建了一块缓冲区,并且返回这个管道的操作句柄。但是,内核中的这块缓冲区没有其他标识符,只能通过操作句柄访问。因此该进程只能与和该进程具有亲缘关系的进程进行通信,因为创建的子进程会复制父进程对管道的操作句柄,进而也能访问这个管道。
实现:
int pipe(int fildes[2]);
fildes:文件描述符,输出型参数,其中fildes[0]是用来读管道的文件描述符,fildes[1]是用来向管道写数据的文件描述符。
返回值:成功创建管道返回0,失败返回-1。
特征:

  1. 管道是半双工通信,同一时间只能有一个进程对其操作。
  2. 管道中没有数据,则read()会阻塞,直到有数据被写进去。
  3. 管道中数据写满,则write()会阻塞,直到有数据被读出去。
  4. 向管道中写数据是追加写进缓冲区。
  5. 从管道中读取数据会将读取到的数据从缓冲区清理掉。
  6. 若管道所有写数据的端口被关闭,则read()会返回0。
  7. 若管道所有读数据的端口被关闭,则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)类型文件,这是一个设备文件,存储在磁盘上。
特性:

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

使用:

  1. mkfifo命令
  2. 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.共享内存

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

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

共享内存使用步骤:

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

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

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

共享内存的特征:

  1. 向文件内写数据是覆盖式写入.
  2. 每次读取数据后不会清除内容,所以用于数据的共享.
  3. 要注意设置共享内存的权限,否则别的进程可能无法进行读写.
  4. 共享内存是有很多属性的,可以通过shmctl进行查看.
发布了35 篇原创文章 · 获赞 13 · 访问量 2113

猜你喜欢

转载自blog.csdn.net/weixin_42458272/article/details/102826485