Linux进程间通讯

目录

  • 进程间通讯概述
  • 进程间通讯方式
  • 具体实现

概述

  • 进程间通讯(IPC)是指在两个或多个不同的进程间传递或者交换信息。
  • 进程是一个独立的资源管理单元,不同的进程之间资源是独立的,不能在一个进程中直接访问另一个进程的资源,但是进程间也不是孤立的,因此,进程间也需要一些信息的交互和状态传递,所以呢也就需要进程间数据传递,同步和异步的机制。

进程间通讯的目的

数据传输:一个进程需要将它的数据发送给另一个进程。

共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

资源共享的同步:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。

进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间访问的方式

现在linux使用的进程间通信方式:

(1)无名管道(pipe)和有名管道(FIFO)

(2)消息队列

(3)共享内存

(4)信号量

(5)信号(signal)

(6)套接字(socket)

具体实现

1.无名管道(PIPE)

  • 概念:

    • 无名管道(匿名管道),是一种具有两个端点的通信通道,管道的一端用于读取管道内数据,另一端用于将数据写入到管道内。创建一个管道后,会获取一对文件描述符,用于读取和写入。匿名管道通常是用在父子进程之间,由父进程创建匿名管道,子进程会继承管道的读端和写端,实现通讯。

    • 管道是单向的、先进先出的、无结构的字节流。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。

    • 由于PIPE 存在于内存中,并且只有创建它的进程可以直接拿到读写两个文件描述符,其他的进程要想获取到这两个描述符相对来说比较困难,通常做法是父进程先创建无名管道,再创建子进程。由于子进程继承父进程打开的文件描述符,所以父子进程就可以通过无名管道进行通信。

    • 无名管道是临时的,在通讯完成进程退出后,将自动消失,对管道的读写可以使用 read 和write函数。

    • 无名管道默认情况下只有64KB

  • PIPE的创建

函数名pipe
头文件#include <unistd.h>
函数原型int pipe(int fd[2]);
功能创建一个匿名管道
参数说明fd:管道的2个文件描述符;
管道创建后,可以直接操作这两个文件描述符:
fd[0] 代表读端
fd[1] 代表写端
返回值成功,返回0,否则返回-1,错误码放在errno

2.有名管道

  • 概念:

    • 有名管道(命名管道),不同与无名管道的是有名管道可以用于不相关的进程之间进行通讯。
    • 创建有名管道的进程可以给管道取名字以及路径,该路径名是以特殊的文件存在放在文件系统中,因此两个进程可以通过访问该路径来建立联系,进行进程间的数据交换,有名管道和无名管道都遵循先进先出的原则。
    • 有名管道是一个特殊的文件,它和普通的文件一样具有磁盘存放路径,文件权限和其他属性,但是两者又有区别,有名管道并不会在磁盘中存放真正的信息,信息存放在内存中。通讯的两个进程结束后,信息会自动消失,但管道文件路径依然存在。
  • 有名管道的特点:

    • 1.如果以写的方式打开有名管道,则其他进程需要以读的方式打开,否则,进程阻塞,如果是读的方式打开有名管道(管道已创建),而没有写进程,读进程会阻塞,反之亦然。显然如果一个进程是以可读写的方式打开有名管道,当然进程充当了读和写两个身份,进程不会阻塞。

    • 2.两个进程已经完成打开有名管道操作,阻塞读操作按以下方式执行:

      a)如果管道中无数据,读进程默认阻塞;

      b)如果管道中有数据,但数据小于欲读取数据量,读出所有数据后返回;

      c)如果管道中有数据,但数据大于欲读取数据量,读出期望数据后返回;

    • 3.两个进程已经完成打开有名管道操作,阻塞写操作按以下方式执行:

      a)如果管道中无空间,写操作阻塞;

      b)如果管道中有空间,但空间小于欲写入数据量,写满空间后阻塞;

      c)如果管道中有空间,但空间大于欲写入数据量,写人数据后返回;

    • 4.两个进程已经完成打开有名管道操作,中途其中一个进程退出:

      a)如果退出的是读操作,则写操作将返回 SIGPIPE 信号;

      b)如果退出的是写操作,则读操作将不再阻塞,直接返回0。

  • FIFO的创建

函数名mkfifo
头文件#include <sys/types.h>
#include <sys/stat.h>
函数原型int mkfifo(const char* pathname,mode_t mode);
功能创建一个命名管道
参数说明1.pathname:欲建立的FIFO 文件路径和文件名;
2. mode: FIFO 文件权限
返回值成功,返回0,否则返回-1,错误码放在errno

3.共享内存

  • 概述:
    • 共享内存是进程间通讯中最简单的一种,也是最快的一种,共享内存就是一段能被其他进程所共同访问的物理内存,这段共享内存由一个进程创建,但多个进程都可以访问。
  • 优点:
    • 因为所有进程共享同一块内存,所以共享内存在各种进程间通讯方式中具有最高的效率,访问共享内存区域和访问进程独有的内存区一样快,并不需要通过其他方式来完成,所以也避免了对数据的不必要的复制。
  • 缺点:
    • 因为所有进程共享内存,就会引起同步的问题,而系统对访问共享内存没有进行同步机制,就需要用户自己来提供同步措施。
  • **例如:**在数据没有写入之前,不允许从共享内存中读取数据。也不允许两个进程在同一时间向共享内存区写入数据,解决这些问题的常见方法就是通过信号量的方式来进行。
