进程间通信

目录

进程间通信介绍

进程间通信的目的: 

进程间通信发展:

进程间通信分类:

管道:

System V IPC:

POSIX IPC:

管道

匿名管道:

深入理解管道(文件描述符角度): 

管道读写规则:

管道特点:

 

命名管道:

创建一个命名管道:

命名管道和匿名管道的区别:

 命名管道的打开规则:

 用命名管道实现serve和client通信

system V共享内存 

共享内存函数

shmget函数:

shmat函数:

shmdt函数:

shmctl函数:

 利用system v进行server和client的通信


进程间通信介绍

进程间通信的目的: 

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

进程间通信发展:

  •  管道
  • System V进程间通信
  • POSIX进程间通信

进程间通信分类:

管道:

  • 匿名管道PIPE

  • 命名管道

System V IPC:

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC:

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

  • 管道是unix中最古老的进程间通信的方式
  • 把一个进程连接到另一个进程的一个数据流称为一个“管道” 

匿名管道:

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
    int fds[2];
    char buf[100];
    int len;
    if (pipe(fds) == -1)
        perror("make pipe"), exit(1);
    // read from stdin
    while (fgets(buf, 100, stdin))
    {
        len = strlen(buf);
        // write into pipe
        if (write(fds[1], buf, len) != len)
        {
            perror("write to pipe");
            break;
        }
        memset(buf, 0, sizeof(buf));
        // read from pipe
        if ((len = read(fds[0], buf, 100)) == -1)
        {
            perror("read from pipe");
            break;
        }
        // write to stdout
        if (write(1, buf, len) != len)
        {
            perror("write to stdout");
            break;
        }
    }
}

深入理解管道(文件描述符角度): 


管道读写规则:

  • 当没有数据可读的时
    • O_NONBLOCK disable: read调用阻塞,即进程暂停执行,一直等到有数据来为止
    • O_NONBLOCK enable: read调用返回-1,errno值为EAGAIN.
  • 当管道满的时候
    • O_NONBLOCK disable:write调用阻塞,直到有进程读邹数据
    • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符关闭,则read返回0
  • 如果所有管道读端对应的文件描述符关闭,则write操作会产生信号SIGPIPE,该信号到时write进退出
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性
  • 到要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性

原子性:执行流程所要完成的各个动作是不可中断的操作就叫做原子操作。所有系统调用都是以原子操作方式执行的,内核保证了某系统调用中的所有步骤为独立操作且一次性执行完毕,中间不会被其他进程或线程中断

管道特点:

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程) 之间进行通信,通常一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可以用管道进行通信
  • 管道提供流式服务
  • 一般而言,进程退出管道就会释放,管道的生命周期随着进程结束而结束
  • 一般而言,内核会对管道操作进行同步互斥
  • 管道是半双工的,即数据只能向一个方向流动,需要双方通信的时候,需要两个管道


命名管道:

  • 匿名管道应用的一个限制就是只能在具有共同祖先的进程间通信
  • 如果想在不相关进程之间交换数据,可以用FIFO文件来,它被称为命名管道
  • 命名管道是一种特殊类型的文件

创建一个命名管道:

  • 命令行上创建

    • $ mkfifo filename

  • 程序上创建

    • int mkfifo (const char * filename, mode_t mode);

    • mode(代表管道文件的权限)


命名管道和匿名管道的区别:

  •  匿名管道由pipe函数创建并打开
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道) 与pipe(匿名管道) 之间唯一的区别在于他们创建与打开的方式不同,之后都一样

 命名管道的打开规则:

  • 如果当前打开操作是为读而打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
    • O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
    • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

 用命名管道实现serve和client通信

serve.cc:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include<iostream>
#include <sys/stat.h>
int main()
{
    umask(0);//不太清楚
    if(mkfifo("p2", 0644)<0) 
    {
        std::cout<<"mkfifo失败"<<std::endl;
        exit(-1);
    }
    int rfd = open("p2", O_RDONLY);
    if (rfd < 0)
        exit(-1);
    char buf[1024];
    int n;
    while (1)
    {
        buf[0] = 0;
        printf("Please wait client cin ..\n");
        ssize_t s = read(rfd,buf,sizeof(buf)-1);
        if(s>0)
        {
            buf[s-1] = 0;
            printf("client cin>> %s \n",buf);
        }
        else if(s == 0)
        {
            printf("client quit!\n");
            exit(-1);
        }
        else{
            exit(0);
        }
    }
    close(rfd);
    return 0;
}

client:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <iostream>
#include <sys/stat.h>
int main()
{
    int wfd;
    wfd = open("p2", O_WRONLY);
    if (wfd < 0 ) exit(-1);
       
    char buf[1024];
    int n;
    while (1)
    {
        buf[0] = 0;
        printf("Please cin>> ");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf) - 1);
        if(s>0)
        {
            buf[s] = 0;
            write(wfd, buf, strlen(buf));
        }
        else if(s<=0)
        {
            exit(-1);
        }
        
    }
    close(wfd);
    return 0;
}


system V共享内存 

共享内存区是最快的IPC,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存函数

shmget函数:

功能:用来创建共享内存 

原型:int shmget(key_t key ,size_t size,int shmflg);

参数:key:共享内存段名字

          size:共享内存大小

          shmflg:由9个权限标志构成,和创建文件的mode模式标志一样

返回值:成功返回一个非负整数,即该内存段的标识码,失败返回-1.


shmat函数:

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

原型:void* shmat(int shmid,const void *shmaddr, int shmflg);

参数:shmid:共享内存标识

          shmaddr:指定连接的地址

          shmflg:它的可能取值是,SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个节,失败返回-1.

shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存


shmdt函数:

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

原型:int shmdt(const void *shmaddr,);

参数:shmaddr:有shmat返回的指针         

返回值:成功返回0,失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段


shmctl函数:

功能:用于控制共享内存

原型:int shmdt(int shmid,int cmd ,struct shmid_ds *buf);

参数:shmid:由shmget返回的共享内存标识码

           cmd:将要采取的动作(有三个可取值)

           buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0,失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

                命令                                                        说明
        IPC_STAT把shmid_ds结构中的数据设置为共享内存的当前关联值                                
        IPC_SET在进程有足够权限的前提下,把共享内存当前的关联值设为shmid_ds数据结构中给出的值
        IPC_RMID删除共享内存段

 利用system v进行server和client的通信

server:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#define PATHNAME "."
#define PROJ_ID 0x66
int main()
{
    key_t key = ftok(".", 0x66); // 获取唯一key
    if (key < 0)
    {
        perror("ftok");
        return -1;
    }

    int shmid = 0; // 创建

    if ((shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666)) < 0)
    {
        perror("shmget");
        return -2;
    }
    char *addr = (char *)shmat(shmid, NULL, 0); // 加载到地址空间

    int i = 0;
    while (i++ < 26)
    {
        printf("client# %s\n", addr);
        sleep(1);
    }
    shmdt(addr); // 脱离

    shmctl(shmid, IPC_RMID, NULL);//删除
    return 0;
}

client:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
#define PATHNAME "."
#define PROJ_ID 0x66
int main()
{
    key_t key = ftok(".",0x66);
    int shmid = shmget(key,4096,IPC_CREAT | 0666);
    char *addr = (char*)shmat(shmid,NULL,0);
    int i = 0;
    while (i < 26)
    {
        addr[i] = 'A' + i;
        i++;
        addr[i] = 0;
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    return 0;
}

 运行结果:

 注意:如果在编译代码的途中报错,改正后再次编译可能会导致文件被重复创建,所以可以使用指令删除指定的共享内存

这里可以使用ipcs命令,ipcs -m

  • ipcs -a:显示全部可以显示的信息
  • ipcs -q:显示活动的消息队列
  • ipcs -m:显示活动的共享内存信息
  • ipcs -s:显示活动的信号量信息

 

然后使用ipcrm命令来删除共享内存,ipcrm -m id

  • ipcrm -m id: 删除共享内存标识
  • ipcrm -M key:删除由关键字创建的共享内存

注意:共享内存没有进行同步与互斥

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Obto-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值