linux进程间的通信-信号量(semget、semstl、semop)

1. semget函数原型

semget(得到一个信号量集标识符或创建一个信号量集对象)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函数说明:

得到一个信号量集标识符或创建一个信号量集对象并返回信号量集标识符

函数原型

int semget(key_t key, int nsems, int semflg)

参数解释:

  • key:键值,用于唯一标识一个信号量集。通常可以通过 ftok 函数生成。
  • nsems:信号量集中信号量的数量。
  • semflg:标志位,用于指定信号量的权限和行为选项。(参数ruxia)
  1. IPC_CREAT:如果指定的键值不存在,则创建一个新的信号量集。如果已存在,则返回已存在的信号量集的标识符。
  2. IPC_EXCL:与 IPC_CREAT 一起使用时,如果指定的键值已存在,则返回错误。通常与 IPC_CREAT 结合使用,用于确保创建一个新的信号量集而不是获取已存在的信号量集。
  3. IPC_NOWAIT:在等待获取信号量资源时,不阻塞进程。如果无法立即获得资源,则直接返回错误。
  4. SEM_R:指定信号量集的读取权限,允许其他进程进行读取操作。
  5. SEM_A:指定信号量集的修改权限,允许其他进程进行修改操作。

函数返回值:

  • 成功:返回一个非负整数,表示对应的信号量集标识符(称为信号量集 ID)。
  • 失败:返回 -1,并设置 errno 错误码来指示具体错误原因。

附加说明

上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

错误代码

EACCESS没有权限
EEXIST信号量集已经存在,无法创建
EIDRM信号量集已经删除
ENOENT信号量集不存在,同时semflg没有设置IPC_CREAT标志
ENOMEM没有足够的内存创建新的信号量集
ENOSPC超出限制

如果用semget创建了一个新的信号量集对象时,则semid_ds(一般与semctl、STAT)结构成员变量的值设置如下:

       sem_otime设置为0。

       sem_ctime设置为当前时间。

       msg_qbytes设成系统的限制值。

       sem_nsems设置为nsems参数的数值。

       semflg的读写权限写入sem_perm.mode中。

       sem_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。
 

 2. semop函数原型

   semop(完成对信号量的P操作或V操作)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函数说明

对信号量集标识符为semid中的一个或多个信号量进行P操作或V操作

函数原型

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

参数解释

semid:信号量集标识符

sops:指向进行操作的信号量集结构体数组的首地址,每个结构体描述了一个操作。此结构的具体说明如下:

struct sembuf {

          unsigned short sem_num;  /* semaphore number */
           short          sem_op;   /* semaphore operation */
           short          sem_flg;  /* operation flags */

  };

  1. short sem_num:要操作的信号量在集合中的索引,从0开始计数。
  2. short sem_op:进行的操作。可以是正数、负数或零。通常情况下,正数表示释放资源,负数表示请求资源,零表示等待资源。
  3. short sem_flg:控制操作的标志位。常用的标志有:
    • SEM_UNDO:使操作可以被撤销,即在进程意外终止时自动撤销操作。
    • IPC_NOWAIT:如果操作无法立即执行,不要阻塞进程,立即返回并报告错误。
    • SEM_NOERROR:如果出现错误(如信号量不存在),不要报告错误。

nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作

函数返回值

成功:返回信号量集的标识符

出错:-1,错误原因存于error中

错误代码

E2BIG一次对信号量个数的操作超过了系统限制
EACCESS权限不够
EAGAIN使用了IPC_NOWAIT,但操作不能继续进行
EFAULTsops指向的地址无效
EIDRM信号量集已经删除
EINTR当睡眠时接收到其他信号
EINVAL信号量集不存在,或者semid无效
ENOMEM使用了SEM_UNDO,但无足够的内存创建所需的数据结构
ERANGE信号量值超出范围

sops为指向sembuf数组,定义所要进行的操作序列。下面是信号量操作举例。

struct sembuf sem_get={0,-1,IPC_NOWAIT}; /*将信号量对象中序号为0的信号量减1*/

struct sembuf sem_get={0,1,IPC_NOWAIT};  /*将信号量对象中序号为0的信号量加1*/

struct sembuf sem_get={0,0,0};           /*进程被阻塞,直到对应的信号量值为0*/

flag一般为0,若flag包含IPC_NOWAIT,则该操作为非阻塞操作。若flag包含SEM_UNDO,则当进程退出的时候会还原该进程的信号量操作,这个标志在某些情况下是很有用的,比如某进程做了P操作得到资源,但还没来得及做V操作时就异常退出了,此时,其他进程就只能都阻塞在P操作上,于是造成了死锁。若采取SEM_UNDO标志,就可以避免因为进程异常退出而造成的死锁。
 

 3. semctl函数原型 

semctl (得到一个信号量集标识符或创建一个信号量集对象)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函数说明

得到一个信号量集标识符或创建一个信号量集对象并返回信号量集标识符

函数原型

int semctl(int semid, int semnum, int cmd, union semun arg)

参数解释