共享内存使用的步骤:
  1. 创建新的共享内存:

    • 也就是说从内存中申请一段共享内存区域,所有进程都共享对同一内存的访问,但只应由一个进程来创建新的共享内存。

    • 再次使用一块已经存在的内存块不会创建新的,而是返回一个标识该内存块的标识符。

    • 具体实现: shmget 函数

      • 例如:shmget(IPC_PRIVATE,512,IPC_CREAT |0600);
  2. 映射共享内存到各进程的地址空间:

    • 一个进程如果想要使用这个共享内存块,首先必须将它映射到自己的地址空间,这样会创建一个从进程虚拟地址空间到共享内存页面的映射关系。
    • 具体实现: shmat 函数
      • 例如:add =shmat(shmid,NULL,0);
  3. 使用共享内存进行数据交换:

    • 进过映射后,进程就可以通过映射的地址来访问这段共享内存。
    • 具体实现: strcpy,memcpy memset,bzero等
  4. 断开与共享内存的映射:

    • 当进程对共享内存的使用结束以后,这个映射关系将被删除,所以进程需要断开与共享内存的映射
    • 具体实现: shmdt 函数,例如: shmdt(addr);
  5. 回收共享内存:

    • 当再没有进程需要使用共享内存的时候,必须要有一个进程负责将这个共享内存块释放,以便系统回收。
    • 具体实现: shmctl函数
      • 例如:shmctl(shmid,IPC_RMID,NULL)

备注:所有的共享内存块的大小都必须是系统页面大小的整数倍,系统页面大小指的是系统中单个内存页面包含的字节数,在linux 系统中,内存页面大小是 4kB,用户可以通过调用 getpagesize() 这个函数来获取这个值。

共享内存相关函数
函数名shmget
头文件#include <sys/ipc.h>
#include <sys/shm.h>
函数原型int shmget(key_t key,size_t size,int shmflg);
功能得到一个共享内存的描述符,创建一个共享内存对象并返回标识符
参数说明1.key:通常此值来源于ftok 返回的IPC值,如果key为(IPC_PRIVATE)则会创建新的共享内存。
2.ftok(“/home/terry”,1); 传入的路径必须是存在的路径且可访问
3.size: 创建的共享内存大小,以字节为单位。
4. shmflg:
当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符: 如果参数shmflg 包含了IPC_CREAT和IPC_EXCL ,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在则报错,可以通过此方式来验证共享内存是否已经存在。shmflg 也用来决定共享内存的访问权限。
返回值成功,返回共享内存标识符,否则返回-1,错误码放在errno
函数名shmat
头文件#include <sys/types.h>
#include <sys/shm.h>
函数原型void* shmat(int shmid,const void* shmaddr,int shmflg);
功能连接共享内存,成功后会将共享内存映射到调用进程的地址空间
参数说明1.shmid:共享内存的标识符;
2.shmaddr: 指定共享内存映射到地址空间的什么位置,可指定NULL 让内核自己决定一个合适的地址位置;
3. shmflg: SHM_RDONLY 只读模式,其他为读写模式,可直接写 0
返回值成功,返回已连接的地址,否则返回(void*)-1,错误码放在errno
函数名shmdt
头文件#include <sys/types.h>
#include <sys/shm.h>
函数原型int shmdt(const void* shmaddr);
功能断开之前连接的共享内存。
参数说明shmaddr: 连接的共享内存的起始地址
返回值成功,返回0,否则返回-1,错误码放在errno
函数名shmctl
头文件#include <sys/types.h>
#include <sys/shm.h>
函数原型int shmctl(int shmid,int cmd, struct shmid_ds *buf);
功能控制共享内存的运行
参数说明1. shmid:共享内存的标识符;
2. cmd: 操作模式,可取以下值。
IPC_STAT : 将共享内存的信息复制到buf;
IPC_SET: 将参数buf 中指示的数据复制到共享内存的信息;
IPC_RMID: 删除共享内存
SHM_LOCK: 不让这个共享内存置换到 SWAP
SHM_UNLOCK: 允许这个共享内存置换到 SWAP
注意: SHM_LOCK和SHM_UNLOCK 唯有root 可以使用
3. buf:
指向struct shmid_ds 结构体的指针,详见下一页相关数据结构。
返回值成功,返回 0,否则返回-1,错误码放在errno
共享内存相关数据结构

在这里插入图片描述

