最初的Unix IPC包括:管道、FIFO、信号;
System V IPC包括:System V消息队列、System V信号灯、System V共享内存区;
POSIX IPC包括:POSIX消息队列、POSIX信号灯、POSIX共享内存区;
由于Unix系统版本的多样性,电子电气工程协会(IEEE)开发了一套独立的Unix标准,这套新的ANSI Unix标准被称为计算机环境的可移植操作系统接口(POSIX:Portable Operation System Interface).现有大部分Unix和流行的版本都遵循POSIX标准;而Linux从一开始就遵循了POSIX标准;
System V IPC对象属于系统内核对象,执行同步操作的时候,系统内核参与完成了大部分操作,所以它是一套重量级同步对象;
POSIX IPC对象属于用户进程对象,执行同步操作的时候,不需要系统内核参与,所以它是一套轻量级同步对象;
一般来说,使用POSIX IPC来控制对共享资源的访问,就足够了;
典型的IPC对象:管道、命名管道、信号、信号灯、信号量集、消息队列、共享内存;下面仅介绍信号量集、共享内存;
一、信号量集:
POSIX IPC标准对信号量的的要求并不高:信号量(sem_init)、命名信号量(sem_open);
System V IPC要求信号量必须是一个集合,即:信号量集;
信号量集和信号量一样,都是为了控制多个进程对共享资源的同步访问而引入的同步对象;System V IPC中规定:不能只单独定义一个信号量,而是只能定义一个信号量的集合,即:信号量集,其中包含一组信号量,同一信号量集中的多个信号量使用同一个唯一的ID来引用,这样做的目的是为了对多个共享资源进行同步控制的需要;
1、信号量集的创建与打开:
系统调用semget()用于创建一个新的信号量集,或者是存取一个已经存在的信号量集;
函数原型: int semget(key_t key, int nsems, int semflag);
参数说明: key --> 需要创建或打开的信号量集的键,用于唯一地标记一个信号量集;这个参数是用户程序可以直接访问的用户态参数;
nsems --> 表示待创建的信号量集key中的信号量的个数,这个参数只在创建信号量集的时候有效;
semflag --> 表示调用函数的操作类型,也可以用于设置信号量集的访问权限,两者通过逻辑或(or)表示;
返回值:成功:返回一个正数,这个正数也用于唯一地标记已经创建或打开的信号量集,这个唯一标示由系统内核使用;这个正数被称为是IPC标识符;
失败:返回-1;并设置错误码errno来标记错误原因:
EEXIST(信号量集已经存在,无法创建)
EIDRM(信号量集已经删除)
ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
ENOMEM(没有足够的内存创建新的信号量集)
ENOSPC(超出限制)
当调用semget()创建一个信号量集的时候,信号量集的semid_ds结构会被初始化;ipc_perm中的各个量被设置为相应的值;sem_nsems被设置为参数nsems的值;sem_otime被设置为0,sem_ctime被设置为当前时间;
系统调用semget()的第一个参数是关键字值(一般由系统调用ftok()返回的).系统内核将此值和系统中存在的其它的信号量集的关键字值进行比较,不存在的话,则直接创建这个新的信号量集,如果已经存在同关键字值的信号量集,则直接打开这个信号量集,不必再新建信号量集;打开和存取操作与参数semflag有关.IPC_CREAT:如果待创建的信号量集在系统内核中不存在,则创建信号量集;IPC_EXCL与IPC_CREAT同时使用时,如果信号量集在系统内核中已经存在,则调用失败;如果单独使用IPC_CREAT,则系统调用semget()要么返回新创建的信号量集的标识符,要么返回系统内核中已经存在的具有相同关键字值的信号量集的标识符;如果IPC_EXCL与IPC_CREAT一起使用,则要么返回新创建的信号量集的标识符,要么返回-1;IPC_EXCEL单独使用时没有意义;
参数nsems指出了一个新创建的信号量集中应该创建的信号量的个数;
2、信号量相关的内核结构:
struct semid_ds
{
struct ipc_perm sem_perm; /* operation permission struct */
struct sem* sem_base; /* ptr to first semaphore in set:指向数组中第一个信号量的指针*/
ushort_t sem_nsems; /* number of semaphores in set:信号量集(数组)中的信号量的个数 */
#if defined(_LP64)
time_t sem_otime; /* last semop time:最后一次semop()操作的时间 */
time_t sem_ctime; /* last change time:最后一次改动此数据结构的时间 */
#else /* _LP64 */
time_t sem_otime; /* last semop time */
int32_t sem_pad1; /* reserved for time_t expansion */
time_t sem_ctime; /* last change time */
int32_t sem_pad2; /* time_t expansion */
#endif /* _LP64 */
int sem_binary; /* flag indicating semaphore type */
long sem_pad3[3]; /* reserve area */
};
/*Common IPC access structure*/
struct ipc_perm
{
uid_t uid; /* owner's user id */
gid_t gid; /* owner's group id */
uid_t cuid; /* creator's user id */
gid_t cgid; /* creator's group id */
mode_t mode; /* access modes */
uint_t seq; /* slot usage sequence number */
key_t key; /* key */
#if !defined(_LP64)
int pad[4]; /* reserve area */
#endif
};
mode的取值:
权限 位
用户读 : 0400
用户写 : 0200
组读 : 0040
组写 : 0020
其他读 : 0004
其他写 : 0002
内核中的sem结构:
在数据结构semid_ds中包含一个指向信号量数组的指针;此数组中的每一个元素都是一个数据结构sem.它定义在linux/sem.h文件中:
/*One semaphore structure for each semaphore in the system.*/
struct sem
{
short sempid; /*pid of last operation:最后一个操作的PID(进程ID)*/
ushort semval; /*current value:信号量的当前值*/
ushort semncnt; /*num procs awaiting increase in semval:等待资源的进程数量*/
ushort semzcnt; /*num procs awaiting semval=0:等待资源完全空闲的进程数量*/
};
3、PV操作:
P操作(代表荷兰语Proberen:尝试):
等待一个信号灯,等待申请使用一个单位的共享资源 ,该操作测试这个信号灯的值,如果小于或等于0,则阻塞调用者,一旦值变大,就将它减1;
V操作(代表荷兰语Verhogen:增加):
挂出(post)一个信号灯,该操作将信号灯的值加1;表示释放一个单位的共享资源;
4、信号量集的操作:
系统调用semop()对信号量集进行原子操作;
函数原型:int semop(int semid, struct sembuf* semoparray, size_t nsops);
参数说明:semid --> 信号量集的IPC标识符,用于引用对应的信号量集;
semoparray --> sembuf结构的数组,用于指定调用semop()函数所做的操作(PV操作);
nsops --> 指出数组semoparray中操作结构元素的个数:有nsops个sembuf元素参与了PV操作;nsops>=1;
返回值:==0:成功; -1:失败;
函数说明:semoparray是一个sembuf结构的数组,其中每个元素都代表一个操作,由于此函数执行的是原子操作,所以,一旦执行,就会执行数组中所有sembuf元素所定义的操作;
当函数返回失败时,errno:
E2BIG(nsops大于最大的ops数目),即:一次对信号的操作数超出系统的限制;
EACCESS(权限不够),即:调用进程没有权能执行请求的操作,并且不具有CAP_IPC_OWNER权能;
EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行),即:信号操作暂时不能满足,需要重试;
EFAULT(sops指向的地址无效),即:sops或timeout指针指向的空间不可访问;
EFBIG:sem_num指定的值无效;
EIDRM(信号量集已经删除)
EINTR(当睡眠时接收到其他信号),即:系统调用阻塞时,被信号中断;
EINVAL(信号量集不存在,或者semid无效)
ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)
ERANGE(信号量值超出范围),即:信号所允许的值越界;
struct sembuf结构:
struct sembuf
{
ushort_t sem_num; /* semaphore index in array:待操作的信号量集中的某一个信号量的索引 */
short sem_op; /* semaphore operatio:对该信号量所执行的操作 */
short sem_flg; /* operation flags:操作标志 */
};
成员sem_num:待操作的信号量集中的某一个信号量的索引,所以,其取值范围是[0,信号量集中信号量的个数);
即:操作信号量在信号集中的编号,第一个信号的编号是0;
成员sem_op:定义了semop()函数对信号量所作的操作;如果其值为正数,该值会加到现有的信号内含值中.通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值.通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0.
成员sem_flg:定义了semop()的操作标志;可能的选择有两种:
IPC_NOWAIT --> 对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息.
IPC_UNDO --> 程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值.这样做的目的在于避免程序在异常情况下结束时未将锁定的资源
解锁,造成该资源永远锁定.
sem_op成员的取值详解:
>0:释放相应数量的资源,将sem_op的值累加到信号量的值上;
=0:进程阻塞,直到相应信号量的值为0,当信号量的值已经为0,函数立即返回;如果信号量的值不为0,则依据sem_flag的IPC_NOWAIT位决定函数操作;如果sem_flag指定了IPC_NOWAIT位,则semop函数出错返回EAGAIN(异步返回).如果sem_flag没有指定IPC_NOWAIT,则将该信号量的semncnt值加1(等待空闲资源的进程数多一个),然后挂起进程,直到下述情况发生:信号量的值为0,将信号量的semzcnt的值减1(等待资源完全空闲的进程数少一个),函数semop成功返回;此信号量被删除(只有超级用户或创建用户进程拥有此权限),函数semop出错返回EIDRM;进程捕捉到系统信号,并从信号处理函数返回,在此情况下,将此信号量的semncnt值减1(等待资源完全空闲的进程数少一个),函数semop出错返回EINTR;
<0:请求sem_op的绝对值所表示的数目的资源;如果相应的资源数可以满足请求,则信号量的值减去sem_op的绝对值,函数成功返回;当相应的资源数目不能满足请求时,这个操作与sem_flag有关;如果sem_flag指定了IPC_NOWAIT,则semop函数出错返回EAGAIN(异步返回);如果sem_flag没有指定IPC_NOWAIT,则将该信号量的semncnt值加1(表示等待空闲资源的进程多了一个),然后挂起进程,直到下述情况发生:当相应的资源数目可以满足请求时,该信号量的值减去sem_op的绝对值(表示|sem_op|个数目的共享资源被申请走了),然后成功返回;当此信号量已被删除(只有超级用户或创建用户进程拥有此权限)时,函数出错返回EIDRM;当进程捕捉到系统信号并从信号处理函数返回时,将此信号量的semzcnt值减1,函数semop出错返回EINTR;
简单地说:如果sem_op为负数,则把信号量的值减去sem_op的绝对值,此时,sem_op的绝对值表示本次调用semop()操作需要申请的共享资源的数量;这与信号量集控制的资源有关;如果没有设置IPC_NOWAIT,那么,调用进程将进入休眠状态,直到信号量控制的资源具有可用的数量为止;如果sem_op为正数,则信号量的值加上sem_op的绝对值,表示调用进程需要归还|sem_op|个单位数目的共享资源给系统,即:调用进程释放信号量集所控制的共享资源;如果sem_op为0,那么,调用进程将调用sleep()休眠,直到信号量的值为0;这在一个调用进程等待完全空闲的共享资源时使用;
5、信号量集的控制:
系统调用semctl()可以实现对信号量集的控制;
函数原型:int semctl(int semid, int semnum, int cmd, /*union semun arg*/...);
参数说明:semid --> 信号量集的IPC标识符;
semnum --> 信号量集semid中的某一个信号量的索引;
cmd --> 对信号量集semid中的特定信号量semnum执行的操作命令;
变参 --> 这是一个联合体类型union semun的副本,而不是一个指向联合类型的指针;联合体中各个量的使用情况与参数cmd的设置有关;
返回值:-1:失败;
>=0:成功(依赖cmd参数的设置):
GETVAL the value of semval
GETPID the value of (int) sempid
GETNCNT the value of semncnt
GETZCNT the value of semzcnt
cmd取其余值时,返回0表示成功;
union semun
{
int val; /* value for SETVAL */
struct semid_ds* buf; /* buffer for IPC_STAT&IPC_SET */
unsigned short* array; /* array for GETALL&SETALL */
structseminfo* __buf; /* buffer for IPC_INFO */
void*__pad;
} arg;
cmd参数的取值详解:
CMD的取值 操作描述
GETVAL 返回成员semnum的semval值,信号量集中的一个单个的信号量的值;
SETVAL 使用arg.val对该信号量的semnum.sempid赋值(需要参数arg),设置信号量集中的一个单独的信号量的值;
GETPID 返回成员semnum的sempid值,最后一个执行semop操作的进程的PID;
GETNCNT 返回成员semnum的semncnt值,正在等待资源的进程数目;
GETZCNT 返回成员semnum的semzcnt值,正在等待完全空闲的资源的进程数目;
GETALL 将该信号量集中所有信号量的值赋值到arg.array(需要参数arg)所指向的数组中,用于读取信号量集中的所有信号量的值;
SETALL 使用arg.array所指向的数组中的值对信号量集赋值(需要参数arg),设置信号量集中的所有的信号量的值;
IPC_RMID 删除信号量集.此操作只能由具有超级用户的进程或信号量集拥有者的进程执行,
这个操作会影响到正在使用该信号量集的进程;
IPC_SET 设置此信号量集的sem_perm.uid、sem_perm.gid以及sem_perm.mode的值.值来自semun.buf结构中
此操作只能由具有超级用户的进程或信号量集拥有者的进程执行;
设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数;
IPC_STAT 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中;
(秩名) |