Linux学习笔记之进程间通信篇(1)_IPC概述

Linux学习笔记系列

前置知识篇
1. 进程
2. 线程
进程间通信篇
1. IPC概述
2. 信号
3. 消息传递
4. 同步
5. 共享内存区


一、前言

在了解完进程线程概念后,现在正式进入进程间通信的篇章,也按照Unix卷2的方式进行编写吧,分为IPC概述、信号、管道、消息传递、同步、共享内存这几个篇章。


二、前置条件

UB18 + 一点点的基础知识


三、本文参考资料

Unix卷2
百度
野火Linux教程


四、正文部分

4.1 IPC概述

IPC(Inter-Process Communication,进程间通信)。进程间通信是指两个进程的数据之间产生交互

Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。
而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室 以及 BSD(加州大学伯克利分校的伯克利软件发布中心),他们在进程间通信方面的侧重点有所不同;

前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system-V IPC”,通信进程局限在单个计算机内(同一个设备的不同进程间通讯);

而后者则跳过了该限制,形成了基于套接字(socket)的进程间通信机制(多用于不同设备的进程间通讯)。

Linux则把两者继承了下来,所以说Linux才是最成功的,既有“system-V IPC”,又支持“socket”。

  1. 本地进程间的通信方式(IPC)
    消息传递(管道、FIFO、消息队列)
    同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
    共享内存(匿名的和具名的)
    远程过程调用(Solaris门和Sun RPC)

  2. 网络中进程之间通信
    UNIX BSD的套接字(socket)
    UNIX System V的TLI(已经被淘汰)

 

4.2 system-V IPC概述

消息队列、共享内存 和 信号量 被统称为 system-V IPC,V 是罗马数字5,是 Unix 的AT&T 分支的其中一个版本,一般习惯称呼他们为 IPC对象,
这些对象的操作接口都比较类似,在系统中他们都使用一种叫做 key 的键值来唯一标识
而且他们都是“持续性”资源——即他们被创建之后, 不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命令删除他们。

Linux的IPC对象(包括消息队列、共享内存和信号量)在内核内部使用链表维护,不同的对象使用 IPC标识符 来标识,如消息队列标识符 msqid、共享内存标识符 shmid,信号量标识符 semid。

对于用户来说,内核提供了简洁的接口,不同的进程通过 IPC关键字(key) 即可访问具体的对象。

通过如下 ipcs 命令可以查看系统当前的IPC对象,没有使用的情况下可能为空:
ipcs
ipcs

 

