进程通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止
    时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程所有的陷入和异常,并能够及时知道它状态的改变。

进程是具有独立性的(进程的代码和数据与另外一个进程的代码和数据是没有关系的)。可是,要通信,就意味着两个进程要进行数据交互,那么数据便有可能相关了。

          • -

要让两个进程实现通信,前提条件是得先让两个进程看到同一份资源
资源:内存空间,它由谁提供,以什么样的方式提供,这就表现了不同的通信方式。

一、管道

什么是管道?

  • 把从一个进程连接到另一个进程的一个数据流称为一个“管道”

匿名管道

#include <unistd.h>
    int pipe(int fd[2]);

功能:创建一个匿名管道。


fd:文件描述符数组,默认会以读写方式打开,把读写方式打开的文件描述符,分别保存到fd[0]和fd[1]中。

返回值:成功返回0,失败返回错误代码。

下面的代码演示了创建管道,同时创建子进程,并实现让子进程写,父进程读的过程。

#include<stdio.h>                                                                                                                                   
#include<unistd.h>
#include<string.h>
int main()
{
    int pipefd[2] = {0};
    pipe(pipefd);
    
    pid_t id = fork();
    if(id == 0)
    {
        // child, write
        close(pipefd[0]);// 关掉读
        const char* msg = "i am child......\n";
        while(1)
        {
            write(pipefd[1], msg, strlen(msg));
            sleep(1);
        }
    }
    else{
        // father, read
        close(pipefd[1]);// 关掉写
        char buff[64];
        ssize_t s = read(pipefd[0], buff, sizeof(buff)); 
        if(s > 0)
        {
            buff[s] = 0;
        }
        printf("father get message : %s\n", buff);
    }
    return 0;
}  

在这里插入图片描述
管道的四种情况(在代码层面体现的四个特征):

1、如果写端不关闭文件描述符,且不写入,此时读出条件不满足(管道为空),那么读端可能会进行长时间阻塞(状态由R变S,由运行队列放置等待队列)。
2、当我们实际在进行写入时,如果写入条件不满足(管道满了),写端会进行阻塞。
3、如果写端关闭文件描述符,读端在读取完数据后,会读取到文件结尾。
4、如果读端关闭,写端进程可能在后续被直接杀掉。因为操作系统不做任何浪费空间和低效的事情,只要发现,它就会进行修正。没有读端,写这个动作就浪费了系统资源和内存。会通过13号信号杀掉。

管道属性特征:
1、只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道。
2、管道提供流式服务
3、当进程退出时,管道释放,所以管道的生命周期随进程。
4、内核会对管道操作进行同步与互斥。
5、管道是半双工的,数据只能向一个方向流动;需要双向通信时,需要建立两个管道。

命名管道

作用:两个毫不相关的进程,实现进程通信。

使用FIFO文件通信,它经常被称为命名管道。

创建:
可以使用命令行mkfifo filename

也可以使用函数:

#include<sys/types.h>
#include<sys/stat.h>
    int mkfifo(const char *filename,mode_t mode);

用命令行创建举例:
进程间通信(匿名管道 | 命名管道 | 共享内存)教程
可以发现,命名管道是一种特殊类型的文件,文件类型为p。
可以把fifo简单理为一种符号或标志,代表的是两个进程通过它通信。

操作系统在内存中创造管道文件,在内存通信,这样数据不会也不需要刷新到磁盘。如果真的是一个进程往文件里写,另一个进程从文件中读,那么就是IO,效率太低。

应用实例:客户端发送,服务端接收

  • server.c
#include<stdio.h>                                                          
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

#define FIFO_FILE "./fifo"
int main()
{
    if(-1 == mkfifo("FIFO_FILE",0644))
    {
        perror("mkfifo");
        return 1;
    }
    
    //打开文件
    int fd = open(FIFO_FILE,O_RDONLY);
    if(fd >= 0)
    {
        char buff[64];
        while(1)
        {
            ssize_t s = read(fd, buff, sizeof(buff) - 1);
            if(s > 0)
            {
                buff[s] = 0;
                printf("client %s\n", buff);
            }
            else if(s == 0)
            {
                printf("client quit, my too.\n");
                break;
            }
            else{
                perror("read");
                break;
            }
        }
    }
    return 0;
}

  • client.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