4.消息队列

  • 概念:
    • 消息队列就是一个消息的链表,提供了一种由一个进程向另一个进程发送块数据的方法。另外,每一个数据块被看作有一个类型,而接收进程可以独立接收具有不同类型的数据块,在许多方面看来,消息队列类似于有名管道,但是却没有与打开与关闭管道的复杂关联。
  • **优点: **
    • 1.通过发送消息来几乎完全避免命名管道的同步和阻塞问题
    • 2.独立于发送和接收进程而存在,这消除了在同步命名管道的打开和 关闭时可能产生的一些困难。
  • 缺点:
    • 1.与管道一样,每个数据块有一个最大长度的限制
    • 2.系统中所有队列所包含的全部数据块的总长度也有一个上限。 Linux 系统中有两个宏定义 MSGMAX, 以字节为单位,定义了一条消息的最大长度。 MSGMNB, 以字节为单位,定义了一个队列的最大长度。

Linux 操作系统中,对消息队列进行了以下的规定,不同的系统的限制值可以通过调用 msgctl 函数使用 IPC_INFO/MSG_INFO 参数获取,或者通过指令ipcs -ql来获取。 需要强调的是,不同的Linux 版本,限制值可能不一样。

​ 1.默认情况下,系统最多允许有16个消息队列;

​ 2.每个消息队列最大为 16384字节;

​ 3.消息队列中的每个消息最大为8192字节 (8KB)

消息队列的使用步骤:

1.创建或打开消息队列:

​ 具体实现: msgget 函数

​ 例: msgget(key,IPC_CREATE |0600);

2.添加消息或读取消息:

​ 具体实现: 添加消息/发送消息 msgsnd函数

​ 读取消息 msgrcv 函数

​ 例:

​ msgsnd(msgid,&sndmsg,size,0);

​ msgrcv(msgid,&rcvmsg,size,type,msgflg);

3.销毁消息队列:

​ 具体实现: msgctl函数

​ 例: msgctl(msgid,IPC_RMID,NULL);

消息队列相关函数
函数名msgget
头文件#include <sys/ipc.h>
#include <sys/msg.h>
函数原型int msgget(key_t key,int msgflg);
功能获取或创建一个消息队列
参数说明1.key:消息队列的关键字,函数将dm.txt它与已有的消息队列关键字进行对比来判断消息队列是否已经创建,函数具体操作由msgflg 来决定。 ftok(“/home/terry”,1);
2. msgflg: 消息队列建立标志和存取权限,可取以下值:IPC_CREAT:如果消息队列不存在就创建,否则打开消息队列; IPC_EXCL:一般是和 IPC_CREAT一起使用,如果消息队列不存在就创建,否则产生一个错误并返回,msgflg 也用来决定消息队列的访问权限。备注:只指定IPC_CREAT,要么返回已存在的标识符,要么返回创建的标识符如果和IPC_EXCL一起指定,要么返回新建的标识符,要么返回-1.
返回值成功,返回消息队列标识符,否则返回-1,错误码放在errno
函数名msgsnd
头文件#include <sys/ipc.h>
#include <sys/msg.h>
函数原型int msgsnd(int msgid,const void* msgp,size_t msgsz,int msgflg);
功能写一个消息到消息队列,为了发送消息,调用进程必须针对该消息队列有写的权限
参数说明1.msgid:消息队列的标识符;
2.msgp: 指向消息缓冲区的指针,临时存放发送的消息;
是一个用户自定义的通用结构体, struct msgbuf{long mtype; char mtext[SIZE];};
注意: mtype 必须 >0
3.msgsz: 要发送消息的实际数据长度
4.msgflg: 可直接写 0
返回值成功,返回0,否则返回-1,错误码放在errno
函数名msgrcv
头文件#include <sys/ipc.h>
#include <sys/msg.h>
函数原型size_t msgrcv(int msgid, void* msgp,size_t msgsz,long msgtype,int msgflg);
功能从消息队列中读取消息,调用进程必须针对该消息队列有读的权限
参数说明1.msgid:消息队列的标识符;
2.msgp: 指向消息缓冲区的指针,临时存放接收的消息;
是一个用户自定义的通用结构体, struct msgbuf{long mtype; char mtext[SIZE];};
3. msgsz: 要接收消息的缓冲区长度
4.msgtype:消息类型( 非负整数,详见手册)
5. msgflg: 可直接写 0
返回值成功,返回实际接收到的字节数,否则返回-1,错误码放在errno
函数名msgctl
头文件#include <sys/ipc.h> #include <sys/msg.h>
函数原型int msgctl(int msgid,int cmd, struct msqid_ds *buf);
功能控制消息队列的运行
参数说明1.msgid:消息队列的标识符;
2. cmd: 操作模式,可取以下值。
IPC_STAT : 将消息队列的信息复制到buf;
IPC_SET: 将参数buf 中指示的数据复制到消息队列的信息;
IPC_RMID: 删除消息队列
IPC_INFO: 获取消息队列的limit 信息;
3. buf: 指向struct_msgid_ds 结构体的指针,详见下一页相关数据结构。
备注:IPC_SET命令中msgid_ds数据中唯一能被设定的只有 msg_perm成员,而msg_perm中能修改的只有 mode,pid和 uid,其他是由系统设定的。
返回值成功,返回 0,否则返回-1,错误码放在errno
消息队列相关数据结构

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值