进程间通信(IPC)——共享内存、消息队列、信号量

IPC通信——共享内存、消息队列、信号量

本文编写时参考了大量博文,如有侵权,联系删除。

消息队列信号灯共享内存
头文件<sys/msg.h><sys/sem.h><sys/shm.h>
创建或打开IPC函数msggetsemgetshmget
控制IPC操作的函数msgctlsemctlshmctl
IPC操作函数msgsnd
msgrcv
semopshmat
shmdt
ftok函数

头文件:

  • <sys/types.h>
  • <sys/ipc.h>

函数
原型

key_t ftok(const char *pathname, int proj_id)

函数
功能

利用已存在的pathname的信息与>0的整数id的低序8位组合成一个唯一的整数IPC键

函数
参数

const char *pathname

指定的带路径文件名,此文件必须已存在且可存取

int proj_id

大于0的整数,计划代号(project ID)

函数
返回

成功:返回key_t值(即IPC 键值)

出错:-1,错误原因存于error中

附加
说明

key_t一般为32位的int型的重定义

一 共享内存(Shared Memory)

简介:

  共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等。共享内存是所有进程间通信方式中效率最高的
步骤

  1. 创建共享内存对象
  2. 映射共享内存
  3. 对共享内存读写
  4. 解除映射
  5. 删除共享内存对象

头文件:

  • <sys/types.h>
  • <sys/ipc.h>
  • <sys/shm.h>

LINUX终端下命令:
ipcs -m 查看内核中的共享内存
ipcrm -m shmid 删除共享内存

1.shmget函数

函数
原型

int shmget(key_t key, size_t size, int shmflg)

函数
功能

得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符

函数
参数

key_t key

IPC_PRIVATE(0):会建立新共享内存对象

大于0的 int 型整数 : 通常要求此值来源于ftok函数返回的IPC键值

size_t size

0:只获取共享内存时指定为0

大于0的整数:新建的共享内存大小,以字节为单位(实际共享内存的开辟是以页为单位的,4k 4094字节)

int shmflg

0:取共享内存标识符,若不存在则函数会报错

IPC_CREAT:如果内核中不存在键值与key相等的消息队列,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错

函数
返回

成功:返回共享内存的标识符

出错:-1,错误原因存于error中

附加说明

上述shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定共享内存的存取权限

错误代码

EINVAL:参数size小于SHMMIN或大于SHMMAX
EEXIST:预建立key所指的共享内存,但已经存在
EIDRM:参数key所指的共享内存已经删除
ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL)
ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位
EACCES:没有权限
ENOMEM:核心内存不足

2.shmat函数

函数
原型

void *shmat(int shmid, const void *shmaddr, int shmflg)

函数
功能

连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问

函数
参数

int shmid

共享内存标识符

const void *shmaddr

指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置

int shmflg

SHM_RDONLY:为只读模式
SHM_REMAP:重新映射一个进程地址空间没这样shmaddr不能为
0:系统默认,可读可写

函数
返回

成功:映射好的共享内存地址

出错:-1,错误原因存于error中

附加说明

fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach)

错误代码

EACCESS:权限不够
EACCES:无权限以指定方式连接共享内存 EINVAL:无效的参数shmid或shmaddr ENOMEM:核心内存不足

3.shmdt函数

函数
原型

int shmdt(const void *shmaddr)

函数
功能

与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存

函数
参数

const void *shmaddr : 连接的共享内存的起始地址

函数
返回

成功:0

出错:-1,错误原因存于error中

附加说明

本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程

错误代码

EINVAL:无效的参数shmaddr

4.shmctl函数

函数
原型

int shmctl(int shmid, int cmd, struct shmid_ds *buf)

函数
功能

对共享内存对象的控制

函数
参数

int shmid

共享内存标识符

int cmd

IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中

IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错

struct shmid_ds *buf

IPC_RMID:删除共享内存对象

函数
返回

成功:0

出错:-1,错误原因存于error中

附加说明

上述msgflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定消息队列的存取权限

错误代码

EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存
EFAULT:参数buf指向无效的内存地址
EIDRM:标识符为msqid的共享内存已被删除
EINVAL:无效的参数cmd或shmid
EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行

