概述
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 |
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 获得的属性信息被存放在以下结构体中: 3,当使用选项MSG_INFO 时,跟IPC_INFO 一样也是获得一个msginfo 结构体的信息,但是有如下几点不同: |
例子:总共三个进程,进程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、共享内存只能以只读或者可读写方式映射,无法以只写方式映射。 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 获得的属性信息被存放在以下结构体中: 3,当使用IPC_INFO 时,需要定义一个如下结构体来获取系统关于共享内存的限制值 |
例子: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,信号量操作结构体的定义如下: short sem_op; /* 操作参数 */ 请注意:信号量元素的序号从 0 开始,实际上就是数组下标。 B) 当 sem_op 等于 0 时:进行等零操作,如果此时 semval 恰好为 0,则 semop( ) 立即成功返回,否则如果 IPC_NOWAIT 被设置,则立即出错返回并将 errno 设置为 EAGAIN,否则将使得进程进入睡眠,直到以下情况发生: 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,否则将使得进程进入睡眠,直到 以下情况发生: 3、sem_flg可取值IPC_NOWAIT、SEM_UNDO, IPC_NOWAIT:表示如果资源不可用,不等待直接返回EAGAIN; SEM_UNDO:如果资源不可用就会进入睡眠直到资源满足,如果占用该资源的程序因异常退出而没有释放该信号量,系统将自动释放该进程持有的信号量,让其它进程继续工作。 /* semop flags */
|
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 的不同,可能需要第四个参数,第四个参数是一个如 下所示的联合体,用户必须自己定义: 权限结构体如下: |
例子:有两个进程,进程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():移除信号量,释放系统资源。
功能 | 打开或创建一个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 时,该函数必须提供后两个参数 |
功能 | 对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( )来替代 |
功能 | 关闭、删除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所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复