《Linux C编程实战》笔记:信号量

news2025/2/23 17:36:37

信号量在操作系统的书里一般都有介绍,这里就只写书上说的了。

信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源访问的同步。临界资源可以简单地理解为在某一时刻只能由一个进程或线程进行操作的资源,这里的资源可以是一段代码、一个变量或某种硬件资源。信号量的值大于或等于0时表示可供并发进程使用的资源实体数;小于0时代表正在等待使用临界资源的进程数。

与消息队列类似,Linux内核也为每个信号量集维护了一个semid_ds数据结构示例。该结构定义在头文件linux/sem.h中。

struct semid_ds {
	struct ipc_perm	sem_perm;		/* 对信号进行操作的许可权,和上一节的消息队列一样的 */
	__kernel_old_time_t sem_otime;		/* 对信号量进行操作的最后时间 */
	__kernel_old_time_t sem_ctime;		/* 对信号量进行修改的最后时间 */
	struct sem	*sem_base;		/* 指向第一个信号量 */
	struct sem_queue *sem_pending;		/* 等待处理的挂起操作 */
	struct sem_queue **sem_pending_last;	/* 最后一个正在挂起的操作 */
	struct sem_undo	*undo;			/* 撤销的请求 */
	unsigned short	sem_nsems;		/* 数组中的信号量个数 */
};

信号量的创建与使用

信号量集的创建与打开

信号量集和消息队列一样,创建了之后在进程间使用。Linux下使用semget创建或打开信号集,这个函数定义在头文件sys/sem.h中

int semget(key_t key, int nsems, int semflg);
  • key:一个键值,用于唯一标识信号量集。通常可以使用 ftok() 函数来生成该键值。
  • nsems:指定要创建或访问的信号量集中的信号量数量。如果只是打开信号量集,取0即可
  • semflg:这个参数和创建消息队列里的msgflg使用方式一样,通过IPC_CREATE,IPC_EXCL等标志来标志操作方式,具体看上一篇文章

semget() 函数成功时返回一个非负整数,表示信号量集的标识符(或称为信号量集描述符),失败返回-1

示例代码1

下面函数演示创建一个信号量集并对其中所有信号量进行初始化

int createsem(const char *pathname,int proj_id,int members,int init_val){
    key_t msgkey;
    int index,sid;
    union semun semopts;//这个结构体后面会讲

    if((msgkey=ftok(pathname,proj_id))==-1){
        perror("ftok error!\n");
        return -1;;
    }
    if((sid=semget(msgkey,members,IPC_CREAT|0666))==-1){
        perror("semget call failed.\n");
        return -1;
    }

    //后面是初始化操作
    semopts.val=init_val;
    for(index=0;index<members;index++){
        semctl(sid,index,SETVAL,semopts);
    }
    return sid;
}

信号量的操作

信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量。当它的值小于0时,其绝对值表示等待使用该资源的进程个数。信号量的值仅能由PV操作来改变。在Linux下,PV操作通过调用semop实现。该函数定义在头文件sys/sem.h中

int semop(int semid, struct sembuf *sops, size_t nsops);
  • semid:信号量集的标识符,通常是由 semget() 函数返回的值。
  • sops:一个指向结构数组的指针,每个结构体描述了对单个信号量的操作。sembuf 结构体包含以下字段:
    • sem_num:要操作的信号量在信号量集中的索引。
    • sem_op:操作类型,可以是正数表示增加信号量的值,负数表示减少信号量的值,0 表示等待信号量值变为 0。
    • sem_flg:标志位,用于指定操作的行为,比如操作的方式(阻塞或非阻塞)等。
  • nsops:操作的数量,即 sops 数组中元素的个数。
  • 成功返回0,失败返回-1

当然还是要看一下sembuf这个结构体具体的内容

struct sembuf
{
  unsigned short int sem_num;	/* 信号在信号量集中的索引 */
  short int sem_op;		/* 操作类型 */
  short int sem_flg;		/* 操作标志 */
};

sem_flg可以设置为IPC_NOWAIT,则调用进程直接返回。如果没设置,semop会阻塞进程直到资源可用。

示例代码2

下面是对一个信号量集中的某个信号进行操作的P、V函数

