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

【Linux】Linux的信号量集相关推荐

  1. Linux进程间通信第四讲 标准IPC之信号量集

    目录 4.3信号量集 4.3.1 概念和原理 4.3.2 使用 4.3信号量集 4.3.1 概念和原理 (不是用于进程间传递数据.而是用于进程间访问控制) 信号量集是一个信号量的集合,可以存放多个信号 ...

  2. Linux环境进程间通信 信号量

    信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制.相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志.除了用于访问控制外,还可用于进程 ...

  3. linux 信号和信号量编程

    对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号.信号,为 Linux 提供了一种处理异步事件的方法.比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序. ...

  4. linux 信号_Linux信号量(1)-SYSTEM V

    ​ 信号量概念 信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送 ...

  5. 【Linux】进程间通信-信号量详解及编程实例

    前面一篇文章线程同步之信号量同步 讲的是线程之间的信号量,这篇讲的更加具有通用性,能够实现进程之间的同步. 信号量概述 信号量定义: 它是一个特殊变量,只允许对它进行等待和发送信号这两种操作. P(信 ...

  6. linux下清理信号量,Linux下进程间通信方式——信号量(Semaphore)

    1.信号量 信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据 ...

  7. 尚观linux视频配套教程,尚观最新嵌入式Linux基础视频教程 98集嵌入式Linux巨制视频教程 新手必备...

    尚观最新嵌入式Linux基础视频教程 98集嵌入式Linux巨制视频教程 新手必备 5.jpg (67.09 KB, 下载次数: 0) 2018-4-16 09:34 上传 1.jpg (44.17 ...

  8. Linux必会命令集

    l一.linux重要命令汇总 1上线查询及帮助(2个) #man  查看命令帮助 #help (help command 查看Linux内置命令的帮助) [,  alias,  bg, bind, b ...

  9. Linux 进程间通信之 - 信号量

    前言 很久不用,基本概念虽然还记得,估计不会用了,写个demo练练手. 概念 信号量(信号灯)本质上是一个计数器,用于协调多个进程(包括但不限于父子进程)对共享数据对象的读/写.它不以传送数据为目的, ...

  10. 【linux】Valgrind工具集详解(八):Memcheck命令行参数详解

    [linux]Valgrind工具集详解(五):命令行详解中不够全,在此专门针对Memcheck工具中的命令行参数做一次详细的解释. Memcheck命令行选项 –leak-check=<no| ...

最新文章

  1. Android 应用程序 降低耗电量的一些思路
  2. python3 字符串替换 replace translate re.sub
  3. pycharm goland clion 常用快捷键 使用技巧
  4. springboot java.util.NoSuchElementException: No value present 异常处理
  5. iOS MMDrawerController源码解读(一)
  6. linux安全策略与实例pdf,实验一:Linux用户管理与安全策略.pdf
  7. 常用数据库连接和diriver以及默认端口
  8. 学成在线--9.页面静态化
  9. 构造函数与toString
  10. Pandas 文本数据方法 get( )
  11. 三维绘图之Mayavi.mlab
  12. springboot 项目中控制台打印日志以及每天生成日志文件
  13. 不是吧!你还不懂DHT协议?
  14. 可以出题的答题小程序
  15. 数字孪生智慧城市建设解决方案
  16. PHP 7从零基础到项目实战,PHP 7从零基础到项目实战
  17. 10天精读掌握:计算机组成与设计COAD:Patterson and Hennessy 第7天 2018/11.1
  18. ffplay控制音量方法
  19. C语言 吃鱼还是吃肉
  20. 模糊查询忽略大小写解决方案

热门文章

  1. 12306的数据库设计
  2. 易升更新完其他用户登录到这台计算机,windows10系统中总是弹出安全登录窗口的解决方法...
  3. Dependency Walker
  4. php 生成导出excel,PHP导出生成EXCEL文件
  5. Linux安装vim命令
  6. 方舟原始恐惧mod生物代码_方舟MOD
  7. 蓝宝石rx470d原版bios_蓝宝石显卡等级划分,如何区分双胞胎矿卡,旗舰值得入手吗?...
  8. onvif 模拟摄像头_ONVIF协议测试工具(ONVIF Device Test Tool)
  9. 一个简单的微信小程序支付demo
  10. 大数据项目实施工作流程及大数据运维的日常工作流程