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

Linux进程同步之POSIX信号量

 
阅读更多

POSIX信号量是属于POSIX标准系统接口定义的实时扩展部分。在SUSSingleUNIXSpecification)单一规范中,定义的XSIIPC中也同样定义了人们通常称为SystemV信号量的系统接口。信号量作为进程间同步的工具是很常用的一种同步IPC类型。

在《UNIX网络编程卷2:进程间通信》的前言第二页与第1版的区别中作者提到“POSIXIPC函数时大势所趋,因为他们比SystemV中的相应部分更具有优势”,这里所说的优势我还得慢慢领会呀。。。<T_T>

信号量是一种用于不同进程间进行同步的工具,当然对于进程安全的对于线程也肯定是安全的,所以信号量也理所当然可以用于同一进程内的不同线程的同步。

有了互斥量和条件变量还提供信号量的原因是:信号量的主要目的是提供一种进程间同步的方式。这种同步的进程可以共享也可以不共享内存区。虽然信号量的意图在于进程间的同步,互斥量和条件变量的意图在于线程间同步,但信号量也可用于线程间同步,互斥量和条件变量也可通过共享内存区进行进程间同步。但应该根据具体应用考虑到效率和易用性进行具体的选择。

1 POSIX信号量的操作

POSIX信号量有两种:有名信号量无名信号量,无名信号量也被称作基于内存的信号量。有名信号量通过IPC名字进行进程间的同步,而无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步。

POSIX信号量有三种操作:

(1)创建一个信号量。创建的过程还要求初始化信号量的值。

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

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