5.拓展
struct shmid_ds
  {
    struct ipc_perm shm_perm;		/* 所有者和权限 */
    size_t shm_segsz;				/* 段大小,以字节为单位 */
    time_t  shm_atime;				/* 最后挂接时间 */
    time_t  shm_dtime;				/* 最后取出时间 */
    time_t  e_t shm_ctime;			/* 最后修改时间 */
    pid_t shm_cpid;					/* 建立者PID */
    pid_t shm_lpid;					/* 最后调用shmat()/shmdt()的PID */
    shmatt_t shm_nattch;			/* 现在挂接的数量 */
    __syscall_ulong_t __glibc_reserved4;
    __syscall_ulong_t __glibc_reserved5;
  };
struct ipc_perm 
{
      key_t   __key; /*  Key提供给semget(2) */
      uid_t   uid;   /*  owner的有效UID */
      gid_t   gid;   /* owner的有效GID */
      uid_t   cuid;  /* 创建器的有效UID */
      gid_t  cgid;   /* E创建者的有效GID */
      unsigned short mode;  /* 权限 */
      unsigned short __seq; /* 序列号 */
};

如果用shmget创建了一个新的消息队列对象时,则shmid_ds结构成员变量的值设置如下:

  • shm_lpid、shm_nattach、shm_atime、shm_dtime设置为0。
  • msg_ctime设置为当前时间。
  • shm_segsz设成创建共享内存的大小。
  • shmflg的读写权限放在shm_perm.mode中。
  • shm_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。
6.示例

进程a 将数据写入共享内存后 用信号通知进程b
进程b捕捉到信号后 将信息打印输出

