搜索
bottom↓
回复: 4

(转贴)【Linux】进程间通信-信号量详解及编程实例

[复制链接]

出0入0汤圆

发表于 2015-8-12 21:01:21 | 显示全部楼层 |阅读模式
  转发一个linux进程间通信使用信号量的文章,用于临界段代码保护!

信号量概述
信号量定义:
它是一个特殊变量,只允许对它进行等待和发送信号这两种操作。
P(信号量变量sv):等待。如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行。
V(信号量变量sv):发送信号。如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv。

PV操作伪代码
[cpp] view plaincopyprint?
semaphore sem_id = 1;  
  
loop{  
    P(sem_id);  
   临界区代码;  
    V(sem_id);  
     非临界区代码;  
}  

信号量机制及相关接口

Linux系统中的信号量接口经过了精心设计,提供了比通常所需更多的机制。所有的Linux信号量函数都是针对成组的通用信号量进行操作,而不只是针对一个二进制信号量。但是在绝大多数情况下,使用一个单个信号量就足够了,所以在这里只讨论单个信号量的使用。

semget函数
用于创建一个新的信号量或者是取得一个已有的信号量的键。

所需包含的头文件:
[cpp] view plaincopyprint?
#include <sys/sem.h>   
它通常依赖于另两个头文件:
[cpp] view plaincopyprint?
#include <sys/types.h>  
#include <sys/ipc.h>   
一般情况下,这两个头文件都会被它自动包含。

功能描述
  函数原型:int semget(key_t key,int nsems,int semflg);
  功能描述
  获取与某个键关联的信号量集标识。信号量集被建立的情况有两种:
  1.如果键的值是IPC_PRIVATE。
  2.或者键的值不是IPC_PRIVATE,并且键所对应的信号量集不存在,同时标志中指定IPC_CREAT。
  当调用semget创建一个信号量时,他的相应的semid_ds结构被初始化。ipc_perm中各个量被设置为相应
  值:
  sem_nsems被设置为nsems所示的值;
  sem_otime被设置为0;
  sem_ctime被设置为当前时间

参数解释:
       key:所创建或打开信号量集的键值。需要是唯一的非零整数。
  nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。几乎总是取值为1.
  flag:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过or表示

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

semop函数
用于改变信号量的值。

[cpp] view plaincopyprint?
#include <sys/sem.h>  
int semop( int semid, struct sembuf semoparray[], size_t nops );   

参数解释:
参数semid是一个通过semget函数返回的一个信号量标识符
参数nops标明了参数semoparray所指向数组中的元素个数
参数semoparray是一个struct sembuf结构类型的数组指针,
结构sembuf来说明所要执行的操作,其定义如下:
  
[cpp] view plaincopyprint?
struct sembuf{  
  unsigned short sem_num;  
  short sem_op;  
  short sem_flg;  
  }  

在sembuf结构中,
sem_num是相对应的信号量集中的某一个资源,所以其值是一个从0到相应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数。除非使用一组讯号了,否则它的取值一般为0.
sem_op的值是一个整数,是信号量在一次操作中需要改变的数值(可以是非1的数值)。通常只会用到两个值:1----P操作,-1---V操作。
sem_flg说明函数semop的行为。通常被设置为SEM_UNDO。它将使得操作系统跟着当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。

注意:
semop调用的一切动作都是一次性完成的,这是为了避免出现因使用多个信号量而可能发生的竞争现象。

semctl函数

原型:
[cpp] view plaincopyprint?
int semctl(int semid,int semnum,int cmd,union semun);  

返回值:
       如果成功,则为一个正数。
  如果失败,则为-1:
errno=EACCESS(权限不够)
  EFAULT(arg指向的地址无效)
  EIDRM(信号量集已经删除)
  EINVAL(信号量集不存在,或者semid无效)
  EPERM(EUID没有cmd的权利)
  ERANGE(信号量值超出范围)

