我是靠谱客的博主 美丽猫咪,最近开发中收集的这篇文章主要介绍Linux-C 进程通信之system-V IPC,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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

一、简述

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

        多进程间使用IPC对象进行通信。有一个key值唯一标识这个IPC对象,进程间通过这个大家都知道的一样的key来进行获取对应的IPC对象,从而进行通信,这个key可以人为直接指定,但需要确保唯一性,不能与已存在的不一样作用的重复,为此可以使用ftok()函数按照相关条件生成,只有双方条件一致key才相同,这样key一样的也就正好对应需要通信的双方。

        使用命令: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 failedn");
		return -1;
	}

	int msg_id;

	msg_id = msgget(key, IPC_CREAT|0666);//不存在则创建,并设置权限为0666
	if(msg_id == -1)
	{
		perror("get msg id errorn");
		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 errorn");
			return -1;
		}
		else if(retval != -1)
		{
			printf("[PID:%d], msg type:%ld, msg:%sn", 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 failedn");
		return -1;
	}

	int msg_id;

	msg_id = msgget(key, IPC_CREAT|0666);
	if(msg_id == -1)
	{
		perror("get msg id errorn");
		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 failedn");
		return -1;
	}

	int msg_id;

	msg_id = msgget(key, IPC_CREAT|0666);
	if(msg_id == -1)
	{
		perror("get msg id errorn");
		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 failedn");
		return -1;
	}

	int shm_id;

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

	char *addr;

	addr = shmat(shm_id, NULL, 0);
	if(addr == (void *)-1)
	{
		perror("map address errorn");	
		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 failedn");
		return -1;
	}

	int shm_id;

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

	char *addr;

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

	while(1)
	{
		while(strlen(addr) ==0 );
		printf("shm data=%sn", 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。一个信号量集合可以由多个信号量,通过索引编号进行操作一个或多个信号量的各自P、V操作。

     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)

3、sem_flg可取值IPC_NOWAITSEM_UNDO,

IPC_NOWAIT:表示如果资源不可用,不等待直接返回EAGAIN

SEM_UNDO:如果资源不可用就会进入睡眠直到资源满足,如果占用该资源的程序因异常退出而没有释放该信号量系统将自动释放该进程持有的信号量,让其它进程继续工作。

/* semop flags */
#define SEM_UNDO        0x1000  /* undo the operation on exit */

 

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操作)

这里旨在说明P、V操作的效果互斥,可以在不同的进程操作分别P、V操作。

测试代码

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[1];
	
	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 failedn");
		return -1;
	}

	int sem_id;

	/*
		函数:获取信号量的id
			1:申请1个信号量(信号量集合里面只有一个元素)
	*/
	sem_id = semget(key, 1, IPC_CREAT|0666);//不存在则创建
	if(sem_id == -1)
	{
		perror("get sem errorn");
		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 successn");
		
	}
	
	semctl(sem_id, 0, 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[0];
	
	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 failedn");
		return -1;
	}

	int sem_id;

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


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

	return 0;
}

运行结果:

例子2:多进程操作共享资源时候的情形,

测试代码:

/*****************************************************************************************
 * Project: sem test
 * File: sem.c
 * Date: 2020.10.1
 * Author: Genven_Liang
 * Description:test for sem p
 *****************************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.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) */

};
	   

/*****************************************************************************************
 * 函数: sem_opt_p
 * 作用: sem P操作
 * 形参:int sem_id -> 信号量集合id
 *		 int sem_num -> 要操作的信号量索引
 * 返回:
 * 说明:
 *****************************************************************************************/		   
int sem_opt_p(int sem_id, int sem_num)
{
	struct sembuf semb[1];
	
	/* P操作 */
	semb[0].sem_num = sem_num;	//信号量集合的下标 信号量集合可以有多个信号量,根据数组下标进行指定要操作的信号量
	semb[0].sem_op = -1;		//信号量进行何种运算,P操作
	semb[0].sem_flg = SEM_UNDO;		//SEM_UNDO若程序退出后没有释放信号量资源,由系统释放 IPC_NOWAIT非阻塞	
	/*
		semop的第二个参数支持传输一个struct sembuf的数组
		第三个参数代表数组的个数
	*/
	return semop(sem_id, semb, 1);	//返回0成功
}		   