#define FIFO_FILE "./fifo"
int main()
{
    //打开文件
    int fd = open(FIFO_FILE,O_WRONLY);
    if(fd >= 0)
    {
        char buff[64];
        while(1)
        {
            printf("please enter message ");
            fflush(stdout);
        
            ssize_t s = read(0, buff, sizeof(buff) - 1);
            if(s > 0)
            {
                buff[s] = 0;
                write(fd, buff, s);
            }
        }
    }
    return 0;
}

进程间通信(匿名管道 | 命名管道 | 共享内存)教程
匿名与命名的区别:

匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open。
匿名管道只能让具有亲缘关系的进程之间进行通信,常用于父子;命名管道可以两个毫不相关的进程之间进行通信。

二、共享内存

有没有绕过文件系统的通信方式?

共享内存几乎是进程通信中速度最快的方式,因为它拷贝的次数少,不需要用户到内核,内核到用户。

管道是基于文件实现通信,而共享内存是操作系统给我们提供的通信相关的数据结构,来实现通信。

管道一般是两个进程通信,而共享内存可以多个。

在这里插入图片描述
我们来认识一些使用共享内存的函数。

  • shmget函数

功能:用来创建共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg);

key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

  • shmat函数

功能:将共享内存段连接到进程地址空间

#include <sys/ipc.h>
#include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);

shmid:共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM\_RND和SHM\_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

  • shmdt函数

功能:将共享内存段与当前进程脱离

#include <sys/ipc.h>
#include <sys/shm.h>
    int shmdt(const void *shmaddr);

shmaddr:由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

  • shmctl

功能:删除共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

在使用这些函数之前,我们要先使用ftok函数来生成一个唯一值key。

#include <sys/types.h>
#include <sys/ipc.h>
    key_t ftok( const char * fname, int id )

ftok这两个参数可以随便填写,但要保证让在同一块共享内存通信的进程所填写的参数相同才行。也就是说,ftok的实际上是通过算法来生成一个唯一值。

举例实现共享内存的创建、关联、取消关联、删除

  • comm.h
#pragma once 
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/ipc.h>

#define PATHNAME "/tmp"
#define PROJ_ID 0x6688
#define SIZE 4096
  • server.c
#include"comm.h"

int main()
{
    //创建了key值,算法生成,保证key唯一性
    key_t k = ftok(PATHNAME, PROJ_ID);
    
    int shmid = shmget(k, SIZE, IPC_CREAT| IPC_EXCL| 0664);
    if(shmid < 0){
        perror("shmget");
        return 1;
    }
    
    printf("shmid:%d\n", shmid);
    
    //关联
    char* str = (char*)shmat(shmid, NULL, 0);
    
    while(1)
    {
        sleep(1);
        printf("%s\n",str);
    }
    
    //取消关联
    shmdt(str);
    
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

  • client.c
#include"comm.h"

int main()
{
    //也得调ftok,传同样的参数
    key_t k = ftok(PATHNAME, PROJ_ID);
    
    int shmid = shmget(k, SIZE, 0);
    if(shmid < 0){
        perror("shmget");
        return 1;
    }
    
    printf("shmid:%d\n", shmid);
    
    //关联
    char* str = (char*)shmat(shmid, NULL, 0);
    
    char c = 'a';
    for(; c<= 'z'; c++)
    {
        str[c - 'a'] = c;
    }
    
    //取消关联
    shmdt(str);
    
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

查看共享内存:ipcs -m
进程间通信(匿名管道 | 命名管道 | 共享内存)教程
ipc资源的生命周期随内核。

共享内存底层不提供任何的同步与互斥机制,而管道提供。所以管道里为空或为满时,对应的读端或写端会被阻塞。

          • -

总结

进程间通信的本质就是看到同一份资源。
在共享内存中,这里的资源是由操作系统提供的。
而管道是由文件系统提供的,文件系统也属于操作系统。

所以进程间所有的共享资源都是由操作系统提供的,只不过是通过文件部分提供还是进程管理部分提供。

操作系统也要对所有通信的进程和资源进行管理。

标签: 进程, int, 共享内存, include, 间通信, 管道, shmid

相关文章推荐

添加新评论,含*的栏目为必填