Linux-C 进程通信之system-V IPC

Linux-C 进程通信之system-V IPC

一、简述

       记--简单的system-V IPC 例子。其中包括消息队列、共享内存、信号量。

        多进程间使用IPC对象进行通信。有一个key值唯一标识这个IPC对象。

        使用命令:ipcs -a 来查看所有IPC对象

        

       或者使用:ipcs -q     查看消息队列,ipcs -s   查看信号量   ,  ipcs -m 查看共享内存

       

      删除指定的消息队列:ipcrm -q MSG_ID 或者 ipcrm -Q msg_key 

      删除指定的共享内存:ipcrm -m SHM_ID 或者 ipcrm -M shm_key 

      删除指定的信号量:ipcrm -s SEM_ID 或者 ipcrm -S sem_key

二、消息队列

       消息队列提供一种带有数据标识的特殊管道,对于多进程间的通信,可以根据数据标识来区分多个进程。

      例如b进程标识数据类型为11,那么可以根据类型11在消息队列中获取b进程的数据。多个进程可以操作同一个消息队列,可以从消息队列中读取数据,也可以往消息队列写入数据。一个进程可以发送多种类型的消息。其实可以将消息队列简单的看做带数据格式的管道。里面的数据格式是自定义的。

     主要操作函数:

          int msgget(key_t key, int msgflg);//获取或创建共享对象的ID号
    
          int msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制指定ID的IPC对象

          int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//往指定的IPC对象(消息队列)写入信息

     ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);//从指定的IPC对象(消息队列)获取消息。

      2.1 ftok()函数

功能

获取一个当前未用的 IPC 的 key

头文件

#include <sys/types.h>

#include <sys/ipc.h>

原型

key_t ftok(const char *pathname, int proj_id);

参数

pathname

一个合法的路径

proj_id

一个整数

 

返回值

成功

合法未用的键值

失败

-1

    备注

1、如果两个参数相同,那么产生的 key 值也相同。

       2、第一个参数一般取进程所在的目录,因为在一个项目中需要通信的几个进程通常会 出现在同一个目录当中。

3、如果同一个目录中的进程需要超过 1 个 IPC 对象,可以通过第二个参数来标识。

         4、系统中只有一套 key 标识,也就是说,不同类型的 IPC 对象也不能重复。

 2.2 msgget()函数

功能

获取消息队列的 ID

头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

原型

int msgget(key_t key, int msgflg);

参数

       key

消息队列的键值

msgflg

IPC_CREAT

如果 key 对应的 MSG 不存在,则创建该对象

IPC_EXCL

如果该 key 对应的 MSG 已经存在,则报错

mode

MSG 的访问权限(八进制,如 0644)

 

返回值

成功

该消息队列的 ID

失败

-1

 

 

 

备注

如果 key 指定为为 IPC_PRIVATE,则会自动产生一个随机未用的新键值

1,选项 msgflg 是一个位屏蔽字,因此 IPC_CREAT、IPC_EXCL 和权限 mode 可以用位

或的方式叠加起来,比如:msgget(key, IPC_CREAT | 0666);  表示如果 key 对应的消息队 列不存在就创建,且权限指定为 0666,若已存在则直接获取 ID。

2,权限只有读和写,执行权限是无效的,例如 0777 跟 0666 是等价的。

3,当 key 被指定为 IPC_PRIVATE 时,系统会自动产生一个未用的 key 来对应一个新的

消息队列对象。一般用于线程间通信。

 3.3 msgsnd()函数

功能

发送、接收消息

头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

原型

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

size_t msgsz, long msgtyp, int msgflg);

参数

msqid

发送、接收消息的消息队列 ID

msgp

要发送的数据、要接收的数据的存储区域指针

msgsz

要发送的数据、要接收的数据的大小

msgtyp

这是 msgrcv 独有的参数,代表要接收的消息的标识

msgflg

IPC_NOWAIT

非阻塞读出、写入消息

MSG_EXCEPT

读取标识不等于 msgtyp 的第一个消息

MSG_NOERROR

消息尺寸比 msgsz 大时,截断消息而不报错

 

返回值

成功

msgsnd( )返回0,msgrcv( )返回读取到的字节数

       失败 -1

        备注

1、发送消息时,消息必须被组织成以下形式: (这个结构体需要自己定义,因为消息的正文长度需要自己确定)

       struct msgbuf
       {
              long mtype;  // 消息的标识
              char mtext[1];    // 消息的正文,长度自定义
       };
也就是说:发送出去的消息必须以一个 long 型数据打头,作为该消息的标识,后 面的数据则没有要求。
2、消息的标识可以是任意长整型数值,但不能是 0L。
3、参数 msgsz 是消息中正文的大小,不包含消息的标识。

  3.4 msgctl()函数

功能 设置或者获取消息队列的相关属性,比如移除某个消息队列
头文件 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数 msqid    消息队列 ID
cmd

IPC_STAT

获取该 MSG 的信息,储存在结构体 msqid_ds 中

IPC_SET

设置该 MSG 的信息,储存在结构体 msqid_ds

