linux 进程同步中的信号量

本贴最后更新于 2213 天前,其中的信息可能已经天翻地覆

ps:这篇博文主要是来记录在学校中学习的信号量机制的,哈哈哈:joy:

信号量机概念是由荷兰科学家 Dijkstr 引入,值得一提的是,它提出的 Dijksrtr 算法解决了最短路径问题。

      信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况,信号量是一个特殊的变量,并且只有两个操作可以改变其值:等待(wait)与信号(signal)。

 

因为在 Linux 与 UNIX 编程中,"wait"与"signal"已经具有特殊的意义了(暂不知这特殊意义是啥),所以原始概念:

    用于等待(wait)的 P(信号量变量) ;
    用于信号(signal)的 V(信号量变量) ;

这两字母来自等待(passeren:通过,如同临界区前的检测点)与信号(vrjgeven:指定或释放,如同释放临界区的控制权)的荷兰语。

 

P 操作 负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。

操作为:申请一个空闲资源(把信号量减 1),若成功,则退出;若失败,则该进程被阻塞;

 

V 操作 负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。

操作为:释放一个被占用的资源(把信号量加 1),如果发现有被阻塞的进程,则选择一个唤醒之。 

补充:查看共享信息的内存的命令是 ipcs [-m|-s|-q] (全部的话是 ipcs -a) ;查看共享信息的内存的命令是 ipcs [-m|-s|-q]。

示例代码:

//testsem.c  主程序,使用PV操作实现三个进程的互斥
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>

#define SEMPERM 0600
#define TRUE 1
#define FALSE 0
typedef union _semun {
  int val;
  struct semid_ds *buf;
  ushort *array;
} semun;


void handlesem(key_t skey);
int initsem(key_t semkey);
int p(int semid);
int v(int semid);

main()
{
  key_t semkey=0x400;
  int i;
  for (i=0;i<3;i++)
  {
    if (fork()==0)           //父进程负责产生3个子进程
      handlesem(semkey);  //子进程中才执行handlesem,做完后就exit。
  }
}

void handlesem(key_t skey)
{
  int semid;
  pid_t pid=getpid();
  
  if ((semid=initsem(skey))<0)
    exit(1);
  printf("进程 %d 在临界资源区之前 \n",pid);
  p(semid);                                      //进程进入临界资源区,信号量减少1
  printf("进程 %d 在使用临界资源时,停止10s \n",pid);

  /*in real life do something interesting */
  sleep(10);
  printf("进程 %d 退出临界区后 \n",pid);

  v(semid);                                //进程退出临界资源区,信号量加1

  printf("进程 %d 完全退出\n",pid);
  exit(0);
}

int initsem(key_t semkey)
{
   int status=0,semid;                    //信号量标识符semid
  if ((semid=semget(semkey,1,SEMPERM|IPC_CREAT|IPC_EXCL))==-1)
  {
    if (errno==EEXIST)               //EEXIST:信号量集已经存在,无法创建
      semid=semget(semkey,1,0);      //创建一个信号量
  }
  else
  {
    semun arg;
    arg.val=1;                                        //信号量的初值
    status=semctl(semid,0,SETVAL,arg);      //设置信号量集中的一个单独的信号量的值。
  }
  if (semid==-1||status==-1)
  {
    perror("initsem failed");
    return(-1);
  }
  /*all ok*/
  return(semid);
}


int p(int semid)
{
  struct sembuf p_buf;

  p_buf.sem_num=0;
  p_buf.sem_op=-1;        //信号量减1,注意这一行的1前面有个负号
  p_buf.sem_flg=SEM_UNDO;
  
  //p_buf = {0,-1,SEM_UNDO};
  if (semop(semid, &p_buf, 1)==-1)   
  {
    perror("p(semid)failed");
    exit(1);
  }
  return(0);
}


int v(int semid)
{
  struct sembuf v_buf;

  v_buf.sem_num=0;
  v_buf.sem_op=1;    //信号量加1
  v_buf.sem_flg=SEM_UNDO;
  
  if (semop(semid, &v_buf, 1)==-1)
  {
    perror("v(semid)failed");
    exit(1);
  }
  return(0);
}

