进程间通信之信号量

简介:

一.概述:

信号量本身并不具备数据交换的功能,它本质只是一个数据操作锁,是通过控制临界资源来实现进程间通信的,它在此过程中实现数据的同步与互斥等功能。

信号量结构体:

内核为每个信号量集维护一个信号量结构体,可在<sys/sem.h>找到该定义:
struct semid_ds {
struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */
struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集
中的每个信号量对应其中一个数组元素 */
ushort sem_nsems; /* sem_base 数组的个数 */
time_t sem_otime; /* 最后一次成功修改信号量数组的时间 */
time_t sem_ctime; /* 成功创建时间 */
};
struct sem {
ushort semval; /* 信号量的当前值 */
short sempid; /* 最后一次返回该信号量的进程ID 号 */
ushort semncnt; /* 等待semval大于当前值的进程个数 */
ushort semzcnt; /* 等待semval变成0的进程个数 */
};


二.信号量的工作原理:

由于信号量只能进行两种操作等待和发送信号,即P和V,他们的行为是这样的:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行(申请资源)

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.(释放资源)


三.相关函数:

  1. semget:int  semget(key_t  key,   int nsems,  int semflg)

           key参数:可以用ftok函数获取。

           nsems参数:当nsems>0 : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不更       改。当nsems==0 : 访问一个已存在的集合。

           semflg参数:IPC_CREATE   和  IPC_EXCL

           返回值:返回一个信号量标识符的整数,semop和semctl函数将使用它,失败返回-1。

          创建成功后信号量结构被设置:
.sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID
.oflag 参数中的读写权限位存入sem_perm.mode
.sem_otime 被置为0,sem_ctime被设置为当前时间
.sem_nsems 被置为nsems参数的值


2.semop:int   semop(int  semid,   struct  sembuf*  sops,   size_t  nsops)

     semid参数:semget产生的信号量标识符。

      struct sembuf:

struct sembuf {
short sem_num; // 要操作的信号量在信号量集里的编号,
short sem_op; // 信号量操作(一般是  1  或   -1)
short sem_flg; // 操作表示符(0  或   SEM_UNDO      或    IPC_NOWAIT)
};  (这样做应该是为了减少semop中的参数个数)

IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
IPC_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

        nsops参数:sops所指向的数组中的sembuf结构体的个数(表示要操作的信号个数)
        返回值:成功返回0,失败返回-1。


3.semctl:int  semctl(int  semid,   int  semnum,   int   cmd,  .  .  . )

        semid参数:semget产生的信号量标识符。

        semnum参数:对在信号集中的第semnum个信号进行cmd操作(从0开始)。

        cmd参数:semctl可能有三个或四个参数,这取决于cmd。

cmd:

IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID将信号量集从系统中删除
GETALL用于读取信号量集中的所有信号量的值,存于semnu的array中
SETALL 设置所指定的信号量集的每个成员semval的值
GETPID返回最后一个执行semop操作的进程的PID。
SETVAL把的val数据成员设置为当前资源数
GETVAL把semval中的当前值作为函数的返回,即现有的资源数,返回值为非负数。

         第四个参数:为一个用户自定义的联合体:

union semun
{
       int val;                                  // cmd == SETVAL
       struct semid_ds *buf        // cmd == IPC_SET或者 cmd == IPC_STAT
       unsigned short *array;                  // cmd == SETALL,或 cmd = GETALL

       struct seminfo  *__buf;  // cmd == IPC_INFO只有在linux下才有。

};

       返回值:错误时返回-1,成功时的返回值根据cmd的结果出来相应的结果。




信号量操作命令:

ipcs   -s  :查看

ipcrm   -s    semid:删除相应的sem



相关代码:

//com.h

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<string.h>
#define PATH "."
const int PROJ_ID = 0x777;

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

int get_sem(int num);  //获得一个有num个信号的信号集
int create_sem(int num);  //确保获得一个新创建的有num个信号的信号集
int init_sem(int semid, int which);
int P(int semid,int which);
int     V(int semid, int which);
int sem_del(int semid);
int show_sem_val(int semid, int sem_num);//展示信号集中的sem_num个信号的值



//....................................................................................................................................................................................
//com.c

#include"com.h"
static int com_sem_create(int num, int semflg)
{
    key_t key = ftok(PATH,PROJ_ID);
    int semid = semget(key, num, semflg);
    if(semid < 0 )
    {
        printf("senget  error!\n");
        return -1;
    }
}

int get_sem(int num)  //获得一个有num个信号的信号集
{
    return com_sem_create(num,IPC_CREAT);
}


int create_sem(int num)  //确保获得一个新创建的有num个信号的信号集
{
    return com_sem_create(num,IPC_CREAT | IPC_EXCL | 0666);//注意要加读写权限 后面的init要用
}

static int com_sem_op(int semid,short sem_op, int which)
{
    struct sembuf buf;
    buf.sem_num = which - 1;   //根据习惯来(下标从0开始)
    buf.sem_op = sem_op;
    buf.sem_flg = 0;

    if(semop(semid, &buf, 1) < 0)
    {
        printf("semop error!\n");
        return -1;
    }
    return 0;
}

int init_sem(int semid, int which)
{
   union semun init;
   init.val = 1;
   int ret = semctl(semid, which - 1,SETVAL, init);
   if(ret < 0)
   {
       perror("semctl");
       return -1;
   }
   return ret;
}

int P(int semid,int which)
{
    return com_sem_op(semid, -1, which);
}

int    V(int semid, int which)
{
    return com_sem_op(semid,1,which);
}

int sem_del(int semid)
{
    if(semctl(semid, 0,IPC_RMID,NULL) < 0)
    {
        printf("destory sem error!\n");
        return -1;
    }
    return 0;
}

