第一部分 简介(第一章 简介 + 第二章 Posix IPC + 第三章 System V IPC)

来源:互联网 发布:海岛奇兵 数据 编辑:程序博客网 时间:2024/04/30 06:15
前言
应用程序三种构建方法:函数间通过参数返回值来交换信息的庞大程序;多个程序或进程间通过某种形式的IPC进行通信;程序包含多个线程,线程间使用某种IPC。后两种方法可减少指定任务时间。
4种不同的IPC:消息传递(管道,FIFO,消息队列);同步(互斥量,条件变量,信号量,文件和记录锁,读写锁);共享内存(匿名和具名);远程过程调用(门和RPC)。其中共享内存和同步只能用于单主机。


第一部分 简介
1.1 概述
消息传递发展阶段:管道-->System V消息队列-->Posix消息队列-->远程过程调用(客户主机调用服务器主机的某个函数)
同步发展阶段:记录上锁-->System V信号量-->Posix信号量-->互斥锁和条件变量-->读写锁
亲缘关系开始于一个登陆shell(会话)以及由该shell派生的所有进程,意味着具有共同的祖先。

1.2 进程,线程与信息共享
3种进程间信息共享访问方式:穿越内核共享文件系统某文件信息;共享驻留内核的某些信息;不涉及内核,访问共享内存区。
线程:一个进程下的所有线程共享进程的全局变量,但需要考虑同步访问。若某一线程阻塞在空管道的read调用上,而其他线程也可继续执行。

1.3 IPC对象的持续性(P5 图1-3:各种类型IPC对象的持续性)
随进程:由最后一个进程关闭。如管道(无名字)和FIFO(路径名,在文件系统中有名字),他们的数据分别在内存和文件系统中;
随内核:内核重新自举或显式删除。如消息队列,信号量和共享内存区。
随文件系统:显式删除。如使用映射文件实现的Posix消息队列,信号量和共享内存区。

1.4 名字空间
针对无亲缘关系的IPC对象进行命名或给予标识符,它是某种给定IPC类型中可能的名字的集合。即某种IPC对象名字的集合。
IPC由Posix.1和Unix 98标准化,套接字API在Posix.1g工作组标准化。对各种IPC特性都有说明,每种特性都有强制,未定义和可选三种选择。

1.5 fork,exec和exit对IPC对象的影响
如System V IPC的三种形式没有打开或关闭的说法,知道IPC标识符的任何进程都能访问它们。我之前看过一遍,以下的两个表格会在以后的阅读中经常用到。
表一注意记住IPC类型对应的IPC打开后的标识;表二注意记住fork后的作用,exec也较多涉及,之后的_exit遇到相应程序时再回头查阅即可。

表1:


表2:


1.6 出错处理:包裹函数
包裹函数执行实际的函数调用,测试其返回值,并在碰到错误时终止进程,此外,在包裹函数中,线程函数出错时可以分配一个变量来保存函数返回值,赋值给errno,之后输出相应的出错消息。
void Pthread_mutex_lock(pthread_mutex_t *mptr){ //包裹函数可以缩短篇幅
int n;
if((n = pthread_mutex_lock(mptr)) == 0) //最底层函数
return;
errno = n;
err_sys("pthread_mutex_lock error");
}
全局变量errno被设置成一个指示错误类型的正数,定义在头文件<sys/errno.h>中,err_sys函数检查errno的值并输出相应的出错消息。errno的值只在某个函数发生错误时设置,如果该函数不返回错误,errno的值就无定义。

1.7 Unix标准
可移植操作系统接口Posix标准:Posix.1定义了访问Unix的基本C接口,Posix定义了标准命令,商业标准也在迅速的吸纳并扩展Posix标准,如Open Group的Unix98标准。

1.8 书中IPC例子索引表

三种交互模式:文件服务器(客户发送路径名,服务器返回文件内容);生产者-消费者(共享缓冲区中数据的存放与取出);序列号持续增1(多线程将序列号持续加1,该序列号有时在共享文件中,有时在共享内存区中)。
/***P12 相应例子在文中的页码,最好实现一下***/

