`
jishublog
  • 浏览: 862105 次
文章分类
社区版块
存档分类
最新评论

Linux进程同步之System V 信号量

 
阅读更多

SystemV信号量是不属于POSIX标准,它属于SUSSingleUNIXSpecification)单一规范中的扩展定义。它和POSIX信号量一样都提供基本的信号量功能操作。

SystemV信号量相对于POSIX信号量最大的区别是在信号量的操作复杂度

POSIX信号量中说过,根据信号量取值(代表可用资源的数目)的不同,POSIX信号量可以分为:

  • 二值信号量:信号量的值只有01,这和互斥量很类型,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1
  • 计数信号量:信号量的值在0到一个大于1的限制值(POSIX指出系统的最大限制值至少要为32767)。该计数表示可用的资源的个数。

相对于POSIX信号量,SystemV信号量增加了复杂度,我们称SystemV信号量之为:计数信号量集。计数信号量集:至少有一个信号量构成的集合,其中集合中的每个信号量都是计数信号量。对于信号量集中的信号量数目系统内核是存在限制的,由内核参数:SEMMSL决定。

[root@localhost ~]# grep SEMMSL -R /usr/include/*
/usr/include/linux/sem.h:#define SEMMSL  250  /* <= 8 000 max num of semaphores per id */
... ...

对于SystemV信号量,系统内核还有很多限制,譬如下面:

SEMMNS:系统中信号量的最大数目,等于SEMMNI*SEMMSL
SEMOPM:一次semopt()操作的最大信号量数目
SEMMNI。系统内核中信号量的最大数目
对于系统中的每个SystemV信号量,即每个信号量集,内核都会维护一个semid_ds的信息结构,如下是Linux2.6.18下的定义:
/* Data structure describing a set of semaphores.  */
struct semid_ds
{
  struct ipc_perm sem_perm;            // IPC的操作权限,每个IPC结构都有
  __time_t sem_otime;                  //上一次执行semop() 的时间
  unsigned long int __unused1;         //预留使用
  __time_t sem_ctime;                  //上一次通过semctl()进行的修改时间
  unsigned long int __unused2;
  unsigned long int sem_nsems;         //信号量集中的信号量数目
  unsigned long int __unused3;
  unsigned long int __unused4;
};

在《APUEP423提到,信号量集中每一个信号量由一个无名结构表示。但是我始终无法找到该结构体的定义书中提到该结构的定义如下:

struct sem {
    unsigned short semval;    //信号量的值
    pid_t  sempid;            //最后一次semctl操作的进程id
    unsigned short semncnt;   //等待semval变为大于当前值的线程数
    unsigned short semzcnt;   //等待semval变为0的线程数

};

1SystemV信号量的创建和打开

#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
                         //若成功就返回飞非负的标识符,否则返回-1

semget用于创建或打开一个已存在的信号量。

key用于生成唯一信号量的key,主要的目的是使不同进程在同一该IPC汇合。key可以是事先不同的进程约定好的一个值,也可以不同进程通过相同的路径名和项目ID,调用ftok()函数,生成一个键。

nsems:表示信号量集中信号量的个数,如果创建一个信号量集,nsems必须是一个非0正整数,如果打开一个指定的信号量集,nsems可以指定为0

semflagIPC_CREAT,IPC_EXCL,以及IPC的指定权限位。如果为IPC_CREATIPC_EXCL,当该信号量集以及存在会返回错误。errnoEEXIST

semget的返回值是被称为信号量标识符的整数,semopsemctl函数将通过该标识符对信号量集进行操作。

这里需要知道是调用semget()创建一个新的信号量集并没有对之初始化,需要调用后面要讲的semctl()函数进行初始化。这样SystemV信号量的创建和初始化就不是一个原子操作,这是一个很大的缺陷。会出现使用未初始化信号量集的问题。

2SystemV信号量的控制操作

#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../* union semun arg*/);
                         //若失败返回-1,并设置errno,成功具体返回值如下

semctl函数主要是对信号量集的一系列控制操作,根据操作命令cmd的不同,执行不同的操作,依赖于所请求的命令,第四个参数是可选的,

semidSystemV信号量的标识符;

semnum:表示信号量集中的第semnum个信号量。它的取值范围:0~nsems-1

cmd:操作命令;

arg:如果使用该参数,该参数的类型为unionsemun,它是多个特定命令的联合。按照SUS明确规定,这个结构必须有用户自己定义,在Linux2.6.18的系统头文件中也没有该结构的定义,但在<bits/sem.h>中有一段对该结构的定义的建议,不过是注释掉的。

include <bits/sem.h>

/* The user should define a union like the following to use it for 
                 arguments for `semctl'.
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)
};
*/

