Linux进程通信

Posted by Run-dream Blog on June 10, 2021

IPC

  1. 匿名管道
    ps -ef | grep <key>
    

    用完了就销毁了

  2. 命名管道

    mkfifo <pipename>
    # 此时命令不会结束
    echo 'content' > <pipename> 
    # 在另一个tty执行 
    cat < <pipename>
    
  3. 消息队列

    ipcmk -Q
    ipcs -q
    ipcrm -Q
    

    发送消息主要调用 msgsnd 函数

    收消息主要调用 msgrcv 函数

  4. 共享内存模型

    ipcmk -M
    ipcs -m
    ipcrm -M
    

    通过 shmat 函数将这个内存加载到自己的虚拟地址空间

    如果共享内存使用完毕,可以通过 shmdt 解除绑定

    通过 shmctl,将 cmd 设置为 IPC_RMID,从而删除这个共享内存对象

  5. 信号量

    ipcmk -S
    ipcs -s
    ipcrm -S
    
    • P 操作 申请资源操作
    • V操作 归还资源操作

    通过semget创建一个信号量

    通过semctl初始化信号量的总的资源数量

    用 semop 进行P,V操作

  6. 信号

    # 查看所有信号
    kill -l
    # 查看信号对应的事件
    man 7 signal 
    

    信号

    用户进程对信号的处理方式

    1. 执行默认操作

      • Term终止进程
      • Core(Core Dump) 终止进程后,通过 Core Dump 将当前进程的运行状态保存在文件里面
    2. 捕获信号

      为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。

    3. 忽略信号

      不做任何处理。

      SIGKILL 和 SIGSTOP 不能被忽略

    信号注册

    信号注册

    信号发送

    • 信号的发送通过 kill/tkill/tgkill/rt_sigqueueinfo 函数执行
    • 最终通过 __send_signal, 将这个信号添加到对应 进程/线程 的信号待处理链表中
      • < 32 为不可靠信号, 待处理列表中存在该信号, 则会自动忽略
      • >= 32 为可靠信号, 同一个信号会被添加到信号队列中

    信号执行

    • 信号的处理会在系统调用或中断处理结束返回用户空间的时机通过 exit_to_usermode_loop 中的 do_signal 执行
    • 修改用户函数栈, 插入我们构建的信号处理函数的栈帧 rt_sigframe, 并且将原来的函数栈信息保存在 uc_mcontext 中
    • 信号处理函数执行结束之后, 会通过系统调用 rt_sigreturn 恢复之前用户态栈

管道

  • 匿名管道: 只能在管道创建进程及其后代之间通信
    • 通过 pipe 系统调用创建
    • inode 由特殊的文件系统 pipefs 创建
    • inode 关联的 fos 为 pipefifo_fops
  • 命名管道: 通过管道文件名, 可以在任意进程之间通信
    • 通过 mkfifo Glibc 库函数创建
    • 内部调用 mknodat 系统调用
    • inode 由普通文件系统创建, 真实存在于磁盘中
    • inode 关联的 fops 与匿名管道一致, 为 pipefifo_fops

匿名管道,其实就是内核里面的一串缓存

IPC

共享内存和信号量

  • 无论是共享内存还是信号量,创建与初始化都遵循同样流程,通过 ftok 得到 key,通过 xxxget 创建对象并生成 id;
  • 生产者和消费者都通过 shmat 将共享内存映射到各自的内存空间,在不同的进程里面映射的位置不同;
  • 为了访问共享内存,需要信号量进行保护,信号量需要通过 semctl 初始化为某个值;
  • 接下来生产者和消费者要通过 semop(-1) 来竞争信号量,如果生产者抢到信号量则写入,然后通过 semop(+1) 释放信号量,如果消费者抢到信号量则读出,然后通过 semop(+1) 释放信号量;
  • 共享内存使用完毕,可以通过 shmdt 来解除映射。