int sem_p(int semid,int index){//p是消耗资源
    struct sembuf buf={0,-1,IPC_NOWAIT};//所以第二个参数给的是负数,这里是-1
    if(index<0){
        perror("index of array cannot equal a minus value!\n");
        return -1;
    }
    buf.sem_num=index;
    if(semop(semid,&buf,1)==-1){//nsops是1,因为数组大小只有1
        perror("a wrong operation to semaphore ocurred!\n");
        return -1;
    }
    return 0;
}
int sem_v(int semid,int index){
    struct sembuf buf={0,1,IPC_NOWAIT};//v是释放资源,所以sem_op是正数1
    if(index<0){
        perror("index of array cannot equal a minus value!\n");
        return -1;
    }
    buf.sem_num=index;
    if(semop(semid,&buf,1)==-1){
        perror("a wrong operation to semaphore ocurred!\n");
        return -1;
    }
    return 0;
}

信号量集的控制

使用信号量时,往往需要对信号量集进行一些控制操作。比如删除信号量集、对内核维护的信号量集的数据结构semid_ds进行设置,获取信号量集中信号值等。通过semtcl控制函数可以完成这些操作,该函数定义在sys/sem.h,如下图所示:

int semctl(int semid, int semnum, int cmd, ...);
  • semid:信号量集的标识符。
  • semnum:信号量在集合中的索引。
  • cmd:要执行的控制操作。
  • ...:根据 cmd 参数指定的控制操作,可能需要附加参数。

最后的...说明函数的参数是可选的,它依赖于第三个参数cmd,它通过共用体变量semun选择要操作的参数,semun定义在linux/sem.h

union semun {
	int val;			/* 设置某个信号的值等于val for SETVAL */
	struct semid_ds *buf;	/* 存取semid_ds for IPC_STAT & IPC_SET */
	unsigned short *array;	/*  for GETALL & SETALL */
	struct seminfo *__buf;	/* 为控制IPC_INFO 提供的缓存 */
	void *__pad;
};

第二个参数cmd,通过宏来只是操作类型

  1. IPC_STAT:获取信号量集的当前状态,包括信号量集中每个信号量的当前值等信息。

  2. IPC_SET:设置信号量集的状态,可以用于设置信号量集中每个信号量的值。

  3. IPC_RMID:从系统中删除信号量集,释放其占用的资源。

  4. GETALL:获取信号量集中所有信号量的当前值。

  5. GETNCNT:获取在等待信号量值增加的进程数量。

  6. GETPID:获取上次执行 semop 函数的进程的进程ID。

  7. GETVAL:获取特定信号量的当前值。

  8. GETZCNT:获取在等待信号量值变为 0 的进程数量。

  9. SETALL:设置信号量集中所有信号量的值。

  10. SETVAL:设置特定信号量的值。

示例代码3

下面是获取和设置单个新信号的函数

int semval_op(int semid,int index,int cmd){
    if(index<0){
        printf("index cannot be minus!\n");
        return -1;
    }
    if(cmd==GETVAL||cmd==SETVAL)
    return semctl(semid,index,cmd,0);//0只有在SETVAL才有用,表示我们要把该信号量的值设置为0

    printf("function cannot support cmd:%d\n",cmd);
    return -1;
}

之前介绍的时候没有说setctl的返回值,因为也是根据cmd的不同而不同的。比如这个示例代码。如果cmd是GETVAL,那么函数的返回值就是对应信号量的值;如果cmd是SETVAL,那么函数的返回值就是用来标识操作是否成功,成功返回0,失败返回-1.

示例程序4

信号量一般用于处理访问临界资源的同步问题。下面也是两个程序,server和client。server创建一个信号量集,并对信号量循环减1,相当于分配资源。client执行时检查信号量,如果其值大于0表示有资源可用,继续执行,如果小于等于0代表资源已经分配完毕,进程client退出。

server:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.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 (Linux-specific)
};