semid:信号量集标识符

semnum:是要操作的信号量在集合中的索引,通常为0,表示第一个信号量。

cmd:见下文表15-4

arg(是一个联合体):

   union semun {

   short val;          /*SETVAL用的值*/

   struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/

   unsigned short* array; /*SETALL、GETALL用的数组值*/

   struct seminfo *buf;   /*为控制IPC_INFO提供的缓存*/

  } arg;

函数返回值

成功:大于或等于0,具体说明请参照表15-4

出错:-1,错误原因存于error中

附加说明

semid_ds结构见上文信号量集内核结构定义

错误代码

EACCESS权限不够
EFAULTarg指向的地址无效
EIDRM信号量集已经删除
EINVAL信号量集不存在,或者semid无效
EPERM进程有效用户没有cmd的权限
ERANGE信号量值超出范围

cmd参数列表: 

cmd解释
IPC_STAT从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
IPC_SET设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
IPC_RMID从内核中删除信号量集合
GETALL从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中
GETNCNT返回当前等待资源的进程个数
GETPID返回最后一个执行系统调用semop()进程的PID
GETVAL返回信号量集合内单个信号量的值
GETZCNT返回当前等待100%资源利用的进程个数
SETALL与GETALL正好相反
SETVAL用联合体中val成员的值设置信号量集合中单个信号量的值

 

 一次之操作一个信号量:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

void v (int semid)
{
	struct sembuf sops;
        sops.sem_num = 0;       // 操作信号量集中的第一个信号量
        sops.sem_op = 1;       // 执行V操作,增加信号量的值 by 1
        sops.sem_flg = SEM_UNDO;
  
        semop(semid, &sops, 1);  // 执行V操作,将信号量的值增加 by 1
}

void p (int semid)
{
	struct sembuf sops;
        sops.sem_num = 0;       // 操作信号量集中的第一个信号量
        sops.sem_op = -1;       // 执行P操作,减少信号量的值 by 1
        sops.sem_flg = SEM_UNDO;
  
        semop(semid, &sops, 1);  // 执行P操作,将信号量的值增加 by 1
}
int main() {
    int semid;
    union semun arg;
    key_t key=ftok(".",3);
    // 创建一个信号量.集合,其中包含一个信号量
    semid = semget(key, 1, 0666 | IPC_CREAT);
    if (semid == -1) {
        perror("semget");
        exit(1);
    }

    // 初始化信号量的值为0,表示子进程需要等待
    arg.val = 0;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("semctl");
        exit(1);
    }

    pid_t pid = fork();
    if (pid > 0) {
        // 父进程
	p(semid);
        printf("Father program pid = %d\n", getpid());
	v(semid);
        // 释放信号量资源
        if (semctl(semid, 0, IPC_RMID) == -1) {
            perror("semctl");
            exit(1);
        }
    } else if (pid == 0) {
        // 子进程
        printf("Child program pid = %d\n", getpid());
        sleep(1); // 模拟一些工作
	v(semid);
    } else {
        perror("fork");
        exit(1);
    }

    return 0;
}

结果:

 一次性操作两个信号量

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>

#define SEM_KEY 1224

void acquire_resources(int semid) {
    struct sembuf operations[2];

    // 第一个信号量操作
    operations[0].sem_num = 0;
    operations[0].sem_op = -1;
    operations[0].sem_flg = SEM_UNDO;

    // 第二个信号量操作
    operations[1].sem_num = 1;
    operations[1].sem_op = -1;
    operations[1].sem_flg = SEM_UNDO;

    if (semop(semid, operations, 2) == -1) {
        perror("semop");
        exit(1);
    }

    printf("Resources acquired.\n");
}

void release_resources(int semid) {
    struct sembuf operations[2];

    // 第一个信号量操作
    operations[0].sem_num = 0;
    operations[0].sem_op = 1;
    operations[0].sem_flg = SEM_UNDO;

    // 第二个信号量操作
    operations[1].sem_num = 1;
    operations[1].sem_op = 1;
    operations[1].sem_flg = SEM_UNDO;

    if (semop(semid, operations, 2) == -1) {
        perror("semop");
        exit(1);
    }

    printf("Resources released.\n");
}

int main() {
    int semid;
    pid_t pid;

    // 创建信号量集合
    semid = semget(SEM_KEY, 2, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget");
        exit(1);
    }

    // 初始化信号量集合
    unsigned short values[2] = {1, 1};
    if (semctl(semid, 0, SETALL, values) == -1) {
        perror("semctl");
        exit(1);
    }

    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {
        // 子进程
        sleep(2);
        acquire_resources(semid);
    } else {
        // 父进程
        printf("Parent process.\n");
        acquire_resources(semid);
        sleep(5);
        release_resources(semid);
    }

    // 删除信号量集合
    if (semctl(semid, 0, IPC_RMID, NULL) == -1) {
    perror("semctl");
    exit(1);
}

    return 0;
}

 结果:(有个小问题就是最后一行 ,由于父子进程都执行了删除信号量集合,所以重复执行。)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>

