您当前的位置:首页 > 计算机 > 系统应用 > Linux

Linux 进程间通信

时间:12-14来源:作者:点击数:

1 IPC 类型简介

IPC 类型 特点
管道 FIFOs(命名管道) 管道为单一方向流动,进程之间具有公共祖先。 不相关进程可以使用
流管道 命名流管道  
双向管道 单个管道就可提供父子进程的双向流动  
消息队列 由关键字打开或者创建  
信号量 用来实现对共享存储存取同步  
共享存储 无需进行客户机和服务器之间的复制,最快的 IPC  
三个V IPC使用关键字 key 进行通信  
套接口 支持不同主机间各个进程间的 IPC  
   

2 管道

管道有两种限制:

  • 它们是半双工的。数据只能在一个方向上流动;
  • 它们只能在具有公共祖先的进程之间使用;(fork)
int pipe (int filedes[2]);

功能:创建管道。

由参数 filedes 返回两个文件描述符:filedes[0] 为读而打开,filedes[1] 为写而打开;filedes[1] 的输出是 filedes[0] 的输入。

标准 I/O 提供的创建管道与关闭管道的函数:

FILE *popen(const char*cmdstring,const char *type);
int pclose(FILE*fp);

函数 popen 实现的方法:函数 popen 先执行 fork,然后调用 exec 以执行 cmdstring,返回一个标准 I/O 文件指针;如果 type 是 r,则文件指针连接到 cmdstring 的标准输出;如果 type 是 w,则文件指针连接到 cmdstring 的标准输入。

举例:父子进程通过管道通信

#include 'ourhdr.h'
int main (void){
 int n,fd[2];
 pid_t pid;
 char line[MAXLINE];
 if(pipe(fd) < 0)
 err_sys(“pipe error”); //创建管道
 if((pid = fork()) < 0) 
 err_sys(“fork error”);
 else if(pid > 0){
 close fd[0]; //关闭父进程的读端,此时子进程无法向父进程传送数据
 write(fd[1],”123456\n”,7); // 打开父进程写端,给子进程传送数据
 } else {
 close fd[1]; //关闭子进程写端
 n = read(fd[0],line,MAXLINE);
 write(STDOUT_FILENO,line,n);}
exit(0);
}

3 FIFO

FIFO称为命名管道,是一种文件类型,所以创建 FIFO 类似于创建文件:

int mkfifo(const char*pathname , mode_t mode);

功能:创建 FIFO。Mode 与 open 函数中一样。

FIFO 的阻塞标志:O_NONBLOCK;

打开 FIFO 时,此标志存在下列影响:

  • 未说明 O_NONBLOCK 情况下,只读打开 FIFO 的进程会阻塞,直到其他进程为写打开此 FIFO,同样,只写打开 FIFO 的进程要等到其他进程为读打开此 FIFO;
  • 指定 O_NONBLOCK 情况下,只读打开立即返回,若无进程只读打开 FIFO,那么只写方式打开此FIFO的进程出错返回。

FIFO有两种用途:

1、FIFO 由 shell 命令使用以便将数据从一条管道线传送到另一条,为此无需 创建中间临时文件。

 -> FIFO -> prog3
输入文件 -> prog1 -> tee |
 -> prog2

2、FIFO 用于客户机-服务器应用程序中,以在客户机和服务器之间传递数据

4 系统 V IPC:消息队列,信号量,共享存储器

创建系统 V IPC 中的任何一种 IPC 首先要指定关键字 key,关键字由系统内核变换为标识符,此后,此标识符用于进程间的通信。下面介绍几种使客户机和服务器在IPC结构上会合的方法。

  • 服务器指定关键字创建一个IPC结构,将返回的标识符存放在某处供客户机使用(关键字也可用于父子进程使用)
  • 在一个公用文件中定义一个客户机和服务器都认可的 key,缺点:在服务器用此 key 创建 IPC 时或许此 key 已经与其他 IPC 相结合,此时需要先删除已存在的 IPC
  • 客户机和服务器认同一个路径名和课题 ID,调用 ftok 将此二者转换为关键字再采用方法(2)

对于是否创建一个IPC或者打开一个现存的 IPC,应该遵循下列规则:

关键字 key 是 IPC_PRIVATE 或者未与特定 IPC 相结合时,创建新的 IPC,此处的 IP_PRIVATE 是一个特殊的键值,为了访问一个现存的队列,不可以指定其为关键字.

系统 V IPC 为每一个IPC结构规定了许可权和所有者的 ipc_perm 结构

struct ipc_perm {
    uid_ t uid ; // 所有者的有效ID
    gid_ t gid; 
    uid_ t cuid // 创建者的有效ID
    gid_ t cgid;
    mode_t mode; // 读写许可权
    ulong seq;
    key_t key; // 关键字
}

注意:对于任何 IPC 结构都不存在执行许可权

5 消息队列 msgget、msgctl、msgsnd、msgrcv

消息队列是消息的链接表,存放在内核中由消息队列标识符(队列ID)标识,内核为消息队列设置了结构msgid_ds,此结构包含了队列的当前状态);

