【Linux】进程间通信--信号量

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ArchyLi/article/details/79164132

一、信号量简介

信号量和之间的进程间的通信(命名管道,匿名管道、消息队列)不同,它不是用来传递数据的,它是一个计数器,用在多个进程提供对共享数据对象的访问,使得资源在一个时刻只有一个进程或者线程所拥有。那么共享内存的访问就存在了两种问题,一种是互斥的访问,一种是同步的访问。

在介绍同步和互斥的操作的之前,我们来介绍一下临界区和临界资源的概念。

  • 临界资源:同一时刻只能被一个进程同时使用,也就是不可被多个进程所共享。
  • 临界区:是一段涉及到了互斥资源的程序代码段,即用锁加上的部分。

因此这个我们对于这个临界资源的访问便有了同步访问和互斥访问两种方式。

  • 同步:同步就是每个进程对这个资源的占用是有先后顺序的
  • 互斥:互斥访问不可共享临界资源。

二、信号量的含义

可以说信号量是一种特殊的变量,程序对于信号量的访问都是原子操作,且只允许它进行等待和发送操作。

struct semaphore
{
    int value;
    pointer_PCB queue;
}

信号量被定义为含有一个整型数据的结构体,其整型值可以大于小于和等于0,他们分别又代表了不同的含义。

  • 大于0:表示可提供给进程使用的资源个数
  • 小于0:表示其等待队列中正在等待使用临界资源的进程数目
  • 等于0:表示没有临界资源可供使用同时没有等待的进程

信号量是有初值的,一旦当我们申请使用信号量,我们则通过P操作对信号量进行减一的操作,一旦当我们的信号量减到0的时候就表示我们没有资源了,其他进程如果想要访问这个临界资源就必须等待,当这个正在拿到临界资源的进程执行完毕,就要执行V操作来对信号进行增加1的操作。

三、PV操作

(1)PV操作的含义

信号量的值是可以进行更改的,也只能由P和V操作来访问,同时P、V操作在执行的过程中不可中断

因此我们可以对之前的同步和互斥的定义再做一个说明:

  • 互斥:P、V操作在同一个进程中
  • 同步:P、V操作不再同一个进程中

(2)P操作

P操作表示申请一个资源,因此要将该信号量的值减去1,如果结果小于0,则调用P操作的进程就进入等待该资源的阻塞队列中。

(3)V操作

V操作表示释放一个资源,因此要将该信号量的值加上1,如果结果不大于0,则从该资源的阻塞队列的首部唤醒一个进程插入就绪队列中。

四、Linux下信号量机制

(1)创建或取得信号量

可以用来控制多个信号量。它是非原子操作。

在system V版本的信号量创建的时候有缺点,是因为由于我们创建的信号还没设置为分开的,此时如果是两个进程,一个进程用来创建,一个进程用来设置,这个时候就会出现错误。

函数原型

int semget(key_t key, int nsems, int semflg);

头文件:
<sys/sem.h>

参数:

  • key:是一个整数,并且唯一且非零,其他的进程可以通过它访问一个信号量。
  • nsems:指的是创造出几个信号量,可以选择一个,如果选择多个,则是信号量集
  • semflg:是一组标记,一般我们选择用IPC_CREAT和IPC_EXCL两个参数,表示创建一个新的且唯一的信号量,如果此信号量存在,则创建失败。

返回值:
成功返回信号量集的标识符,失败返回-1。

例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h> 

int main()
{
    int id = semget(1234,1,IPC_CREAT|0644);
    if(id == -1 )
    perror("semget"),exit(1);
    printf("ok\n");
}

我们可以用ipcs -s查看当前信号量

[root@localhost sem]# vim 01cret.c
[root@localhost sem]# gcc 01cret.c -ocreat
[root@localhost sem]# ./creat 
ok
[root@localhost sem]# ipcs -s

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x00000000 0          root       600        1         
0x00000000 32769      root       600        1         
0x000004d2 65538      root       644        1     

如果我们想删除信号量用ipcrm -S 信号量Key

注意删除的时候是大S,同时信号量的删除和消息队列的删除是不一样的,它是想删就删除

[root@localhost sem]# ipcrm -S 0x4d2
[root@localhost sem]# ipcs -s

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x00000000 0          root       600        1         
0x00000000 32769      root       600        1   

(2)控制信号量函数semctl

这个函数用来控制信号量的相关信息

函数原型:

int semctl(int semid, int semnum, int cmd, ...);

头文件:
<sys/sem.h>

参数:

前面两个参数和前面一个函数的参数是一样的,cmd参数通常是下面两个值中的一个:

  • SETVAL:把信号量初始化为一个已知的值,其中P的值是通过联合体semun中val成员设置
  • IPC_RMID:用于删除一个不用继续使用的信号量标号

虽然从函数原型看我们没有看到有第四个参数,因为第四个参数是一个可选参数,是否使用这个参数取决于所请求的命令。如果使用该参数,则其类型是一个联合体。

所以我们必须要设置和下面这个联合体类似的东西。

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) */
};

返回值:
成功返回正数,失败返回-1。

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>

union semun{
    int val;
};
 int sem_setval(int id,int val)
 {

     union semun su;
     su.val = val;
     return semctl(id, 0 ,SETVAL,su);
 }
 int sem_getval(int id)
 {
     return semctl(id,0,GETVAL);
 }
 int main()
 {
     int id = semget(1234,1,IPC_CREAT|0644);
     if(id == -1 )
         perror("semget"),exit(1);
     printf("%d",sem_getval(id));
 }

(3)执行信号量集合的操作数组semop

semop函数用来改变信号量的值
函数原型:

int semop(int semid, struct sembuf *sops, unsigned nsops);

头文件:
<sys/sem.h>

参数:

  • semid:是由semget返回的信号量标识符。
  • sops:指向一个struct sembuf的指针。
  • nsops:表示想同时操作几个信号量

其中sembuf的结构是

struct sembuf{
    unsigned short sem_num;  /* semaphore number */信号量集中信号量下标
    short sem_op;   /* semaphore operation */P操作-1,V操作+1short sem_flg;  /* operation flags */0
};

返回值:
成功返回,失败返回-1。

例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>

int sem_p(int id)
{
   struct sembuf sb[1];
    sb[0].sem_num = 0;
    sb[0].sem_op = -1;
    sb[0].sem_flg = 0;
    return semop(id,sb,1);
}

int sem_v(int id)
{
    struct sembuf sb[1];
    sb[0].sem_num = 0;
    sb[0].sem_op = 1;
    sb[0].sem_flg = 0;
    return semop(id,sb,1);
}
int main(void)
{
    int id = semget(1234,0,0);
    if(id == -1)
        perror("semget"),exit(1);
    sem_p(id);
}

猜你喜欢

转载自blog.csdn.net/ArchyLi/article/details/79164132