/****************进程a******************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SIZE 4096
int createShm();

int main()
{
	int shmid = createShm();
	int* p = shmat(shmid,NULL,0);
	if(p == (void*)-1)
	{
		perror("shmat");
		exit(3);
	}
	printf("等待读取进程b的pid\n");
	while(*p == 0);
	pid_t pid = *p;//取到进程b的pid

	char data[40] = "\0";
	printf("输入要写入共享内存的字符串:\n");
	gets(data);
	strcpy((char*)p,data);//写入数据
	printf("已读取进程b的pid,数据已写入共享内存\n");

	kill(pid,SIGUSR1);
	printf("信号已发送给进程b\n");

	int retval = shmdt(p);
	if(retval == -1)
	{
		perror("shmdt");
		exit(4);
	}
	return 0;
}
int createShm()
{
	key_t key = ftok("/home",1);
	if(key == -1)
	{
		perror("ftok");
		exit(1);
	}
	printf("key:%d\n",key);
	int shmid = shmget(key,50,0);
	if(shmid == -1)
	{
		perror("shmget");
		exit(2);
	}
	printf("shmid:%d\n",shmid);
	return shmid;
}



/****************进程b******************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SIZE 4096
int createShm();
void sigFun(int s);

int main()
{
	signal(SIGUSR1,sigFun);
	int shmid = createShm();
	int* p = (int*)shmat(shmid,NULL,0);
	if(p == (int*)-1)
	{
		perror("shmat");
		exit(3);
	}
	*p = getpid();//把pid写入共享内存
	printf("已经将pid写入共享内存,等待进程a给我发信号\n");
	pause();
	printf("read:%s\n",(char*)p);

	int retval = shmdt(p);
	if(retval == -1)
	{
		perror("shmdt");
		exit(4);
	}
	shmctl(shmid,IPC_RMID,NULL);
	printf("共享空间对象已删除\n");

	return 0;
}
int createShm()
{
	key_t key = ftok("/home",1);
	if(key == -1)
	{
		perror("ftok");
		exit(1);
	}
	printf("key:%#x\n",key);
	int shmid = shmget(key,50,0);
	if(shmid == -1)
	{
		perror("shmget");
		exit(2);
	}
	printf("shmid:%d\n",shmid);
	return shmid;
}

void sigFun(int s)
{
	printf("收到信号\n");
}

二 消息队列 (Message Queue)

简介:

  消息队列是内核地址空间中的内部链表,每个消息队列都有一个队列头,用结构struct msg_queue来描述。队列头中包含了该队列的大量信息,包括消息队列的键值、用户ID、组ID、消息数目、读写进程ID等。通过Linux内核在各个进程之间传递内容。

步骤

  1. 创建消息队列对象 msgget
  2. 发送消息 msgsnd
  3. 接收消息 msgrcv
  4. 删除消息队列对象 msgctl

头文件

  • <sys/types.h>
  • <sys/ipc.h>
  • <sys/msg.h>

LINUX终端下命令:
ipcs -q 查看内核中消息队列
ipcrm -q msqid 删除消息队列

1.msgget函数

函数
原型

int msgget(key_t key, int msgflg)

函数
功能

得到消息队列标识符或创建一个消息队列对象并返回消息队列标识符

函数
参数

key_t key

IPC_PRIVATE(0):建立新的消息队列

大于0的 int 型整数 : 通常要求此值来源于ftok函数返回的IPC键值

int msgflg

0:取消息队列标识符,若不存在则函数会报错

IPC_CREAT:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错

函数
返回

成功:返回消息队列的标识符

出错:-1,错误原因存于error中

附加说明

上述msgflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定消息队列的存取权限

错误代码

EACCES:指定的消息队列已存在,但调用进程没有权限访问它
EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志
ENOENT:key指定的消息队列不存在同时msgflg中没有指定IPC_CREAT标志
ENOMEM:需要建立消息队列,但内存不足
ENOSPC:需要建立消息队列,但已达到系统的限制

2.msgsnd函数

函数
原型

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)

函数
功能

将msgp消息写入到标识符为msqid的消息队列

函数
参数

int msqid

消息队列标识符

const void *msgp

发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型,即表明此发送消息的类型,msgrcv根据此接收消息。msgp定义的参照格式如下:
typedef struct
{
long type; //必须大于0,消息类型
char text[256]; //消息正文,可以是其他任何类型
} Msg_t;

size_t msgsz

要发送消息的大小,不含消息类型占用的4个字节,即 text 的长度

int msgflg

0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列

IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回,出错并返回-1

IPC_NOERROR:若发送的消息大于参数2的msgsz字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。

函数
返回

成功:0

出错:-1,错误原因存于error中

附加说明

发送到消息队列中的实际字节数遵循结构体的内存对齐规则

错误代码

EAGAIN:参数msgflg设为IPC_NOWAIT,而消息队列已满
EIDRM:标识符为msqid的消息队列已被删除
EACCESS:无权限写入消息队列
EFAULT:参数msgp指向无效的内存地址
EINTR:队列已满而处于等待情况下被信号中断
EINVAL:无效的参数msqid、msgsz或参数消息类型type小于0

3.msgrcv函数

函数
原型

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)

函数
功能

从标识符为msqid的消息队列读取消息并存于msgp中,读取后把此消息从消息队列中删除

函数
参数

int msqid

消息队列标识符

const void *msgp

存放消息的结构体,结构体类型要与msgsnd函数发送的类型相同

size_t msgsz

要接收消息的大小,不含消息结构体第一个成员即消息类型占用的sizeof(long)个字节

long msgtyp

0:接收第一个消息

>0:接收类型等于msgtyp的第一个消息

<0:接收类型小于或者等于msgtyp绝对值的第一个消息

int msgflg

0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待

IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG

IPC_EXCEPT:与msgtyp配合使用返回队列中第一个类型不为msgtype的消息

IPC_NOERROR:如果队列中满足条件的消息内容大于参数3所请求的msgsz字节,则把该消息截断,截断部分将被丢弃,且不通知接收进程

函数
返回

成功:实际读取到的消息字节数

出错:-1,错误原因存于error中

附加说明

接收消息时,即使截断了消息,消息队列中这条消息的所有字节也会被删除

错误
代码

E2BIG:消息数据长度大于msgsz而msgflag没有设置IPC_NOERROR
EIDRM:标识符为msqid的消息队列已被删除
EACCESS:无权限读取该消息队列
EFAULT:参数msgp指向无效的内存地址
ENOMSG:参数msgflg设为IPC_NOWAIT,而消息队列中无消息可读
EINTR:等待读取队列内的消息情况下被信号中断

4.msgctl函数

函数
原型

int msgctl(int msqid, int cmd, struct msqid_ds *buf)

函数
功能

获取或者设置消息队列的属性

函数
参数

int msqid

消息队列标识符

int cmd

IPC_STAT:获得msqid的消息队列头信息到参数3 buf中

IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes

struct msqid_ds *buf

消息队列管理结构体,结构体类型定义在msq.h中

函数
返回

成功:0

出错:-1,错误原因存于error中

错误代码

EACCESS:参数cmd为IPC_STAT,确无权限读取该消息队列
EFAULT:参数buf指向无效的内存地址
EIDRM:标识符为msqid的消息队列已被删除
EINVAL:无效的参数cmd或msqid
EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行

5.拓展
struct msqid_ds {
	struct ipc_perm msg_perm;	/* 用于存放消息队列的许可权限信息,其中包括访问许可信息,队列创建者有关信息*/
	struct msg *msg_first;		/* 队列上的第一个消息,未使用 */
	struct msg *msg_last;		/* 队列中的最后一条消息,未使用 */
	__kernel_time_t msg_stime;	/* 最后msgsnd时间,发送到队列的最后一个消息的时间戳 */
	__kernel_time_t msg_rtime;	/* 最后msgrcv时间,从队列获取的最后一个消息的时间戳 */
	__kernel_time_t msg_ctime;	/* 最后更改时间,对队列进程最后一次变动的时间戳 */
	unsigned long  msg_lcbytes;	/* 重用垃圾字段为32位 */
	unsigned long  msg_lqbytes;	/* 同上 */
	unsigned short msg_cbytes;	/* 当前队列上的字节数 */
	unsigned short msg_qnum;	/* 队列中的消息数 */
	unsigned short msg_qbytes;	/* 队列上的最大字节数 */
	__kernel_ipc_pid_t msg_lspid;	/* 发送最后一个消息进程的PID */
	__kernel_ipc_pid_t msg_lrpid;	/* 接收最后一个消息进程的PID */
};
struct ipc_perm
{
	__kernel_key_t	key;		/* 函数msgget()使用的键值,用于区分消息队列 */
	__kernel_uid_t	uid;		/* 用户者的UID */
	__kernel_gid_t	gid;		/* 用户者的GID */
	__kernel_uid_t	cuid;		/* 建立者的UID */
	__kernel_gid_t	cgid;		/* 建立者的GID */
	__kernel_mode_t	mode; 		/* 权限 */
	unsigned short	seq;		/* 序列号 */
};

