1. 消息队列
答:队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式及特定的优级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息,对消息队列有读权限的进程则可以从消息队列中读走消息,消息队列是随内核持续的。
2. 消息队列的操作有下面三种类型
答:
(1) 打开或创建消息队列。消息队列的内核持续性要求每个消息队列都在系统范围內对应唯一的键值,所以,要获得一个消息队列的描述字,只需提供该消息队列的键值即可
注:消息队列描述字是由在系统范围内唯一的键值生成的,而键值可以看
作对应系统内的一条路经
(2) 写操作。消息读写操作非常简单,对开发人员来说,每个消息都类似如下的数据
struct msgbuf ,
{
Long mtype;
Char mtext[1];
}
mtype成员代表消息类型,从消息队列中读取消息的一个重要依据就是消息的类型; mtext是消息内容,当然长度不一定为1。因此,对于发送消息来说,首先预置 msgbuf 缓冲区并写入消息类型和内容,调用相应的发送函数即可;对读取消息来说,首先分配这样一个 msgbuf 缓冲区,然后把消息读入该缓冲区即可
(3) 获得或设置消息队列属性。消息队列的信息基本上都保存在消息队列头中,因此可以分配一个类似于消息队列头的结构。
3. 消息队列API
答:
(1) ftok 函数
key_t ftok(const char *pathname, int proj_id);
其中参数fname是指定的文件名,这个文件必须是存在的而且可以访问的。id是子序号,它是一个8bit的整数。即范围是0~255。当函数执行成功,则会返回key_t键值,否则返回-1。在一般的UNIX中,通常是将文件的索引节点取出,然后在前面加上子序号就得到key_t的值。
(2) msgget 函数
int msgget(key_t key, int msgflg);
参数key是一个键值,由ftok获得;msgtag参数是一些标志位。该调用返回与健值key相对应的消息队列描述字
在以下两种情况下,该调用将创建一个新的消息队列。
●如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
●key参数为 IPC_PRIVATE。
参数 msgtag 可以为IPC_CREAT、 IPC_EXCI、 IPC_NOWAIT,或三者的或结果。
返回值成功返回消息队列描述字,否则返回-1。注:参数key设置成常数IPC_PRIVATE 并不意味着其他进程不能访问该消息队只意味着即将创建新的消息队列。
4. 信号灯
答:信号灯与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以改该标志。除了用于访问控制外,还可用于进程同步。信号灯有以下两种类型。
(1)二值信号灯:最简单的信号灯形式,信号灯的值只能取0或1,类似于互斥锁。注:二值信号灯能够实现互斥锁的功能,但两者的关注内容不同。信号灯强调共享资源要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
(2)计算信号灯:信号灯的值可以取任意非负值(当然受内核本身的约束)Linux 对信号灯的支持状况与消息队列一样,在 Red had 8.0发行版本中支持的是系统V的信号灯。因此,本文将主要介绍系统V信号灯及其相应API。在没有声明的情况下,以下讨论中指的都是系统V信号灯注意:通常所说的系统V信号灯指的是计数信号灯集。
5. 信号灯实现原理
答:
(1)系统V信号灯是随内核持续的,只有在内核重起或者显删除一个信号灯集时,该信号灯集才会真正被删除。因此系统中记录信号灯的数据结构(struct ipc_ids sem_ids)位于内核中,系统中的所有信号灯都可以在结构sem_ids中找到访问入口。
(2)图13-6-1说明了内核与信号灯是怎样建立起联系的,其struct ipc_ids sem_ids是内核中记录信号灯的全局数据结构,描述一个具体的信号灯及其相关信息。
6. 信号灯的三个操作
答:
(1) 打开或创建信号灯。与消息队列的创建及打开基本相同,不再详述对信号灯的操作无非有下面三个步骤对共享资源的释放
(2) 信号灯值操作。Linux可以增加或减小信号的值,相对于共享资源的释放和占有。具体参见后面的semop系统调用。
(3)获得或设置信号灯属性。系统中的每一个信号灯集都对应一个struct sem away结构,如图13-6-2所示,该结构记录了信号灯集的各种信息,存在于系统空间。为了设置、获得该信号灯集的各种信息及属性,在用户空间有一个重要的联合结构与之对应,即 union senun。
7. 信号灯API
答:
① semget 函数
函数定义:int semget(key_t key, int nsems, int semflag)
函数说明:参数key是一个键k获得,唯一标识一个信号灯集,用法与msgget()中的key相同;参数nsems指定打开或者新创建的信号灯集中将包含信号灯的数目;semflag参数是一些标志位。参数key和semflg的取值,以及何时打开已有信号灯集或者创建一个新的信号灯集与msgget()中的对应部分相同,不再详述。 返回值:该调用返回与健值key相对应的信号灯集描述字返回值成功返回信号灯集描述字,否则返回。
② Semop 函数
函数定义:int semop(int semid, struct sembuf *sops, unsigned nsops);
函数声明:semid是信号灯集ID,sops指向数组的每一个sembuf结构都刻画一个在特定信号灯上的操作。nsops为sops可数组的大小。 sembuf结构为
struct sembuf
{
unsigned short sem_num;
short sem_op;
short sem_flg;
}
返回值:成功返回0.否则返回-1
③ semctl 函数
函数定义:int semctl(int semid, int semnum, int cmd, union semun);
函数声明:该系统调用实现对信号灯的各种控制操作,参数semid指定信号灯集;参数cmd指定具体的操作类型参数semnum指定对哪个信号灯操作,只对几个特殊的cmd操作有意义;arg用于设置或返回信号灯信息该系统调用详细信息请参见其手册页,这里只给出参数cmd所能指定的操作。
●IPC_STAT:获取信号灯信息,信息由arg.buf返回
●IPC_SET:设置信号灯信息,待设置信息保存在arg.buf中(manpage给出了可以设置哪些信息)
●GETALL:返回所有信号灯的值,结果保存在arg.array中,参数sennum被忽略
●GETNCNT:返回等待semnum所代表信号灯的值增加的进程数,相当于目前有多少进程在等待semnum代表的信号灯所代表的共享资源
●GETPID:返回最后对 semnum所代表信号灯执行 semop 操作的进程ID
●GETVAL:返回 semnan 所代表信号灯的值
●GETZCNT:返回等待 semnan 所代表信号灯的值变成0的进程数 ●SETALL:通过arg.aray更新所有信号灯的值;同时,更新与本信号集相关的semid_ds结构的sem_ctime成员
●SETVAL:设置 semnum所代表信号灯的值为arg.val
返回值: 调用失败返回-1,成功返回与cmd相关
●Cmd:return value
●GETNCNT:Semncnt
●GETPID:Semp
●GETVAL:Serval
●GETZCNT:Semant
8. 共享内存
答: 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间,进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的复制。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据复制,而共享内存则只复制两次数据次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域,而是保持共享区域,直到通信完毕为止这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的 Linux 的2.2.x内核支持多种共享内存方式,如mmap()系统调用、Posix共享内存,以及系统V共享内存。
9. mmap()及其相关系统调用
答:mmap()系统调用使得进程之间可以通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访间普通内存一样对文件进行访问,不必再调用read()、write(()等操作注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的,它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而POSIX或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。
10.系统调用mmap()用于共享内存的两种方式:
(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap()。
(2)用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间,由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork(之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系,一般来说,子进程单独维护从父进程继承下来的一些变量,而mmap()返回的地址,却由父子进程共同维护对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。