进程间通信

进程间通信

在不同的进程之间传播或交换信息,让不同的进程看到相同的文件资源

进程间通信的目的

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

进程间通信的分类

  • 管道:命名管道、匿名管道
  • System V IPC:消息队列、共享内存、信号量

管道

  • Unix中最古老的进程间通信的方式
  • 从一个进程连接到另一个进程的一个数据流称为管道

匿名管道

#include <unistd.h>
int pipe(int fd[2]);//创建匿名管道
//fd:文件描述符,fd[0]表示读端,fd[1]表示写端
//成功返回0,失败返回错误码
  • 管道两端是两个进程,一个负责从管道读,另一个负责从管道写,每一个进程只能打开读端或写端
  • 调用匹配函数首先在内核中开辟一块缓存区用于通信,然后传给用户两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写端
  • 打开管道就是打开一个文件,通过read()或write()向内核缓存区读写数据

  • 如果写端对应的文件描述符被关闭,则read返回0
  • 如果读端对应的文件描述符被关闭,则write产生SIGPIPE导致进程退出
  • 如果指向写端的文件描述符引用计数大于0,而持有管道写端的进程没有向管道内写数据,假如这时有进程从管道读数据,读完管道内的数据就会阻塞等待,直到有数据可读
  • 如果指向读端的文件描述符引用计数大于0,而持有管道读端的进程没有从管道内读数据,假如这时有进程从管道写数据,管道被写满后就会阻塞等待,直到管道由空位置

命名管道

  • 命令行创建
$ mkfifo filename

  • 函数创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* filename, mode_t mode);
//成功返回0,失败返回-1

匿名管道与命名管道

  • 匿名管道由pipe函数创建并打开,命名管道由mkfifo创建,open打开
  • 匿名管道只适用于具有亲缘关系的进程间通信,而命名管道适用于任何两个进程间通信

管道特点

  • 管道基于字节流来通信的
  • 管道的生命周期随进程
  • 内核会对管道的操作进行同步与互斥
  • 管道是半双工的,如果进行双方通信需要建立两个管道

System V IPC

  • 内核为每个IPC对象维护一个数据结构(ipc_perm)
struct ipc_perm{
    key_t _key;
    uid_t uid;
    gid_t gid;
    uid_t cuid;
    gid_t cgid;
    unsigned short mode;
    unsigned short _seq;
};
  • ftok(建立IPC通信指定ID值)
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char* filename, int id);
  • IPC资源必须删除,系统不会自动清除,除非重启,所以IPC资源生命周期随内核

消息队列

  • 消息结构
struct msgbuf{
    long mtype;
    char mtext[1];
};
  • 消息队列结构
struct msqid_ds{
  struct ipc_perm msg_perm;
  struct msg* msg_first;
  struct msg* msg_last;
  _kernel_time_t msg_stime;
  _kernel_time_t msg_rtime;
  _lernel_time_t msg_ctime;
  unsigned long msg_lcbytes;
  unsigned long msg_lqbytes;
  unsigned short msg_cbytes;
  unsigned short msg_qnum;
  unsigned short msg_qbytes;
  _kernel_ipc_pid_t msg_lspid;
  _kernel_ipc_pid_t msg_lrpid;
};
  • 消息队列提供了一种从进程一个进程向另外一个进程发送一块数据的想法
  • 每个数据块都被认为含有一个类型,接收进程可以接收含有不同类型的数据结构
  • 消息队列的每个消息的最大长度有上限,每个消息队列的总字节数有上限,系统上消息队列的总数有上限

创建

  • msgget
int msgget(key_t key, int msgflg);
//创建和访问一个消息队列
//key:消息队列的名字,msgflg:用法和创建文件时的mode一样
//成功返回消息队列的必标识码,失败返回-1
  • msgctl
int msgclt(int msqid, int cmd, struct msqid_ds *buf);
//消息队列的控制函数
//msqid:消息队列的标识码,cmd:将要采取的动作,有以下值
//IPC_STAT:把msqid_ds结构中的数据设置为消息队列的当前关联值
//IPC_SET:若进程有足够权限,把消息队列的当前关联值设置为msqid_ds数据结构的值
//IPC_RMID:删除消息队列
//buf:指向保存消息队列的模式状态和访问权限的数据结构
//成功返回0,失败返回-1
  • msgsnd
int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg);
//把一条消息添加到消息队列中
//msgp:指向准备发出的消息,msgsz:msgp指向消息的长度,不包括long int型
//msgflg:控制队列满或达到系统上限时发生的事情,有以下值
//IPC_NOWAIT:表示队列满不等待,返回EAGAIN错误,MSG_NOERROR:消息大小超过msgsz截断
//成功返回0,失败返回-1
  • msgrcv
ssize_t msgrcv(int msqid, void* msgp, size_t msgsz, long msgtype, int msgflg);
//从一个消息队列接收消息
//msgtype:实现接收优先级,有以下值
//0:返回第一条消息,大于0:返回第一条类型等于msgtype的消息
//小于0:返回队列第一条类型小于msgtyp绝对值并且消息类型最小的消息
//大于0且msgflg=MSG_EXCEPT:接收类型不等于msgtype的第一条消息
//成功返回实际放到接收缓冲区的字符个数,失败返回-1

