进程间通信

与世无争的帅哥 提交于 2020-01-30 11:34:38

进程间通信原因
因为每一个进程都是拥有一个独立的虚拟地址空间的,促使进程独立,导致了进程之间需要协作。
进程间通信分为

  • 数据传输-----管道,消息队列
  • 数据共享-----共享内存
  • 进程控制-----信号量

首先先谈谈管道和共享内存

管道---匿名管道,命名管道

本质:管道其实就是一块内存,是内核当中的缓冲区

  • 匿名管道---没有标识
    特性:
  1. 具有亲缘关系的进程间通信
  2. 半双工,数据只能有一个流向
    进程间通信
  3. 提供字节流服务
  4. 自带同步与互斥功能
  5. 生命周期随进程,进程终止,生命周期消失
  6. 如果管道为空,则读阻塞
  7. 如果管道为满,则写阻塞
  8. 如果管道的读端被关闭,则写端往管道当中写数据的时候,会造成管道破裂,并且导致进程收到SIGPIPE信号,从而进程终止
  9. 如果管道的写端被关闭掉了,则读端读完管道的数据之后,read不会陷入阻塞状态,而是返回。执行代码剩下的正常流程
  10. 管道大小PIPE_SIZE:64K,PIPE_BUG:4K
  11. 如果写入的数据大于PIPE_BUG的话,则不保证写入数据的原子性

    原子性:当前操作不被打断,换句话说,在管道的读写操作是不可以被打断的
    临界资源:同一时间,当前资源只能被一个进程所访问
    互斥:同一时间,保证只能有一个进程访问临界资源
    同步:保证临界资源访问的合理性

创建一个管道(亲缘关系的进程中使用)
int pipe(int pipefd[2]);
pipefd[0]:从pipefd[0]文件描述符中去读
pipefd[1]:从pipefd[1]文件描述符中去写
返回值:0(成功) -1(失败)

    int fd[2];
    int ret = pipe(fd);
    if(ret != 0)
    {
        perror("pipe");//创建错误
        return 0;
    }

    pid_t pid = fork();
    if(pid < 0)
    {
        return 0;//创建失败
    }
    else if(pid == 0)//创建成功
    {
        //child
        sleep(5);
        close(fd[0]);
        write(fd[1], "hehe", 4);
    }
    else
    {
        //father
        close(fd[1]);
        char buf[1024] = {0};
        read(fd[0], buf, sizeof(buf));
        printf("buf = %s\n", buf);
    }

命名管道---有标识的管道
特性:

  1. 具有标识符,可以满足不同进程进程进程间通信
  2. 其他的特性和匿名管道相似
    创建命名管道的方式
    1.使用命令去出创建
    mkfifo+[命名管道文件名称] 创建出来的文件类型是p
    2.使用函数去创建命名管道文件
    mkfifo(char* pathname,mode_t mode)

共享内存
原理:
创建共享内存的时候,首先在物理内存当中创建一块内存,各个进程都通过页表结构将该段内存映射到自己的虚拟内存地址空间上的共享区,各个进程通过映射的地址来进行通信
特性:
1.共享内存是最快的进程间通信方式
2.共享内存是不带有同步和互斥功能的
3.写入数据是按照覆盖的方式进行的

创建共享内存

int shmget(key_t key,size_t size,int shmflg)
    key:共享内存标识符
    size:共享内存的大小
    shmflg:1.IPC_CREAT:如果共享内存不存在,则创建共享内存,如果已经存在,则返回
                            2.IPC_CREAT | IPC_EXCL如果共享内存依然存在,则报错,如果不存在则创建
                            3.+权限:按位或权限,权限相当于文件的权限(使用8进制的数字)

返回值:
成功返回共享内存的操作句柄

将进程附加到共享内存上

void *shmat(int shmid,const void *shmaddr,int shmflg);
    shmid:共享内存的操作句柄
    shmaddr:映射到共享区的地址,一般不选择(填NULL),由操作系统自己映射到虚拟地址空间
    shmflg:0:可读可写        IPC_RDONLY:只读

返回值:
返回映射到的那个地址

分离进程和共享内存

int shmdt(const void *shmaddr);

shmaddr:shmat返回的地址
返回值:
成功返回0,失败返回1
共享内存的销毁

int shmctl(int shmid,int cmd,struct shmid_ds *buf);
    shmid:共享内存的操作句柄
    cmd:要使用什么操作
                    IPC_STAT:获取当前共享内存的状态,要搭配shmid_ds一起使用
                    IPC_RMID:删除共享内存,标记共享内存为删除状态
    buf:一个出参,结构体当中是共享内存的一些信息

返回值:
失败返回-1

共享内存的生命周期

生命周期跟随操作系统内核
    ipcs -m 查看共享内存
    ipcrm -m [shmid] 删除一个共享内存
如果删除一个有进程的共享内存,操作系统的做法是,先标记当前的共享内存为destroy的状态,并且将key设置为0x00000000,表示当前的共享内存不能再被其他进程所附加,同时会释放内存,也就导致了正在附加到该共享内存上的进程有崩溃的风险,一般禁止这样去操作。当附加的进程退出的时候,操作系统就会将共享内存清除掉

消息队列
消息队列本质上是内核当中的一个优先级队列,进程通过访问优先级队列来增加节点或者查看节点,来进行进程间通信
特性:生命周期跟随进程,如果用户进程不删除消息队列资源,则资源一直在操作系统内核当中自带同步和互斥功能
接口:msgget/msgsnd/msgrcv/msgct

信号量-----计数器+PCB进程的等待队列
作用:实现进程的控制,也就是实现进程的同步和互斥

如何实现互斥?---PV操作
1.实现互斥的时候,信号量只有两个取值,也就是0或1,0表示当前资源不可用,1表示当前资源可用
2.当进程需要访问一个临界资源的时候,会先访问信号量,预计算信号量的值
先对当前信号进行预减1操作,判断当前信号是否小于0
如果是小于0,则表示信号量之前的值是等于0,则表示当前的临界资源不可用;将当前的进程放到PCB等待队列中去
如果是等于0,则表示信号量之前的值是等于1,则表示当前的临界资源可用;访问临界资源,并且信号量减1,标志着临界资源不可以再被访问了
3.如果访问完成了,则需要进行结束对临界资源的访问,对信号进行加1操作

减1操作:P操作
加1操作:V操作

怎么实现同步?---计数资源的个数
前提:信号量的值不在是0或者1了,信号量大于0的时候,表示有多少个资源可以使用。当信号量小于0的时候,小于0的数值的绝对值表示多少个进程在等待资源
1.如果需要访问该资源的时候,对信号量进行-1,访问资源
2.如果不访问该资源的时候,对信号量进行+1
3.如果当前信号量值是小于0,表示当前PCB等待队列当中还有进程在等待资源,当一个进程结束对该资源的访问的时候,应当唤醒PCB等待队列当中的一个进程,使之去获取资源
4.如果当前的信号量值大于0,表示PCB等待队列当中是没有进程在等待的,也就是不牵扯说需要唤醒没某个进程去获取资源

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!