目录
一.消息队列
概念:
Linux消息队列是一种可用于进程间通信的机制。它是一种先进先出的数据结构,用于将消息存储在队列中。在Linux中,消息队列是系统内核维护的一种特殊类型的文件,可以通过系统调用来进行操作。由于消息队列是异步的,因此生产者和消费者进程可以并行执行,而无需同步。
在消息队列中,消息的发送方将消息放入队列中,而接收方将消息从队列中取出。消息队列支持多个读取和写入进程。这种机制也提供了一种在进程之间共享数据的方式,而无需使用共享内存或其他类似机制来同步访问。因此,消息队列被广泛应用于进程间通信和实现各种高级应用程序的功能,如进程间通信、同步和异步通信、进程同步、并发控制和分布式系统等。
使用步骤
Linux消息队列用于进程之间进行通信,它允许一个或多个进程将消息放入队列中,另外一个或多个进程从队列中获取消息并处理。下面是使用Linux消息队列的步骤:
-
创建消息队列:使用msgget函数创建消息队列,它返回一个唯一的标识符。
-
发送消息:使用msgsnd函数向消息队列发送消息,它需要指定消息队列的标识符、消息缓冲区及消息长度。
-
接收消息:使用msgrcv函数从消息队列接收消息,它需要指定消息队列的标识符、消息缓冲区及消息长度。此外,还可以指定接收的消息类型和等待方式等参数。
-
删除消息队列:使用msgctl函数删除消息队列,它需要指定消息队列的标识符和控制命令。
Linux消息队列的主要函数定义:
-
int msgget(key_t key, int msgflg); 功能:创建或打开一个消息队列。 参数:key是消息队列的关键字,msgflg表示创建标志。 返回值:成功返回消息队列标识符,失败返回-1。
- int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 功能:向消息队列中发送消息。 参数:msqid是消息队列标识符,msgp是指向消息缓冲区的指针,msgsz是消息长度,msgflg表示发送标志。 返回值:成功返回0,失败返回-1。
-
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 功能:从消息队列中接收消息。 参数:msqid是消息队列标识符,msgp是指向消息缓冲区的指针,msgsz是消息长度,msgtyp表示接收的消息类型,msgflg表示接收标志。 返回值:成功返回接收到的消息长度,失败返回-1。
-
int msgctl(int msqid, int cmd, struct msqid_ds *buf); 功能:控制消息队列。 参数:msqid是消息队列标识符,cmd表示控制命令,buf是指向消息队列信息结构体的指针。 返回值:成功返回0,失败返回-1。
消息队列实例
接受:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct{
long msg_type;
char buf[128];
}msgT;
#define MSGLEN (sizeof(msgT)-sizeof(long))
int main(){
int msgid;
key_t key;
msgT msg;
int ret;
key = ftok(".",100);
if(key<0){
perror("ftok");
return 0;
}
msgid = msgget(key,IPC_CREAT|0666);
if(msgid<0){
perror("msgget");
return 0;
}
int count=0;
while(1){
ret = msgrcv(msgid,&msg,MSGLEN,0,0);
if(ret<0){
perror("msgrcv");
return 0;
}
count++;
if(count>3){
break;
}
printf("receiv msg type=%d,buf=%s\n",(int)msg.msg_type,msg.buf);
}
ret = msgctl(msgid,IPC_RMID,NULL);
if(ret<0){
perror("msgctl");
return 0;
}
}
发送:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct{
long msg_type;
char buf[128];
}msgT;
#define MSGLEN (sizeof(msgT)-sizeof(long))
int main(){
key_t key;
int msgid;
int ret;
msgT msg;
key = ftok(".",100);
if(key<0){
perror("ftok");
return 0;
}
msgid = msgget(key,IPC_CREAT|0666);
if(msgid<0){
perror("msgget");
return 0;
}
msg.msg_type = 1;
strcpy(msg.buf,"this msg type 1");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
msg.msg_type = 2;
strcpy(msg.buf,"this msg type 2");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
msg.msg_type = 3;
strcpy(msg.buf,"this msg type 3");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
msg.msg_type = 4;
strcpy(msg.buf,"this msg type 4");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
msg.msg_type = 5;
strcpy(msg.buf,"this msg type 5");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
}
二.信号灯
概念:
Linux信号量是一种用于进程间通信和同步的机制。它是一个计数器,用于保护临界区资源,只有持有对应信号量的进程才能访问这些资源。
信号量分为二进制信号量和计数信号量。二进制信号量只有两个值(0和1),通常用于实现互斥锁;计数信号量可以有任意数量的值,常用于限制并发数。
Linux内核中提供了多种信号量实现方式,包括System V信号量和POSIX信号量。其中System V信号量使用IPC ID进行标识,而POSIX信号量使用命名对象进行标识。
在Linux中,可以使用多种系统调用函数来操作信号量,例如semget、semctl、semop等等。这些函数可以创建信号量、修改信号量值、阻塞进程等等。在使用这些函数时,需要注意信号量的正确使用,以避免出现死锁等问题。
信号量P/V操作
P/V操作是通过操作系统中提供的信号量来实现的,它用于实现进程同步和互斥。P操作(Wait操作)和V操作(Signal操作)是信号量的两种基本操作。
P操作,也称为Wait操作,用于对信号量做减1操作,以占用资源或等待资源的释放。其语义为 “试图获取一个资源,如果有可用资源,则占用(-1),否则阻塞等待直到有可用资源为止”。
V操作,也称为Signal操作,用于对信号量做加1操作,以释放占用的资源或通知其他进程资源已经可用。其语义为 “释放一个原先被占用的资源,使其可用(+1)”。
操作系统中信号量的P/V操作可以用以下伪代码来实现:
//P操作实现 P(Semaphore s) { s.value = s.value - 1; if(s.value < 0) //如果信号量值小于0,则进程阻塞 { //将当前进程加入信号量的等待队列中 add_to_waiting_queue(s, current_process); block(current_process); //阻塞当前进程 } }
//V操作实现 V(Semaphore s) { s.value = s.value + 1; if(s.value <= 0) //如果等待队列中有进程,则唤醒等待队列中的第一个进程 { //将等待队列中的第一个进程移出队列 process p = remove_from_waiting_queue(s); unblock(p); //解除进程阻塞状态 } }
信号量的P/V操作常常用于进程同步和互斥,实现多个进程之间的资源共享和并发执行。
三种信号灯:
Posix 有名信号灯
Posix 无名信号灯 (linux只支持线程同步)
System V 信号灯
有名信号灯的使用步骤:
- 首先,需要在代码中定义一个有名信号灯对象,例如:
sem_t *sem = sem_open("my_semaphore", O_CREAT, 0644, 1);
这个函数会创建一个有名信号灯对象,并初始化其值为1。
- 在需要使用信号灯的进程中,可以使用下列函数来等待信号灯:
sem_wait(sem);
如果信号灯的值为0,那么这个函数会阻塞当前进程,直到信号灯的值变为非0。
- 在进程完成对共享资源的访问后,需要释放信号灯:
sem_post(sem);
这个函数会将信号灯的值加1。如果此时有其他进程正在等待这个信号灯,那么其中的一个进程会被唤醒。
- 在程序结束时,需要关闭并删除这个有名信号灯对象:
sem_close(sem);
sem_unlink("my_semaphore");
函数定义:
有名信号灯的函数定义与无名信号灯相同,通常包括以下函数:
sem_open()
:创建或打开一个有名信号灯对象;sem_wait()
:等待信号灯,并在需要时阻塞当前进程;sem_post()
:释放信号灯;sem_close()
:关闭一个有名信号灯对象;sem_unlink()
:删除一个有名信号灯对象。
无名信号灯的使用步骤和函数定义:
-
使用步骤: (1)定义一个无名信号灯(Semaphore)变量。 (2)使用sem_init初始化信号灯。 (3)使用sem_wait等待信号灯,直到信号灯的值大于0。 (4)对共享资源进行操作。 (5)使用sem_post释放信号灯。
-
函数定义:(1)sem_init:初始化信号灯。 sem_init(sem_t *sem, int pshared, unsigned int value);
(2)sem_wait:等待信号灯。 int sem_wait(sem_t *sem);
(3)sem_post:释放信号灯。 int sem_post(sem_t *sem);
三个函数的参数sem均代表Semaphore信号灯变量。
System V 信号灯是一种在进程之间进行同步和互斥的机制。所有的 System V 信号灯使用都需要使用一些函数进行定义和操作。
常用的 System V 信号灯函数定义:
-
int semget(key_t key, int nsems, int semflg)
:创建或获取一个信号灯集合。 -
int semop(int semid, struct sembuf *sops, size_t nsops)
:进行信号灯的操作,包括设置信号灯的值、等待信号灯为某个值、递增或递减信号灯的值等。 -
int semctl(int semid, int semnum, int cmd, ...)
:控制信号灯的属性,包括设置信号灯的值、获取信号灯的值、删除信号灯等。
在使用 System V 信号灯时,一般按照以下步骤进行:
-
调用
semget()
函数创建或获取一个信号灯集合,并获得该集合的标识符。 -
初始化信号灯的初始值。
-
在进程中使用
semop()
函数进行信号灯的操作,包括等待、递增或递减等。 -
当不再需要该信号灯时,调用
semctl()
函数将其删除。
信号灯的集合的代码示例:
#include <sys/sem.h>
#include <stdio.h>
int main() {
int semid;
key_t key = 1234; // 信号灯集合的键值
int nsems = 1; // 信号灯的数量
int semflg = IPC_CREAT | 0666; // 创建并设置权限
// 创建一个包含一个信号灯的集合
semid = semget(key, nsems, semflg);
if (semid == -1) {
perror("semget");
return -1;
}
// 初始化信号灯的值为 1
struct sembuf init_op;
init_op.sem_num = 0;
init_op.sem_op = 1;
init_op.sem_flg = 0;
semop(semid, &init_op, 1);
printf("Initialize semaphore successfully.\n");
return 0;
}
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#define SEM_READ 0
#define SEM_WRITE 1
union semun {
int val;
};
void Poperation(int semid,int semindex){
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = -1;
sbuf.sem_flg = 0;
semop(semid,&sbuf,1);
}
void Voperation(int semid,int semindex){
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = 1;
sbuf.sem_flg = 0;
semop(semid,&sbuf,1);
}
int main(){
key_t key;
char *shmaddr;
int semid,shmid;
key = ftok(".",100);
if(key<0){
perror("ftok");
return 0;
}
semid = semget(key,2,IPC_CREAT |0666);
if(semid<0){
perror("semget");
return 0;
}
shmid = shmget(key,500,IPC_CREAT |0666);
shmaddr = shmat(shmid,NULL,0);
union semun mysem;
mysem.val = 0;
semctl(semid,SEM_READ,SETVAL,mysem);
mysem.val = 1;
semctl(semid,SEM_WRITE,SETVAL,mysem);
pid_t pid;
pid = fork();
if(pid<0){
perror("fork");
shmctl(shmid,IPC_RMID,NULL);
semctl(semid,0,IPC_RMID);
exit(-1);
}else if(pid == 0){
while(1){
Poperation(semid,SEM_READ);
printf("%s\n",shmaddr);
Voperation(semid,SEM_WRITE);
}
}else{
while(1){
Poperation(semid,SEM_WRITE);
printf(">");
fgets(shmaddr,32,stdin);
Voperation(semid,SEM_READ);
}
}
}