Linux编程--消息队列和信号灯

文章详细介绍了Linux系统中消息队列的概念、使用步骤和主要函数,包括创建、发送和接收消息的示例代码。同时,文章也阐述了信号量的基本原理,如P/V操作,以及有名和无名信号灯的使用方法,提供了SystemV信号灯的函数定义和使用示例。
摘要由CSDN通过智能技术生成

目录

一.消息队列 

概念:

使用步骤

Linux消息队列的主要函数定义:

消息队列实例

接受:

 发送:

二.信号灯

概念:

信号量P/V操作

三种信号灯:

有名信号灯的使用步骤:

 无名信号灯的使用步骤和函数定义:

常用的 System V 信号灯函数定义:

信号灯的集合的代码示例:


一.消息队列 

概念:

Linux消息队列是一种可用于进程间通信的机制。它是一种先进先出的数据结构,用于将消息存储在队列中。在Linux中,消息队列是系统内核维护的一种特殊类型的文件,可以通过系统调用来进行操作。由于消息队列是异步的,因此生产者和消费者进程可以并行执行,而无需同步。

在消息队列中,消息的发送方将消息放入队列中,而接收方将消息从队列中取出。消息队列支持多个读取和写入进程。这种机制也提供了一种在进程之间共享数据的方式,而无需使用共享内存或其他类似机制来同步访问。因此,消息队列被广泛应用于进程间通信和实现各种高级应用程序的功能,如进程间通信、同步和异步通信、进程同步、并发控制和分布式系统等。

使用步骤

Linux消息队列用于进程之间进行通信,它允许一个或多个进程将消息放入队列中,另外一个或多个进程从队列中获取消息并处理。下面是使用Linux消息队列的步骤:

  1. 创建消息队列:使用msgget函数创建消息队列,它返回一个唯一的标识符。

  2. 发送消息:使用msgsnd函数向消息队列发送消息,它需要指定消息队列的标识符、消息缓冲区及消息长度。

  3. 接收消息:使用msgrcv函数从消息队列接收消息,它需要指定消息队列的标识符、消息缓冲区及消息长度。此外,还可以指定接收的消息类型和等待方式等参数。

  4. 删除消息队列:使用msgctl函数删除消息队列,它需要指定消息队列的标识符和控制命令。

Linux消息队列的主要函数定义:

  1. int msgget(key_t key, int msgflg); 功能:创建或打开一个消息队列。 参数:key是消息队列的关键字,msgflg表示创建标志。 返回值:成功返回消息队列标识符,失败返回-1。 

  2. int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 功能:向消息队列中发送消息。 参数:msqid是消息队列标识符,msgp是指向消息缓冲区的指针,msgsz是消息长度,msgflg表示发送标志。 返回值:成功返回0,失败返回-1。
  3. ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 功能:从消息队列中接收消息。 参数:msqid是消息队列标识符,msgp是指向消息缓冲区的指针,msgsz是消息长度,msgtyp表示接收的消息类型,msgflg表示接收标志。 返回值:成功返回接收到的消息长度,失败返回-1。

  4. 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 信号灯

有名信号灯的使用步骤:

  1. 首先,需要在代码中定义一个有名信号灯对象,例如:
sem_t *sem = sem_open("my_semaphore", O_CREAT, 0644, 1);

这个函数会创建一个有名信号灯对象,并初始化其值为1。

  1. 在需要使用信号灯的进程中,可以使用下列函数来等待信号灯:
sem_wait(sem);

如果信号灯的值为0,那么这个函数会阻塞当前进程,直到信号灯的值变为非0。

  1. 在进程完成对共享资源的访问后,需要释放信号灯:
sem_post(sem);

这个函数会将信号灯的值加1。如果此时有其他进程正在等待这个信号灯,那么其中的一个进程会被唤醒。

  1. 在程序结束时,需要关闭并删除这个有名信号灯对象:
sem_close(sem);
sem_unlink("my_semaphore");

函数定义:

有名信号灯的函数定义与无名信号灯相同,通常包括以下函数:

  • sem_open():创建或打开一个有名信号灯对象;
  • sem_wait():等待信号灯,并在需要时阻塞当前进程;
  • sem_post():释放信号灯;
  • sem_close():关闭一个有名信号灯对象;
  • sem_unlink():删除一个有名信号灯对象。

 无名信号灯的使用步骤和函数定义:

  1. 使用步骤: (1)定义一个无名信号灯(Semaphore)变量。 (2)使用sem_init初始化信号灯。 (3)使用sem_wait等待信号灯,直到信号灯的值大于0。 (4)对共享资源进行操作。 (5)使用sem_post释放信号灯。

  2. 函数定义:(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 信号灯函数定义:

  1. int semget(key_t key, int nsems, int semflg):创建或获取一个信号灯集合。

  2. int semop(int semid, struct sembuf *sops, size_t nsops):进行信号灯的操作,包括设置信号灯的值、等待信号灯为某个值、递增或递减信号灯的值等。

  3. int semctl(int semid, int semnum, int cmd, ...):控制信号灯的属性,包括设置信号灯的值、获取信号灯的值、删除信号灯等。

在使用 System V 信号灯时,一般按照以下步骤进行:

  1. 调用 semget() 函数创建或获取一个信号灯集合,并获得该集合的标识符。

  2. 初始化信号灯的初始值。

  3. 在进程中使用 semop() 函数进行信号灯的操作,包括等待、递增或递减等。

  4. 当不再需要该信号灯时,调用 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);
 
        }
 
 
 
    }
 
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值