/*****************************************************************************************
 * 函数: sem_opt_v
 * 作用: sem V操作
 * 形参:int sem_id -> 信号量集合id
 *		 int sem_num -> 要操作的信号量索引
 * 返回:
 * 说明:
 *****************************************************************************************/		
int sem_opt_v(int sem_id, int sem_num)
{
	struct sembuf semb[1];
	
	/* V操作 */
	semb[0].sem_num = sem_num;
	semb[0].sem_op = 1;
	semb[0].sem_flg = SEM_UNDO;//SEM_UNDO若程序退出后没有释放信号量资源,由系统释放 IPC_NOWAIT非阻塞	
	return semop(sem_id, semb, 1);	//返回0成功
}	
		   
		   
int main(int argc, char **argv)
{
	int ret;
	key_t key;

	/* 1 获取key */
	key = ftok(".", 1); /* 根据当前路径获取key,也可以直接指定key为某个数值 */
	if (-1 == key) {
		perror("get key failedn");
		return -1;
	} else {
		printf("key_t:0x%08xn", key);
	}

	/* 2 获取信号量集合的id */
	int sem_id;
	sem_id = semget(key, 1, IPC_CREAT|0666);//不存在则创建
	if (-1 == sem_id) {
		perror("get sem errorn");
		return -1;
	} else {
		printf("sem_id:%dn", sem_id);
	}

	if (argc >= 2) {
		/* 设置信号量0初值 */
		//设置初值为1 注意信号量值大于0才能进行sem_op_p 操作,不然会默认阻塞等待
		union semun su;
		su.val = 1;	
		if (-1 == semctl(sem_id, 0, SETVAL, su)) {
			semctl(sem_id, 0, IPC_RMID);//移除sem(IPC对象)
			printf("init sem value failed.n");
			return -1;
		} else {
			printf("init sem value success.n");
		}
	}

	/* 3 操作临界资源 */
	int i = 2;
	
	while(i--) {
		printf("wait for sem pn");
		ret = sem_opt_p(sem_id, 0); /* P操作上锁 */
		if (0 == ret) {
			printf("sem p.n");

			fflush(stdin);/* 手动控制时间,模拟操作共享资源的耗时、两个进程共同操作同一个资源的情况 */
			getchar();		

			printf("op resources.n");/* 操作共享资源 */

			fflush(stdin);/* 手动控制时间,模拟操作共享资源的耗时、两个进程共同操作同一个资源的情况 */
			getchar();

			ret = sem_opt_v(sem_id, 0); /* V操作解锁 */	
			if (0 == ret) {
				printf("sem v.n");/* 操作共享资源 */
			} else {
				printf("sem v failed.n");
			}
		} else {
			printf("sem p failed.n");
		}

		fflush(stdin);/* 手动控制时间,模拟操作共享资源的耗时、两个进程共同操作同一个资源的情况 */
		getchar();		
	}
	
	/* 4 释放资源 */
	ret = semctl(sem_id, 0, IPC_RMID);//移除sem(IPC对象)
	printf("IPC_RMID ret:%dn", ret);

	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[1];
	
	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 failedn");
		return -1;
	}

	int sem_id;

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

	int shm_id;
	shm_id = shmget(key, SHM_SIZE, IPC_CREAT|0666);//共享内存与信号量可使用同一个key值
	if(shm_id == -1)
	{
		perror("get shm errorn");
		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 inputn");
		
		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, 0, 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[1];
	
	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 failedn");
		return -1;
	}

	int sem_id;

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

	int shm_id;

	shm_id = shmget(key, SHM_SIZE, IPC_CREAT|0666);
	if(shm_id == -1)
	{
		perror("get shm errorn");
		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, 0, 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 errorn");
		return -1;
	}

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


	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 errorn");
		return -1;
	}

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

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

运行效果:

    

最后

以上就是美丽猫咪为你收集整理的Linux-C 进程通信之system-V IPC的全部内容,希望文章能够帮你解决Linux-C 进程通信之system-V IPC所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(41)

评论列表共有 0 条评论

立即
投稿
返回
顶部