4.3 system-V IPC相关属性

  1. ftok函数
    三种类型均采用key_t值作为他们的名字,<sys/types.h>将其定义为一个至少32位的整数。
    这个整数由ftok函数,将一个已存在的路径名和一个整数标识符,转换成一个key_t值,称为IPC键 ==> IPC = ftok(path + id)

    	key_t ftok(const char *pathname, int id);
    

    该函数把从pathname导出的信息与id的低序8位组合成一个整数IPC键。
    pathname不得是反复创建和删除的,因为这样系统赋予的索引节点号可能不同,导致对于下一个调用者ftok的返回键也可能不同
    ftok的典型实现调用stat函数(获取文件信息),然后组合以下三个值,产生一个32位键IPC(高序 --> 低序: id(8) + st_dev(12) + st_ino(12) 实现方式不唯一)

    	struct stat stat;
    	stat("/usr/tmp", &stat);
    	printf("%lx , %lx, %x\n", (u_long)stat_st_dev, (u_long)stat.st_ino, ftok("/usr/tmp", 0x57));
    	a. pathname所在的文件系统的信息(st_dev成员)
    	b. 该文件在本文件系统内的索引节点号(st_ino成员)
    	c. id的低序8位(不能为0,可能导致行为未指定或是报错)
    
  2. ipc_perm结构
    内核给每个IPC对象维护一个信息结构,其内容跟内核给文件维护的信息类似。

    	struct ipc_perm
    	{
    		__kernel_key_t	key;
    		
    		/* 属主ID(可变,由xxxctl修改) */
    		__kernel_uid_t	uid;
    		__kernel_gid_t	gid;
    		/* 创建者ID(不可变) */
    		__kernel_uid_t	cuid;
    		__kernel_gid_t	cgid;
    		
    		__kernel_mode_t	mode; 
    		
    		/* 标识符重用 */
    		unsigned short	seq;
    	};
    
  3. 创建或打开一个IPC对象
    创建或打开一个IPC对象的三个getXXX函数(msg/sem/shm get),第一个参数key的类型是key_t的IPC键,返回值id是一个整数标识符(不同于ftok函数的id参数
    对于key值,应用程序有两种选择

     	a. 调用ftok,给它传递pathname和id
     	
     	b. 指定key位IPC_PRIVATE,这将保证会创建一个新的、唯一的IPC对象
    

    IPC_CREAT未设置是不能创建新对象的,即只设置IPC_EXCL是没有意义的

  4. IPC重用性
    由于IPC标识符是可以使用在所有进程的,不同于文件的标识符只能用在当前进程内,
    为了避免有行为不端的进程尝试遍历获取一个正在使用且允许大家读的消息队列,
    每次重用一个IPC表项时,把返回给调用进程的标识符值增加一个IPC表项数。(重新运行仍递增,即跨进程仍然保持)

    假设系统配置成最多50个消息队列:

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    
    int main(void) 
    {
    	int i, msqid;
    	struct msqid_ds info;
    
    	for (i = 0; i < 10; i++) {
    		msqid = msgget(IPC_PRIVATE, 0677 | IPC_CREAT);
    		msgctl(msqid, IPC_STAT, &info);
    		printf("msqid = %d seq = %u\n", msqid, info.msg_perm.__seq);
            
    		msgctl(msqid, IPC_RMID, NULL);
    	}
    
    	exit(0);
    }
    

    msqid
     

4.4 POSIX IPC概述

可移植操作系统接口 (Portable Operating System Interface,缩写为POSIX),
POSIX是IEEE为要在各种UNIX操作系统上运行软件,而定义API接口的一系列互相关联的标准的总称,
其正式称呼为IEEEStd 1003,而国际标准名称为ISO/IEC9945,此标准源于一个大约开始于1985年的项目。
POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。
它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写, 而X则表明其对Unix API的传承。

简单来说,如果应用程序使用POSIX标准的接口来调用系统函数, 那么应用程序将非常容易移植甚至直接兼容到遵循POSIX标准的系统上。

在Linux系统下的多线程遵循POSIX标准,而其中的一套常用的线程库是 pthread,它是一套通用的线程库,是由 POSIX提出的,因此具有很好的可移植性,使用时必须包含以下头文件:

	#include <pthread.h>

除此之外在链接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库, 所以在编译时要加上-lpthread 选项。

 

4.5 POSIX IPC相关类型

这三种POSIX IPC机制具体如下

  1. 消息队列可以用来在进程间传递消息。
    与System V消息队列一样,消息边界被保留了下来,这样读者和写着就以消息为单位进行通信了。
    POSIX消息队列允许给每个消息赋一个优先级,这样在队列中优先级较高的消息就会排在优先级较低的消息前面
  2. 信号量允许多个进程同步各自的动作。
    System V 信号量一样,POSIX 信号量也是一个由内核维护的整数,其值永远都不会小于 0
    与 System V 信号量相比,POSIX信号量在用法上要简单一些:它们是逐个分配的(与 System V 信号量集相比),
    并且在单个信号量上只能使用两个操作来将信号量的值加 1 或减 1(与 semop()系统调用能原子地在一个 System V 信号量集中的多个信号量上加上或减去一个任意值相比)
  3. 共享内存使得多个进程能够共享同一块内存区域,
    与System V共享内存一样,POSIX共享内存提供了一种快速IPC,一旦进程更新了共享内存之后,所发生的变更立即对共享同一区域的其他进程可见。
    Posix
     

4.6 POSIX IPC相关属性

  1. IPC 对象名字
    mq / sem / shm均采用路径名标识,但可以不是文件系统中的实际路径名,所以会导致移植性问题
    要访问一个POSIX IPC对象就必须通过某种方式来识别出它。
    SusV3规定的唯一一种用来标识POSIX IPC对象的可移植方式是使用以斜线打头后面跟着一个或者多个非斜线字符的名字,比如/myproject。
    Linux 和其他一些实现(如 Solaris)允许采用这种可移植的命名方式来给 IPC 对象命名
    SUSv3 并没有禁止使用形式不为/myobject 的名字,但表示这种名字的语义是由实现定义的。
    在一些系统上,创建 IPC 对象名字的规则是不同的。可移植的应用程序中应该将IPC对象名的生成工作放在一个根据目标裁剪过的单独的函数会在头文件中

  2. mq_open / sem_open / shm_open

    1. o_flag标志位

       O_CREAT
       	用户ID:
       		均用当前进程的有效用户ID
       	组ID:
       		sem / shm:当前进程的有效组ID,或者某个系统默认组ID
       		mq:当前进程的有效组ID
       	若未指定O_EXCL,则无法判断是否为新创建还是原已有
       O_EXCL
       	只有在O_CREAT | O_EXCL时,创建已存在的新对象才会返回EEXIST错误
       	检查与创建必须为原子操作
       O_NONBLOCK
       	使一个mq为队列为空时读或是满时写不阻塞
       O_TRUNC
       	读写模式打开一个已存在的shm,使该对象的长度被截成0
      
    2. 打开或创建后,需要进行权限测试:
      a. 若当前进程的有效用户ID为0(超级用户),则允许访问
      b. 若当前进程的有效用户ID等于该IPC对象的属主ID,且相应的访问权限位已设置,则允许访问
      c. 若当前进程的有效组ID 或 某个辅助组ID 等于该IPC对象的组ID且相应的访问权限位已设置,则允许访问
      d. 若其他用户的相应的访问权限位已设置,则允许访问

 

4.7 IPC持续性

分为三类:随进程 / 随内核 / 随文件系统
IPC持续性
 

4.8 IPC比较

与 System V IPC 相比,POSIX IPC 拥有下列常规优势

  • POSIX IPC 的接口比 System V IPC 接口简单。(反观System V 出现时间较早,许多系统都支持,但是接口复杂,并且可能各平台上实现与配置/查看略有区别)
  • POSIX IPC 模型——使用名字替代键、使用 open、close以及 unlink 函数——与传统的 UNIX 文件模型更加一致。(反观System V 不能用类似修改文件属性的函数(ls / rm / chmod)来访问或是修改属性,故后续新增了msgget / semop / shmat等)
  • POSIX IPC 对象是引用计数的。这就简化了对象删除,因为可以断开一个 POSIX IPC对象的链接,并且知道当所有进程都关闭该对象之后对象就会被销毁。(反观System V 是在系统范围内起作用,没有引用计数)
  • POSIX 在无竞争条件下,不需要陷入内核,其实现是非常轻量级的。(反观System V 无论有无竞争都要执行系统调用,因此性能落了下风。)

然而 System V IPC 具备一个显著的优势:可移植性(这个似乎不太确定?)


五、总结

getXXX函数(msg/sem/shm get),第一个参数key的类型是key_t的IPC键,返回值id是一个整数标识符(不同于ftok函数的id参数

在Linux系统下的多线程遵循POSIX标准,而其中的一套常用的线程库是 pthread,它是一套通用的线程库,是由 POSIX提出的,因此具有很好的可移植性。除此之外在链接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库, 所以在编译时要加上-lpthread 选项。

只有在O_CREAT | O_EXCL时,创建已存在的新对象才会返回EEXIST错误,检查与创建必须为原子操作
 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值