semctlcmd命令有10种,如下:

  • IPC_STAT:获取此信号量集合的semid_ds结构,存放在第四个参数argbuf中;
  • IPC_SET:通过arg.buf来设定信号量集相关联的semid_ds中信号量集合权限为sem_perm中的uidgidmode
  • IPC_RMID:从系统中删除该信号量集合。这种删除立即发生,仍在使用该信号量集的其他进程,在下次对该信号量集进行操作的时候,会发生错误并返回EIDRM。这和POSIX信号量是不一样的。POSIX信号量sem_unlink只是会立即删除信号量的在文件系统中的文件,而信号量的析构是在最后一个sem_close发生是进行的。
  • GETVAL:返回第semnum个信号量的值;
  • SETVAL:设置第semnum个信号量的值,该值由第四个参数arg中的val指定;
  • GETPID:返回第semnum个信号量的sempid,最后一个操作的pid
  • GETNCNT:返回第semnum个信号量的semncnt。等待semval变为大于当前值的线程数;
  • GETZCNT:返回第semnum个信号量的semzcnt。等待semval变为0的线程数。
  • GETALL:去信号量集合中所有信号量的值,将结果存放到arg中的array所指向的数组。
  • SETALL:按arg.array所指向的数组中的值,设置集合中所有信号量的值。

对于GETALL以外的所有GET命令,semctl都返回相应的值,其他命令的返回值为0

3 SystemV信号量的信号量操作

#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, 
                                       struct timespec *timeout);
                                          //成功返回0,出错返回-1

semop函数主要是在已打开的信号量集上,对其中的一个或多个信号量的值进行操作

semidSystemV信号量的标识符,用来标识一个信号量集。

sops:是指向一个structsembuf结构体数组的指针,该数组是一个信号量操作数组。

nsopssops所指向sembuf结构体数组中元素的个数。

sembuf结构体的定义如下:

struct sembuf
{
  unsigned short int sem_num;   /* 信号量的序号从0~nsems-1 */
  short int sem_op;            /* 对信号量的操作,>0, 0, <0 */
  short int sem_flg;            /* 操作标识:0, IPC_WAIT, SEM_UNDO */
};

sem_num标识信号量集中的第几个信号量,0表示第1个,1表示第2个,nsems-1表示最后一个。

sem_op标识对信号量的所进行的操作类型。对信号量的操作有三种类型:

  • sem_op>0,对该信号量执行挂出操作,挂出的值由sem_op决定,系统会把sem_op的值加到该信号量的当前值semval(参考文章开头关于每个信号量结构的定义)上。如果sem_flag指定了SEM_UNDO(还原)标志,那么相应信号量的semadj值会减掉sem_op的值。下面会说明semadj的含义。
  • sem_op<0,对该信号量执行等待操作,当信号量的当前值semval>=-sem_op时,semval减掉sem_op的绝对值,为该线程分配对应数目的资源。如果指定SEM_UNDO,相应信号量的semadj就加上sem_op的绝对值。当semval<-sem_op时,相应信号量的semncnt就加1,调用线程被阻塞,直到semval>=-sem_op,当此条件满足时,调用线程被唤醒,执行相应的分配操作,然后semncnt减去1.
  • sem_op=0,表示调用者希望semval变为0。如果为0则立即返回,如果不为0,相应信号量的semzcnt1,调用调用线程被阻塞。