(2)等待一个信号量(wait。该操作会检查信号量的值,如果其值小于或等于0,那就阻塞,知道该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限。这整个操作必须是一个原子操作。该操作还经常被称为P操作(荷兰语Proberen,意为:尝试)。

(3)挂出一个信号量(post。该操作将信号量的值加1,如果有进程阻塞着等待该信号量,那么其中一个进程将被唤醒。该操作也必须是一个原子操作。该操作还经常被称为V操作(荷兰语Verhogen,意为:增加)

下面演示经典的生产者消费者问题,单个生产者和消费者共享一个缓冲区;

下面是生产者和消费者同步的伪代码:


//信号量的初始化
get = 0;//表示可读资源的数目
put = 1;//表示可写资源的数目

//生产者进程                               //消费者进程
for(; ;){                                    for(; ;){
Sem_wait(put);                                 Sem_wait(get);
写共享缓冲区;                               读共享缓冲区;
Sem_post(get);                                 Sem_post(put);
}                                           }
上面的代码大致流程如下:当生产者和消费者开始都运行时,生产者获取put信号量,此时put1表示有资源可用,生产者进入共享缓冲区,进行修改。而消费者获取get信号量,而此时get0,表示没有资源可读,于是消费者进入等待序列,直到生产者生产出一个数据,然后生产者通过挂出get信号量来通知等待的消费者,有数据可以读。

很多时候信号量和互斥量,条件变量三者都可以再某种应用中使用,那这三者的差异有哪些呢,下面列出了这三者之间的差异

  • 互斥量必须由给它上锁的线程解锁。而信号量不需要由等待它的线程进行挂出,可以在其他进程进行挂出操作。
  • 互斥量要么被锁住,要么是解开状态,只有这两种状态。而信号量的值可以支持多个进程成功进行wait操作。
  • 信号量的挂出操作总是被记住,因为信号量有一个计数值,挂出操作总会将该计数值加1,然而当向条件变量发送一个信号时,如果没有线程等待在条件变量,那么该信号会丢失。

2POSIX信号量函数接口

POSIX信号量的函数接口如下图所示:


2.1有名信号量的创建和删除

#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
                  mode_t mode, unsigned int value);
                              //成功返回信号量指针,失败返回SEM_FAILED

sem_open用于创建或打开一个信号量,信号量是通过name参数即信号量的名字来进行标识的。关于POSICIPC的名字可以参考《UNIX网络编程卷2:进程间通信》P14

oflag参数可以为:0O_CREATO_EXCL。如果为0表示打开一个已存在的信号量,如果为O_CREAT,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回。此时modevalue需要指定。如果为O_CREAT|O_EXCL,表示如果信号量已存在会返回错误。

mode参数用于创建信号量时,表示信号量的权限位,和open函数一样包括:S_IRUSRS_IWUSRS_IRGRPS_IWGRPS_IROTHS_IWOTH

value表示创建信号量时,信号量的初始值。

#include <semaphore.h>

int sem_close(sem_t *sem);
int sem_unlink(const char *name);
                              //成功返回0,失败返回-1

sem_close用于关闭打开的信号量。当一个进程终止时,内核对其上仍然打开的所有有名信号量自动执行这个操作。调用sem_close关闭信号量并没有把它从系统中删除它,POSIX有名信号量是随内核持续的。即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。

sem_unlink用于将有名信号量立刻从系统中删除,但信号量的销毁是在所有进程都关闭信号量的时候。

2.2信号量的P操作

#include <semaphore.h>

int sem_wait (sem_t *sem);

#ifdef __USE_XOPEN2K
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
#endif

int sem_trywait (sem_t * sem);
                              //成功返回0,失败返回-1
sem_wait()用于获取信号量,首先会测试指定信号量的值,如果大于0,就会将它减1并立即返回,如果等于0,那么调用线程会进入睡眠,指定信号量的值大于0.

sem_trywaitsem_wait的差别是,当信号量的值等于0的,调用线程不会阻塞,直接返回,并标识EAGAIN错误。

sem_timedwaitsem_wait的差别是当信号量的值等于0时,调用线程会限时等待。当等待时间到后,信号量的值还是0,那么就会返回错误。其中structtimespec*abs_timeout是一个绝对时间,具体可以参考条件变量关于等待时间的使用

2.3信号量的V操作
#include <semaphore.h>

int sem_post(sem_t *sem);
                            //成功返回0,失败返回-1

当一个线程使用完某个信号量后,调用sem_post,使该信号量的值加1,如果有等待的线程,那么会唤醒等待的一个线程。

2.4获取当前信号量的值
#include <semaphore.h>

int sem_getvalue(sem_t *sem,  int *sval);
                            //成功返回0,失败返回-1

该函数返回当前信号量的值,通过sval输出参数返回,如果当前信号量已经上锁,那么返回值为0,或为负数,其绝对值就是等待该信号量解锁的线程数。

下面测试在Linux下的信号量是否会出现负值:

#include <iostream>

#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>

using namespace std;

#define SEM_NAME "/sem_name"

sem_t *pSem;

void * testThread (void *ptr)
{
    sem_wait(pSem);
    sleep(10);
    sem_close(pSem);
}

int main()
{
    pSem = sem_open(SEM_NAME, O_CREAT, 0666, 5);

    pthread_t pid;
    int semVal;

    for (int i = 0; i < 7; ++i)
    {
        pthread_create(&pid, NULL, testThread, NULL);

        sleep(1);

        sem_getvalue(pSem, &semVal); 
        cout<<"semaphore value:"<<semVal<<endl;
    }

    sem_close(pSem);
    sem_unlink(SEM_NAME);
}

执行结果如下:

semaphore value:4
semaphore value:3
semaphore value:2
semaphore value:1
semaphore value:0
semaphore value:0
semaphore value:0

这说明在Linux2.6.18中POSIX信号量是不会出现负值的。

2.5无名信号量的创建和销毁

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
                            //若出错则返回-1
int sem_destroy(sem_t *sem);
                            //成功返回0,失败返回-1

sem_init()用于无名信号量的初始化。无名信号量在初始化前一定要子啊内存中分配一个sem_t信号量类型,这就是无名信号量又称为基于内存的信号量的原因。

sem_init()第一个参数是指向一个已经分配的sem_t变量。第二个参数pshared表示该信号量是否由于进程间通步,当pshared=0,那么表示该信号量只能用于进程内部的线程间的同步。当pshared!=0,表示该信号量存放在共享内存区中,使使用它的进程能够访问该共享内存区进行进程同步。第三个参数value表示信号量的初始值。

这里需要注意的是,无名信号量不使用任何类似O_CREAT的标志,这表示sem_init()总是会初始化信号量的值,所以对于特定的一个信号量,我们必须保证只调用sem_init()进行初始化一次,对于一个已初始化过的信号量调用sem_init()的行为是未定义的如果信号量还没有被某个线程调用还好,否则基本上会出现问题。

使用完一个无名信号量后,调用sem_destroy摧毁它。这里要注意的是:摧毁一个有线程阻塞在其上的信号量的行为是未定义的

2.6有名和无名信号量的持续性

有名信号量是随内核持续的。当有名信号量创建后,即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。

无名信号量的持续性要根据信号量在内存中的位置:

  • 如果无名信号量是在单个进程内部的数据空间中,即信号量只能在进程内部的各个线程间共享,那么信号量是随进程的持续性,当进程终止时它也就消失了。
  • 如果无名信号量位于不同进程的共享内存区,因此只要该共享内存区仍然存在,该信号量就会一直存在。所以此时无名信号量是随内核的持续性

2.7信号量的继承和销毁

1)继承

