信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作。
semget
int semget(key_t key, int nsems, int semflg);
注意:由于系统原因,第一次创建信号灯集,semid为0,不能用,要排除。故第一次创建完之后要删除semid为0的信号灯集,重新创建。
→查看系统当前信号灯集:ipcs -s
→删除系统信号灯集:ipcrm -s semid
semctl
int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
用法:第四个参数是一个共用体,需自己创建,共用体第一个参数就是信号灯的初值
一般,该共用体只用到第一个成员变量,信号灯集的初始化代码可如下:
union semun{
int val;
}mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);
semop
int semop (int semid, struct sembuf *opsptr, size_t nops);
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
#include
#include
#include
#include
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;
}
#include
#include
#include
#include
#include
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;
}
两个进程实现通信(分文件编写),一个进程循环从终端输入,另一个进程循环打印,输入一次打印一次,当输入quit时结束。封装函数,使用用共享内存加信号灯集实现。
/***input.c***/
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
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;
}
msgget
int msgget(key_t key, int flag);
msgsnd
int msgsnd(int msqid, const void *msgp, size_t size, int flag);
msgrcv
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
msgctl
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
用法:要删除消息队列就用→ msgctl(msgid, IPC_RMID, NULL)
单个消息读取
若将msgrcv的倒数第二个参数msgtype改为0,也可以打印出类型1的消息,原因是msgrcv中msgtype为0时,可以匹配任意类型消息,且默认读取消息队列中第一个消息(更改后的代码如下图所示)。
多个消息读取
即,添加多个消息类型,进程按类别发送,各取所需。
需要修改的代码如下:
运行结果:
从运行结果可以看出,发出的类型1和类型2消息已被接收,由于程序中并没有发送类型3消息,所以在接收3类型消息时程序会发生阻塞。
当然,上面代码示例是在一个进程中实现的消息接收发送的功能,只是为了函数的简单使用,最重要的韩还是进程间的通信。
消息队列不同于管道,通信的两个进程可以是完全无关的进程,它们之间不需要约定同步的方法。只要消息队列存在并且有存放消息的空间,发送进程就可以向队列中存放消息,并且可以在接收进程开始之前终止其执行。但使用管道通信的进程,不论是匿名管道还是有名管道,通信的两个进程都必须是正在运行的进程。这一点正是消息队列的优点。