IPC_RMID

立即删除该MSG,并且唤醒所有阻塞在该 MSG 上的进程,

同时忽略第三个参数

IPC_INFO

获得关于当前系统中 MSG 的限制值信息

MSG_INFO

获得关于当前系统中 MSG 的相关资源消耗信息

MSG_STAT

同 IPC_STAT,但 msgid 为该消息队列在内核中记录所有 消息队列信息的数组的下标,因此通过迭代所有的下标 可以获得系统中所有消息队列的相关信息

buf

相关信息结构体缓冲区

返回值

成功

IPC_STAT

0

IPC_SET

IPC_RMID

IPC_INFO

内核中记录所有消息队列信息的数组的下标最大值

MSG_INFO

MSG_STAT

返回消息队列的 ID

失败 -1
备注

1,IPC_STAT 获得的属性信息被存放在以下结构体中:
    struct msqid_ds
    {
        struct ipc_perm msg_perm; /* 权限相关信息*/
        time_t msg_stime; /* 最后一次发送消息的时间*/
        time_t msg_rtime; /* 最后一次接收消息的时间*/
        time_t msg_ctime; /* 最后一次状态变更的时间*/
        unsigned long __msg_cbytes; /* 当前消息队列中的数据尺寸*/
        msgqnum_t msg_qnum; /* 当前消息队列中的消息个数*/
        msglen_t msg_qbytes; /* 消息队列的最大数据尺寸*/
        pid_t msg_lspid; /* 最后一个发送消息的进程PID */
        pid_t msg_lrpid; /* 最后一个接收消息的进程PID */
    };
    
    其中,权限相关的信息用如下结构体来表示:
    struct ipc_perm
    {
        key_t __key; /* 当前消息队列的键值key */
        uid_t uid; /* 当前消息队列所有者的有效UID */
        gid_t gid; /* 当前消息队列所有者的有效GID */
        uid_t cuid; /* 当前消息队列创建者的有效UID */
        gid_t cgid; /* 当前消息队列创建者的有效GID */
        unsigned short mode; /* 消息队列的读写权限*/
        unsigned short __seq; /* 序列号*/
    };
2,当使用IPC_INFO 时,需要定义一个如下结构体来获取系统关于消息队列的限制值
信息,并且将这个结构体指针强制类型转化为第三个参数的类型。
    struct msginfo
    {
        int msgpool; /* 系统消息总尺寸(千字节为单位)最大值*/
        int msgmap; /* 系统消息个数最大值*/
        int msgmax; /* 系统单个消息尺寸最大值*/
        int msgmnb; /* 写入消息队列字节数最大值*/
        int msgmni; /* 系统消息队列个数最大值*/
        int msgssz; /* 消息段尺寸*/
        int msgtql; /* 系统中所有消息队列中的消息总数最大值*/
        unsigned short int msgseg; /* 分配给消息队列的数据段的最大值*/
    };

3,当使用选项MSG_INFO 时,跟IPC_INFO 一样也是获得一个msginfo 结构体的信息,但是有如下几点不同:
    A) 成员msgpool 记录的是系统当前存在的MSG 的个数总和
    B) 成员msgmap 记录的是系统当前所有MSG 中的消息个数总和
    C) 成员msgtql 记录的是系统当前所有MSG 中的所有消息的所有字节数总和

   

例子:总共三个进程,进程receive负责监听并读取消息队列的内容,并打印对应打印消息类型,进程send_a,send_b负责添加消息到队列中,其中进程send_a的消息类型为10,进程send_b的消息类型为11。例子中用的是不断轮询方式监听消息队列(就是轮流地读取消息队列),也可以使用线程方式对某种消息类型进行单独监听。

        测试代码

        receive.c文件

        

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <string.h>

#define MSG_TYPE	0
#define MSG_TYPE_A	10
#define MSG_TYPE_B	11

//自定义,方便按照消息类型进行处理
struct msgbuf 
{
    long mtype;       /* message type, must be > 0 */
    char mtext[100];    /* message data */
	int pid;
};


int main(void)
{
	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int msg_id;

	msg_id = msgget(key, IPC_CREAT|0666);//不存在则创建,并设置权限为0666
	if(msg_id == -1)
	{
		perror("get msg id error\n");
		return -1;
	}

	struct msgbuf msg;
	int retval;

	while(1)
	{
		retval = msgrcv(msg_id,&msg, sizeof(msg), MSG_TYPE, IPC_NOWAIT);//IPC_NOWAIT,不等待,即不阻塞	    
		if((retval == -1)&&(errno != ENOMSG))//ENOMSG 还没有信息
		{
			perror("rcv error\n");
			return -1;
		}
		else if(retval != -1)
		{
			printf("[PID:%d], msg type:%ld, msg:%s\n", msg.pid, msg.mtype, msg.mtext);
			if(strncmp(msg.mtext, "exit", 4) == 0)//可以指定长度为4时strlen(msg.mtext)才进行比较,仅仅对exit生效
			{
				break;
			}
		}
		

	}

	msgctl(msg_id, IPC_RMID, NULL);

	return 0;
}