1.9 小结
a,4个主要领域:消息传递(管道,FIFO,消息队列);同步(互斥锁,条件变量,读写锁,信号量);共享内存区(匿名共享内存区,有名共享内存区);过程调用(Solaris门,Sun RPC)。
b,有些类型的IPC没有名字(管道,互斥锁,条件变量,读写锁),有些具有在文件系统中的名字(FIFO)。
c,posix和system v有什么区别:System VC IPC存在时间比较老,许多系统都支持但是接口复杂,可能各平台上实现略有区别;POSIX是新标准,现在多数UNIX也已实现,语法简单,并且各平台上实现都一样。他们都是在Unix下应用程序共同遵循的一种规范。



第二章 Posix IPC
2.1 概述
3种Posix IPC的共同属性:用于标识IPC的路径名,打开或创建时需要指定的标志以及访问权限,即pathname,oflag,mode。
下表汇总了所有Posix IPC函数,最好记住这些接口函数,记忆时主要看_前面的前缀:
图1:


说说unlink与close的区别:每一个文件,都可以通过一个stat结构体来获得文件信息,其中一个成员st_nlink代表文件的链接数。当通过touch或指定O_CREAT创建文件时,会新增一目录项且文件的链接数为1。当文件存在时,进程打开该文件并不会影响该文件的链接数,只是使调用进程与文件之间建立一种访问关系,即引用数增1,open之后返回fd,此后调用进程可以通过fd来read 、write 、 ftruncate等等一系列对文件的操作,close()的作用是消除这种调用进程与文件之间的访问关系,使文件引用数减1,自然,不会影响文件的链接数。在调用close时,内核会检查打开该文件的进程数,如果此引用数为0,进一步检查文件的链接数,如果这个数也为0,那么就删除文件(在内存中的)内容。
unlink函数删除目录项,并且减少一个链接数,如果链接数达到0并且没有任何进程打开该文件,该文件内容才被真正删除。如果在unlilnk之前没有close,即还有进程引用,那么依旧可以访问文件内容,当然文件名不存在了,fd还是可以访问。
link函数创建一个新目录项,并且增加一个链接数。综上所诉,真正影响链接数的操作是link、unlink以及open的创建。 删除文件内容的真正含义是文件的链接数为0,而这个操作的本质完成者是unlink。

2.2 IPC名字
IPC名字是指mq_open,sem_open和shm_open的第一个参数,如/tmp/queue.1234。名字以单独的斜杠符打头,假如在/tmp目录下用mq_open创建消息队列,且参数为/queue.1234,在平台Solaris2.6上的/tmp下创建
/tmp/.MQDqueue.1234,/tmp/.MQLqueue.1234,/tmp/.MQPqueue.1234,而平台Digital Unix4.0仅创建/tmp/.queue.1234。
考虑到移植性:1,应该把Posix IPC名字的#define行放在一个头文件中,移植到另一平台上,只需修改该命令行即可;2此外也可以使用px_ipc_name函数解决移植性。
例子:

1,i = snprintf(a, 9, "%012d", 12345); printf("i = %lu, a = %s\n", i, a);// 输出:i = 12, a = 00000001;函数功能是将可变个参数(...)按照format格式化成字符串,返回值是欲写入的字符串长度。

2,char *getenv(char *envvar); //getenv()用来取得参数envvar环境变量的内容,找不到符合的环境变量名称则返回NULL。

3,char *pc_ipc_name(const char *name); //为IPC名字添加上正确的前缀目录,并返回完整的IPC名字,如调用pc_ipc_name("test1")

{pc_ipc_name函数具体实现中包含snprintf(dst, PATH_MAX, "%S%S%S", dir, "/", name); 其中当dir = getenv("PC_IPC_NAME")返回NULL后,dir = POSIX_IPC_PREFIX或"/tmp",即直接获取IPC名字或获取系统环境变量设置的目录作为前缀}

此外,Posix.1定义了三个宏:S_TYPEISMQ(buf),S_TYPEISSEM(buf),S_TYPEISSHM(buf),buf指向stat结构,用来判别是否是队列,信号量或共享内存的IPC对象。