参数解释:
sem_id是由semget返回的信号量标识符。
sem_num与前面一个函数相同。
cnd:表示将要采取的动作。最常用的两个值如下:
SETVAL:用来把信号量初始化为一个已知的值。这个值通过union semun中的val成员设置。其作用是在信号量第一次使用之前对它进行设置。
IPC_RMID:用于删除一个无需继续使用的信号量标志符。
semun联合结构的定义:
[cpp] view plaincopyprint?
semun是在linux/sem.h中定义的:  
  /*arg for semctl systemcalls.*/  
  union semun{  
  int val;/*value for SETVAL*/  
  struct semid_ds *buf;/*buffer for IPC_STAT&IPC_SET*/  
  ushort *array;/*array for GETALL&SETALL*/  
  struct seminfo *__buf;/*buffer for IPC_INFO*/  
  void *__pad;   

信号量的使用

虽然上述函数调用看似很复杂,但是我们可以用这些接口来创建一个简单的PV类型的接口,然后用这个简单的接口来进行信号量相关操作。
下面的程序使用上述接口实现了P、V操组以及设置信号量、删除信号量的操作。
然后利用这些新的函数接口实现了两个程序实例访问临界区的功能。

在这里同时访问临界区的是一个程序的两个不同实例,并且使用参数个数的不同来进行区别。其中一个需要完成信号量的创建及其删除的额外操作。

两个程序在进入临界区和离开临界区的时候分别都会输出两个不同的字符,以此来进行区分。可以发现,两个不同的字符是成对出现的。因为同一时刻只有一个进程可以进入临界区。

完整代码:
[cpp] view plaincopyprint?
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
  
#include <sys/sem.h>//包含信号量定义的头文件  
  
//联合类型semun定义  
union semun{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *array;  
};  
  
//函数声明  
//函数:设置信号量的值  
static int set_semvalue(void);  
//函数:删除信号量  
static void del_semvalue(void);  
//函数:信号量P操作  
static int semaphore_p(void);  
//函数:信号量V操作  
static int semaphore_v(void);  
  
static int sem_id;//信号量ID  
  
  
int main(int argc,char *argv[])  
{  
    int i;  
    int pause_time;  
    char op_char = 'O';  
  
    srand((unsigned int)getpid());  
  
    //创建一个新的信号量或者是取得一个已有信号量的键  
    sem_id = semget((key_t)1234,1,0666 | IPC_CREAT);  
  
    //如果参数数量大于1,则这个程序负责创建信号和删除信号量  
    if(argc > 1)  
    {  
        if(!set_semvalue())  
        {  
            fprintf(stderr,"failed to initialize semaphore\n");  
        exit(EXIT_FAILURE);  
        }  
  
        op_char = 'X';//对进程进行标记  
        sleep(5);  
    }  
  
    //循环:访问临界区  
    for(i = 0;i < 10;++i)  
    {  
        //P操作,尝试进入缓冲区  
        if(!semaphore_p())  
        exit(EXIT_FAILURE);  
        printf("%c",op_char);  
        fflush(stdout);//刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上  
  
        pause_time = rand() % 3;  
        sleep(pause_time);  
  
        printf("%c",op_char);  
        fflush(stdout);  
  
        //V操作,尝试离开缓冲区  
        if(!semaphore_v())  
        exit(EXIT_FAILURE);  
        pause_time = rand() % 2;  
        sleep(pause_time);  
    }  
  
    printf("\n %d - finished \n",getpid());  
  
    if(argc > 1)  
    {  
        sleep(10);  
        del_semvalue();//删除信号量  
    }  
}  
  
  
//函数:设置信号量的值  
static int set_semvalue(void)  
{  
    union semun sem_union;  
    sem_union.val = 1;  
  
    if(semctl(sem_id,0,SETVAL,sem_union))  
        return 0;  
  
    return 1;  
}  
  
//函数:删除信号量  
static void del_semvalue(void)  
{  
    union semun sem_union;  
  
    if(semctl(sem_id,0,IPC_RMID,sem_union))  
        fprintf(stderr,"Failed to delete semaphore\n");  
}  
  
//函数:信号量P操作:对信号量进行减一操作  
static int semaphore_p(void)  
{  
    struct sembuf sem_b;  
  
    sem_b.sem_num = 0;//信号量编号  
    sem_b.sem_op = -1;//P操作   
    sem_b.sem_flg = SEM_UNDO;  
  
    if(semop(sem_id,&sem_b,1) == -1)  
    {  
        fprintf(stderr,"semaphore_p failed\n");  
        return 0;  
    }  
  
    return 1;  
}  
  
//函数:信号量V操作:对信号量进行加一操作  
static int semaphore_v(void)  
{  
    struct sembuf sem_b;  
  
    sem_b.sem_num = 0;//信号量编号  
    sem_b.sem_op = 1;//V操作   
    sem_b.sem_flg = SEM_UNDO;  
  
    if(semop(sem_id,&sem_b,1) == -1)  
    {  
        fprintf(stderr,"semaphore_v failed\n");  
        return 0;  
    }  
  
    return 1;  
  
}  


阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入0汤圆

 楼主| 发表于 2015-8-12 21:05:01 | 显示全部楼层
再转发一个linux线程间使用信号量的例子!

linux中两种基本的同步方法是信号量和互斥量。这两种方法很相似,而且它们可以相互通过对方来实现。

信号量概述

下面介绍用信号量进行同步。

信号量概念由荷兰科学家Dijkstra首先提出。信号量是一个特殊类型的变量,它可以被增加或者减少。但对其的关键访问被保证是原子操作,即使在一个多线程程序中也是如此。

信号量有两种类型:
(1)二进制信号量。它只有0和1两种取值。
(2)计数信号量。它可以有更大的取值范围。
如果要用信号量来保护一段代码,使其每次只能被一个执行线程运行,就要用到二进制信号量、。
如果要允许有限数目的线程执行一段指定的代码,就需要用到计数信号量。

由于计数信号量并不常用,而且它实际上仅仅是二进制信号量的一种扩展,这里之介绍二进制信号量。

信号量的相关函数
信号量函数的名字都以sem_开头。线程中使用的基本函数有4个。
注意,需要包含头文件:
[cpp] view plaincopyprint?
#include<semaphore.h>  

创建信号量
[cpp] view plaincopyprint?
int sem_init(sem_t *sem, int pshared, unsigned int value);  
函数解释:
sem_init() 初始化一个定位在 sem 的匿名信号量。value 参数指定信号量的初始值。 pshared 参数指明信号量是由进程内线程共享,还是由进程之间共享。如果 pshared 的值为 0,那么信号量将被进程内的线程共享,并且应该放置在所有线程都可见的地址上(如全局变量,或者堆上动态分配的变量)。
  如果 pshared 是非零值,那么信号量将在进程之间共享,并且应该定位共享内存区域(见 shm_open(3)、mmap(2) 和 shmget(2))。(因为通过 fork(2) 创建的孩子继承其父亲的内存映射,因此它也可以见到这个信号量。)所有可以访问共享内存区域的进程都可以使用sem_post(3)、sem_wait(3) 等等操作信号量。初始化一个已经初始的信号量其结果未定义。
返回值
  sem_init() 成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值。
错误
  EINVAL
  value 超过 SEM_VALUE_MAX。
  ENOSYS
  pshared 非零,但系统还没有支持进程共享的信号量。

下面是控制信号量的两个函数:
信号量减一操作
[cpp] view plaincopyprint?
int sem_wait(sem_t * sem);  
函数说明
  sem_wait函数也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执行,这信号量的值将减到1。如果对一个值为0的信号量调用sem_wait(),这个函数就 会地等待直到有其它线程增加了这个值使它不再是0为止。如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加 一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。
返回值
  所有这些函数在成功时都返回 0;错误保持信号量值没有更改,-1 被返回,并设置 errno 来指明错误。
错误
  EINTR
  这个调用被信号处理器中断,
  EINVAL
  sem 不是一个有效的信号量。

信号量加一操作
[cpp] view plaincopyprint?
int sem_post(sem_t * sem);   
说明
  sem_post函数的作用是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;而同 时对同一个文件进行读、加和写操作的两个程序就有可能会引起冲突。信号量的值永远会正确地加一个“2”--因为有两个线程试图改变它。
返回值
  sem_post() 成功时返回 0;错误时,信号量的值没有更改,-1 被返回,并设置 errno 来指明错误。
错误
  EINVAL
  sem 不是一个有效的信号量。
  EOVERFLOW
  信号量允许的最大值将要被超过。

清理信号量
[cpp] view plaincopyprint?
int sem_destroy (sem_t *sem);  
这个函数也使用一个信号量指针做参数,归还自己战胜的一切资源。在清理信号量的时候如果还有线程在等待它,用户就会收到一个错误。
        与其它的函数一样,这些函数在成功时都返回“0”。

信号量的使用
下面主线程中创建了一个新线程,用来统计输入的字符串中字符的个数。信号量用来控制两个线程对存储字符串数组的访问。

代码:
[cpp] view plaincopyprint?
#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <string.h>  
#include <pthread.h>  
#include <semaphore.h>  
  
//线程函数   
void *thread_function(void *arg);  
  
sem_t bin_sem;//信号量对象  
  
#define WORK_SIZE 1024  
  
char work_area[WORK_SIZE];//工作区  
  
int main()  
{  
    int res;  
    pthread_t a_thread;  
    void *thread_result;  
  
    res = sem_init(&bin_sem,0,0);//初始化信号量对象  
  
    if(res)//初始化信号量失败  
    {  
    perror("Semaphore initialization failed\n");  
    exit(EXIT_FAILURE);  
    }  
  
    //创建新线程  
    res = pthread_create(&a_thread,NULL,thread_function,NULL);  
    if(res)  
    {  
    perror("Thread creation failed\n");  
    exit(EXIT_FAILURE);  
    }  
  
    printf("Input some text.Enter 'end' to finish\n");  
  
    while(strncmp("end",work_area,3) != 0)  
    {//输入没有结束  
    fgets(work_area,WORK_SIZE,stdin);  
    sem_post(&bin_sem);//给信号量值加一  
    }  
  
    printf("waiting for thread to finish\n");  
  
    //等待子线程结束,收集子线程信息  
    res = pthread_join(a_thread,&thread_result);  
    if(res)  
    {  
    perror("Thread join failed\n");  
    exit(EXIT_FAILURE);  
    }  
  
    printf("Thread joined\n");  
  
    //销毁信号量对象  
    sem_destroy(&bin_sem);  
  
    exit(EXIT_SUCCESS);  
}  
  
void *thread_function(void *arg)  
{  
    sem_wait(&bin_sem);//将信号量值减一。  
  
    while(strncmp("end",work_area,3))  
    {  
        printf("You input %d characters\n",strlen(work_area) - 1);  
        sem_wait(&bin_sem);  
    }  
  
    pthread_exit(NULL);//线程终止执行  
}  

出0入0汤圆

发表于 2015-8-12 21:14:18 | 显示全部楼层
哈哈,好久没玩了。进程通讯:共享内存、消息队列、信号、管道、套接字……。最喜欢用kill发信号。话说以前用信号量多数是用来定时的。

出50入0汤圆

发表于 2015-8-12 22:27:31 | 显示全部楼层
正准备学Linux,果断收藏,谢谢分享。

出0入0汤圆

发表于 2016-5-13 10:51:35 | 显示全部楼层
正在学信号量和共享内存。。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 13:52

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表