send_a.c文件

include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#define MSG_TYPE_A	10
struct msgbuf 
{
    long mtype;       /* message type, must be > 0 */
    char mtext[100];    /* message data */
	int pid;
};


int main(void)
{
	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int msg_id;

	msg_id = msgget(key, IPC_CREAT|0666);
	if(msg_id == -1)
	{
		perror("get msg id error\n");
		return -1;
	}

	char buffer[80];
	struct msgbuf msg;
	msg.mtype = MSG_TYPE_A;
	msg.pid = getpid();
	while(1)
	{
		
		fgets(msg.mtext, sizeof(msg.mtext), stdin);//含回车符

		msgsnd(msg_id, &msg, sizeof(msg), 0);	
				
		if(strncmp(msg.mtext, "exit", 4) == 0)
		{
			break;
		}
	}

	msgctl(msg_id, IPC_RMID, NULL);

	return 0;
}

send_b.c文件

  

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#define MSG_TYPE_B	11
struct msgbuf 
{
    long mtype;       /* message type, must be > 0 */
    char mtext[100];    /* message data */
	int pid;
};


int main(void)
{
	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int msg_id;

	msg_id = msgget(key, IPC_CREAT|0666);
	if(msg_id == -1)
	{
		perror("get msg id error\n");
		return -1;
	}

	char buffer[80];
	struct msgbuf msg;
	msg.mtype = MSG_TYPE_B;
	msg.pid = getpid();
	while(1)
	{
		
		fgets(msg.mtext, sizeof(msg.mtext), stdin);//含回车符

		msgsnd(msg_id, &msg, sizeof(msg), 0);	
				
		if(strncmp(msg.mtext, "exit", 4) == 0)
		{
			break;
		}
	}

	msgctl(msg_id, IPC_RMID, NULL);

	return 0;
}

        运行效果

       

      

三、共享内存 (shm:shared memory)

     在内核空间中开辟一片内存,并且将其映射到虚拟内存中使用。多个进程可对这一片共享内存进行单读写。

         使用共享内存的一般步骤是:
            1,获取共享内存对象的ID
            2,将共享内存映射至本进程虚拟内存空间的某个区域
            3,当不再使用时,解除映射关系
            4,当没有进程再需要这块共享内存时,删除它

        主要操作函数:

               int shmget(key_t key, size_t size, int shmflg);//获取或创建共享对象的ID号

               void *shmat(int shmid, const void *shmaddr, int shmflg);//映射共享内存到虚拟内存中

               int shmdt(const void *shmaddr);// 解除映射 

               int shmctl(int shmid, int cmd, struct shmid_ds *buf);//控制当前指定ID的共享内存信息

       3.1 shmget()函数

功能

获取共享内存的 ID

头文件

#include <sys/ipc.h>

#include <sys/shm.h>

原型

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

参数

     key

共享内存的键值

size

共享内存的尺寸(PAGE_SIZE 的整数倍)

shmflg

IPC_CREAT

如果 key 对应的共享内存不存在,则创建

IPC_EXCL

如果该 key 对应的共享内存已存在,则报错

SHM_HUGETLB

使用“大页面”来分配共享内存

SHM_NORESERVE

不在交换分区中为这块共享内存保留空间

mode

共享内存的访问权限(八进制,如 0644)

返回值

成功

该共享内存的 ID

失败

-1

备注

如果 key 指定为为 IPC_PRIVATE,则会自动产生一个随机未用的新键值

3.2 shmat()函数 和shmdt()函数

       

功能

对共享内存进行映射,或者解除映射

头文件

#include <sys/types.h>

#include <sys/shm.h>

原型

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

int shmdt(const void *shmaddr);

参数

shmid

共享内存 ID

shmaddr

shmat( )

1,如果为 NULL,则系统会自动选择一个合适的虚拟内存空间地址去映射共享内存。

2,如果不为 NULL,则系统会根据 shmaddr 来选择一 个合适的内存区域。

shmdt( )

共享内存的首地址

shmflg

SHM_RDONLY

以只读方式映射共享内存

SHM_REMAP

重新映射,此时 shmaddr 不能为 NULL

SHM_RND

自动选择比 shmaddr 小的最大页对齐地址

 

返回值

成功

共享内存的首地址

失败

-1

备注

1、共享内存只能以只读或者可读写方式映射,无法以只写方式映射。
2,shmat()第二个参数shmaddr一般都设为NULL,让系统自动找寻合适的地址。但 当其确实不为空时,那么要求 SHM_RND 在 shmflg 必须被设置,这样的话系统将会选择比 shmaddr 小而又最大的页对齐地址(即为 SHMLBA 的整数倍)作为共享内存区域的起始地址。如果没有设置 SHM_RND,那么 shmaddr 必须是严格的页对齐地址。总之,映射时将shmaddr设置为NULL是更明智的做法,因为这样更简单,也更具移植性。

3,解除映射之后,进程不能再允许访问 SHM。