对于有名信号量在父进程中打开的任何有名信号量在子进程中仍是打开的。即下面代码是正确的:

sem_t *pSem;
pSem = sem_open(SEM_NAME, O_CREAT, 0666, 5);

if(fork() == 0)
{
    //...
    sem_wait(pSem);
    //...
}

对于无名信号量的继承要根据信号量在内存中的位置:

  • 如果无名信号量是在单个进程内部的数据空间中,那么信号量就是进程数据段或者是堆栈上,当fork产生子进程后,该信号量只是原来的一个拷贝,和之前的信号量是独立的。下面是测试代码:

int main()
{
    sem_t mSem;
    sem_init(&mSem, 0, 3);

    int val;
    sem_getvalue(&mSem, &val);
    cout<<"parent:semaphore value:"<<val<<endl;

    sem_wait(&mSem);
    sem_getvalue(&mSem, &val);
    cout<<"parent:semaphore value:"<<val<<endl;

    if(fork() == 0)
    {   
        sem_getvalue(&mSem, &val);
        cout<<"child:semaphore value:"<<val<<endl;  

        sem_wait(&mSem);

        sem_getvalue(&mSem, &val);
        cout<<"child:semaphore value:"<<val<<endl;

        exit(0);
    }
    sleep(1);

    sem_getvalue(&mSem, &val);
    cout<<"parent:semaphore value:"<<val<<endl;
}

测试结果如下:

parent:semaphore value:3
parent:semaphore value:2
child:semaphore value:2
child:semaphore value:1
parent:semaphore value:2
  • 如果无名信号量位于不同进程的共享内存区,那么fork产生的子进程中的信号量仍然会存在该共享内存区,所以该信号量仍然保持着之前的状态。

2)销毁

对于有名信号量,当某个持有该信号量的进程没有解锁该信号量就终止了,内核并不会将该信号量解锁。这跟记录锁不一样。

对于无名信号量,如果信号量位于进程内部的内存空间中,当进程终止后,信号量也就不存在了,无所谓解锁了。如果信号量位于进程间的共享内存区中,当进程终止后,内核也不会将该信号量解锁。

下面是测试代码:

int main()
{
    sem_t *pSem;
    pSem = sem_open(SEM_NAME, O_CREAT, 0666, 5);

    int val;
    sem_getvalue(pSem, &val);
    cout<<"parent:semaphore value:"<<val<<endl;   

    if(fork() == 0)
    {   
        sem_wait(pSem);
        sem_getvalue(pSem, &val);
        cout<<"child:semaphore value:"<<val<<endl;

        exit(0);
    }
    sleep(1);

    sem_getvalue(pSem, &val);
    cout<<"parent:semaphore value:"<<val<<endl;

    sem_unlink(SEM_NAME);
}

下面是测试结果:

parent:semaphore value:5
child:semaphore value:4
parent:semaphore value:4

2.8信号量代码测试

对于有名信号量在父进程中打开的任何有名信号量在子进程中仍是打开的。即下面代码是正确的:

对于信号量用于进程间同步的代码的测试,我没有采用经典的生产者和消费者问题,原因是这里会涉及到共享内存的操作。我只是简单的用一个同步文件操作的例子进行描述。 在下面的测试代码中,POSIX有名信号量初始值为2,允许两个进程获得文件的操作权限。代码如下:

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

#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>

using namespace std;

#define SEM_NAME "/sem_name"