int show_sem_val(int semid, int sem_num)//展示信号集中的sem_num个信号的值
{
    union semun semun;
    unsigned short* sem_list = (unsigned short*)malloc(sizeof(unsigned short)*sem_num);
    if (sem_list == NULL)
    {
        printf("malloc error\n");
        return -1;
    }
    memset(sem_list, '\0', sizeof(sem_list));
    semun.array =sem_list;

    int ret = semctl(semid, 0, GETALL,semun);
    if(ret < 0)
    {
         perror("semctl");
         return -1;
    }
    else
    {
        int i = 0;
        for(;i < sem_num;i++)
        {
            printf("sem[%d]: %d\n",i,semun.array[i]);
        }
    }
    free(sem_list);
    semun.array = NULL;
    return ret;
}

int main()
{
    int semid = create_sem(1);
    init_sem(semid,1);
    show_sem_val(semid,1);
    return 0;
}

执行结果:

wKiom1cQYd-i_dAFAAAs_3q1-P8747.png



//test.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include"com.h"
int  main()
{
     char * bufOne =  "<<" ;    
     char * bufTwo =  ">>" ;
     pid_t pid = fork();
     if (pid < 0)
     {
         perror ( "fork" );
         return  -1;
     }
     else  if (pid == 0)
     {
          while (1)
          {
             printf ( "%s " ,bufOne);
                fflush (stdout);
             sleep(6);
             printf ( "%s" ,bufTwo);
              fflush (stdout);
              sleep(2);
          }
    }
    else
    {
        char  *bufOne =  "(" ;
        char  *bufTwo =  ")" ;
        while (1)
        {
            printf ( "%s " ,bufOne);
            fflush (stdout);
            sleep(6);
            printf ( "%s" ,bufTwo);
            fflush (stdout);
            sleep(2);
        }
    }
     return  0;
}

执行结果:

wKioL1cQbFKQAzD7AAAdQW6cP98597.png

父进程和子进程都会争夺显示器这个资源。

注意:仔细看会发现后面顺序乱了。




在子进程和父进程中用信号量解决冲突:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include"com.h"
 
int  main()
{
     char * bufOne =  "<" ;    
     char * bufTwo =  ">" ;
     int  semid = create_sem(1);
     init_sem(semid,1);
 
     pid_t pid = fork();
     if (pid < 0)
     {
         perror ( "fork" );
         return  -1;
     }
     else  if (pid == 0)
     {   
          int  semid = get_sem(1); //注意这里要用get,因为父子进程要用同一个信号量
          while (1)
          {
             P(semid,1);
             printf ( "%s " ,bufOne);
                fflush (stdout);
             sleep(2);
             printf ( "%s" ,bufTwo);
              fflush (stdout);
              sleep(1);
             V(semid,1);
          }
 
          sem_del(semid);
    }
    else
    {
        char  *bufOne =  "(" ;
           char  *bufTwo =  ")" ;
        while (1)
        {
             P(semid,1);
            printf ( "%s " ,bufOne);
            fflush (stdout);
            sleep(2);
            printf ( "%s" ,bufTwo);
               fflush (stdout);
            sleep(1);
            V(semid,1);
        }
          
     }
     sem_del(semid);
     return  0;
}

wKioL1cQjgPAz_MjAAAosPQm4ho562.png










本文转自 ye小灰灰  51CTO博客,原文链接:http://blog.51cto.com/10704527/1764091,如需转载请自行联系原作者
目录
相关文章
|
4月前
|
消息中间件 存储 Linux
Linux进程间通信【消息队列、信号量】
Linux进程间通信【消息队列、信号量】
68 0
|
1月前
|
存储 Linux 程序员
Linux进程间通信(IPC)教程 Linux信号量:讲解POSIX信号量在Linux系统进程间通信中的编程实践
Linux进程间通信(IPC)教程 Linux信号量:讲解POSIX信号量在Linux系统进程间通信中的编程实践
21 1
|
6月前
|
Linux 程序员 数据安全/隐私保护
嵌入式 Linux进程间通信之信号量
嵌入式 Linux进程间通信之信号量
|
6月前
|
数据安全/隐私保护
计算机操作系统中实现进程间同步的信号量概念讲解
计算机操作系统中实现进程间同步的信号量概念讲解
49 0
|
8月前
|
消息中间件 缓存 算法
【Linux】进程间通信——system V共享内存 | 消息队列 | 信号量
system V共享内存、system V消息队列和system V信号量的介绍。
|
10月前
|
消息中间件 安全 Linux
Linux之进程间通信——system V(共享内存、消息队列、信号量等)(下)
Linux之进程间通信——system V(共享内存、消息队列、信号量等)(下)
86 0
|
10月前
|
消息中间件 存储 Linux
Linux之进程间通信——system V(共享内存、消息队列、信号量等)(上)
Linux之进程间通信——system V(共享内存、消息队列、信号量等)(上)
88 0
|
10月前
|
消息中间件 算法 Unix
进程间通信—管道,共享内存,消息队列,信号量
本文讲述了几种进程间通信的方式,包括具有亲缘关系进程使用的匿名管道,命名管道。systemV中共享内存,消息队列,还浅浅介绍了信号量概念,还有IPC资源之间的关系
11546 0
进程间通信—管道,共享内存,消息队列,信号量
|
11月前
|
消息中间件 存储 Linux
【Linux】进程间通信 --- 管道 共享内存 消息队列 信号量-1
【Linux】进程间通信 --- 管道 共享内存 消息队列 信号量-1
|
11月前
|
消息中间件 存储 负载均衡
【Linux】进程间通信 --- 管道 共享内存 消息队列 信号量
【Linux】进程间通信 --- 管道 共享内存 消息队列 信号量

相关实验场景

更多