3.3   shmctl()函数

      

功能

获取或者设置共享内存的相关属性

头文件

#include <sys/ipc.h>

#include <sys/shm.h>

原型

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

参数

shmid

共享内存 ID

cmd

IPC_STAT

获取属性信息,放置到 buf 中

IPC_SET

设置属性信息为 buf 指向的内容

IPC_RMID

将共享内存标记为“即将被删除”状态

IPC_INFO

获得关于共享内存的系统限制值信息

SHM_INFO

获得系统为共享内存消耗的资源信息

SHM_STAT

同 IPC_STAT,但 shmid 为该 SHM 在内核中记录所有 SHM 信息的数组的下标,因此通过迭代所有的 下标可以获得系统中所有 SHM 的相关信息

SHM_LOCK

禁止系统将该 SHM 交换至 swap 分区

SHM_UNLOCK

允许系统将该 SHM 交换至 swap 分区

buf

属性信息结构体指针

返回值

成功

IPC_INFO

 

内核中记录所有 SHM 信息的数组的下标最大值

SHM_INFO

SHM_STAT

下标值为 shmid 的 SHM 的 ID

失败

-1

 

 

 

 

 

 

 

 

 

 

 

 

 

备注

1,IPC_STAT 获得的属性信息被存放在以下结构体中:
    struct shmid_ds
    {
        struct ipc_perm shm_perm; /* 权限相关信息*/
        size_t shm_segsz; /* 共享内存尺寸(字节) */
        time_t shm_atime; /* 最后一次映射时间*/
        time_t shm_dtime; /* 最后一个解除映射时间*/
        time_t shm_ctime; /* 最后一次状态修改时间*/
        pid_t shm_cpid; /* 创建者PID */
        pid_t shm_lpid; /* 最后一次映射或解除映射者PID */
        shmatt_t shm_nattch;/* 映射该SHM 的进程个数*/
    };
    
    其中权限信息结构体如下:
    struct ipc_perm
    {
        key_t __key; /* 该SHM 的键值key */
        uid_t uid; /* 所有者的有效UID */
        gid_t gid; /* 所有者的有效GID */
        uid_t cuid; /* 创建者的有效UID */
        gid_t cgid; /* 创建者的有效GID */
        unsigned short mode; /* 读写权限+
        SHM_DEST +
        SHM_LOCKED 标记*/
        unsigned short __seq; /* 序列号*/
    };
    
2,当使用IPC_RMID 后,上述结构体struct ipc_perm 中的成员mode 将可以检测出
SHM_DEST,但SHM 并不会被真正删除,要等到shm_nattch 等于0 时才会被真正删除。
IPC_RMID 只是为删除做准备,而不是立即删除。

3,当使用IPC_INFO 时,需要定义一个如下结构体来获取系统关于共享内存的限制值
信息,并且将这个结构体指针强制类型转化为第三个参数的类型。
    struct shminfo
    {
        unsigned long shmmax; /* 一块SHM 的尺寸最大值*/
        unsigned long shmmin; /* 一块SHM 的尺寸最小值(永远为1) */
        unsigned long shmmni; /* 系统中SHM 对象个数最大值*/
        unsigned long shmseg; /* 一个进程能映射的SHM 个数最大值*/
        unsigned long shmall; /* 系统中SHM 使用的内存页数最大值*/
    };
    
4,使用选项SHM_INFO 时,必须保证宏_GNU_SOURCE 有效。获得的相关信息被存放在如下结构体当中:
    struct shm_info
    {
        int used_ids; /* 当前存在的SHM 个数*/
        unsigned long shm_tot; /* 所有SHM 占用的内存页总数*/
        unsigned long shm_rss; /* 当前正在使用的SHM 内存页个数*/
        unsigned long shm_swp; /* 被置入交换分区的SHM 个数*/
        unsigned long swap_attempts; /* 已废弃*/
        unsigned long swap_successes; /* 已废弃*/
    };
    
5,注意:选项SHM_LOCK 不是锁定读写权限,而是锁定SHM 能否与swap 分区发生
交换。一个SHM 被交换至swap 分区后如果被设置了SHM_LOCK,那么任何访问这个SHM
的进程都将会遇到页错误。进程可以通过IPC_STAT 后得到的mode 来检测SHM_LOCKED
信息。

例子:shm_write进程进行写数据,shm_read进程进行读数据,shm_read进程在读取到一个数据后,写入ok后才进行读取数据。  shm_write进程写入exit后两个进程退出。

测试代码:

shm_write.c文件

include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>

#define SHM_SIZE	100

int main(void)
{
	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int shm_id;

	shm_id = shmget(key, SHM_SIZE, IPC_CREAT|0666);
	if(shm_id == -1)
	{
		perror("get shm id error\n");
		return -1;
	}

	char *addr;

	addr = shmat(shm_id, NULL, 0);
	if(addr == (void *)-1)
	{
		perror("map address error\n");	
		return -1;
	}

	while(1)
	{
		fgets(addr, SHM_SIZE, stdin);
		if(strncmp(addr, "exit", 4) == 0)
		{
			break;
		}
		while( strncmp(addr, "ok", 2) != 0);
	}
	shmdt(addr);


	shmctl(shm_id , IPC_RMID, 0);

	return 0;
}

 shm_read.c文件

