进程间通信

进程间通信

  基本介绍:

    操作系统提供给用户的几种进程间的通信方式

    进程间通信方式是干什么的:

      进程间数据传输  数据共享  进程控制  事件通知

      正式因为有不同的需求,因此操作系统之间有人提供了多找不同的进程间的通信方式:

        续继承与unix而来的管道(匿名管道/命名管道)  共享内存  消息队列  信号量

    为什么操作系统要给用户提供进程间通信方式:

    进程的独立性导致进程之间没有办法进行通信——由操作系统来提供一个公共的媒介,来进行通信

  通信方式:

    管道:(本质是内存的缓冲区)

      管道创建在内核态,是一个“半双工通信”(提供双向选择但是只能单向传输)。在传输信息时提供io操作——返回文件描述符作为

      句柄(两个文件描述符)一个用于写入数据,另一个用于读数据

    匿名管道/命名管道:

      命名管道:有名字则可以通过名字来打开相同的管道进行通信

      匿名管道没有名字,因此只能通过子进程复制父进程的方式实现通信(复制了文件描述符)

      *匿名管道只能同于具有亲缘关系的进程通信

      *命名管道可以用于任意进程间通信

      

      匿名管道:

        创建匿名管道(在创建子进程之前):

          int pipe(int pipefd[2])

          pipefd[0]——管道的读取端

          pipefd[1]——管道的写入端

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

          读写特性:

            1.管道中若没有数据,则read会阻塞,直到读取数据(由数据被写入管道了)。

            2.管道中若数据满了,则write会阻塞,直到由空闲空间(有数据被读取走了)。

            3.若所有读端被关闭,则wriet会触发异常——SIGPIPE(导致进程退出)

             若所有写端被关闭,则read读完数据之后不会阻塞,而是返回0

            进程在操作管道的时候如果没有用到某一端,则把这一端关闭

              管道符 | 的实现:  ps -ef | grep pipe

                shell  父进程pipe

                ps   子进程1——>将处理结果打印到标准输出——标准输出重定向

                grep   子进程2——>循环从标准输入读取数据——标准输入重定向

     命名管道:

      为管道创建了一个管道文件,这个管道文件就是管道的名字(有名字体现在)

      创建名字管道:(本质还是在内核的缓冲区)

        int mkfifo(const char* pathname, mode_t mode)

        pathname——管道我呢见的路劲名

        mode——创建文件的权限

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

      打开特性:

        若管道文件文件没有被写的方式打开,则以只读打开则会阻塞;

      读写特性:

        与匿名管道一样

    管道总结:(管道的本质是内核的一块缓冲区)

  1. 匿名管道只能用与具有亲缘关系的进程间通信/命名管道可以用于任意进程间通信
  2. 管道的读写特性+命名管道的打开特性
  3. 管道是半双工通信——可以选方向的单向通信
  4. 管道的生命周期随进程
  5. 管道提供流式服务:字节流传输。传输数据的形式以字节流的形式(缺点:不能知道数据的间隔,造成数据粘连)
  6. 管道自带同步与互斥功能(读写操作数据大小不超过PIPE_BUF(默认4096)大小,读写操作受保护)
    互斥:对临界资源(公共资源)的同一时间唯一访问性(我操作的时候别人不能操作;不写则读阻塞)
          保证安全
    同步:对临界资源(公共资源)的时序可控性(我操作完了别人才能操作)
               保证合理
    管道的同步与互斥体现在:管道美没有放数据则读阻塞,放了数据之后换对方阻塞,对方读数据
    (我放了,你才能读——同步);数据往管道中没有放完别人不能读也不能写(互斥)
  7. 命名管道独有的特性:打开特性

  共享内存:(是所有进程间通信方式中最快的一种)

    原理:

      1.多个进程通过将同一块内存映射到自己的虚拟地址空间中,实现对相同的一块物理内存进行操作,
      2.通过这种方式实现多个进程间的数据共享功能
      3.因为共享内存是直接通过虚拟地址操作内存实现共享,相较于其他方式少了两次用户态/内核态之间的数据拷贝

    操作步骤:
      1.创建一块共享内存(一个修改,一个查看)
        key_t ftok(const char* pathname, int proj_id)
          通过文件inode结点号与proj_id生成一个key值
        shmget(key_t key, size_t size, int shmflg)
          key:共享内存标识符
          size:共享内存大小
          shmflg:选项标识
            IPC_CREAT:共享内存不存在则创建,存在则打开
            IPC_EXCL:与IPC_CREAT同用,则共享内存存在时报错
            shm_mode 权限
          返回值:标识符(代码中的操作句柄) 失败:-1
        进程间通信方式的查看:
          ipcs命令:[-m -s ]
            可以查看共享内存(nattch表示有多少个进程与它连接)
        删除共享内存:ipcrm [-m -s -q] shmid
      2.将共享内存映射到虚拟地址空间
        void* shmat(int shmid, const void* shmaddr, int shmflg)
          shmid:创建共享内存,返回句柄
          shmaddr:置空,映射首地址由操作系统分配
          shmflg:映射成功后的操作权限
            SHM_RDONLY: 只读
            0 默认—可读可写
          返回值:映射首地址 失败:(void* )-1

      3.直接对这块内存进行操作

        memcpy...
      4.解除映射关系
        int shmdt(const void* shmaddr)
        shmaddr: 映射首地址
      5.删除共享内存(删除时会检测是否还有进程与内存映射,如果还有则拒绝其他进程的映射)
        int shmctl(int shmid, int cmd, struct shmid_ds* buf)
          shmid: 操作句柄
          cmd: 操作类型
            IPC_RMID: 删除共享内存
          buf: 设置/获取共享内存的信息
      共享内存的删除:
        不会被直接删除,而是会判断映射连接数是否为0,为0则直接删除,不为0,则拒绝后续其他进程
        的后续连接,当映射连接数为0 时自动删除。

  消息队列:(消息队列是可以双向通信,传输的是有类型的数据快)
    通过 msgget 创建一个队列:
      int msgget(key_t key, int msgflg);
      利用 msgsnd 来保证获取的数据类型
    本质:内核中的优先级队列

  信号量:
    本质:内核中的一个计数器,并且带有pcb等待队列

  计数器:数据资源的计数器
    如果获取数据时,先判断计数器是否>0;
      若 > 0,则表示有资源,则可以获取数据,并且资源数据-1;
      若 <= 0,则表示没有资源,则阻塞等待;
      产生数据,直接计数+1,并且唤醒等待的进程(只会唤醒在pcb等待队列上的进程)。

    作用:用于实现进程/线程间的同步与互斥
          信号量实现同步依靠:等待+唤醒
          信号量实现互斥依靠:计数器(只有0/1)+等待+唤醒         

原文地址:https://www.cnblogs.com/cuckoo-/p/11105327.html