【Linux】Linux的信号量集

article/2025/11/7 4:20:49

所谓信号量集,就是由多个信号量组成的一个数组。作为一个整体,信号量集中的所有信号量使用同一个等待队列。Linux的信号量集为进程请求多个资源创造了条件。Linux规定,当进程的一个操作需要多个共享资源时,如果只成功获得了其中的部分资源,那么这个请求即告失败,进程必须立即释放所有已获得资源,以防止形成死锁。

 

信号量集的结构

信号量结构

描述信号量的内核数据结构如下:

struct sem {int	semval;		/* 信号量的当前值 */int	sempid;		/* 上一次操作本信号的进程PID */
};

其中,域semval为一个整型变量,表示相应共享资源的被占用情况;域sempid则记录了上一次使用这个信号量的进程的标识号。

信号量集的结构

如果把若干个信号量组成一个数组sem[],那么这个数组就是信号量集。使用信号量集可以同时把多个共享资源设置为互斥资源。

Linux用一个数组头来管理这个信号量集,它包含信号量集的所有基本信息。

数组头的结构sem_array如下:

struct sem_array {struct kern_ipc_perm	sem_perm;	/* IPC许可结构 */time_t			sem_otime;	/* 上一次信号量的操作时间 */time_t			sem_ctime;	/* 信号量变化时间 */struct sem		*sem_base;	/* 指向信号量数组的指针 */struct list_head	sem_pending;	/* 等待队列 */struct list_head	list_id;	/* undo结构 */unsigned long		sem_nsems;	/* 信号量集里面信号量的数目 */
};

结构的第一个域sem_perm为检查用户权限的许可结构。数组头结构中的指针sem_base指向信号量数组,该数组中的每一个元素都是sem结构的变量,即信号量。

一个信号量集的结构如下图所示:

从上图可以看出,信号量集统一有一个进程等待队列,而不是每个信号量都有一个,这正是信号量集的特点。

进程等待队列结构sem_queue如下:

struct sem_queue {struct list_head	list;	 /* queue of pending operations */struct task_struct	*sleeper; /* 指向等待进程控制块的指针 */struct sem_undo		*undo;	 /* undo请求操作结构指针 */int    			pid;	 /* 请求操作的进程标识 */int    			status;	 /* 操作完成状态 */struct sembuf		*sops;	 /* 挂起的操作集 */int			nsops;	 /* 操作数目 */int			alter;   /* does the operation alter the array? */
};

等待队列是一个由进程控制块所组成的队列,每个进程控制块代表着一个等待进程,sem_queue的域为sleeper指向了该等待队列。

另外,为了使系统可以从等待进程控制块中得到该进程所在的等待队列,进程控制块task_struct中有一个指向等待队列的指针semsleeping。

内核管理结构

Linux系统所有的信号量集都注册在一个数组中,该数组是内核全局数据结构struct ipc_id_ary的一个域。结构struct ipc_id_ary的定义如下:

struct ipc_id_ary
{int size;struct kern_ipc_perm *p[0];            //存放段描述结构的数组
};

结构中的数组p[]就是信号量集的注册数组。

数组p[]暂时只定义了0个元素,数组的长度在系统运行时会在相应的操作里动态地增加或减少。

为了方便对上述数组进行管理,Linux又定义了一个数组头struct ipc_ids。struct ipc_ids的定义如下:

struct ipc_ids {int in_use;unsigned short seq;unsigned short seq_max;struct rw_semaphore rw_mutex;struct idr ipcs_idr;struct ipc_id_ary *entries;        //指向struct ipc_id_ary的指针
};

很清楚,域entries就是指向信号量集数组的指针。

另外需要注意的是,由于为了充分利用内存空间,进程消亡时需要及时释放其所创建的信号量集,所以数组p[]的下标是动态的。这也就意味着以信号量在数组中的位置(下标)来作为标识不唯一,因此在上述结构中有一个叫做序列号的域seq,系统每增加一个信号量集,系统就会将seq加1,然后把信号量集在数组p[]中的下标与之拼接起来形成唯一的标识,以供内核来识别。

内核对于信号量集的管理结构如下图所示:

 

信号量集的操作

信号量集的创建或打开

进程可以通过调用函数semget()创建或打开一个信号量集,这个函数是通过系统调用sys_semget()来实现的。系统调用sys_semget()的原型如下:

asmlinkage long sys_semget(key_t key, int nsems, int semflg);

其中,参数key是用户给定的键值;参数semflg是该函数的功能标志。

系统调用sys_semget()有两个功能:如果参数semflg的IPC_CREATE的值给定为1,则这个系统调用会为用户创建或打开一个信号量集,并返回信号量集标识符;如果为0,则会在系统已有的信号量集中寻找与键值相同的信号量集,找到后,打开该信号量集并返回信号量集的标识号。参数nsems用来指明在所创建的信号量集中信号量的个数,即定义sem_base指向的数组的大小。