ipcs和ipcrm

  • ipcs -q:显示IPC资源
  • ipcm -q 消息队列标识码:手动删除IPC资源

共享内存

  • 共享内存结构
struct shmid_ds{
    struct ipc_perm;
    int shm_segsz;
    _kernel_time_t shm_atime;
    _kernel_time_t shm_dtime;
    _kernel_time_t shm_ctime;
    _kernel_ipc_pid_t shm_cpid;
    _kernel_ipc_pid_t shm_lpid;
    unsigned short shm_nattch;
    unsigned short shm_unused;
    void* shm_unused2;
    void* shm_unused3;
};
  • 共享内存时最快的IPC形式
  • 共享内存就是将内存映射到共享它的进程地址空间,进程间数据传递不再涉及到内核

创建

  • shmget
int shmget(key_t key, size_t size, int shmflg);
//创建共享内存
//key:共享内存的名字,size:共享内存的大小,shmflg:用法和创建文件时的mode一样
//成功返回共享内存的标识码,失败返回-1
  • shmctl
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
//共享内存的控制函数
//shmid:共享内存标识码,cmd:将要采取的动作,有以下值
//IPC_STAT:把shmid_ds结构中的数据设置为共享内存当前关联值
//IPC_SET:若进程权限足够,把共享内存的当前关连值设置为shmid_ds数据结构中的值
//IPC_RMID:删除共享内存段
//buf:指向保存共享内存的模式状态和访问权限的数据结构
//成功返回0,失败返回-1
  • shmat
void* shmat(int shmid, const void* shmaddr, itn shmflg);
//将共享内存段连接到地址空间
//shmaddr指定链接地址,shmaddr为NULL,核心自动选择一个地址
//shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
//shmaddr不为NULL且shmflg设置SHM_RND标记,则连接地址会自动向下调整为SHMLBA的整数倍(shmaddr-shmaddr%SHMLBA)
//成功返回指向共享内存第一节的指针,失败返回-1
  • shmdt
int shmdt(const void* shmaddr);
//将共享内存与当前进程脱离,不删除共享内存段
//成功返回0,失败返回-1

信号量

  • 信号量结构体伪代码
struct semaphore{
    int value;
    pointer_PCB queue;
};
  • 信号量集结构
struct semid_ds{
    struct ipc_perm sem_perm;
    time_t sem_otime;
    time_t sem_ctime;
    unsigned short sem_nsems;
};
  • 信号量主要用于进程的同步与互斥,由于各进程要求共享资源,而且有的资源需要互斥利用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥,系统中某些资源一次只允许一个进程使用,这要的资源称之为临界资源或互斥资源,涉及到互斥资源的程序称之为临界区
  • P原语指阻塞原语
P(s){
    s.value = s.value--;
    if(s.value < 0){
        //将该进程设置为等待状态
        //将该进程的PCB插入相应的等待队列的末尾
    }
}
  • V原语指唤醒原语
V(s){
    s.value = s.value++;
    if(s.value <= 0){
        //唤醒相应等待队列中等待的一个进程
        //改变其状态为就绪状态并将其插入就绪队列
    }
}
  • 信号量的同步与互斥由PV原语来处理,同步时,P、V在同一个进程中,互斥时,P、V在不同进程中,S表示可用资源个数执行一个P操作S减一,执行一个V操作S加一,S>0时S表示可用资源个数,S<0时S的绝对值表示等待队列的进程个数,S=0时表示无可用资源,无等待进程

创建

  • semget
int semget(key_t key, int nsems, int semflg);
//创建和访问一个信号量集
//key:信号量集的名字,nsems:信号量集中信号量的个数,semflg:用法和创建文件时的mode一样
//成功返回信号量集的表示码,失败返回-1
  • semctl
int semctl(int semid, int semnum, int cmd, ...);
//信号量的控制函数
//semid:信号量标识码,semnum:信号量集信号量的序号,cmd:将要采取的动作,有以下值
//SETVAL:设置信号集中信号量计数值,GETVAL:获取信号集信号量的计数值
//IPC_STAT:把semid_ds结构中的数据设置为信号集当前关联值
//IPC_SET:若进程权限足够,把信号集的当前关联值设置为semid_ds数据结构中给出的值
//IPC_RMID:删除信号集
//成功返回0,失败返回-1
  • semop
int semop(int semid, struct sembuf* sops, unsigned nsops);
//创建和访问一个信号集
//sops:指向一个结构数值的指针,nsops:信号量的个数
//成功返回0,失败返回-1
  • sembuf结构
struct sembuf{
    short sem_num; //信号量的编号
    short sem_op;  //信号量一次PV操作时的加减值,‘-1’指P操作,‘+1’指V操作
    short sem_flg; //取值为IPC_NOWAIT或SEM_UNDO
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值