1、XSI IPC message queues
semaphores
share memory
(1)它们在内核都与一个非负整数对应,这个数通常很大;当IPC结构体移除时,这个数会增加到最大值并最终变成0.
(2)这个整数是内部IDENTIFIERS,而进程之间必须用外部IDENTIFI ERS,这就是KEY;而KEY转化成内部IDENTIFIERS由KERNEL完成
(3)SERVER和client的KEY的创建:
IPV_PRIVATE KEY:server使用 IPC_PRIVATE创建,并存储起来(比如一个文件)让client去获取;也可用于父子进程(作为一个参数传递给EXEC)
在一个共同的HEADER文件定义KEY:必须防止KEY已经关联一个IPC结构体
使用pathname和PID,用ftok函数转变成一个KEY:
#include <sys/ipc.h>
key_t ftok(const char *pathname, int id);
key if OK, (key_t)-1 on error
|
(pathname必须对应1个文件,只有id的低8位用来生成key;)注1:不同的pathname形同的PID可能产生相同的KEY
注2:想关联到一个已经存在的queue(一般是由client),key必须与queue刚创建的key相等,并且必须设定为IPC_CREAT;
(4)Permission Structure
struct ipc_perm {
uid_t uid; // owner's effective user id
gid_t gid; // owner's effective group id
uid_t cuid; // creator's effective user id
gid_t cgid; // creator's effective group id
mode_t mode; // access mode
.
.
.
}
|
可以使用msgctl, semctl或shmctl修改上述值(5)Advantages and Disadvantages
所有的XSI IPC结构体都是systemwide并且没有reference计数;
它们不是以名字显示在文件系统里,所以无法用read write 之类的文件操作函数;
它们不使用文件描述符,所以不能使用multiplexed I/O 函数比如 select poll;
它们可以使用1个函数msgsnd调用完成一个进程的传递1个消息到message queue,而其他形式的IPC一般需要open,write,和close;
它们是reliable, flow controlled, record oriented,可以被processed in(除了FIFO顺序)
IPC type |
Connectionless? |
Reliable? |
Flow Control? |
Records? |
Message types or priority? |
|
|
|
|
|
|
message queue |
no |
yes |
yes |
yes |
yes |
STREAMS |
no |
yes |
yes |
yes |
yes |
UNIX domain stream socket |
no |
yes |
yes |
no |
no |
UNIX domain datagram socket |
yes |
yes |
no |
yes |
no |
FIFOs(non-STREAMS) |
no |
yes |
yes |
no |
no |
注1:Connectionless意味着发送信息不用调用open之类的函数
注2:Flow Control意味着如果资源(buffer)短缺或receiver无法接受更多信息,sender会进入sleep;当flow control条件达到,sender自动awaken.
2、MESSAGE QUEUE
(1)message queue是1个内核中的message链表,有1个message queue identifier(queue ID)
(2)msgget:创建或者打开1个已经存在的queue
msgsnd:添加新message加到queue的末尾
(注:包括message的长整数,非负长度,实际数据)
msgrcv:从queue上获取messages,按下面的type field获取
struct msquid_ds {
struct ipc_perm msg_perm; //权限
msgqnum_t msg_qnum; // # of messages on queue
msglen_t msg_qbytes; // max # of bytes on queue
pid_t msg_lspid; // pid of last msgsnd()
pid_t msg_lrpid; // pid of last msgrcv()
time_t msg_stime; // last-msgsnd() time
time_t msg_rtime; // last-msgrcv() time
time_t msg_ctime; // last change time
.
.
.
}
|
(3)system limits(linux) message最大byte数: 8192
queue最大byte数: 16384
最大queue数: 16
最大message数: 16384*16
(4)msgget(打开一个已经存在的queue或者创建一个新queue)
#include <sys/msg.h>
int msgget(key_t key, int flag);
Returns: message queue ID if OK, -1 on error
|
创建一个新queue,struct msqid_ds的初始化 .ipc_perm相应
.msg_qnum,msg_lspid, msg_lrpid, msg_stime和msg_rtime全部设置为0
.msgctime设置为当前时间
.msg_qbytes设置为system limit
(5)msgctl(在queue上执行各种操作,同semaphores的semctl以及shared memory的shmctl)
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
Returns: 0 if OK, -1 on error
|
cmd(这三个命令也用于semaphores和shared memory): .IPC_STAT:获取struct msqid_ds并把指针赋给buf;
.IPC_SET:拷贝buf指向的相关数据到queue关联的struct msqid_ds,包括下面数据,msg_perm.uid, msg_perm.gid, msg_pern.mode, msg_perm_qbytes(只有euid与msg_perm.cuid或msg_perm.uid相等 或者 具有superuser权限的进程才能执行命令,只有superuser可以增加msg_qbytes)
.IPC_RMID:移除message queue以及相关数据(权限同IPC_SET)这个移除是立即性的,任何使用该queue的进程会返回1个EIDRM的错误
(6)msgsnd(把数据放到1个message queue)
#include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
Returns: 0 if OK, -1 on error
|
新加的message一般都放在queue的最后面
ptr:包含message类型(正整数)和message数据,例如
struct mymesg{
long mtype; // positive message type
char mtext[512]; // message data, of length nbytes
};
|
flag:可以设置为IPC_NOWAIT,跟I/O flag的nonblock类似;如果message queue满了,立即返回错误EAGAIN; 如果没设置IPC_NOWAIT,必须等到有空闲空间,或者系统移除这个queue(返回EIDRM“identifier removed”),或者获取到一个signal并且signal handler返回(返回EINTR);
注:msgsnd成功后,与message queue关联的struct msqid_ds会更新调用者相关的msg_lspid,msg_stime,msg_qnum。
(7)msgrcv(从queue上获取mssage)
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
Returns: size of data portion of message if OK, -1 on error
|
ptr:同msgsndnbytes:data buffer的大小(如果message大于data buffer,flag会被设置成MSG_NOERROR, message会被truncated,如果message太大并且flag的值未设置,会返回E2BIG的错误)
type: ==0 返回queue上的第一个message
> 0 返回第一个type相等的message
< 0 返回第一个type值最低的但得小于等于type的绝对值
flag: IPC_NOWAIT,没有立即返回-1并把error设置为ENOMSG,
否则一直等到有,或者系统移除queue(返回EIDRM),或者
捕获到signal并且signal handler返回(返回EINTR)
注:msgrcv成功后,与message queue关联的struct msqid_ds会更新调用者相关的msg_lrpid,msg_rtime,msg_qnum。
3、SEMAPHORES
(1)sempahores是一个计数器,用于提供多进程之间共享数据的访问
获取一个共享资源步骤:
.测试semaphore从而控制资源
.如果semaphore是正值,那么进程可以使用该资源,并把semaphore值-1
.如果semaphore值为0,进程进入sleep直到semaphore值大于0;当进程wake up,返回第一步
注:当进程完成对共享资源使用,semaphore值+1
(2)system limits(linux)
semaphore最大值 32767
semaphore的adjust_on_exit最大值 32767
semaphore sets最大数量 128
semaphore最大数量 32000
每个semaphore set最大semaphore数 250
undo struct最大数量 32000
每个undo struct最大entry数量 32
每个semop调用的最大操作数 32
(3)semget(获取一个semaphore ID)
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);
Returns: semaphore ID if OK,-1 on error
|
将KEY转换为一个identifier,初始化struct semid_dsstruct semid_ds{
struct ipc_perm sem_perm;
unsigned short sem_nsems; // # of semaphores in set
time_t sem_otime; // last-semop() time
time_t sem_ctime; // last-change time
.
.
.
}
|
(sem_otime设置为0sem_ctime设置为当前时间
sem_nsems设置为nsems,如果是reference已存在的set,则为0)
(4)semctl(各种semaphore操作的集合)
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /*union semun arg */);
Returns: see following
|
注:第四个参数是可选的,根据cmd扩展:union semun {
int val; //for SETVAL
struct semid_ds *buf; // for IPC_STAT和IPC_SET
unsigned short *array; //for GETALL和SETALL
}; |
cmd:(0 .IPC_STAT:为set获取struct semid_ds并存储在arg.buf中
.IPC_SET:设置sem_perm.uid sem_perm.gid和sem_perm.mode但进程必须有权限
.IPC_RMID:从系统中移除semaphore set,这个移除是立即性的,接下来跟这个set相关的操作会返回EIDRM,移除必须有相应权限
注:每一个semaphore都会被一个匿名的struct表示
struct {
unsigned short semval; // semaphore value, always >= 0
pid_t sempid; // pid for last operation
unsigned short semncnt; // #processes awaiting semval>curval
unsigned short semzcnt; // #processes awaiting semval == 0
.
.
.
};
|
.GETVAL:返回semval值(for the member semnum) .SETVAL:设置semval值(同上),值由arg.val指定
.GETPID:返回sempid值(同上)
.GETNCNT:返回semncnt值(同上)
.GETZCNT:返回semzcnt值(同上)
.GETALL:返回set里所有semaphore值,存储在arg.arry指针中
.SETALL:设置所有semaphore值,由arg.array指向
注:除了GETALL,其他get命令返回相应值,其他命令返回0
(5)semop(atomically performs an array of operations on a semaphore set)
#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
Returns: 0 if OK, -1 on error
|
sembuf结构体struct sembuf {
unsigned short sem_num; // member # in set (0, 1,... , nsems -1)
short sem_op; // operation (negative, 0, or positive )
short sem_flg; // IPC_NOWAIT, SEM_UNDO
};
|
nops:设置array操作号sem_op:
.positive: semaphore值会加上这个值,如果flag为undo,则减去这个值
.negative: 如果semaphore值>=|sem_op|,那么semaphore值减去|sem_op|,若flag为undo,则增加相应值
如果semaphore值<|sem_op|:
- 如果设置了IPC_NOWAIT,则返回EAGAIN错误
- 如果没有IPC_NOWAIT,semaphore的semncnt值增加,调用者进入sleep直到下面三个有1个发生
【1】semaphore值 >= |sem_op|,semncnt值减小 【2】系统移除semaphore,函数返回EIDRM
【3】进程捕获signal且signal handler返回,semncnt值减小,函数返回EINTR
.0: 调用进程想等待semaphore值变成0
如果sempahore值为0,立即返回
如果semaphore值不是0
- 如果设置了IPC_NOWAIT,则返回EAGAIN错误
- 如果没有IPC_NOWAIT,semaphore的semzcnt值增加,调用者进入sleep直到下面三个有1个发生
【1】semaphore值 >= |sem_op|,semncnt值减小 【2】系统移除semaphore,函数返回EIDRM
【3】进程捕获signal且signal handler返回,semzcnt值减小,函数返回EINTR
4、SHARED MEMORY
(1)最快形式的IPC,一般semaphore用于同步共享内存访问,内核都有一个结构体与每个shared memeory segment对应
struct shmid_ds {
struct ipc_perm shm_perm;
size_t shm_segsz; // size of segment in bytes
pid_t shm_lpid; // pid of last shmop()
pid_t shm_cpid; // pid of creator
shmatt_t shm_nattch;// number of current attaches
time_t shm_atime; // last-attach time
time_t shm_dtime; // last-detach time
time_t shm_ctime; // last-change time
.
.
.
};
|
shmatt_t 大小一般等同于 unsigned short
(2)system limit(linux)
1个shared memory segment最大值: 33,554,332bytes
1个shared memory segment最小值: 1byte
system wide,shared memory segment最大数量: 4096
每个进程 shared memory segment最大数量: 4096
(3)shmget(获取1个shared memory identifier)
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
Returns: shared memory ID if OK, -1 on error
|
新segment初始化: .ipc_perm初始化到相应的权限
.shm_lpid, shm_nattach, shm_atime, shm_dtime为0
.shm_ctime为当前时间
.shm_segsz为size大小
size:shared memory segment大小(单位byte),一般是page size的倍数,否者,最后剩余的空间无法利用;如果reference已存在的segment,size为0;如果是新segment,则指定大小,并初始化内容为0
(4)shmctl(各种shared memory操作集合)
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
Returns: 0 if OK, -1 on error
|
cmd: .IPC_STAT:获取shmid_ds结构体,并存储在buf中
.IPC_SET:根据buf内容,设置shm_perm.uid, shm_perm.gid, shm_perm.mode,必须要有相应权限
.IPC_RMID:移除shared memory segment,直到最后一个进程使用完segment,这个segment才会被移除,但是segment的identifier立即移除,shmat()无法attach segment;移除必须有相应权限。
.SHM_LOCK:锁住shared memory segment中的内存区域,只能由superuser发起
.SHM_UNLOCK:对应上面的操作,也只能是superuser
(5)shmatt(进程用这个函数跟segment的地址空间attach)
#include <sys/shm.h>
void *shmat(int shmid, const void *addr, int flag);
Returns: pointer to shared memroy segment if OK, -1 on error
|
addr: .如果addr==0,segment attach到内核所选的第一个可用地址,这是推荐用法
.如果addr!=0,flag!=SHM_RND,segment attach到addr指定的地址
.如果addr!=0,flag==SHM_RND,segment attach到(addr-(addr modulus SHMLBA));SHM_RND代表"round",SHMLBA代表"low boundary address multiple",总是2的次方;算法是round地址到下一个SHMLBA的倍数
flag:
SHM_RDONLY,segment只读,否则是可读可写的
注:若shmat成功,shm_nattch值增加
(6)shmdt(与shmat对应,detach segment)
#include <sys/shm.h>
int shmdt(void *addr);
Returns: 0 if OK, -1 on error
|
注:并没有移除identifier和相关数据结构,直到一些进程调用shmctl使用命令IPC_RMID。成功后,shm_nattch值减小