sem_flag:信号量操作的属性标志,如果为0,表示正常操作,如果为IPC_WAIT,使对信号量的操作时非阻塞的。即指定了该标志,调用线程在信号量的值不满足条件的情况下不会被阻塞,而是直接返回-1,并将errno设置为EAGAIN。如果为SEM_UNDO,那么将维护进程对信号量的调整值,以便进程结束时恢复信号量的状态。

下面解释一下与单个信号量相关的几个值:

semval:信号量的当前值,在文章开头信号量的结构中已提到。

semncnt:等待semval变为大于当前值的线程数。在文章开头信号量的结构中已提到。

semzcnt:等待semval变为0的线程数。在文章开头信号量的结构中已提到。

semadj:指定信号量针对某个特定进程的调整值。只有sembuf结构的sem_flag指定为SEM_UNDO后,semadj才会随着sem_op而更新。讲简单一点:对某个进程,在指定SEM_UNDO后,对信号量semval值的修改都会反应到semadj上,当该进程终止的时候,内核会根据semadj的值,重新恢复信号量之前的值

这里我们可以看到SystemV信号量可以对信号量集中的某一信号量进行加减sem_op,该值不仅仅为1,而在POSIX信号量中只能对信号量进行加减1的操作。

4SystemV信号量的继承和销毁

(1)继承

POSIX的有名信号量一样,父进程中打开的信号量在子进程中仍然是保持着打开状态的。

(2)销毁

  1. 对于一个持有该信号量的进程在没有释放该信号量的情况下就终止了。那么该进程所占用的信号量内核是不会进行释放的。
  2. 当通过semctl传入IPC_RMID对该信号量集进行删除时,会立即将该信号量从系统中彻底删除,不能再对该信号量进行任何访问。这和POSIX信号量是不一样的。

下面代码进行了测试:

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>

using namespace std;

#define SEM_PATHNAME "/tmp/sem_name"

union semun
{
    int val;                           
    struct semid_ds *buf;             
    unsigned short int *array;      
    struct seminfo *__buf;          
};

int CreateKey(const char * pathName)
{
    int fd = open( pathName, O_CREAT , 0666);

    if (fd < 0)
    {
        cout<<"open file error..."<<strerror(errno)<<endl;
        return -1;
    }
    close(fd);

    return ftok(pathName, 0);
}

