【Linux】Linux的信号量集

所谓信号量集,就是由多个信号量组成的一个数组。作为一个整体,信号量集中的所有信号量使用同一个等待队列。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队列被组织在进程控制块和信号量集两个队列中,如下图所示:

 

<p> 这是一门linux下c++通讯架构实战课程,针对c/c++语言已经掌握很熟并希望进一步深造以将来用c++在linux下从事网络通讯领域/网络服务器开发和架构工作。<br /> <br /> 这门课程学习难度颇高但也有着极其优渥薪水(最少30K月薪,最高可达60-80K月薪),这门课程,会先从nginx源码分析和讲解开始,逐步开始书写属于自己高性能服务器框架代码,完善个人代码库,这些,将会是您日后能取得高薪重要筹码。 </p> <p> <br /> </p> <p> <span style="color:#E53333;">本课程原计划带着大家逐行写代码,但因为代码实在过于复杂和精细,带着写代码可能会造成每节课至少要4~5小时超长时间,所以老师会在课前先写好代码,主要时间花费在逐行讲解这些代码上,这一点望同学们周知。如果你觉得非要老师领着写代码才行话,老师会觉得你当前可能学习本门课程会比较吃力,请不要购买本课程,以免听不懂课程并给老师差评,差评也会非常影响老师课程销售并造成其他同学误解。</span> </p> <p> <br /> </p> <p> 这门课程要求您具备下面技能:<br /> (1)对c/c++语言掌握非常熟练,语言本身已经不是继续学习障碍,并不要求您一定熟悉网络或者linux;<br /> (2)对网络通讯架构领域有兴趣、勇于挑战这个高难度开发领域并期望用大量付出换取高薪;<br /> <br /> 在这门课程中,实现了一个完整项目,其中包括通讯框架和业务逻辑框架,浓缩总结起来包括如下几点:<br /> (1)项目本身是一个极完整多线程高并发服务器程序;<br /> (2)按照包头包体格式正确接收客户端发送过来数据包, 完美解决收包时数据粘包问题;<br /> (3)根据收到不同来执行不同业务处理逻辑;<br /> (4)把业务处理产生结果数据包正确返回给客户端;<br /> <br /> 本项目用到主要开发技术和特色包括:<br /> (1)epoll高并发通讯技术,用到触发模式是epoll中水平触发模式LT;<br /> (2)自己写了一套线程池来处理业务逻辑,调用适当业务逻辑处理函数处理业务并返回给客户端处理结果;<br /> (3)线程之间同步技术包括互斥量,信号量等等;<br /> (4)连接池中连接延迟回收技术,这是整个项目中精华技术,极大程度上消除诸多导致服务器程序工作不稳定因素;<br /> (5)专门处理数据发送一整套数据发送逻辑以及对应发送线程;<br /> (6)其他次要技术,包括信号、日志打印、fork()子进程、守护进程等等; </p> <div> <br /> </div>
相关推荐
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页