e <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>

#define SHM_SIZE	100

int main(void)
{
	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int shm_id;

	shm_id = shmget(key, SHM_SIZE, IPC_CREAT|0666);
	if(shm_id == -1)
	{
		perror("get shm id error\n");
		return -1;
	}

	char *addr;

	addr = shmat(shm_id, NULL, 0);
	if(addr == (void *)-1)
	{
		perror("map address error\n");	
		return -1;
	}

	while(1)
	{
		while(strlen(addr) ==0 );
		printf("shm data=%s\n", addr);
		if(strncmp(addr, "exit", 4) == 0)
		{
			break;
		}
		do
		{
			scanf("%s", addr);
		}while(strncmp(addr, "ok", 2) != 0);
		while(strncmp(addr, "ok", 2) == 0);

	}
	shmdt(addr);


	shmctl(shm_id , IPC_RMID, 0);

	return 0;
}

运行结果:

四、信号量 (sem)

       不是用来传输数据的,一般用来实现进程的同步互斥,就有点像是某个进程对一个值进行操作时,将某个标志置为正在使用,当另一个进程也想对该值进行操作时,看到这个标志,就会等待,直到可以操作。有点像是对一个全局变量进行操作,一个进行减操作(P操作),一个进行加操作(V操作)。P操作只有在信号量值>0才能成功,否则被阻塞,等待信号量值>0。比如信号量的值原来为1,进行P操作后变为0,然后在进行P操作时,由于信号量的值不满足>0,所以此时会被阻塞,直到信号量的值被其他进程改变为>0。

     4.1 semget()函数

功能

获取信号量的 ID

头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

原型

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

参数

       key

信号量的键值

nsems

信号量元素的个数

semflg

IPC_CREAT

如果 key 对应的信号量不存在,则创建之

IPC_EXCL

如果该 key 对应的信号量已存在,则报错

mode

信号量的访问权限(八进制,如 0644)

 

返回值

成功

该信号量的 ID

失败

-1

   备注

创建信号量时,会受到以下系统信息的影响:
    1,SEMMNI:系统中信号量的总数最大值。
    2,SEMMSL:每个信号量中信号量元素的个数最大值。
    3,SEMMNS:系统中所有信号量中的信号量元素的总数最大值。Linux 中,以上信息在/proc/sys/kernel/sem 中可查看

4.2 semop()函数

功能

对信号量进行 P/V 操作,或者等零操作

头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

原型

int semop(int semid, struct sembuf sops[ ], unsigned nsops);

    参数

semid

信号量 ID

sops

信号量操作结构体数组

nsops

结构体数组元素个数

返回值 成功返回0,失败返回-1

备注

1,信号量操作结构体的定义如下:
struct sembuf
{
    unsigned short sem_num; /*  信号量元素序号(数组下标) */ short sem_op; /*  操作参数 */
    short sem_flg;  /*  操作选项 */
};

请注意:信号量元素的序号从 0 开始,实际上就是数组下标。
2,根据 sem_op 的数值,信号量操作分成 3 种情况:
    A) 当 sem_op 大于 0 时:进行 V 操作,即信号量元素的值(semval)将会被加上 sem_op 的值。如果 SEM_UNDO 被设置了,那么该 V 操作将会被系统记录。V 操作永 远不会导致进程阻塞。

    B) 当 sem_op 等于 0 时:进行等零操作,如果此时 semval 恰好为 0,则 semop( ) 立即成功返回,否则如果 IPC_NOWAIT 被设置,则立即出错返回并将 errno 设置为 EAGAIN,否则将使得进程进入睡眠,直到以下情况发生:
        B1) semval 变为 0。
        B2) 信号量被删除。(将导致 semop( )出错退出,错误码为 EIDRM) B3) 收到信号。(将导致 semop( )出错退出,错误码为 EINTR)

    C) 当 sem_op 小于 0 时:进行 P 操作,即信号量元素的值(semval)将会被减去 sem_op 的绝对值。如果 semval 大于或等于 sem_op 的绝对值,则 semop( )立即成功 返回,semval 的值将减去 sem_op 的绝对值,并且如果 SEM_UNDO 被设置了,那么该 P 操作将会被系统记录。如果 semval 小于 sem_op 的绝对值并且设置了 IPC_NOWAIT, 那么 semop( )将会出错返回且将错误码置为 EAGAIN,否则将使得进程进入睡眠,直到 以下情况发生:
        C1) semval 的值变得大于或者等于 sem_op 的绝对值。
        C2) 信号量被删除。(将导致 semop( )出错退出,错误码为 EIDRM) C3) 收到信号。(将导致 semop( )出错退出,错误码为 EINTR)
 

4.3 semctl()函数

     功能

获取或者设置信号量的相关属性

    头文件

#include <sys/types.h>

  #include <sys/ipc.h>

