semop系统调用及示例

semop – 信号量操作

函数介绍

semop系统调用用于对System V信号量集执行操作。它是信号量机制的核心操作函数,通过原子性地执行一系列信号量操作来实现进程同步。
](https://www.calcguide.tech/2025/08/18/semop%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

功能

对信号量集执行原子操作,用于进程间同步。

参数

  • int semid: 信号量集的标识符(由semget返回)
  • struct sembuf *sops: 指向sembuf结构体数组的指针struct sembuf { unsigned short sem_num; // 信号量在集合中的索引 short sem_op; // 操作类型 short sem_flg; // 操作标志 };
    • sem_op > 0: 释放资源(V操作),将sem_op加到信号量值上
    • sem_op < 0: 请求资源(P操作),从信号量值中减去sem_op的绝对值
    • sem_op = 0: 等待信号量值变为0
  • unsigned nsops: sops数组中操作的数量

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EAGAIN: 操作会阻塞但设置了IPC_NOWAIT
    • EIDRM: 信号量集已被删除
    • EINTR: 系统调用被信号中断
    • EINVAL: 参数无效
    • EFBIG: sem_op绝对值过大

相似函数

  • semtimedop(): 带超时的信号量操作
  • semget(): 获取信号量集
  • semctl(): 控制信号量集

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

// 信号量操作联合体
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// P操作(等待/减1)
int P(int semid, int sem_num) {
    struct sembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = -1;  // P操作
    sb.sem_flg = 0;
    return semop(semid, &sb, 1);
}

// V操作(发送/加1)
int V(int semid, int sem_num) {
    struct sembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = 1;   // V操作
    sb.sem_flg = 0;
    return semop(semid, &sb, 1);
}

// 等待信号量为0
int wait_zero(int semid, int sem_num) {
    struct sembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = 0;
    sb.sem_flg = 0;
    return semop(semid, &sb, 1);
}

int main() {
    key_t key;
    int semid;
    union semun arg;
    
    printf("=== Semop函数示例 ===\n");
    
    // 创建信号量集
    key = ftok(".", 'b');
    if (key == -1) {
        perror("ftok失败");
        exit(EXIT_FAILURE);
    }
    
    semid = semget(key, 2, 0666 | IPC_CREAT | IPC_EXCL);
    if (semid == -1) {
        if (errno == EEXIST) {
            // 如果已存在,先删除再创建
            semid = semget(key, 2, 0666);
            semctl(semid, 0, IPC_RMID);
            semid = semget(key, 2, 0666 | IPC_CREAT | IPC_EXCL);
        } else {
            perror("semget失败");
            exit(EXIT_FAILURE);
        }
    }
    
    if (semid == -1) {
        perror("创建信号量集失败");
        exit(EXIT_FAILURE);
    }
    printf("成功创建信号量集,标识符: %d\n", semid);
    
    // 初始化信号量
    // 信号量0:用于互斥,初始值为1(二进制信号量)
    // 信号量1:用于资源计数,初始值为3(表示有3个资源)
    unsigned short init_values[2] = {1, 3};
    arg.array = init_values;
    if (semctl(semid, 0, SETALL, arg) == -1) {
        perror("初始化信号量失败");
        semctl(semid, 0, IPC_RMID);
        exit(EXIT_FAILURE);
    }
    printf("信号量初始化完成\n");
    printf("  信号量0(互斥): %d\n", semctl(semid, 0, GETVAL));
    printf("  信号量1(资源): %d\n", semctl(semid, 1, GETVAL));
    
    // 示例1: 基本的P/V操作
    printf("\n示例1: 基本的P/V操作\n");
    printf("操作前 - 信号量0: %d, 信号量1: %d\n",
           semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
    
    // P操作获取互斥锁
    printf("执行P操作获取互斥锁...\n");
    if (P(semid, 0) == -1) {
        perror("P操作失败");
    } else {
        printf("成功获取互斥锁\n");
        printf("操作后 - 信号量0: %d, 信号量1: %d\n",
               semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
        
        // V操作释放互斥锁
        printf("执行V操作释放互斥锁...\n");
        if (V(semid, 0) == -1) {
            perror("V操作失败");
        } else {
            printf("成功释放互斥锁\n");
            printf("操作后 - 信号量0: %d, 信号量1: %d\n",
                   semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
        }
    }
    
    // 示例2: 多进程同步演示
    printf("\n示例2: 多进程同步演示\n");
    pid_t pid = fork();
    
    if (pid == -1) {
        perror("fork失败");
        semctl(semid, 0, IPC_RMID);
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程
        printf("子进程 %d 开始执行\n", getpid());
        
        // 模拟需要互斥访问的临界区
        printf("子进程尝试获取互斥锁...\n");
        if (P(semid, 0) == 0) {
            printf("子进程 %d 进入临界区\n", getpid());
            printf("子进程在临界区工作2秒...\n");
            sleep(2);
            printf("子进程 %d 离开临界区\n", getpid());
            V(semid, 0);  // 释放锁
        }
        
        exit(EXIT_SUCCESS);
    } else {
        // 父进程
        sleep(1);  // 让子进程先运行
        
        printf("父进程 %d 开始执行\n", getpid());
        printf("父进程尝试获取互斥锁...\n");
        
        if (P(semid, 0) == 0) {
            printf("父进程 %d 进入临界区\n", getpid());
            printf("父进程在临界区工作2秒...\n");
            sleep(2);
            printf("父进程 %d 离开临界区\n", getpid());
            V(semid, 0);  // 释放锁
        }
        
        // 等待子进程结束
        wait(NULL);
    }
    
    // 示例3: 资源计数信号量演示
    printf("\n示例3: 资源计数信号量演示\n");
    printf("当前可用资源数: %d\n", semctl(semid, 1, GETVAL));
    
    // 模拟多个进程竞争资源
    for (int i = 0; i < 4; i++) {
        pid = fork();
        if (pid == 0) {
            // 子进程
            printf("进程 %d 尝试获取资源...\n", getpid());
            
            // P操作获取资源
            if (P(semid, 1) == 0) {
                printf("进程 %d 成功获取资源,剩余资源: %d\n", 
                       getpid(), semctl(semid, 1, GETVAL));
                
                // 使用资源
                printf("进程 %d 使用资源2秒...\n", getpid());
                sleep(2);
                
                // V操作释放资源
                V(semid, 1);
                printf("进程 %d 释放资源,剩余资源: %d\n", 
                       getpid(), semctl(semid, 1, GETVAL));
            } else {
                printf("进程 %d 获取资源失败\n", getpid());
            }
            
            exit(EXIT_SUCCESS);
        }
    }
    
    // 等待所有子进程结束
    for (int i = 0; i < 4; i++) {
        wait(NULL);
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("删除信号量集失败");
    } else {
        printf("成功删除信号量集\n");
    }
    
    return 0;
}
此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

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