mq_unlink系统调用及示例

mq_unlink函数详解

1. 函数介绍

mq_unlink函数是Linux系统中用于删除POSIX消息队列的函数。可以把mq_unlink想象成一个”消息队列删除器”,它能够从系统中移除指定名称的消息队列。

POSIX消息队列是进程间通信(IPC)的一种机制,允许不同进程通过队列发送和接收消息。mq_unlink的作用类似于文件系统的unlink函数,它删除消息队列的名称,但不会立即销毁队列本身。只有当所有打开该队列的进程都关闭了队列描述符后,队列才会被真正销毁。

使用场景:

  • 进程间通信系统的清理
  • 服务器程序的资源管理
  • 系统维护和清理脚本
  • 消息队列生命周期管理

2. 函数原型

#include <mqueue.h>

int mq_unlink(const char *name);

3. 功能

mq_unlink函数的主要功能是删除指定名称的POSIX消息队列。它从系统中移除队列的名称,使得后续无法通过该名称打开队列,但已打开的队列描述符仍然有效。

4. 参数

  • name: 消息队列名称
    • 类型:const char*
    • 含义:要删除的消息队列名称
    • 名称必须以’/’开头,如”/my_queue”

5. 返回值

  • 成功: 返回0
  • 失败: 返回-1,并设置errno错误码
    • EACCES:权限不足
    • ENOENT:指定名称的消息队列不存在
    • EINVAL:名称无效

6. 相似函数或关联函数

  • mq_open(): 打开或创建消息队列
  • mq_close(): 关闭消息队列描述符
  • mq_send(): 发送消息
  • mq_receive(): 接收消息
  • mq_getattr(): 获取队列属性
  • mq_setattr(): 设置队列属性
  • unlink(): 删除文件

7. 示例代码

示例1:基础mq_unlink使用 – 简单队列删除

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>

// 创建消息队列
mqd_t create_message_queue(const char* name) {
    struct mq_attr attr = {
        .mq_flags = 0,
        .mq_maxmsg = 10,
        .mq_msgsize = 256,
        .mq_curmsgs = 0
    };
    
    mqd_t mq = mq_open(name, O_CREAT | O_RDWR, 0644, &attr);
    if (mq == (mqd_t)-1) {
        perror("创建消息队列失败");
        return -1;
    }
    
    printf("创建消息队列: %s (描述符: %d)\n", name, (int)mq);
    return mq;
}

// 显示消息队列属性
void show_queue_attributes(mqd_t mq, const char* name) {
    struct mq_attr attr;
    if (mq_getattr(mq, &attr) == -1) {
        perror("获取队列属性失败");
        return;
    }
    
    printf("队列 %s 属性:\n", name);
    printf("  最大消息数: %ld\n", attr.mq_maxmsg);
    printf("  最大消息大小: %ld\n", attr.mq_msgsize);
    printf("  当前消息数: %ld\n", attr.mq_curmsgs);
    printf("  标志: %ld\n", attr.mq_flags);
}

int main() {
    printf("=== 基础mq_unlink使用示例 ===\n");
    
    const char* queue_name = "/test_queue";
    
    // 创建消息队列
    printf("1. 创建消息队列:\n");
    mqd_t mq = create_message_queue(queue_name);
    if (mq == -1) {
        exit(EXIT_FAILURE);
    }
    
    show_queue_attributes(mq, queue_name);
    
    // 发送一些测试消息
    printf("\n2. 发送测试消息:\n");
    const char* messages[] = {
        "第一条测试消息",
        "第二条测试消息",
        "第三条测试消息"
    };
    
    for (int i = 0; i < 3; i++) {
        if (mq_send(mq, messages[i], strlen(messages[i]), 0) == -1) {
            perror("发送消息失败");
        } else {
            printf("发送消息: %s\n", messages[i]);
        }
    }
    
    show_queue_attributes(mq, queue_name);
    
    // 使用mq_unlink删除队列名称
    printf("\n3. 使用mq_unlink删除队列名称:\n");
    if (mq_unlink(queue_name) == 0) {
        printf("✓ 成功删除队列名称: %s\n", queue_name);
        printf("注意: 队列本身仍然存在,因为还有打开的描述符\n");
    } else {
        printf("✗ 删除队列名称失败: %s\n", strerror(errno));
    }
    
    // 验证队列名称已被删除
    printf("\n4. 验证队列名称删除效果:\n");
    mqd_t mq2 = mq_open(queue_name, O_RDONLY);
    if (mq2 == -1) {
        printf("✓ 无法通过名称重新打开队列 (预期行为): %s\n", strerror(errno));
    } else {
        printf("✗ 仍然可以通过名称打开队列\n");
        mq_close(mq2);
    }
    
    // 原有描述符仍然可以使用
    printf("\n5. 原有描述符仍然有效:\n");
    char buffer[256];
    ssize_t bytes_received;
    unsigned int priority;
    
    while ((bytes_received = mq_receive(mq, buffer, sizeof(buffer), &priority)) > 0) {
        buffer[bytes_received] = '\0';
        printf("接收到消息: %s (优先级: %u)\n", buffer, priority);
    }
    
    // 关闭队列描述符(此时队列才会被真正销毁)
    printf("\n6. 关闭队列描述符:\n");
    if (mq_close(mq) == 0) {
        printf("✓ 队列描述符已关闭,队列被真正销毁\n");
    } else {
        perror("关闭队列描述符失败");
    }
    
    printf("\n=== 基础mq_unlink演示完成 ===\n");
    
    return 0;
}