#include <sys/sem.h>

原型

int semctl(int semid, int semnum, int cmd, ...);

参数

semid

信号量 ID

  semnum

信号量元素序号(数组下标)

cmd

IPC_STAT

获取属性信息

IPC_SET

设置属性信息

IPC_RMID

立即删除该信号量,参数 semnum 将被忽略

IPC_INFO

获得关于信号量的系统限制值信息

SEM_INFO

获得系统为共享内存消耗的资源信息

SEM_STAT

同 IPC_STAT,但 shmid 为该 SEM 在内核中记录所有 SEM 信息的数组的下标,因此通过迭代所有的下标可 以获得系统中所有 SEM 的相关信息

GETALL

返回所有信号量元素的值,参数 semnum 将被忽略

GETNCNT

返回正阻塞在对该信号量元素 P 操作的进程总数

GETPID

返回最后一个队该信号量元素操作的进程 PID

GETVAL

返回该信号量元素的值

GETZCNT

返回正阻塞在对该信号量元素等零操作的进程总数

SETALL

设置所有信号量元素的值,参数 semnum 将被忽略

SETVAL

设置该信号量元素的值

返回值

成功

GETNCNT

semncnt

GETPID

sempid

GETVAL

semval

GETZCNT

semzcnt

IPC_INFO

内核中记录所有 SEM 信息的数组的下标最大值

SEM_INFO

同 IPC_INFO

SEM_STAT

内核中记录所有 SEM 信息的数组,下标为 semid 的信

号量的 ID

其他

0

失败

-1

备注

1,这是一个变参函数,根据 cmd 的不同,可能需要第四个参数,第四个参数是一个如 下所示的联合体,用户必须自己定义:
        union semun
        {
               int val; /* 当cmd 为SETVAL 时使用*/
               struct semid_ds *buf; /* 当cmd 为IPC_STAT 或IPC_SET 时使用*/
               unsigned short *array; /* 当cmd 为GETALL 或SETALL 时使用*/
               struct seminfo *__buf; /* 当cmd 为IPC_INFO 时使用*/
        };
2、使用IPC_STAT 和IPC_SET 需要用到以下属性信息结构体
    struct semid_ds
    {
             struct ipc_perm sem_perm; /* 权限相关信息*/
             time_t sem_otime; /* 最后一次semop( )的时间*/
             time_t sem_ctime; /* 最后一次状态改变时间*/
             unsigned short sem_nsems; /* 信号量元素个数*/
    };

    权限结构体如下:
    struct ipc_perm
    {
            key_t __key; /* 该信号量的键值key */
            uid_t uid; /* 所有者有效UID */
            gid_t gid; /* 所有者有效GID */
            uid_t cuid; /* 创建者有效UID */
            gid_t cgid; /* 创建者有效GID */
            unsigned short mode; /* 读写权限*/
            unsigned short __seq; /* 序列号*/
    };
3,使用IPC_INFO 时,需要提供以下结构体:
struct seminfo
{
        int semmap; /* 当前系统信号量总数*/
        int semmni; /* 系统信号量个数最大值*/
        int semmns; /* 系统所有信号量元素总数最大值*/
        int semmnu; /* 信号量操作撤销结构体个数最大值*/
        int semmsl; /* 单个信号量中的信号量元素个数最大值*/
        int semopm; /* 调用semop( )时操作的信号量元素个数最大值*/
        int semume; /* 单个进程对信号量执行连续撤销操作次数的最大值*/
        int semusz; /* 撤销操作的结构体的尺寸*/
        int semvmx; /* 信号量元素的值的最大值*/
        int semaem; /* 撤销操作记录个数最大值*/
};
4,使用SEM_INFO 时,跟IPC_INFO 一样都是得到一个seminfo 结构体,但其中几个成员的含义发生了变化:
    A) semusz 此时代表系统当前存在的信号量的个数
    B) semaem 此时代表系统当前存在的信号量中信号量元素的总数

例子:有两个进程,进程sem_p进行P操作,进程sem_v进行V操作。(sem_v回车,进行V操作之后,sem_p才能进行P操作)

测试代码

sem_p.c文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>

union semun 
{
	int              val;    /* Value for SETVAL */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */

};
	   
		   
int sem_opt_p(int sem_id, int sem_num)
{
	struct sembuf  semb;
	
	semb.sem_num = sem_num;	//信号量集合的下标
	semb.sem_op = -1;		//信号量进行何种运算,P操作
	semb.sem_flg = 0;		//0代表默认操作
	
	return semop(sem_id, &semb, 1);
	
}		   
	