int main()
{
   int semId;
   semun arg;

   //解决信号量的创建和初始化不是原子操作的一种方案
   if ((semId = semget(CreateKey(SEM_PATHNAME), 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0)
   {
       arg.val = 4;

       if (semctl(semId, 0, SETVAL, arg) < 0)
       {
           cout<<"semctl error "<<strerror(errno)<<endl;
           return -1;
       }
   }
   else if (errno == EEXIST)
   {
       semId = semget(CreateKey(SEM_PATHNAME), 1, 0666);
   }
   else
   {
       cout<<"semget error "<<strerror(errno)<<endl;
       return -1;
   }

   cout<<"parent:sem value:"<<semctl(semId, 0, GETVAL)<<endl;

   if (fork() == 0)
   {
       struct sembuf buffer;
       buffer.sem_num = 0;
       buffer.sem_op = -2;
       buffer.sem_flg = 0;

       semop(semId, &buffer, 1);

       cout<<"child:sem value:"<<semctl(semId, 0, GETVAL)<<endl;
       exit(0);
   }

   sleep(1);
   cout<<"parent:sem value:"<<semctl(semId, 0, GETVAL)<<endl;
}
执行结果如下:
parent:sem value:4
child:sem value:2
parent:sem value:2

下面代码测试信号量集的删除:

int main()
{
   int semId;
   semun arg;

   //解决信号量的创建和初始化不是原子操作的一种方案
   if ((semId = semget(CreateKey(SEM_PATHNAME), 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0)
   {
       arg.val = 4;
       if (semctl(semId, 0, SETVAL, arg) < 0)
       {
           cout<<"semctl error "<<strerror(errno)<<endl;
           return -1;
       }
   }
   else if (errno == EEXIST)
   {
       semId = semget(CreateKey(SEM_PATHNAME), 1, 0666);
   }
   else
   {
       cout<<"semget error "<<strerror(errno)<<endl;
       return -1;
   }
   cout<<"parent:sem value:"<<semctl(semId, 0, GETVAL)<<endl;

   if (fork() == 0)
   {
       semctl(semId, 0, IPC_RMID);
       exit(0);
   }
   sleep(1);

   if (semctl(semId, 0, GETVAL) == -1)
   {
       cout<<"semctl error:"<<strerror(errno)<<endl;
       return -1;
   }
}

测试结果如下:

parent:sem value:4
semctl error:Invalid argument

5SystemV信号量的测试

下面是测试代码,信号量集中只有一个信号量,该信号量的值被设成6,每次semop获取两个信号量才能对共享资源(这里用文件代替)进行访问;

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>

using namespace std;

#define SEM_PATHNAME "/tmp/sem_name"

union semun
{
    int val;                           
    struct semid_ds *buf;             
    unsigned short int *array;      
    struct seminfo *__buf;          
};

int CreateKey(const char * pathName)
{
    int fd = open( pathName, O_CREAT , 0666);

    if (fd < 0)
    {
        cout<<"open file error..."<<strerror(errno)<<endl;
        return -1;
    }

    close(fd);
    return ftok(pathName, 0);
}

void semTest(int flag, int semId)
{ 
    struct sembuf buffer;
    buffer.sem_num = 0;
    buffer.sem_op = -2;
    buffer.sem_flg = 0;

    semop(semId, &buffer, 1);

    ofstream fileStream("./test.txt", ios_base::app); 
 
    for (int i = 0; i < 5; ++i)  
    {  
        sleep(1);  
        fileStream<<flag;  
        fileStream<<' '<<flush;  
    }  

    buffer.sem_op = 2;
    semop(semId, &buffer, 1);
}

int main()
{
   int semId;
   semun arg;

   //解决信号量的创建和初始化不是原子操作的一种方案
   if ((semId = semget(CreateKey(SEM_PATHNAME), 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0)
   {
       arg.val = 4;
       if (semctl(semId, 0, SETVAL, arg) < 0)
       {
           cout<<"semctl error "<<strerror(errno)<<endl;
           return -1;
       }
   }
   else if (errno == EEXIST)
   {
       semId = semget(CreateKey(SEM_PATHNAME), 1, 0666);
   }
   else
   {
       cout<<"semget error "<<strerror(errno)<<endl;
       return -1;
   }

   for (int i = 0; i < 3; ++i)
   {
       if (fork() == 0)
       {
            semTest(i + 1, semId);
            sleep(1);

            exit(0);
       }
   }
}
执行结果如下:

./test.txt
1 2 1 2 1 2 2 1 1 2 3 3 3 3 3


Jul 4, 2013 AM 00:26 @dorm

有想法就要努力的去尝试---skywalker

分享到:
评论

相关推荐

    进程同步与互斥:System V 信号量示例代码

    进程同步与互斥:System V 信号量,相关使用教程链接如下: http://blog.csdn.net/tennysonsky/article/details/47811201

    Linux System V信号量(互斥操作)

    System V信号量与System V其他两种通信机制(消息队列、共享内存)不同,其用来实现同步、互斥进程动作,通过semget函数创建一个信号集或打开一个信号量集,信号集又包括多个信号量,信号量的值要大于等于0,小于0,...

    linux系统进程间通信——共享内存(System V版本)

    之前用过Prosix版本的共享内存和信号量,一直没有实践System V版本的,主要是因为其信号量集的概念操作有些复杂,今天试着写一个SV版本的共享内存进程间通信,使用信号量同步。程序提供了几个简单的用于操作SV版本...

    Linux网络编程 视频 教程

    33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux...

    C++教程网《Linux网络编程》视频百度云地址

    33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux...

    c++教程网的linux网络编程视频下载

    33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux...

    [免费]2018年C++教程网的linux网络编程视频百度云下载链接.rar

    33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux...

    linux网络编程

    信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34...

    操作系统之Linux下的生产者-消费者模型

    结合System V信号量机制,利用Linux下的多线程库实现了Linux下的操作系统生产者-消费者模型,具体原理可参考博文:: http://blog.csdn.net/Mikeoperfect/article/details/79431642

    C++教程网视频:linux网络编程

    33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux...

    2018年C++教程网的linux网络编程视频共41集百度云下载链接.rar

    33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之...

    linux网络编程全套代码

    无名信号量(内存信号量): sem_t, p, v, 互斥锁:pthread_mutex_t 进程: Fork()/ execve();// system(); 进程内存空间 环境变量: 从哪里来, 放在那里, 如何取用 进程通信: 传统: 无名管道 pipe: 亲缘...

    清华大学Linux操作系统原理与应用

    3.4.3 Linux进程调度时机 50 3.4.4 进程调度的依据 51 3.4.5 调度函数schedule()的实现 52 3.5 进程的创建 54 3.5.1 创建进程 55 3.5.2 线程及其创建 56 3.6 与进程相关的系统调用及其应用 58 3.6.1 fork系统调用 58...

    linux网络编程-宋敬彬-part3

    1.7 Linux软件开发的可借鉴之处 12 1.8 小结 13 第2章 Linux编程环境 14 2.1 Linux环境下的编辑器 14 2.1.1 vim使用简介 14 2.1.2 使用vim建立文件 15 2.1.3 使用vim编辑文本 16 2.1.4 vim的格式设置 ...

    linux网络编程-宋敬彬-part2

    1.7 Linux软件开发的可借鉴之处 12 1.8 小结 13 第2章 Linux编程环境 14 2.1 Linux环境下的编辑器 14 2.1.1 vim使用简介 14 2.1.2 使用vim建立文件 15 2.1.3 使用vim编辑文本 16 2.1.4 vim的格式设置 ...

    进程通信.doc

    Linux进程间通信 一、进程间通信概述 进程通信有如下一些目的: A、数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间 B、共享数据:多个进程想要操作共享数据,一个进程...

    UNIX网络编程 卷2 进程间通信 带完整书签,完整目录

    10.16 使用System V信号量实现Posix信号量 218 10.17 小结 224 习题 225 第11章 System V信号量 226 11.1 概述 226 11.2 semget函数 227 11.3 semop函数 229 11.4 semctl函数 231 11.5 简单的程序 232 ...

    深入分析Linux内核源码

    5.3.2 Linux进程调度时机 5.3.3 进程调度的依据 5.3.4 进程可运行程度的衡量 5.3.5 进程调度的实现 5.4 进程切换 5.4.1 硬件支持 5.4.2 进程切换 第六章 Linux内存管理 6.1 Linux的内存管理概述 6.1.1 ...

    高级UNIX编程 pdf 电子书

    7.9 System V信号量 7.10 POSIX信号量 7.11 文件锁 7.12 关于共享内存 7.13 System V共享内存 7.14 POSIX共享内存 7.15 性能比较 练习 第8章 网络和套接字 8.1 套接字基础 8.2 套接字地址 8.3 套接字选项 8.4 简单套...

Global site tag (gtag.js) - Google Analytics