2.3 创建与打开IPC对象
mq_open,sem_open,shm_open函数的三个参数:name + oflag(如共享存储区不以O_WRONLY模式打开) + mode(umask函数指定权限位S_IRUSR,S_IRGRP,S_IWOTH等)
如下所示,其中共享存储区不以O_WRONLY模式打开,信号量操作都需要读写访问权:

图2:


oflag:表示怎样打开IPC对象,O_RDONLY(只读),O_WRONLY(只写),O_RDWR(读写),O_CREAT(新建),O_EXCL(排他,文件不能在已存在时创建),O_NOBLOCK(不阻塞),O_TRUNC(截断)
O_CREATE:打开IPC对象时,其用户ID设置为当前进程有效用户ID,组ID设置为当前进程有效组ID。
Posix创建与打开一个IPC对象的逻辑:进程open某一IPC对象,若不存在,检查是否设置O_CREAT,未设置时,出错返回ENOENT,否则检查设备表格,无空间返回ENOSPC;
若对象存在,检查是否同时设置了O_CREAT和O_EXCL,是返回EEXIST,否则检查访问权限,出错返回EACCES。
说说在Unix进程中用户(组)ID和有效用户(组)ID的区别:
1、实际用户ID和实际用户组ID:标识我是谁。也就是登录用户的uid和gid,比如我的Linux以simon登录,在Linux运行的所有的命令的实际用户ID都是simon的uid,实际用户组ID都是simon的gid(可以用id命令查看)。
2、有效用户ID和有效用户组ID:进程用来决定我们对资源的访问权限。一般情况下,有效用户ID等于实际用户ID,有效用户组ID等于实际用户组ID。当设置-用户-ID(SUID)位设置,则有效用户ID等于文件的所有者的uid,而不是实际用户ID;同样,如果设置了设置-用户组-ID(SGID)位,则有效用户组ID等于文件所有者的gid,而不是实际用户组ID。

2.4 IPC权限
进程的相关权限信息:创建IPC对象时的权限位+访问类型(O_RDONLY..);调用进程的有效用户ID,有效组ID和辅助组ID。
IPC对象的权限测试步骤:
1,进程身份为root直接允许访问IPC对象;2,进程有效用户ID等于IPC对象属主ID,且设置用户权限位(读位或写位)则可访问(读或写IPC对象);
3,进程有效组ID和辅助组ID等于IPC对象的组ID且设置组访问权限(读位或写位)则可访问(读或写IPC对象);
4,进程的其他用户访问权限位已设置(读位或写位)则可访问(读或写IPC对象)。

2.5 小结
三种类型的Posix IPC都是用路径名标识的,以pc_ipc_name获得实际路径名。
创建或打开IPC对象时,指定了一组oflag标记。




第三章 System V IPC
3.1 概述
本章描述System V IPC三种方式的共同属性。
下图是System V IPC函数汇总,注意两种标准的函数命名区别:msgget,msgctl,msgsnd,msgrcv VS mq_open,mq_close,mq_unlink,mq_getattr,mq_setattr,mq_send,mq_receive,mq_notify
图1:


3.2 key_t键和ftok函数
key_t ftok(const char *pathname, int id); //把一个路径名和一个整数标识符的低序8位转换成至少32位的整数类型key_t值,即返回IPC键或-1
注意查看第一章的表1,System V的三种IPC是以IPC标识符或key_t值标识。
由pathname查找所在文件的信息存到stat结构中,id的低序8位(标识一个通道,从1开始编号) + stat结构的st_dev的低12位(文件设备编号/文件系统信息) + st_ino的低12位(文件i-node索引结点号) = 组合成IPC键
文件每次创建时由系统赋予的索引节点号很可能不一样,对于下一个调用者来说,由ftok返回的键也可能不同,因此路径名产生键的文件不能是服务器存活期间反复创建并删除的文件。