运行结果:
1png

2png

3png

相关说明
(一)系统调用函数 semget()
函数原型:int semget(key_t key,int nsems,int semflg);

功能描述: 创建一个新的信号量集,或者存取一个已经存在的信号量集。

当调用 semget 创建一个信号量时,他的相应的 semid_ds 结构被初始化。ipc_perm 中各个量被设置为相应
值:
        sem_nsems 被设置为 nsems 所示的值;    
        sem_otime 被设置为 0; 
        sem_ctime 被设置为当前时间

参数介绍:
         key:所创建或打开信号量集的键值,键值是 IPC_PRIVATE,该值通常为 0,创建一个仅能被进程进程给我的信号量, 键值不是 IPC_PRIVATE,我们可以指定键值,例如 1234;也可以一个 ftok()函数来取得一个唯一的键值。
         nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。
         semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过 or 表示:

                有 IPC_CREAT,IPC_EXCL 两种:

IPC_CREAT 如果信号量不存在,则创建一个信号量,否则获取。

IPC_EXCL 只有信号量不存在的时候,新的信号量才建立,否则就产生错误。

返回值说明:
如果成功,则返回信号量集的 IPC 标识符,其作用与信息队列识符一样。
如果失败,则返回-1,errno 被设定成以下的某个值
EACCES:没有访问该信号量集的权限
EEXIST:信号量集已经存在,无法创建
EINVAL:参数 nsems 的值小于 0 或者大于该信号量集的限制;或者是该 key 关联的信号量集已存在,并且 nsems
大于该信号量集的信号量数
ENOENT:信号量集不存在,同时没有使用 IPC_CREAT
ENOMEM :没有足够的内存创建新的信号量集
ENOSPC:超出系统限制

每个信号量都有一些相关值:

      semval 信号量的值,一般是一个正整数,它只能通过信号量系统调用 semctl 函数设置,程序无法直接对它进行修改。

      sempid 最后一个对信号量进行操作的进程的 pid.

      semcnt 等待信号量的值大于其当前值的进程数。

      semzcnt 等待信号量的值归零的进程数。

 
(二)信号量的控制 semctl()
原型:int semctl(int semid,int semnum,int cmd,union semun ctl_arg); 
参数介绍: semid 为信号量集引用标志符,即 semget 的返回值。 

               semnum 第二个参数是信号量数目;

               cmd 表示调用该函数执行的操作,其取值和对应操作如下:

标准的 IPC 函数

(注意在头文件 <sys/sem.h> 中包含 semid_ds 结构的定义)

IPC_STAT 把状态信息放入 ctl_arg.stat 中

IPC_SET 用 ctl_arg.stat 中的值设置所有权/许可权

IPC_RMID 从系统中删除信号量集合

单信号量操作

(下面这些宏与 sem_num 指定的信号量合 semctl 返回值相关)

GETVAL 返回信号量的值(也就是 semval)

SETVAL 把信号量的值写入 ctl_arg.val 中

GETPID 返回 sempid 值

GETNCNT 返回 semncnt(参考上面内容)

GETZCNT 返回 semzcnt(参考上面内容)

全信号量操作

GETALL 把所有信号量的 semvals 值写入 ctl_arg.array

SETALL 用 ctl_arg.array 中的值设置所有信号量的 semvals

 

参数 arg 代表一个 union 的 semun 的实例。semun 是在 linux/sem.h 中定义的:

union semun {
int val; //执行 SETVAL 命令时使用
struct semid_ds *buf; //在 IPC_STAT/IPC_SET 命令中使用
unsigned short *array; //使用 GETALL/SETALL 命令时使用的指针
}

联合体中每个成员都有各自不同的类型,分别对应三种不同的 semctl 功能,如果 semval 是 SETVAL.则使用的将是 ctl_arg.val.

。     

功能:smctl 函数依据 command 参数会返回不同的值。它的一个重要用途是为信号量赋初值,因为进程无法直接对信号量的值进行修改。

(三)信号量操作 semop 函数
在 Linux 下,PV 操作通过调用 semop 函数来实现,也只有它能对 PV 进行操作

