目录
- 一、信号灯集
- 1.1 概念
- 1.2 信号灯集创建步骤⭐⭐⭐
- 1.3 信号灯集对应函数
- 1.4 练习
- 二、消息队列
- 2.1 特点
- 2.2 消息队列的创建步骤
- 2.3 函数
- 2.4 练习
一、信号灯集
1.1 概念
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作。
1.2 信号灯集创建步骤⭐⭐⭐
- 创建key值 ftok
- 创建或打开信号灯集 semget
- 初始化信号灯:semctl
- PV操作:semop
- 删除信号灯集 :semctl
1.3 信号灯集对应函数
- semget
int semget(key_t key, int nsems, int semflg);
- 功能:创建/打开信号灯
- 参数:
- key:ftok产生的key值
- nsems:信号灯集中包含的信号灯数目
- semflg:信号灯集的访问权限,通常为
IPC_CREAT | IPC_EXCL |0666
- 返回值:
- 成功:信号灯集ID
- 失败:-1
例:创建或打开信号灯集 ↓
运行:
🚨注意:由于系统原因,第一次创建信号灯集,semid为0,不能用,要排除。故第一次创建完之后要删除semid为0的信号灯集,重新创建。
→查看系统当前信号灯集:ipcs -s
→删除系统信号灯集:ipcrm -s semid
- semctl
int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
- 功能:信号灯集合的控制(初始化/删除)
- 参数:
- semid:信号灯集ID
- semnum: 要操作的集合中的信号灯编号
- cmd:信号灯集的控制方式
- GETVAL:获取信号灯的值,返回值是获得值
- SETVAL:设置信号灯的值(初始化信号灯),需要用到第四个参数:共用体
- IPC_RMID:从系统中删除信号灯集合(如有多个信号灯,最后只删除任意一个信号灯即可将所有的信号灯删除)
- 返回值:成功 0;失败 -1
用法:第四个参数是一个共用体,需自己创建,共用体第一个参数就是信号灯的初值
一般,该共用体只用到第一个成员变量,信号灯集的初始化代码可如下:
union semun{
int val;
}mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);
- semop
int semop (int semid, struct sembuf *opsptr, size_t nops);
- 功能:对信号灯集合中的信号量进行PV操作
- 参数:
- semid:信号灯集ID
- opsptr:操作方式,注意是一个结构体指针
- nops: 要操作的信号灯的个数1个
- 返回值:成功 :0;失败:-1
struct sembuf结构体成员:
struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op;
// 0 : 等待,直到信号灯的值变成0
// 1 : 释放资源,V操作
//-1 : 申请资源,P操作
short sem_flg;
// 0(阻塞),IPC_NOWAIT, SEM_UNDO
};
例:信号灯创建整个流程👇
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
union semun //创建共用体
{
int val;
};
int main(int argc, char const *argv[])
{
#if 1
//1.创建key值
key_t key = ftok(".", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%d\n", key);
//2.创建或打开信号灯集
int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666); //创建信号灯集,包含2个信号灯
if (semid <= 0)
{
if (errno == EEXIST) //存在则只打开
semid = semget(key, 2, 0666);
else
{
perror("semget err");
return -1;
}
}
#endif
#if 1
else //3.初始化。若信号灯集已经创建好,则不需要初始化
{ // 所以要放到else里面
union semun sem; //共用体变量
sem.val = 0;
semctl(semid, 0, SETVAL, sem);
sem.val = 5;
semctl(semid, 1, SETVAL, sem);
}
printf("semid:%d\n", semid);
printf("0:%d\n", semctl(semid, 0, GETVAL));
printf("1:%d\n", semctl(semid, 1, GETVAL));
//4.pv操作
struct sembuf buf;
//v
buf.sem_num = 0; //信号灯编号
buf.sem_op = 1; //sem.val+1
buf.sem_flg = 0; //阻塞:当信号的值减为0时,申请不到资源,阻塞
semop(semid, &buf, 1); //1:1个灯
//p
buf.sem_num = 1; //信号灯编号
buf.sem_op = -1; //sem.val-1
buf.sem_flg = 0; //阻塞:当信号的值减为0时,申请不到资源,阻塞
semop(semid, &buf, 1);
printf("0:%d\n", semctl(semid, 0, GETVAL));
printf("1:%d\n", semctl(semid, 1, GETVAL));
//5.删除信号灯集
semctl(semid, 0, IPC_RMID);
#endif
return 0;
}
- 对部分信号灯集创建步骤进程简单 函数封装
将信号灯集的初始化和PV操作进行函数的封装,目的是为了代码结构的模块化,使得代码不显得过于啰嗦,对于部分操作的代码实现只需要一次编写便可以对此调用,减少代码编写的重复工作。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
union semun {
int val;
};
void myinit(int semid,int num,int val) //3.信号灯集初始化函数
{
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
void semop(int semid,int num,int op) //4.pv操作函数
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
//创建key值
key_t key = ftok(".", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%d\n", key);
//2.创建或打开信号灯集
int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == EEXIST)
semid = semget(key, 2, 0666);
else
{
perror("semget err");
return -1;
}
}
else
{//3.初始化
myinit(semid,0,0);
myinit(semid,1,5);
}
printf("semid:%d\n", semid);
//4.pv操作
semop(semid,0,1);
semop(semid,1,-1);
printf("0:%d\n", semctl(semid, 0, GETVAL));
printf("1:%d\n", semctl(semid, 1, GETVAL));
//5.删除信号灯集
semctl(semid, 0, IPC_RMID);
return 0;
}
1.4 练习
两个进程实现通信(分文件编写),一个进程循环从终端输入,另一个进程循环打印,输入一次打印一次,当输入quit时结束。封装函数,使用用共享内存加信号灯集实现。
/***input.c***/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>
union semun {
int val;
};
void myinit(int semid,int num,int val)
{
//3.初始化
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
void sem_op(int semid,int num,int op)
{//4.pv操作
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
//创建key值
key_t key;
key = ftok(".", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
//创建或打开共享内存
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0) //=0不使用
{
//若已经创建,给shmid重新赋值
if (errno == 17)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
//映射
char *p = shmat(shmid, NULL, 0);
if (p == (void *)-1)
{
perror("shmat err");
return -1;
}
//2.创建或打开信号灯集
int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == EEXIST)
semid = semget(key, 2, 0666);
else
{
perror("semget err");
return -1;
}
}
else
{//3.初始化
myinit(semid,0,0);
}
printf("semid:%d\n", semid);
//写操作
while (1)
{
fgets(p, 32, stdin); //将终端输入放到共享内存
sem_op(semid,0,1); //v
printf("0:%d\n", semctl(semid, 0, GETVAL));
if (strcmp(p, "quit\n") == 0)
{
break;
}
}
//5.删除信号灯集
semctl(semid, 0, IPC_RMID);
return 0;
}
/***output.c***/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>
union semun {
int val;
};
void myinit(int semid,int num,int val)
{
//3.初始化
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
void sem_op(int semid,int num,int op)
{//4.pv操作
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
//创建key值
key_t key;
key = ftok(".", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
//创建或打开共享内存
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0) //=0不使用
{
//若已经创建,给shmid重新赋值
if (errno == 17)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
//映射
char *p = shmat(shmid, NULL, 0);
if (p == (void *)-1)
{
perror("shmat err");
return -1;
}
//2.创建或打开信号灯集
int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == EEXIST)
semid = semget(key, 2, 0666);
else
{
perror("semget err");
return -1;
}
}
else
// {//3.初始化
// myinit(semid,0,0);
// }
printf("semid:%d\n", semid);
while (1)
{
printf("0:%d\n", semctl(semid, 0, GETVAL));
sem_op(semid,0,-1);//p
if (strcmp(p, "quit\n") == 0)
{
break;
}
fputs(p,stdout); //共享内存内容输出到终端
//printf("0:%d\n", semctl(semid, 0, GETVAL));
}
//5.删除信号灯集
semctl(semid, 0, IPC_RMID);
return 0;
}
二、消息队列
2.1 特点
- 消息队列是IPC对象的一种
- 消息队列由消息队列ID来唯一标识
- 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
- 消息队列可以按照类型来发送(添加)/接收(读取)消息
2.2 消息队列的创建步骤
- 创建key值 ftok
- 创建或打开消息队列 msgget
- 添加消息:按照类型将消息添加到已打开的消息队列的末尾 msgsnd
- 读取消息:可以按照类型将消息从消息队列中读走 msgrcv
- 删除消息队列 msgctl
2.3 函数
- msgget
int msgget(key_t key, int flag);
- 功能:创建或打开一个消息队列
- 参数:
- key值
- flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
- 返回值:成功:msgid;失败:-1
- msgsnd
int msgsnd(int msqid, const void *msgp, size_t size, int flag);
- 功能:往消息队列中添加消息
- 参数:
- msqid:消息队列的ID
- msgp:指向消息的指针,指向一个消息结构体(需自己创建)
→常用消息结构msgbuf如下:
struct msgbuf{
long mtype; //消息类型
char mtext[N] //消息正文
}; - size:发送的消息正文的字节数(即除了结构体消息类型的剩余大小)。
- flag:
- IPC_NOWAIT消息没有发送完成函数也会立即返回
- 0:直到发送完成函数才返回
- 返回值:成功:0;失败:-1
使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)
🚨注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。
- msgrcv
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
- 功能:读取消息
- 参数:
- msgid:消息队列的ID
- msgp:存放读取消息的空间
- size:接受的消息正文的字节数
- msgtype:
- 0:接收消息队列中第一个消息。
- >0:接收消息队列中第一个类型为msgtyp的消息.
- <0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
- flag:
- 0:若无消息函数会一直阻塞
- IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
- 返回值:成功:接收到的消息的长度;失败:-1
- msgctl
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
- 功能:对消息队列的操作,删除消息队列
- 参数:
- msqid:消息队列的队列ID
- cmd:
- IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
- IPC_SET:设置消息队列的属性。这个值取自 buf参数。
- IPC_RMID:从系统中删除消息队列。
- buf:消息队列缓冲区
- 返回值:成功:0;失败:-1
用法:要删除消息队列就用→ msgctl(msgid, IPC_RMID, NULL)
2.4 练习
- 单个消息读取
若将msgrcv的倒数第二个参数msgtype改为0,也可以打印出类型1的消息,原因是msgrcv中msgtype为0时,可以匹配任意类型消息,且默认读取消息队列中第一个消息。 - 多个消息读取