如果用msgget创建了一个新的消息队列对象时,则msqid_ds结构成员变量的值设置如下:

  • msg_qnum、msg_lspid、msg_lrpid、 msg_stime、msg_rtime设置为0
  • msg_ctime设置为当前时间。
  • msg_qbytes设成系统的限制值。
  • msgflg的读写权限写入msg_perm.mode中。
  • msg_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。
6.示例

运行两个进程,进程A命令行传参传入普通文件名,进程B打印出文件内容

/***************queSend.c*******************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/sem.h>

typedef struct
{
	long msgType;
	char msgText[300];
}Msg_t;

int createMQ();
void getMsgStat(int msqid,struct msqid_ds msgInfo);
void setMsgStat(int msqid,struct msqid_ds* msgInfo);

int main(int argc,char* argv[])
{
	if(argc != 2)
	{
		printf("argc error\n");
		exit(0);
	}
	printf("PID:%d\n",getpid());
	int msqid = createMQ();
	struct msqid_ds msgInfo;
	getMsgStat(msqid,msgInfo);
	FILE* fp = fopen(argv[1],"r");
	if(fp == NULL)
	{
		perror("fopen");
		exit(7);
	}
	Msg_t message = {1,"\0"};
	while(fgets(message.msgText,sizeof(message.msgText),fp) != NULL)//读一行
	{
		int ret = msgsnd(msqid,&message,strlen(message.msgText)+1,0);//队列满 阻塞
		bzero(message.msgText,sizeof(message.msgText));
		if(ret == -1)
		{
			perror("msgsnd");
			exit(3);
		}
	}
	fclose(fp);
	printf("Message send over\n");
	getMsgStat(msqid,msgInfo);
	printf("touch Enter to destroy the Message queue\n");
	getchar();
	int ret = msgctl(msqid,IPC_RMID,NULL);
	if(ret == -1)
	{
		perror("msgctl");
		exit(4);
	}

	return 0;
}

int createMQ()
{
	key_t key = ftok(".",50);
	if(key == -1)
	{
		perror("ftok");
		exit(1);
	}
	printf("key = %d\n",key);
	int msqid = msgget(key,0666|IPC_CREAT);
	if(msqid == -1)
	{
		perror("msgget");
		exit(2);
	}
	printf("msqid = %d\n",msqid);
	return msqid;	
}

void getMsgStat(int msqid,struct msqid_ds msgInfo)
{
	int ret = msgctl(msqid,IPC_STAT,&msgInfo);
	if(ret<0)
	{
		perror("msgctl");
		exit(5);
	}
	printf("\n");
	printf("队列中能容纳的字节的最大数目:    %ld bytes\n",msgInfo.msg_qbytes);
	printf("当前在队列上所驻留的字节数:        %ld bytes\n",msgInfo.msg_cbytes);
	printf("当前处于队列中的消息数目:         %ld \n",msgInfo.msg_qnum);
	printf("当前消息队列的读写权限:            %#x\n",msgInfo.msg_perm.mode);
	printf("发送最后一个消息进程的PID:         %d\n",msgInfo.msg_lspid);
	printf("接收最后一个消息进程的PID:         %d\n",msgInfo.msg_lrpid);
	printf("发送到队列的最后一个消息的时间戳:  %s",ctime(&(msgInfo.msg_stime)));
	printf("从队列中获取的最后一个消息的时间戮:%s",ctime(&(msgInfo.msg_rtime)));
	printf("对队列进行最后一次变动的时间戳:  %s",ctime(&(msgInfo.msg_ctime)));
	printf("消息队列用户ID:                   %d\n",msgInfo.msg_perm.uid);
	printf("消息队列用户组ID:                  %d\n",msgInfo.msg_perm.gid);
	printf("\n");
}
void setMsgStat(int msqid,struct msqid_ds* msgInfo)
{
	(*msgInfo).msg_perm.uid = 8;
	(*msgInfo).msg_perm.gid = 8;
	(*msgInfo).msg_qbytes = 16388;
	int ret = msgctl(msqid,IPC_SET,msgInfo);
	if(ret == -1)
	{
		perror("msqctl");
		exit(6);
	}
}


/***************queRece.c*******************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/sem.h>

typedef struct
{
	long msgType;
	char msgText[200];
}Msg_t;

int getMQ();
void getMsgStat(int msqid,struct msqid_ds msgInfo);

int main()
{
	printf("PID:%d\n",getpid());
	int msqid = getMQ();
	struct msqid_ds msgInfo;
	Msg_t message = {.msgText = "\0"};
	getMsgStat(msqid,msgInfo);
	while(1)
	{
		sleep(1);				//接收队列第一个消息,不阻塞,截断为指定长度
		int ret = msgrcv(msqid,&message,sizeof(message.msgText),0,IPC_NOWAIT|MSG_NOERROR);
		printf("%s",message.msgText);
		bzero(message.msgText,sizeof(message.msgText));
		if(ret == -1)
		{
			printf("Message Queue is Empty\n");
			break;
		}
	}
	getMsgStat(msqid,msgInfo);
	printf("After Destroy Message Queue Touch Enter\n");
	getchar();
	getMQ();

	return 0;
}
void printMessage(Msg_t message,int bytes)
{
	printf("read from Message Queue %d bytes:\ntype:%ld  msgText:%s\n",bytes,message.msgType,message.msgText);
}
int getMQ()
{
	key_t key = ftok(".",50);
	if(key == -1)
	{
		perror("ftok");
		exit(1);
	}
	printf("key = %d\n",key);
	int msqid = msgget(key,0);
	if(msqid == -1)
	{
		perror("msgget");
		exit(2);
	}
	printf("msqid = %d\n",msqid);
	return msqid;	
}
void getMsgStat(int msqid,struct msqid_ds msgInfo)
{
	int ret = msgctl(msqid,IPC_STAT,&msgInfo);
	if(ret<0)
	{
		perror("msgctl");
		exit(5);
	}
	printf("\n");
	printf("队列中能容纳的字节的最大数目:    %ld bytes\n",msgInfo.msg_qbytes);
	printf("当前在队列上所驻留的字节数:        %ld bytes\n",msgInfo.msg_cbytes);
	printf("当前处于队列中的消息数目:         %ld \n",msgInfo.msg_qnum);
	printf("当前消息队列的读写权限:            %#x\n",msgInfo.msg_perm.mode);
	printf("发送最后一个消息进程的PID:         %d\n",msgInfo.msg_lspid);
	printf("接收最后一个消息进程的PID:         %d\n",msgInfo.msg_lrpid);
	printf("发送到队列的最后一个消息的时间戳:  %s",ctime(&(msgInfo.msg_stime)));
	printf("从队列中获取的最后一个消息的时间戮:%s",ctime(&(msgInfo.msg_rtime)));
	printf("对队列进行最后一次变动的时间戳:  %s",ctime(&(msgInfo.msg_ctime)));
	printf("消息队列用户ID:                   %d\n",msgInfo.msg_perm.uid);
	printf("消息队列用户组ID:                  %d\n",msgInfo.msg_perm.gid);
	printf("\n");
}

三 信号量(Semaphores)

简介:

信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问,为获取共享资源,进程需执行以下操作:
    ①P:如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
    ②V:如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1

PV都是原子操作:不能进行分割的操作,一次性完成的操作执行期间不允许有中断的发生

头文件

步骤

  1. 创建信号量对象 semget
  2. 初始化信号量 semctl
  3. 操作信号量 semop
  4. 删除信号量 semctl
  • <sys/types.h>
  • <sys/ipc.h>
  • <sys/sem.h>

LINUX终端下命令:
ipcs -s 查看内核中信号量
ipcrm -s semid 删除信号量

1.semget函数

函数
原型

int semget(key_t key, int nsems, int semflg)

函数
功能

得到一个信号量集标识符或创建一个信号量集对象并返回信号量集标识符

函数
参数

key_t key

0(IPC_PRIVATE):会建立新信号量集对象

大于0的 int 型整数 : 通常要求此值来源于ftok函数返回的IPC键值

int nsems

创建信号量集中信号量的个数,该参数只在创建信号量集时有效

int semflg

0:取信号量集标识符,若不存在则函数会报错

IPC_CREAT:如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错

函数
返回

成功:返回信号量集的标识符

出错:-1,错误原因存于error中

附加说明

上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

错误代码

EACCESS:没有权限
EEXIST:信号量集已经存在,无法创建
EIDRM:信号量集已经删除
ENOENT:信号量集不存在,同时semflg没有设置IPC_CREAT标志
ENOMEM:没有足够的内存创建新的信号量集
ENOSPC:超出限制

2.semop函数

函数
原型

int semop(int semid, struct sembuf *sops, unsigned nsops)

函数
功能

对信号量集标识符为semid中的一个或多个信号量进行P(-1)操作或V(+1)操作

函数
参数

int semid

信号量集标识符

struct sembuf *sops

指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下:
struct sembuf
{
short semnum;
short val;
short flag;
}

short semnum:信号量集合中的信号量编号,0代表第1个信号量

short val:
若val>0进行V操作信号量值加val,表示进程释放控制的资源
若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误
若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误

short flag:
0 :设置信号量的默认操作
IPC_NOWAIT:设置信号量操作不等待
SEM_UNDO :使操作系统跟踪信号, 并在进程异常退出没有释放该信号量而终止时,操作系统释放信号量

unsigned nsops

进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作

函数
返回

成功:返回信号量集的标识符

出错:-1,错误原因存于error中

错误代码

E2BIG:一次对信号量个数的操作超过了系统限制
EACCESS:权限不够
EAGAIN:使用了IPC_NOWAIT,但操作不能继续进行
EFAULT:sops指向的地址无效
EIDRM:信号量集已经删除
EINTR:当睡眠时接收到其他信号
EINVAL:信号量集不存在,或者semid无效
ENOMEM:使用了SEM_UNDO,但无足够的内存创建所需的数据结构
ERANGE:信号量值超出范围

3.semctl函数

函数
原型

int semctl(int semid, int semnum, int cmd, union semun arg)

函数
功能

在指定的信号集或信号集内的某个信号上执行控制操作

函数
参数

int semid

信号量集标识符

int semnum

信号量集数组上的下标,表示某一个信号量

int cmd

见下表

union semun arg

union semun
{
short val; /*SETVAL用的值*/
struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/
unsigned short* array; /*SETALL、GETALL用的数组值*/
struct seminfo *buf; /*为控制IPC_INFO提供的缓存*/
}arg;