int sem_opt_v(int sem_id, int sem_num)
{
	struct sembuf  semb[1];
	
	semb[0].sem_num = sem_num;
	semb[0].sem_op = 1;
	semb[0].sem_flg = 0;
	
	
	/*
		semop的第二个参数支持传输一个struct sembuf的数组
		第三个参数代表数组的个数
	*/
	return semop(sem_id, semb, 1);
	
}	
		   
		   
int main(void)
{

	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int sem_id;

	/*
		函数:获取信号量的id
			1:申请1个信号量(信号量集合里面只有一个元素)
	*/
	sem_id = semget(key, 1, IPC_CREAT|0666);//不存在则创建
	if(sem_id == -1)
	{
		perror("get sem error\n");
		return -1;
	}

	union semun su;
	su.val = 1;	//设置初值为1
	semctl(sem_id, 0, SETVAL, su);

	int i = 5;
	while(i--)
	{
		sem_opt_p(sem_id, 0);
		printf("sem_opt_p success\n");
		
	}
	
	semctl(sem_id, 1, IPC_RMID);//移除sem(IPC对象)

	return 0;
}

sem_v.c文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>

union semun 
{
	int              val;    /* Value for SETVAL */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */

};
	   
		   
int sem_opt_p(int sem_id, int sem_num)
{
	struct sembuf  semb;
	
	semb.sem_num = sem_num;	//信号量集合的下标
	semb.sem_op = -1;		//信号量进行何种运算,P操作
	semb.sem_flg = 0;		//0代表默认操作
	
	return semop(sem_id, &semb, 1);
	
}		   
	
int sem_opt_v(int sem_id, int sem_num)
{
	struct sembuf  semb[1];
	
	semb[0].sem_num = sem_num;
	semb[0].sem_op = 1;
	semb[0].sem_flg = 0;
	
	
	/*
		semop的第二个参数支持传输一个struct sembuf的数组
		第三个参数代表数组的个数
	*/
	return semop(sem_id, semb, 1);
	
}	
		   
		   
int main(void)
{

	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int sem_id;

	/*
		函数:获取信号量的id
			1:申请1个信号量(信号量集合里面只有一个元素)
	*/
	sem_id = semget(key, 1, IPC_CREAT|0666);//不存在则创建
	if(sem_id == -1)
	{
		perror("get sem error\n");
		return -1;
	}


	int i = 4;
	while(i--)
	{
		getchar();
		sem_opt_v(sem_id, 0);
		printf("sem_op_v success\n");
	}
	
	semctl(sem_id, 1, IPC_RMID);//移除sem(IPC对象)

	return 0;
}

运行结果:

sem结合shm使用例子:sema进程写入信息,semb进程读取信息

sema.c文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <string.h>

#define SHM_SIZE	100
union semun 
{
	int              val;    /* Value for SETVAL */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};

		   
int sem_opt_p(int sem_id, int sem_num)
{
	struct sembuf  semb;
	
	semb.sem_num = sem_num;	//信号量集合的下标
	semb.sem_op = -1;		//信号量进行何种运算,P操作
	semb.sem_flg = 0;		//0代表默认操作
	
	return semop(sem_id, &semb, 1);
	
}		   
	
int sem_opt_v(int sem_id, int sem_num)
{
	struct sembuf  semb[1];
	
	semb[0].sem_num = sem_num;
	semb[0].sem_op = 1;
	semb[0].sem_flg = 0;
	
	
	/*
		semop的第二个参数支持传输一个struct sembuf的数组
		第三个参数代表数组的个数
	*/
	return semop(sem_id, semb, 1);
	
}	
		   
		   
int main(void)
{

	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int sem_id;

	/*
		函数:获取信号量的id
			1:申请1个信号量(信号量集合里面只有一个元素)
	*/
	sem_id = semget(key, 2, IPC_CREAT|0666);
	if(sem_id == -1)
	{
		perror("get sem error\n");
		return -1;
	}

	int shm_id;
	shm_id = shmget(key, SHM_SIZE, IPC_CREAT|0666);//共享内存与信号量可使用同一个key值
	if(shm_id == -1)
	{
		perror("get shm error\n");
		return -1;
	}

	char *addr;
	addr = shmat( shm_id, NULL, 0);

	union semun su;
	su.val = 1;	//设置信号量0初值为1
	semctl(sem_id, 0, SETVAL, su);

	su.val = 0;//设置信号量1初值为0
	semctl(sem_id, 1, SETVAL, su);

	
	while(1)
	{
		sem_opt_p(sem_id, 0);//P操作,若信号量0值<0 则阻塞。
        
		printf("start input\n");
		
		fgets(addr, SHM_SIZE, stdin);
		sem_opt_v(sem_id, 1);//不阻塞信号量1的P操作
		
		if(strncmp(addr, "exit", 4) == 0)
		{
			break;
		}
		
		
	}
	
	shmctl(shm_id , IPC_RMID, 0);
	semctl(sem_id, 1, IPC_RMID);

	return 0;
}

semb.c文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <string.h>

#define SHM_SIZE	100
union semun 
{
	int              val;    /* Value for SETVAL */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};

		   
		   
int sem_opt_p(int sem_id, int sem_num)
{
	struct sembuf  semb;
	
	semb.sem_num = sem_num;	//信号量集合的下标
	semb.sem_op = -1;		//信号量进行何种运算,P操作
	semb.sem_flg = 0;		//0代表默认操作
	
	return semop(sem_id, &semb, 1);
	
}		   
	