调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);

返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops 大于最大的 ops 数目)
EACCESS(权限不够)
EAGAIN(使用了 IPC_NOWAIT,但操作不能继续进行)
EFAULT(sops 指向的地址无效)
EIDRM(信号量集已经删除)
EINTR(当睡眠时接收到其他信号)
EINVAL(信号量集不存在,或者 semid 无效)
ENOMEM(使用了 SEM_UNDO,但无足够的内存创建所需的数据结构)
ERANGE(信号量值超出范围)

参数介绍:

第一个参数 semid 是信号量集合标识符,它可能是从前一次的 semget 调用中获得的。

第二个参数是一个 sembuf 结构的数组,每个 sembuf 结构体对应一个特定信号的操作,sembuf 结构在,<sys/sem.h> 中定义

struct sembuf{
usign short sem_num;/信号量索引/
short sem_op;/要执行的操作/
short sem_flg;/操作标志/
}

sem_num 存放集合中某一信号量的索引,如果集合中只包含一个元素,则 sem_num 的值只能为 0。


Sem_op 取得值为一个有符号整数,该整数实际给定了 semop 函数将完成的功能。包括三种情况:

      如果 sem_op 是负数,那么信号量将减去它的值,对应于 p()操作。这和信号量控制的资源有关。如果没有使用 IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。

      如果 sem_op 是正数,则信号量加上它的值。对应于 v()操作。这也就是进程释放信号量控制的资源。

      最后,如果 sem_op 是 0,那么调用进程将调用 sleep(),直到信号量的值为 0。这在一个进程等待完全空闲的资源时使用。


sem_flag 是用来告诉系统当进程退出时自动还原操作,它维护着一个整型变量 semadj(信号灯的计数器),可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新,即减去减去 sem_num 的值。 此外,如果此操作指定 SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。

 

第三个参数是 sembuf 组成的数组中索引。参数 sops 指向由 sembuf 组成的数组,结构数组中的一员。

整理自_linux 进程同步之信号量:http://www.cnblogs.com/LZYY/p/3453582.html

  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    939 引用 • 940 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    167 引用 • 1509 回帖
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 257 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    85 引用 • 165 回帖
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    124 引用 • 580 回帖
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 6 关注
  • Spark

    Spark 是 UC Berkeley AMP lab 所开源的类 Hadoop MapReduce 的通用并行框架。Spark 拥有 Hadoop MapReduce 所具有的优点;但不同于 MapReduce 的是 Job 中间输出结果可以保存在内存中,从而不再需要读写 HDFS,因此 Spark 能更好地适用于数据挖掘与机器学习等需要迭代的 MapReduce 的算法。

    74 引用 • 46 回帖 • 561 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖 • 1 关注
  • FFmpeg

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

    23 引用 • 32 回帖 • 3 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    311 引用 • 546 回帖
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 652 关注
  • V2Ray
    1 引用 • 15 回帖 • 1 关注
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 733 关注
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 615 关注
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 209 关注
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 383 回帖 • 6 关注
  • IPFS

    IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。请浏览 IPFS 入门笔记了解更多细节。

    21 引用 • 245 回帖 • 248 关注
  • 周末

    星期六到星期天晚,实行五天工作制后,指每周的最后两天。再过几年可能就是三天了。

    14 引用 • 297 回帖
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    264 引用 • 665 回帖 • 1 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 2 关注
  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    30 引用 • 114 回帖 • 2 关注
  • 音乐

    你听到信仰的声音了么?

    60 引用 • 511 回帖
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 127 关注
  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 705 关注
  • OnlyOffice
    4 引用 • 2 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1347 回帖
  • 百度

    百度(Nasdaq:BIDU)是全球最大的中文搜索引擎、最大的中文网站。2000 年 1 月由李彦宏创立于北京中关村,致力于向人们提供“简单,可依赖”的信息获取方式。“百度”二字源于中国宋朝词人辛弃疾的《青玉案·元夕》词句“众里寻他千百度”,象征着百度对中文信息检索技术的执著追求。

    63 引用 • 785 回帖 • 181 关注
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    6 引用 • 14 回帖