函数
返回

成功:大于或等于0,具体说明见下表

出错:-1,错误原因存于error中

附加说明

semid_ds结构见下文信号量集内核结构定义

错误代码

EACCESS:权限不够
EFAULT:arg指向的地址无效
EIDRM:信号量集已经删除
EINVAL:信号量集不存在,或者semid无效
EPERM:进程有效用户没有cmd的权限
ERANGE:信号量值超出范围

上表中int cmd 参数及说明

IPC_STAT

从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中

IPC_SET

设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值

IPC_RMID

从内核中删除信号量集合

GETALL

从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中

GETNCNT

返回当前等待资源的进程个数

GETPID

返回最后一个执行系统调用semop()进程的PID

GETVAL

返回信号量集合内单个信号量的值

GETZCNT

返回当前等待100%资源利用的进程个数

SETALL

与GETALL正好相反

SETVAL

用联合体中val成员的值设置信号量集合中单个信号量的值

4.拓展
struct semid_ds {
	struct ipc_perm	sem_perm;		/* permissions .. see ipc.h */
	__kernel_time_t	sem_otime;		/* last semop time */
	__kernel_time_t	sem_ctime;		/* last change time */
	struct sem	*sem_base;		/* ptr to first semaphore in array */
	struct sem_queue *sem_pending;		/* pending operations to be processed */
	struct sem_queue **sem_pending_last;	/* last pending operation */
	struct sem_undo	*undo;			/* undo requests on this array */
	unsigned short	sem_nsems;		/* no. of semaphores in array */
};