int msgget(key_t key, int flag) ;

功能:打开一个现存队列或创建一个新队列。返回队列 ID;当创建一个新队列时初始化 msqid_ds;

int msgctl(int msgid, int cmd, struct msgid_ds *buf);

功能:对指定的队列进行操作。

其中,msgid是要执行的队列ID,cmd是要执行的命令,buf为指向队列当前状态结构的指针;

cmd 解释如下:

  • IPC_STAT 取此队列的 msgid_ds 结构,并将其存放在 buf 指向的结构中
  • IPC_SET 根据 buf 指向结构的值,更改原队列结构中 msg_perm.uid,msg_perm.gid,msg_perm.mode,msg_perm.qbytes
  • IPC_RMID 从系统中删除该消息队列以及仍在该队列上的所有数据
int msgsnd(int msgid, const void *ptr, size_t nbytes, int flag);

功能:向指定队列(msgid)发送类型为 ptr 的消息数据。对于接受信息的进程则可以调取消息类型来以非先进先出的顺序读取消息; flag 的值可以指定为 IPCNOWAIT。指定其为非阻塞

int msgrcv(int msgid, void*ptr,size_t nbytes,long type, int flag)

功能:从指定的队列中读消息,该函数为阻塞式调用。

从指定队列 msgid 中读取类型为 type 的消息,nbytes 说明所要接受的数据缓存的长度,参数 type 解释如下:

  • type == 0,返回队列中的第一个消息
  • type > 0, 返回队列中消息类型type的第一个消息
  • type < 0, 返回队列中消息类型值小于或等于type绝对值,而且在这种消息中,其类型值又最小的消息

6 信号量

信号量是一个计数器,用来实现多进程对共享数据的存取。

同消息队列一样,内核也为信号量设置了结构semid_ds结构,此结构包含了信号量的当前状态;

下面介绍进程获得共享数据的步骤:

  • 测试控制该资源的信号量
  • 若信号量值为正,进程使用资源,信号量值减1,当一个进程不再使用共享资源时,对应的信号量值增1
  • 若信号量的值为0,则进程进入睡眠状态,直到信号量大于0,进程被唤醒,返回第一步

系统 V 的信号量:系统V的信号量较复杂,它的信号量并非是一个非负值,而且是一个或多个信号量值的集合.创建信号时需要指定该集合中的各个值;

semget (key_t key, int nsems, int flag);

功能:创建一个新集合或是引用一个现存的集合。由key决定;当创建一个新集合时,对semid_ds结构的相应成员赋初值, nsems 为信号量数,flag 设置许可权位;

注意: nsems是该集合中的信号量数,若创建新集合,必须设置nsems,若引用一个现存的集合,则将nsems指定为0。

int semctl(int semid, int semnum, int cmd,union semun arg);

功能:对信号量进行操作的函数。cmd指定执行的命令, semnum指定该集合中的某一个成员,区别是此函数的第三个参数是一个联合而并非指针。

semop(int semid,sruct sembuf semoparray[ ],size_t nops);

功能:此函数自动执行ID为semid的信号量集合上的操作数组。其中, nops规定了数组中操作的数量,第二个参数如下:

struct sembuf {
 ushort sem_num;
 short sem_op; 
 short sem_flag; // 可以设置 IPC_NOWAIT,或者 SEM_UNDO
}

对集合中每个成员的操作由相应的 sem_op 规定:

  • sem_op为正,返回进程占用的资源,信号量加上 sem_op
  • sem_op为负,则表示要获取由该信号量控制的资源
  • sem_op为0,这表示希望等待到该信号量值变成0

共享存储(信号量被用来实现对共享存储存取的同步)

共享存储允许两个或多个进程共享一给定的存储区。因为数据不需要在客户机和服务器之间复制,所以这是最快的一种IPC。

同消息队列,信号量一样,共享存储有内核分配的结构:shmid_ds;

几个基本控制函数:

int shmget(key_t key, int size, int flag);

功能:获得或者创建一个共享存储标识符。size 是该共享存储段的最小值。

int shmctl(int shmid, int cmd, struct shmid_ds buf);

功能:对共享存储段执行多种操作。指定 cmd 命令在 shmid 上执行。

区别:进程对共享字段进行操作时,需要先进行地址映射,结束之后需要对此映射地址进行脱节。

void *shmat(int shmid, void *addr, int flag) ;

功能:映射地址的函数。

flag 中指定了 SHMRDONLY 位,则以只读方式连接此段。否则以读写方式连接此段。addr 解释如下:

  • 如果addr为0,则此段连接到由内核选择的第一个可用地址上。
  • 如果addr非0,并且没有指定 SHM_RND,则此段连接到addr所指定的地址上。
  • 如果addr非0,并且指定了 SHM_RND,则此段连接到(addr-(addr mod SHMLBA))所表示的地址上。该算式是将地址向下取最近1个 SHMLBA 的倍数。

int shmdt(void *addr); 当对共享存储段的操作已经结束时,则调用 shmdt 脱节该段。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