int sem_opt_v(int sem_id, int sem_num)
{
	struct sembuf  semb[1];
	
	semb[0].sem_num = sem_num;
	semb[0].sem_op = 1;
	semb[0].sem_flg = 0;
	
	
	/*
		semop的第二个参数支持传输一个struct sembuf的数组
		第三个参数代表数组的个数
	*/
	return semop(sem_id, semb, 1);
	
}	
		   
		   
int main(void)
{

	key_t key;

	key = ftok(".", 1);
	if(key == -1)
	{
		perror("get key failed\n");
		return -1;
	}

	int sem_id;

	/*
		函数:获取信号量的id
			2:申请2个信号量(信号量集合里面只有一个元素)
	*/
	sem_id = semget(key, 2, IPC_CREAT|0666);
	if(sem_id == -1)
	{
		perror("get sem error\n");
		return -1;
	}

	int shm_id;

	shm_id = shmget(key, SHM_SIZE, IPC_CREAT|0666);
	if(shm_id == -1)
	{
		perror("get shm error\n");
		return -1;
	}

	char *addr;
	addr = shmat( shm_id, NULL, 0);
	
	while(1)
	{
		sem_opt_p(sem_id, 1);//对信号量1进行P操作(成功操作则使得信号量1的下次P操作阻塞,否则就等待)

		printf("addr=%s", addr);
		if(strncmp(addr, "exit", 4) == 0)
		{
			break;
		}
		
		sem_opt_v(sem_id, 0);//对信号量0进行V操作(使得信号量0的下次P操作不阻塞)
	}
	
	shmctl(shm_id , IPC_RMID, 0);
	semctl(sem_id, 1, IPC_RMID);

	return 0;
}

运行效果:

 

posix标准信号量:有名信号量、匿名信号量(待补充)

 其中有名信号量的特点:
                1,它每次只能操作一个信号量
                2,每次操作只能+1或者-1
                3,简单直接(高度封装IPC的sem)
                4,有文件诞生    /dev/shm/sem.xxx      (xxx是有名信号量的名称)

 主要函数:

   1、sem_open():用来打开或创建一个posix有名信号量。

   2、sem_close():关闭一个posix有名信号量。

   3、sem_wait():P操作(减操作)。

   4、sem_post:V操作(加操作)。

   5、sem_unlink():移除信号量,释放系统资源。

sem_open()函数
功能 打开或创建一个posix有名信号量
头文件 #include <fcntl.h>          
#include <sys/stat.h>       
#include <semaphore.h>
原型 sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数

name:自定义的信号量的名字

oflag:O_CREATE 如果该名字对应的信号量不存在,则创建

           O_EXCL 如果该名字对应的信号量已存在,则报错

mode: 八进制读写权限,比如0666。(与O_CREATE结合使用)

value :信号量的初始值

返回值 成功信号量的地址
失败SEM_FAILED
备注 跟open( )类似,当oflag 中包含O_CREATE 时,该函数必须提供后两个参数
sem_wait()和sem_post()函数
功能 对POSIX 有名信号量进行P、V 操作
头文件 #include <semaphore.h>
原型 int sem_wait(sem_t *sem);
int sem_post(sem_t *sem)
参数 sem:信号量指针
返回值 成功0
失败-1
备注 不像system-V 的信号量可以申请或者释放超过1 个资源,对于POSIX 信号量而言,每
次申请和释放的资源数都是1。其中调用sem_wait( )在资源为0 时会导致阻塞,如果不想
阻塞等待,可以使用sem_trywait( )来替代
sem_close()和sem_unlink()
功能 关闭、删除POSIX 有名信号量
头文件 #include <semaphore.h>
原型 int sem_close(sem_t *sem);
int sem_unlink(const char *name);
参数 sem 信号量指针
name 信号量名字
返回值 成功0
失败-1

例子:posix_sem_p进程进行P操作,posix_sem_v进程进行V操作。(posix_sem_v进程每回车一次进行V操作)

posix_sem_p.c文件

#include <stdio.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

int main(void)
{
	sem_t *st;

	st = sem_open("mysem", O_CREAT, 0666, 1);
	if(st == SEM_FAILED)
	{
		perror("open sem error\n");
		return -1;
	}

	int i = 5;
	while(i--)
	{
		sem_wait(st);//p操作
		printf("sem_wait opt p success\n");
	}


	sem_close(st);
	sem_unlink("mysem");
	return 0;
}

posix_sem_v.c文件

#include <stdio.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>
#include <semaphore.h>


int main(void)
{
	sem_t *st;

	st = sem_open("mysem", O_RDWR);
	if(st == SEM_FAILED)
	{
		perror("open sem error\n");
		return -1;
	}

	int i = 4;
	while(i--)
	{
		getchar();
		sem_post(st);//v操作
		printf("sem_post opt v success\n");
	}

	sem_close(st);
	sem_unlink("mysem");
	return 0;
}

运行效果:

    

猜你喜欢

转载自blog.csdn.net/nanfeibuyi/article/details/81945484