[Linux]System V 信号量

介绍

为了防止出现因多个进程同时访问一个共享资源引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。信号量就可以提供这样的一种访问机制,确保多个线程在对共享内存这样的共享资源同时读写时,使之实现同步与互斥,也就是说信号量用来调协进程对公共资源的访问的
信号量本质上是一个计数器,也是临界资源,进程对其访问都是原子操作,信号量记录着临界资源的个数。

信号量可以使用ipcs -s查看
使用ipcrm -s [semid]删除

原理

信号量只能进行等待和发送信号两种操作,即P(sv)和V(sv)
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程并将其PCB放入队列中
V(sv):如果等待队列中有其他进程因等待sv而被挂起,就让一个恢复运行,如果没有进程因等待sv而挂起,就给它加1
所以一个信号量不仅包含临界资源的数目,还必须维护一个队列。

实际应用中信号量是不能单独定义的,而是定义一个信号量集,其中包含一组信号量,同一信号集中的信号量使用同一个引用ID,每个信号量集有一个与之对应的结构体,其中记录着各种信息。

struct semid_ds {
	struct ipc_perm sem_perm;
	struct sem *sem_base; // 指向sem结构数组,数组中每一个元素对应一个信号量
	unsigned short sem_nsems; // 信号量的个数
	...
	// 时间相关描述
};

其中struct sem才是信号量的描述结构体:

struct sem {
	unsigned short semval; // 信号量的值
	pid_t semid; // 上一次进行操作的进程号
	unsigned short semncnt; // 等待可利用资源出现的进程数
	unsigned short semzcnt; // 等待全部资源可被独占的进程数
}

系统中的限制:
SEMVMX最大的信号值
SEMMNI系统允许的最大信号量集个数
SEMMNS系统允许的最大信号量个数
SEMMSL每个信号集中最大的信号量个数

创建/打开信号集

int semget(key_t key, int nsems, int semflg);
参数key和semflg用法和共享内存相同
nsems信号量集中创建的信号量数量,如果创建信号集sem_nsems将被设置为nsems,如果打开一个已存在的信号集,此参数就被忽略
如果调用成功,则返回信号量集合标识符,否则返回-1,设置error参数

操作信号量集(原子操作)

int semop(int semid, struct sembuf *sops, unsigned nsops);
semid为semget返回的信号量集标识符
sops为sembuf结构数组指针,每一个元素表示一个操作,这个函数是一个原子操作,一旦执行就将执行数组中所有的操作。
nsops为数组中元素的个数。

struct sembuf {
	unsigned short sem_num; // 表示信号量集中某一信号量序号(0~ipc_perm.sem_nsems)
	short sem_op; // 所执行的操作,如-1表示P操作消耗一个临界资源, 1表示V操作释放了一个临界资源
	short sem_flg; // 两个取值,IPC_NOWAIT,或 SEM_UNDO(将已申请的信号量还原为初识状态,即刚开始获得这个信号量时的状态)
}
控制信号量集

int semctl(int semid, int semnum, int cmd, ...);
semid:semget返回的信号集标识符
semnum:信号集中信号量的序号
cmd:将要采取的动作
根据cmd参数具体内容,可能有第四个参数,是一个联合体
当cmd为GET时返回对应值,没设置时返回0,失败返回-1,设置errno

union semun {
	int val; // 用于SETVAL命令
	struct semid_ds *buf; // 用于IPC_STAT/IPC_SET命令,输入/输出型参数表示存放信号量集数据结构缓冲区
	unsigned short array; // 用于GETALL/SETALL命令,存放获得/设置信号量集合中所有信号量的值
}
操作演示

子进程循环输出AA 到显示器上这2个A中间不可被打断
父进程循环输出BB 到显示器上这2个B中间不可被打断

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main() {
    pid_t id = fork();
    if (id < 0) {
        perror("fork err");
        exit(EXIT_FAILURE);
    } else if (id == 0) {
        while (1) {
            printf("A");
            fflush(stdout);
            usleep(100000);
            printf("A ");
            fflush(stdout);
            usleep(100000);
        }
    } else {
        while (1) {
            printf("B");
            fflush(stdout);
            usleep(100000);
            printf("B ");
            fflush(stdout);
            usleep(100000);
        }
        wait(NULL);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值