3.3 ipc_perm结构
内核为每一个IPC对象维护一个信息结构:
struct ipc_perm { uid; gid; cuid; cgid; mode; seq; key;}; //IPC对象信息结构。实际用户ID,实际用户组ID,属主用户ID,属主用户组ID,读写权限;槽位使用序列号;键值

3.4 创建与打开IPC通道
int msgget(key_t key, int msgflag); //用于创建一个新的(IPC_PRIVATE)或打开一个已经存在的(由pathname和id生成的key_t值)消息队列。调用成功返回队列标识符或者返回-1。
msgget(), semget(), shmget(),如果key为IPC_PRIVATE则能保证创建一个唯一的IPC对象。 由key + oflag来判断逻辑

图2:


System V创建与打开一个IPC对象的逻辑:进程首先判断键值是否为IPC_PRIVATE,若是,则检测系统表格是否还有空间,若还有空间,则此时oflag标志为O_CREATE或O_CREATE|O_EXCL时都会调用成功;

若系统表格已满,则返回ENOSPC。若键值不为IPC_PRIVATE,且键值不存在,若指定了O_CREAT,也会调用成功,未指定O_CREAT则会返回ENOENT;若此时键值存在,则看O_CREAT和O_EXCL是否都被设置,
是的话返回EEXIST,否则查看访问权限是否允许,最后成功调用或返回EACCES。

3.5 IPC权限
System V的6个权限位:MSG_R MSG_W MSG_R>>3 MSG_W>>3 MSG_R>>6 MSG_W>>6。(信号量SEM_R...;共享内存区SHM_R...)
调用getXXX函数时,oflag参数中的某些位初始化ipc_perm结构中的mode成员。创建者ID(cuid)和属主ID(uid)设置初始时都可设置为调用进程的有效用户ID和有效组ID。其中创建者(组)ID(ipc_perm结构中的cuid成员)一直不变,因为对IPC对象而言,创建者只有一个,但是可调用相应IPC机制的ctlXXX函数或者超级用户使用chown命令来修改属主ID(ipc_perm结构的uid成员),即改变IPC对象的属主。
每当有一个进程试图使用msgsnd函数往某个消息队列放置一个消息时,就按照以下顺序测试权限:
超级用户直接赋予访问权-->当前进程的有效用户ID等于IPC对象的uid或cuid值,而且相应的访问位在该IPC对象的mode成员中是打开的,则赋予访问权-->当前进程的有效用户组ID等于IPC对象的gid或cgid值,而且相应的访问位在该IPC对象的mode成员中是打开的,则赋予访问权-->其他用户访问位在该IPC对象的mode成员中必须是打开的才能赋予访问权。

3.6 标识符重用
int msgctl(int msgqid, int cmd, struct msqid_ds *buf);//对msqid标识的消息队列执行cmd操作列。成功返回0否则返回-1.队列的msqid_ds结构来描述队列当前的状态。

cmd:

IPC_STAT读取消息队列的数据结构msqid_ds并存储在buf中;IPC_SET由buf来设置消息队列的msqid_ds中的ipc_perm元素的值;  IPC_RMID从系统内核中移走消息队列。
每删除一个IPC对象时,内核就递增相应的槽位号seq,如果一个进程最多有约50个描述符,则递增50。它实则是内核为系统中每个潜在的IPC对象维护的计数器。
每次重用一个IPC表项时,把返回给调用进程的标识符值增加一个IPC表项数。
槽位号seq存在的原因:1,把IPC标识符的可能范围扩大到包含所有整数;2,避免短时间内重用System V IPC标识符。

3.7 ipcs和ipcrm程序
输出System V IPC对象特性的各种信息;删除一个System V消息队列,信号量集或共享内存区。

3.8 内核限制
例如消息队列的最大数目,每个信号量集的最大信号量等等。
/sbin/sysconfig -q ipc //查询System V IPC的当前各种输出限制值,如max-max,msg-mnb等
也可在/etc/system文件中加入一些语句进行修改,如:set msgsys:msginfo_msgseg = value

3.9 小结
msgget,semget,shmget函数的用途和参数使用;ipc_perm结构各成员表示的信息,如槽号seq;属主uid及权限等;内核限制。

















1 0