void semTest(int flag)
{ 
    sem_t *pSem;
    pSem = sem_open(SEM_NAME, O_CREAT, 0666, 2);

    sem_wait(pSem);

    ofstream fileStream("./test.txt", ios_base::app);  

    for (int i = 0; i < 5; ++i)  
    {  
        sleep(1);  

        fileStream<<flag;  
        fileStream<<' '<<flush;  
    }  

    sem_post(pSem);
    sem_close(pSem);
}

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

           sleep(1);
           exit(0);
       }
   }
}

程序的运行结果,“./test.txt”文件的内容如下:

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

Jul 1, 2013 PM 22:04 @dorm

杂谈:

转眼间到7月份了,按着之前的计划,我现在应该在干自己想干的事,可是总是事与愿违,的确很大因素上是自己的原因,哎,苦逼呀。。。只能多看点书,9月份找个好工作吧,希望暑假没有乱七八糟的事烦心。。。

今天是莎姐的生日,老弟这里祝你生日快乐,虽然不能一起帮你过个生日,还是希望你每天都开心快乐,越来越漂亮,爱情甜蜜蜜。。。<^_^>

分享到:
评论

相关推荐

    Linux网络编程 视频 教程

    39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一...

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

    39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一...

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

    39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一...

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

    39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一...

    linux网络编程

    N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 ...

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

    39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一...

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

    39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的...

    LINUX高级程序设计(中文第二版)杨宗德 (1)

    详细介绍了linux系统下编程环境及编程工具、文件管理(文件类型、ansi以及posix标准下文件读写操作)、进程管理(创建、退出、执行、等待、属性控制)、进程间通信(管道、消息队列、共享内存)、进程间同步机制(信号量)、...

    LINUX高级程序设计(中文第二版)杨宗德 (2)end

    详细介绍了linux系统下编程环境及编程工具、文件管理(文件类型、ansi以及posix标准下文件读写操作)、进程管理(创建、退出、执行、等待、属性控制)、进程间通信(管道、消息队列、共享内存)、进程间同步机制(信号量)、...

    Linux高级程序设计 (不完整版只到11.5.2章节)

    详细介绍了Linux系统下编程环境及编程工具、文件管理(文件类型、ANSI以及POSIX标准下文件读写操作)、进程管理(创建、退出、执行、等待、属性控制)、进程间通信(管道、消息队列、共享内存)、进程间同步机制...

    linux网络编程全套代码

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

    linux-ipcs:Linux进程间通信(Inter-Process Communication)方式汇总

    POSIX信号量 域套接字(Domain Socket) 信号(Signal) 互斥量(Mutex) 其中信号(signal)和信号量(semaphore)本质上并不算是进程间通信方式,应该是进程间同步的方式,但是也可以起到一定的通信作用,故也列在上面。 ...

    操作系统课程实验.rar

    (4) 通过对 linux 的 Posix 信号量的应用,加深对信号量同步机制的理解。 (5)请根据自身情况,进一步阅读分析相关系统调用的内核源码实现。 设计内容 (1)熟悉 linux 常用命令:pwd,useradd,passwd, who, ps, ...

    清华大学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的格式设置 ...

    Linux程序设计 第4版.haozip01

    14.1.3 linux的信号量机制 490 14.1.4 使用信号量 492 14.2 共享内存 496 14.2.1 shmget函数 497 14.2.2 shmat函数 497 14.2.3 shmdt 498 14.2.4 shmctl 498 14.3 消息队列 502 14.3.1 msgget函数 502 ...

    Linux高性能服务器编程

    多线程编程 14.1 Linux线程概述 14.1.1 线程模型 14.1.2 Linux线程库 14.2 创建线程和结束线程 14.3 线程属性 14.4 POSIX信号量 14.5 互斥锁 14.5.1 互斥锁基础API 14.5.2 互斥锁属性 14.5.3 死锁举例 ...

    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程序设计 第4版.haozip02

    14.1.3 linux的信号量机制 490 14.1.4 使用信号量 492 14.2 共享内存 496 14.2.1 shmget函数 497 14.2.2 shmat函数 497 14.2.3 shmdt 498 14.2.4 shmctl 498 14.3 消息队列 502 14.3.1 msgget函数 502 ...

Global site tag (gtag.js) - Google Analytics