union semun {
    int val; /* value for SETVAL */
    struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
    unsigned short *array; /* array for GETALL, SETALL */
    struct seminfo *__buf; /* buffer for IPC_INFO */
};

/***对信号量数组semnum编号的信号量做P操作***/
int P(int semid, int semnum)
{
    struct sembuf sops = {semnum, -1, SEM_UNDO};
    return (semop(semid, &sops, 1));
}

/***对信号量数组semnum编号的信号量做V操作***/
int V(int semid, int semnum)
{
    struct sembuf sops = {semnum, +1, SEM_UNDO};
    return (semop(semid, &sops, 1));
}

int main(int argc, char **argv)
{
    int key;
    int semid, ret;
    union semun arg;
    int flag;

    key = ftok(".", 0x66);
    if (key < 0)
    {
        perror("ftok key error");
        return -1;
    }

    /***本程序创建了三个信号量,实际使用时只用了一个0号信号量***/
    semid = semget(key, 3, IPC_CREAT | 0600);
    if (semid == -1)
    {
        perror("create semget error");
        return -1;
    }

    if (argc == 1)
    {
        arg.val = 1;
        /***对0号信号量设置初始值***/
        ret = semctl(semid, 0, SETVAL, arg);
        if (ret < 0)
        {
            perror("ctl sem error");
            semctl(semid, 0, IPC_RMID, arg);
            return -1;
        }
    }

    /***取0号信号量的值***/
    ret = semctl(semid, 0, GETVAL, arg);
    printf("after semctl setval sem[0].val = [%d]\n", ret);
    system("date");

    printf("P operate begin\n");
    flag = P(semid, 0);
    if (flag)
    {
        perror("P operate error");
        return -1;
    }
    printf("P operate end\n");

    ret = semctl(semid, 0, GETVAL, arg);
    printf("after P sem[0].val = [%d]\n", ret);
    system("date");

    if (argc == 1)
    {
        sleep(120);
    }

    printf("V operate begin\n");
    if (V(semid, 0) < 0)
    {
        perror("V operate error");
        return -1;
    }
    printf("V operate end\n");

    ret = semctl(semid, 0, GETVAL, arg);
    printf("after V sem[0].val = %d\n", ret);
    system("date");

    if (argc > 1)
    {
        semctl(semid, 0, IPC_RMID, arg);
    }

    return 0;
}

 结果:

 生产者与消费者:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>

#define BUFFER_SIZE 5
#define SEM_EMPTY 0      // 空闲缓冲区数量的信号量
#define SEM_FULL 1       // 可用资源数量的信号量

void produce(int semid) {
    struct sembuf operation;

    // 等待空闲缓冲区
    operation.sem_num = SEM_EMPTY;    // 执行操作的信号量在信号量集合中的索引
    operation.sem_op = -1;            // 对信号量进行的操作,-1 表示减少信号量值
    operation.sem_flg = SEM_UNDO;     // 操作完成后是否撤销操作
    semop(semid, &operation, 1);      // 执行信号量操作

    // 生产资源
    printf("Producing resource...\n");

    // 增加可用资源数量
    operation.sem_num = SEM_FULL;
    operation.sem_op = 1;
    operation.sem_flg = SEM_UNDO;
    semop(semid, &operation, 1);
}

void consume(int semid) {
    struct sembuf operation;

    // 等待可用资源
    operation.sem_num = SEM_FULL;
    operation.sem_op = -1;
    operation.sem_flg = SEM_UNDO;
    semop(semid, &operation, 1);

    // 消费资源
    printf("Consuming resource...\n");

    // 增加空闲缓冲区数量
    operation.sem_num = SEM_EMPTY;
    operation.sem_op = 1;
    operation.sem_flg = SEM_UNDO;
    semop(semid, &operation, 1);
}

int main() {
    int semid;
    pid_t pid;

    // 创建信号量集合
    semid = semget(IPC_PRIVATE, 2, IPC_CREAT | 0666);   // 创建一个包含2个信号量的信号量集合
    if (semid == -1) {
        perror("semget");
        exit(1);
    }

    // 初始化信号量集合
    unsigned short values[2] = {BUFFER_SIZE, 0};    // 初始信号量的值,表示初始时有BUFFER_SIZE个空闲缓冲区,0个可用资源
    if (semctl(semid, 0, SETALL, values) == -1) {    // 设置信号量集合中所有信号量的值
        perror("semctl");
        exit(1);
    }

    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {
        // 子进程作为生产者
        for (int i = 0; i < BUFFER_SIZE; i++) {
            produce(semid);
            sleep(1);
        }
    } else {
        // 父进程作为消费者
        for (int i = 0; i < BUFFER_SIZE; i++) {
            consume(semid);
            sleep(1);
        }
    }

    // 删除信号量集合
    if (semctl(semid, 0, IPC_RMID, NULL) == -1) {   // 删除信号量集合,释放资源
        perror("semctl");
        exit(1);
    }

    return 0;
}

结果:

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MichstaBe#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值