信号量集的操作

用于信号量操作的函数是semop()。为了用户的方便,Linux提供了数据结构sembuf,用户在这个数据结构中指明对信号量的操作。sembuf结构定义如下:

struct sembuf {unsigned short  sem_num;	/* 信号量集在集中的序号 */short		sem_op;		/* 信号量操作 */short		sem_flg;	/* 操作标志 */
};

其中,域sem_num指明待操作信号量在集中的位置;域sem_op就是对信号量的增量。通常,在访问共享资源之前,域sem_op应设为-1(对信号量进行减1的P操作);访问之后,设为1(对信号量进行加1的V操作)。

前面讲过,为了防止产生死锁,信号量集的操作必须对集中的所有信号量同时操作,所以用户还需要定义一个其长度与信号量数目相等的sembuf类型数组,以便把各个信号量的sembuf结构数据存放到对应的元素中。

函数semop()由系统调用sys_semop()实现,其原型如下:

asmlinkage long sys_semop(int semid, struct sembuf __user *sops,unsigned nsops);

其中,参数semid为信号量集的标识;参数sops就是指向上述sembuf数组的指针,数组每个元素都是对应信号量集的操作结构sembuf;参数nspos为这个数组的长度。

结构undo

介绍信号量的基本原理时曾经说过,P和V操作必须成对出现。也就是说,对于Linux信号量集,在临界段前要用semop()来请求资源,而在临界段后要用semop()来释放资源,但在具体应用中可能会因进程非正常中止而导致临界段没有机会来释放资源。

如果有产生这种情况的可能,进程必须将释放资源的任务转交给内核来完成。即在调用semop()请求资源时,把传递给函数的sembuf结构的域sem_flg设置为SEM_UNDO。这样,函数semop()在执行时就会为信号量配置一个sem_undo的结构,并在该结构中记录释放信号量的调整值;然后把信号量集中所有sem_undo组成一个队列,并在等待进程队列中用指针undo指向该队列。

也就是说,通过设置SEM_UNDO,当进程非正常中止时内核会产生响应操作,以保证信号量处于正常状态。

结构sem_undo定义如下:

struct sem_undo {struct list_head	list_proc;	/* per-process list: all undos from one process. *//* rcu protected */struct rcu_head		rcu;		/* rcu struct for sem_undo() */struct sem_undo_list	*ulp;		/* sem_undo_list for the process */struct list_head	list_id;	/* per semaphore array list: all undos for one array */int			semid;		/* 信号量集标识符 */short *			semadj;		/* 存放信号量集调整值的数组指针 */
};

于是,当系统执行内核函数do_ext()结束一个进程时,如果sembuf结构中的sem_flg的值为SEM_UNDO,则该函数会扫描该进程的sem_undo队列,并根据每个sem_undo结构中的调整信息,依次调整各个信号量值,以释放各个信号量。

信号量的控制

为实现对信号量的初始化等控制,Linux提供了函数semctl()。其对应的内核函数原型如下:

asmlinkage long sys_semctl(int semid, int semnum, int cmd, union semun arg);

其中,semid为信号量集的表示;semnum为信号量的数目;cmd为操作命令;arg为信号量的初始值。

从参数定义中可知,arg的类型为union semun。该类型定义如下:

union semun {int val;			/* 信号量初始值 */struct semid_ds __user *buf;	/* buffer for IPC_STAT & IPC_SET */unsigned short __user *array;	/* array for GETALL & SETALL */struct seminfo __user *__buf;	/* buffer for IPC_INFO */void __user *__pad;
};

内核函数sys_semctl()将根据其命令参数cmd(第三个参数)及参数arg来对信号量集实时控制。

 

进程控制块中关于信号量集的域

进程使用信号量集的相关信息也被记录在进程控制块中。进程控制块与信号量及相关的域如下:

struct task_struct
{...struct sem_undo * semundo;        //指向进程使用的信号量集undo队列struct sem_queue * semsleeping;        //指向进程所在等待队列的指针...
};

其实就是两个指针:一个指向进程使用的信号量undo队列;另一个指向进程所在的等待队列。

特别地,信号量集的undo队列被组织在进程控制块和信号量集两个队列中,如下图所示:

 


http://chatgpt.dhexx.cn/article/S61FttDO.shtml

相关文章

linux信号量简介

一、什么是信号量 为了防止多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种访问机制,它可以通过生成并使用令牌来授权,在同一时刻只能有一个线程访问代码的临界区域。 临界区域是指执行数据更新的代码需要独占式地执行。而信…

Linux下信号量使用总结