#define MAX_RESOURCE 5
int main(int argc,char **argv,char **environ){
    key_t key;
    int semid;
    struct sembuf sbuf={0,-1,IPC_NOWAIT};//-1代表使用资源
    union semun semopts;
    if((key=ftok(".",'s'))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((semid=semget(key,1,IPC_CREAT|0666))==-1){//1表面信号量集只有一个信号量
        perror("semget error!\n");
        exit(1);
    }
    semopts.val=MAX_RESOURCE;
    if(semctl(semid,0,SETVAL,semopts)==-1){//把信号量集的那个信号量的值设置成MAX_RESOURCE
        perror("semctl error!\n");
        exit(1);
    }
    while (1)
    {
        if(semop(semid,&sbuf,1)==-1){//程序循环减少信号量的值,也就是隔3秒使用一个资源
            perror("semop error!\n");
            exit(1);
        }
        sleep(3);
    }
    exit(0);
}

编译这个文件的问题很多,按照书上所说的应该加上linux/sem.h这个头文件,因为union semun就是在这个头文件定义的,但是它和 <sys/sem.h>一起包含的话会出现很多的重复定义,最后只能不包含linux/sem.h,自己去定义union semun了。

而如果只用linux/sem.h的话像semctl这些函数又没有定义,程序也用不了。

所以感觉要么是书上有错误,要么是书太老了,很多东西都改了导致照书上敲有问题

client:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/sem.h>
//把sys/ipc.h删了是因为我发现sem.h里就已经包含了它
#include <unistd.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 (Linux-specific)
};
int main(int argc,char **argv,char **environ){
    key_t key;
    int semid,semval;
    union semun semopts;
    if((key=ftok(".",'s'))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((semid=semget(key,1,IPC_CREAT|0666))==-1){
        perror("semget error!\n");
        exit(1);
    }

    while (1)
    {
        if((semval=semctl(semid,0,GETVAL))==-1){//去获取信号量的值
            perror("semctl error!\n");
            exit(1);
        }
        if(semval>0){//大于0说明还有资源可用
            printf("Still %d resources can be used\n",semval);
        }
        else{
            printf("No more resources can be used!\n");
            break;
        }
        sleep(3);
    }
    exit(0);
}

运行结果:

稍微解释一下 ,先执行server,再执行client,资源不是从5开始应该是因为client不是第一时间执行,漏了两个。而server在信号量为0后就自动退出也是因为struct sembuf sbuf={0,-1,IPC_NOWAIT};设置了IPC_NOWAIT,这样在semop(semid,&sbuf,1)==-1这个判断中不会阻塞而是直接返回-1从而退出程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1465904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ArcGIS】利用高程进行坡度分析

在ArcGIS中利用高程进行坡度分析 坡度ArcGIS实操参考 坡度 坡度是地表单元陡缓的程度&#xff0c;通常把坡面的垂直高度和水平距离的比值称为坡度。 坡度的表示方法有百分比法、度数法、密位法和分数法四种&#xff0c;其中以百分比法和度数法较为常用。 &#xff08;1&#…

opencv-python保存视频为mp4格式并支持在浏览器播放

前言 之前在项目上使用yolov8进行视频检测的时候&#xff0c;yolov8默认windows系统下保存的是avi格式 suffix, fourcc (.mp4, avc1) if MACOS else (.avi, WMV2) if WINDOWS else (.avi, MJPG) self.vid_writer[idx] cv2.VideoWriter(str(Path(save_path).with_suffix(suf…

SpringBoot对于SpringMVC的支持

创建项目 版本说明这里使用的 SpringBoot 2.0.0.Release SpringBoot对于SpringMVC的支持 在之前的开发中很多场景下使用的是基于xml配置文件或者是Java配置类的方式来进行SpringMVC的配置。一般来讲&#xff0c;初始的步骤如下所示 1、初始化SpringMVC的DispatcherServlet2、…

Linux命令之ls命令

ls命令 ls命令的作用是列出目录下的内容&#xff0c;语法如下&#xff1a; ls [ -a -l -h ] [ Linux路径 ] 1、 -a -l -h 是可选的选项。 2、Linux路径是此命令可选的参数。 当不使用选项和参数&#xff0c;直接使用 ls 命令本体&#xff0c;表示&#xff1a;以平…

网工内推 | 网络安全工程师,软考认证优先,最高15K+绩效奖金

01 南京古田化工有限公司 招聘岗位&#xff1a;网络安全工程师 职责描述&#xff1a; 1. 负责公司日常网络与安全设备的实施、安装、运维、监控、巡检工作&#xff0c;如防火墙&#xff0c;交换机&#xff0c;路由器&#xff0c;VPN,WAF,IPS/IDS,抗DDOS&#xff0c;终端准入&a…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture07多维输入

lecture07多维输入 课程网址 Pytorch深度学习实践 部分课件内容&#xff1a; import torch import numpy as npxy np.loadtxt(diabetes.csv.gz, delimiter,, dtypenp.float32) x_data torch.from_numpy(xy[:,:-1]) #第一列开始最后一列不要 y_data torch.from_numpy(…

读取7400MB/s!华为发布eKitStor Xtreme M.2闪存条

今日&#xff0c;华为举行数据存储新春新品发布会&#xff0c;不仅发布全新数据湖解决方案&#xff0c;华为还针对商业市场与分销市场发布了全闪存存储新品。 据介绍&#xff0c;面向游戏加速、影视编辑、户外作业等场景&#xff0c;华为发布eKitStor Xtreme系列高性能M.2闪存条…

IOS不使用默认的mainStroryboard作为首个controller的方法

步骤1&#xff1a; 删除info.plist文件下的一条配置&#xff0c;如图 步骤2&#xff1a; 编辑AppDelegate.m&#xff0c;参考以下代码 interface AppDelegate () //property (strong, nonatomic) UIWindow * window; property(nonatomic,strong) UIWindow * win; property(…

社区店发展趋势预测:未来哪些行业将崛起?

在当今的商业环境中&#xff0c;社区店作为一种贴近消费者生活的商业模式&#xff0c;具有很大的发展潜力。 作为一名社区开鲜奶吧5年的创业者&#xff0c;我将根据自己的经验和观察&#xff0c;预测未来哪些行业将在社区店中崛起。 一、健康与养生行业 随着人们健康意识的提…

函数栈帧的创建及销毁(超详解)

目录 1.预备知识 1.1内存区的划分 1.2认识相关寄存器和汇编指令 1.2.1寄存器 1.2.2相关汇编指令 2.测试前 2.1测试代码及环境 2.2 main函数也是被其他函数调用的 3.函数栈帧的创建 4.进入函数内部 5.形参与实参 6.call/jump add函数 7.函数栈帧的销毁 7.1保存…

【C++】C++入门篇,初识C++----第一个C++结构,C++关键字,命名空间,C++的输入输出,缺省参数【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C】C入门篇&#xff0c;初识C----第一个C结构&#xff0c;命名空间&#xff0c;C的输入输出&#xff0c;缺省参数【图文详解】&#xff0c;深刻理解命名空间&#xff0c;带大家入门C&#xff0c;感谢观看&#xff0c;支持的可以给个一…

灌水:powershell 练习正则表达式

亲爱的读者们&#xff0c;请展示你们的能力&#xff1a;解析&#xff08;使用代码&#xff09;解析以下字符串 <鱼龙混杂的奇葩文件#> UI1|System.Windows.Forms.linklabel #创建用户对象 1.location.250.250 1.text.磁盘清理 1.autosize #自适应大小 #存在混淆风险…

队列的基本操作——常见队列的对比分析(c语言完整代码包含注释)

目录 一、队列 1.1基本概念 1.2基本操作 1.3 队列分类 1.3.1带头队列 1.3.2不带头队列 1.3.3 循环带头队列 1.3.4 循环不带头队列 1.3.5 总结 二、代码实现 2.1带头队列 2.2不带头队列 2.3循环带头队列 2.4循环不带头队列 一、队列 1.1基本概念 队列&#xff08…

85、字符串操作的优化

上一节介绍了在模型的推理优化过程中,动态内存申请会带来额外的性能损失。 Python 语言在性能上之所以没有c++高效,有一部分原因就在于Python语言将内存的动态管理过程给封装起来了,我们作为 Python 语言的使用者是看不到这个过程的。 这一点有点类似于 c++ 标准库中的一些…

VegaPrime 2013 VP2013

Vega Prime 2013 VegaPrime 2013 VP2013

并发编程(2)管程(悲观锁)

4 共享模型之管程 本章内容 共享问题synchronized线程安全分析Monitorwait/notify线程状态转换活跃性Lock 4.1 共享带来的问题 4.1.1 小故事 老王&#xff08;操作系统&#xff09;有一个功能强大的算盘&#xff08;CPU&#xff09;&#xff0c;现在想把它租出去&#xff…

Linux(ACT)权限管理

文章目录 一、 ATC简介二、 案例1. 添加测试目录、用户、组&#xff0c;并将用户添加到组2. 修改目录的所有者和所属组3. 设定权限4. 为临时用户分配权限5. 验证acl权限 6. 控制组的acl权限 一、 ATC简介 ACL&#xff08;Access Control List&#xff0c;访问控制列表&#xf…

运维SRE-15 自动化批量管理-ansible1

## 1.什么是自动化批量管理重复性工作与内容: 思考如何自动化完成. 部署环境,批量查看信息,批量检查:自动化 一般步骤:1.如何手动实现2.如何自动化管理工具&#xff0c;批量实现3.注意事项&#xff1a;想要自动化一定要先标准化(所有环境&#xff0c;软件&#xff0c;目录一致)…

Vant轮播多个div结合二维数组的运用

需求说明 在开发H5的时候&#xff0c;结合Vant组件的轮播组件Swipe实现如下功能。我们查阅vant组件库官方文档可以得知&#xff0c;每个SwipeItem组件代表一个卡片&#xff0c;实现的是每屏展示单张图片或者单个div轮播方式&#xff0c;具体可以查阅&#xff1a;Vant 2 - 轻量、…

springboot750人职匹配推荐系统

springboot750人职匹配推荐系统 获取源码——》公主号&#xff1a;计算机专业毕设大全