示例2:多个进程共享队列的删除管理

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#define MAX_MESSAGES 5
#define MESSAGE_SIZE 256

// 生产者进程
void producer_process(const char* queue_name, int producer_id) {
    printf("生产者 %d 启动\n", producer_id);
    
    // 打开已存在的队列
    mqd_t mq = mq_open(queue_name, O_WRONLY);
    if (mq == (mqd_t)-1) {
        perror("生产者打开队列失败");
        exit(EXIT_FAILURE);
    }
    
    srand(time(NULL) + producer_id);
    
    // 发送消息
    for (int i = 0; i < MAX_MESSAGES; i++) {
        char message[MESSAGE_SIZE];
        snprintf(message, sizeof(message), "生产者%d的消息%d", producer_id, i + 1);
        
        // 随机优先级
        unsigned int priority = rand() % 10;
        
        if (mq_send(mq, message, strlen(message), priority) == -1) {
            perror("发送消息失败");
        } else {
            printf("生产者 %d 发送: %s (优先级: %u)\n", producer_id, message, priority);
        }
        
        sleep(1);  // 模拟处理时间
    }
    
    printf("生产者 %d 完成\n", producer_id);
    mq_close(mq);
}

// 消费者进程
void consumer_process(const char* queue_name, int consumer_id) {
    printf("消费者 %d 启动\n", consumer_id);
    
    // 打开已存在的队列
    mqd_t mq = mq_open(queue_name, O_RDONLY);
    if (mq == (mqd_t)-1) {
        perror("消费者打开队列失败");
        exit(EXIT_FAILURE);
    }
    
    // 接收消息
    char buffer[MESSAGE_SIZE];
    ssize_t bytes_received;
    unsigned int priority;
    int message_count = 0;
    
    while (message_count < MAX_MESSAGES * 2) {  // 期望接收所有生产者的消息
        bytes_received = mq_receive(mq, buffer, sizeof(buffer), &priority);
        if (bytes_received > 0) {
            buffer[bytes_received] = '\0';
            printf("消费者 %d 接收: %s (优先级: %u)\n", consumer_id, buffer, priority);
            message_count++;
        } else if (errno == EAGAIN) {
            // 非阻塞模式下没有消息
            printf("消费者 %d: 暂无消息\n", consumer_id);
            sleep(1);
        } else {
            perror("接收消息失败");
            break;
        }
    }
    
    printf("消费者 %d 完成,接收 %d 条消息\n", consumer_id, message_count);
    mq_close(mq);
}

// 管理进程
void manager_process(const char* queue_name) {
    printf("管理进程启动\n");
    
    // 创建消息队列
    struct mq_attr attr = {
        .mq_flags = 0,
        .mq_maxmsg = 20,
        .mq_msgsize = MESSAGE_SIZE,
        .mq_curmsgs = 0
    };
    
    mqd_t mq = mq_open(queue_name, O_CREAT | O_RDWR, 0644, &attr);
    if (mq == (mqd_t)-1) {
        perror("管理进程创建队列失败");
        exit(EXIT_FAILURE);
    }
    
    printf("管理进程创建队列: %s\n", queue_name);
    
    // 启动生产者和消费者进程
    pid_t producers[2], consumers[2];
    
    // 启动生产者
    for (int i = 0; i < 2; i++) {
        producers[i] = fork();
        if (producers[i] == 0) {
            producer_process(queue_name, i + 1);
            exit(EXIT_SUCCESS);
        }
    }
    
    // 启动消费者
    for (int i = 0; i < 2; i++) {
        consumers[i] = fork();
        if (consumers[i] == 0) {
            consumer_process(queue_name, i + 1);
            exit(EXIT_SUCCESS);
        }
    }
    
    // 等待生产者完成
    printf("管理进程等待生产者完成...\n");
    for (int i = 0; i < 2; i++) {
        waitpid(producers[i], NULL, 0);
    }
    
    printf("所有生产者已完成\n");
    
    // 模拟一段时间让消费者处理完消息
    sleep(3);
    
    // 删除队列名称(但队列仍存在,因为消费者还在使用)
    printf("管理进程删除队列名称...\n");
    if (mq_unlink(queue_name) == 0) {
        printf("✓ 队列名称已删除,但队列仍存在(消费者仍在使用)\n");
    } else {
        printf("✗ 删除队列名称失败: %s\n", strerror(errno));
    }
    
    // 等待消费者完成
    printf("管理进程等待消费者完成...\n");
    for (int i = 0; i < 2; i++) {
        waitpid(consumers[i], NULL, 0);
    }
    
    printf("所有消费者已完成\n");
    
    // 现在队列才会被真正销毁(所有描述符都已关闭)
    printf("队列已被真正销毁\n");
    mq_close(mq);
    
    printf("管理进程完成\n");
}

int main() {
    printf("=== 多进程共享队列删除管理示例 ===\n");
    
    const char* queue_name = "/shared_queue";
    
    // 启动管理进程
    pid_t manager = fork();
    if (manager == 0) {
        manager_process(queue_name);
        exit(EXIT_SUCCESS);
    }
    
    // 父进程等待管理进程完成
    waitpid(manager, NULL, 0);
    
    // 验证队列是否已被删除
    printf("\n验证队列删除效果:\n");
    mqd_t mq = mq_open(queue_name, O_RDONLY);
    if (mq == -1) {
        printf("✓ 队列已成功删除: %s\n", strerror(errno));
    } else {
        printf("✗ 队列仍然存在\n");
        mq_close(mq);
    }
    
    printf("\n=== 多进程队列管理演示完成 ===\n");
    
    return 0;
}
此条目发表在未分类分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注