目录 1.Linux下信号量简介 2.POSIX信号量 2.1 无名信号量 2.2 有名信号量 3.System V信号量 1.Linux下信号量简介 信号量是解决进程之间的同步与互斥的IPC机制,互斥与同步关系存在的症结在于临界资源。 临界资源是在同一个时刻只容许有限个(一般只有…

Linux信号量详解

Linux信号量详解 1.什么是信号量信号量是一种特殊的变量,访问具有原子性。只允许对它进行两个操作:1)等待信号量当信号量值为0时,程序等待;当信号量值大于0时,信号量减1,程序继续运行。2)发送信号量将信号量…

Linux进程间通信—信号量

一、概述 进程间通信(interprocess communication,简称 IPC)指两个进程之间的通信。系统中的每一个进程都有各自的地址空间,并且相互独立、隔离,每个进程都处于自己的地址空间中。所以同一个进程的不同模块譬如不同的函…

Linux操作系统-信号量

信号量也属于一种进程间通信的机制,与其他的进程间通信不同,信号量不是用来传输数据的,而是用来进程间同步与互斥。除此之外,信号量还可以实现线程间的互斥。 信号量是什么? 信号量的本质是一个计数器。 一个信号量…

Linux·信号量全解

目录 信号量 进程间 【无名信号量完成 有血缘关系的进程间 互斥】 知识点2【有名信号量 没有血缘进程互斥】 1、创建一个有名信号量 2、信号量的关闭: 3、信号量文件的删除 4、P操作 sem_wait V操作sem_post 销毁信号量sem_destroy 知识点3【有名信号量 没…

Linux-----信号量

信号量 信号量原理信号量概念信号量函数基于环形队列的生产消费模型空间和数据资源生产者和消费者申请、释放信号量模拟实现基于环形队列的生产者消费者模型 信号量原理 之前我们知道被多个执行流同时访问的公共资源叫做临界资源,而临界资源不保护的话会造成数据不…

Linux信号量

文章目录 POSIX信号量信号量的原理信号量的概念信号量函数 二元信号量模拟实现互斥功能基于环形队列的生产消费模型空间资源和数据资源生产者和消费者申请和释放资源必须遵守的两个规则代码实现信号量保护环形队列的原理 POSIX信号量 信号量的原理 我们将可能会被多个执行流同…

Linux —— 信号量

目录 一、POSIX信号量 1. 什么是信号量 2. 信号量的基本原理 二、与信号量相关的操作 1. 初始化信号量 2. 销毁信号量 3. 等待信号量 4. 发布信号量 三、基于环形队列的生产者消费者模型 1. 空间资源和数据资源 2. 生产者和消费者申请和释放资源 四、模拟实现基于…

Double取值intValue()与doubleValue()之参数缺省

Double调用intValue()是四舍五入向下取整。 调用doubleValue()才是取double真实值。

java.lang.NullPointerException: Attempt to invoke virtual method ‘int java.lang.Integer.intValue()‘

问题 对于PreparedStatement 对象设置参数时, 提示该错误; java.lang.NullPointerException: Attempt to invoke virtual method ‘int java.lang.Integer.intValue()’ 具体问题 2022-09-06 21:28:10.695 11368-11755/com.example.electronicmall E/AndroidRunt…

IntValue()方法 和 ValueOf()方法

intValue() 1.intValue()是java.lang.Number类的方法,Number是一个抽象类。Java中所有的数值类都继承它。也就是说,不单是Integer有intValue方法,Double,Long等都有此方法。 2.此方法的意思是:输出int数据。每个数值类…

Double取值intValue()与doubleValue()

描述一个之前没注意,手误造成的bug。 可以看出,Double调用intValue()结果类似于RoundingMode.DOWN。 调用doubleValue()才是取double真实值。

java中valueof_JAVA中intValue()和ValueOf()什么意思,还有Value什么意思

展开全部 intValue()和ValueOf()是数据类62616964757a686964616fe59b9ee7ad9431333366306538型转化的两个方法。 intValue() 如Integer类型,就会有intValue()方法,意思是说,把Integer类型转化为Int类型。 valueOf() 如String就有valueOf()方法…

IDEA告警:Unnecessary unboxing ‘xxx.intValue()‘

显式编码拆箱已包装的原始数值。在Java5及以上的版本,拆箱是不必要的,可以安全地删除。那么 JDK5 到底做了啥? 自动装箱(auto-boxing)与自动拆箱(auto-unboxing) Java语言的基本类型都有包装&…

latex自定义插入空行或者空格

空行有几种方法: 1.~\\ 2.\\[行距] 例如:\\[3pt] 最后,我的选择是: \vspace*{n\baselineskip}空格:

latex中加入空白行的一种方法

在两行文字中间加入“~\\”就可以达到空行的目的了 如图所示,上面是加入“~\\”,下图是得到的结果

latex句首缩进空格

有时候想要再句首加空格&#xff0c;但是会被自动忽略&#xff0c;于是可以使用命令 \hspace*{0.6cm} 例如&#xff1a; $initialize the initial solutions w,precision,max_iters,\\ while (w< precision) and (iters < max_iters):\\\hspace*{0.6cm} grad \gets \…