struct ipc_perm
{
	__kernel_key_t	key;
	__kernel_uid_t	uid;
	__kernel_gid_t	gid;
	__kernel_uid_t	cuid;
	__kernel_gid_t	cgid;
	__kernel_mode_t	mode; 
	unsigned short	seq;
};

如果用semget创建了一个新的信号量集对象时,则semid_ds结构成员变量的值设置如下:

  • sem_otime设置为0。
  • sem_ctime设置为当前时间。
  • msg_qbytes设成系统的限制值。
  • sem_nsems设置为nsems参数的数值。
  • semflg的读写权限写入sem_perm.mode中。
  • sem_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID
5.示例

进程A运行时阻塞,进程B给进程A解除阻塞

/***************进程A*******************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/sem.h>
int createSem();
int main()
{
	//创建
	int semid = createSem();
	//设置信号量的值
	int retval = semctl(semid,0,SETVAL,0);//0灯集下标,0,初始值

	struct sembuf s;
	s.sem_num = 0;
	s.sem_op = -1;
	s.sem_flg = SEM_UNDO;

	printf("我想要继续执行,但是被信号量阻塞了\n");
	semop(semid,&s,1);//子阻塞
	printf("信号量已允许我操作,继续执行\n");
	semctl(semid,0,IPC_RMID,0);

	return 0;
}
int createSem()
{
	key_t key = ftok("/home",1);
	if(key == -1)
	{
		perror("ftok");
		exit(1);
	}
	int semid = semget(key,1,IPC_CREAT|0777);// 1 信号量集中信号量个数
	if(semid ==-1)
	{
		perror("semget");
		exit(1);
	}
	printf("semid:%d\n",semid);
	return semid;
}

/***************进程B*******************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/sem.h>
int createSem()int main()
{
	int semid = createSem();
	int retval = semctl(semid,0,SETVAL,0);//设置初始值0

	struct sembuf s;
	s.sem_num = 0;
	s.sem_op = 1;
	s.sem_flg = SEM_UNDO;
	semop(semid,&s,1);
	printf("已经给阻塞的进程放行\n");
	return 0;
}
int createSem()
{
	key_t key = ftok("/home",1);
	if(key == -1)
	{
		perror("ftok");
		exit(1);
	}
	int semid = semget(key,1,IPC_CREAT|0777);// 1 信号量集中信号量个数
	if(semid ==-1)
	{
		perror("semget");
		exit(1);
	}
	printf("semid:%d\n",semid);
	return semid;
}

四 结尾

  • 在编写此文时参考了多处博文,如有侵权联系删除
  • 水平有限,如有错误自行更正
  • 来“华清远见” http://www.hqyj.com/学习更多编程知识
  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值