Linux(高级编程)9————进程间通信6(信号量)

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

信号量是是什么?
信号量是进程间通信方式之一,用来实现进程间的同步与互斥。信号量的原理是一种数据操作锁的概念,它本身不具备数据交换的功能,而是通过控制其他通信资源(如文本、外部设备等)来实现进程间通信。信号量本身不具备数据传输的功能,他只是一种外部资源的标识。
信号量的本质是:具有等待队列的计数器。
相关概念介绍:
临界资源:多个进程或线程可以共享的资源。
临界区:对临界资源进行操作的代码。
同步:访问临界资源的时序可控性。
互斥:同一时间访问临界资源的唯一性。
为了获得共享资源,进程需要执行以下步骤:
1.测试控制该资源的信号量
2.若此信号为正,当进程对信号量做减1操作时,表明它使用了一个单位的资源。
3.如此信号量的值为0,则进程进入休眠状态,直至信号量值大于0时。进程被唤醒后再执行第1步。
4.当进程不再使用该信号量控制的共享资源时,该信号值增加1.如果此时有等待此信号量的进程,则唤醒它。
注意:常用的信号量形式被称为二元信号量或双态信号量。它控制单个资源,初值为1。其实信号量初值可以为任一正值,该值说明有多少个共享资源单位可供使用。信号量是对临界资源的一种保护机制,所以对它操作是不能被打断的即原子性,而这些都是内核帮我们做好的。
信号量的操作步骤:

  • 信号量创建:
int semget(key_t key, int nsems, int semflg);

参数:
key:操作系统对该信号量的标识。
nsems:创建该信号量集合中的信号量数。
semflg:IPC_CREAT、IPC_EXCL等(创建标记)。
返回值:成功返回信号量操作句柄semid;失败返回-1,并设置errno。

  • 信号量的控制:
 int semctl(int semid, int semnum, int cmd, ...);

参数:
semid:信号量ID
semnum:只要操作的第几个信号量。
cmd:为具体所要进行的操作,常用的操作:SETVAL设置单个信号量的初值。
SETALL:设置所有信号量的初值,此时的semnum会被忽略。
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) */
          };

当我们要控制信号量的时候,必须手动定义这个联合体。
val:是对信号量所要赋的初值。
buf:关于信号量的一些详细信息存放在buf中
array:信号量集,用来设置信号量集中的信号量或获取。

  • 操作信号量:
int semop(int semid, struct sembuf *sops, unsigned nsops);

参数:
semid:所要操作信号量集的ID。
sops:对该信号量集的操作(下面详细说明sembuf)。
nsops:要操作的信号数量。
返回值:成功返回0;失败返回-1,并设置errno。
struct sembuf:这个结构体在我们PV操作时会用到。

struct sembuf
{
  unsigned short int sem_num;   /* semaphore number */
  short int sem_op;             /* semaphore operation */
  short int sem_flg;            /* operation flag */
};

数据成员:
sem_num:所要操作第几个信号量
sem_op:所要做的操作,赋值为-1表示P操作(资源申请),赋值为1表示V操作(资源释放)。
short_flag:通常设置为SEM_UNDO,使得操作系统能够跟踪信号量,并在没有释放信号量时自动释放。
下面我们可以通过一个小案例来实现一个同步互斥的例子:
案例1:加入现在我们有一个人是专门生产糖果的,还有一个人是专门吃糖果的,那么这两个人就必须遵守一个时序(吃糖果的人必须要在生糖果人生产出糖果后才能吃到糖果),这用专业术语就叫对同一资源访问的时序性控制。程序说明:父进程(生产糖果的人)、子进程(吃糖果的人)
sync.c(同步):

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/sem.h>
#define KEY 0x01234567

union semun 
{
        int val;
        struct semid_ds *buf;
        unsigned short *array;
};
//PV操作
void sem_p(int semid)
{
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = -1;
        buf.sem_flg = SEM_UNDO;
        semop(semid,&buf,1);
}

void sem_v(int semid)
{
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = 1;
        buf.sem_flg = SEM_UNDO;
        semop(semid,&buf,1);
}

int main()
{
        //1.创建1个信号量
        int semid = semget(KEY,1,IPC_CREAT|0664);
        if(semid<0)
        {
                perror("semget error!\n");
                return 1;
        }
        //同过父子进程完成同步操作
        if(fork()==0)
        {
                while(1)
                {
                        sem_p(semid);
                        printf("吃了一颗糖!!\n");
                        sem_v(semid);
                }
        }
        else
        {       //2.设置信号量初值,需要定义一个union semun
                union semun sem;
                sem.val = 1;
                if(semctl(semid,0,SETVAL,sem)<0)
                {
                        perror("semctl error!");
                        return 2;
                }

                while(1)
                {
                        sem_p(semid);
                        printf("生产了一颗糖!!\n");
                        sleep(1);
                        sem_v(semid);
                }
        }
         //3.最后要释放信号量
        semctl(semid,0,IPC_RMID);
       return 0;
}

效果展示:
在这里插入图片描述
案例2:父子进程争抢显示器资源父进程打印B然后睡上1毫秒再打印个B、子进程打印A然后睡上1毫秒再打印个A。案例分析:如果我们不对显示器资源进行保护,那么可能在父子进程休眠的过程中,双方都对显示器资源操作,此时打印出来的将是 父子进程数据的交错信息。
print.c:

#include<stdio.h>
#include<unistd.h>

int main()
{
        pid_t pid = -1;
        if((pid = fork())<0)
        {
                perror("fork!");
                return 1;
        }
        if(pid == 0)
        {
                while(1)
                {
                        printf("A");
                        fflush(stdout);
                        usleep(1000);
                        printf("A ");
                        fflush(stdout);
                }
        }
        else if(pid>0)
        {
                while(1)
                {
                        printf("B");
                        fflush(stdout);
                        usleep(1000);
                        printf("B ");
                        fflush(stdout);
                }
        //3.最后要释放信号量
        semctl(semid,0,IPC_RMID);
       return 0;
        }
}

不对显示器资源进行保护打印的结果将会是这样:
在这里插入图片描述

此时我们看到父子进程交叉进行,而没有做到(同一时刻对共享资源的唯一访问性即就是互斥)。
**下面通过互斥来保护同一时刻只能有一个进程访问显示器。

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/sem.h>
#define KEY 0x01234567

union semun 
{
        int val;
        struct semid_ds *buf;
        unsigned short *array;
};
//PV操作
void sem_p(int semid)
{
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = -1;
        buf.sem_flg = SEM_UNDO;
        semop(semid,&buf,1);
}

void sem_v(int semid)
{
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = 1;
        buf.sem_flg = SEM_UNDO;
        semop(semid,&buf,1);
}

int main()
{
        //1.创建1个信号量
        int semid = semget(KEY,1,IPC_CREAT|0664);
        if(semid<0)
        {
                perror("semget error!\n");
                return 1;
        }
        //2.设置信号量初值,需要定义一个union semun
        union semun sem;
        sem.val = 1;
        if(semctl(semid,0,SETVAL,sem)<0)
        {
                perror("semctl error!");
                return 2;
        }
        //同过父子进程完成同步操作
        if(fork()==0)
        {
        
                while(1)
                {
                        sem_p(semid);
                        printf("A");
                        fflush(stdout);
                        usleep(1000);
                        printf("A ");
                        fflush(stdout);
                        sem_v(semid);
                }
        }
        else
        {
        
                while(1)
                {
                        sem_p(semid);
                        printf("B");
                        fflush(stdout);
                        usleep(1000);
                        printf("B ");
                        fflush(stdout);
                        sem_v(semid);
                }
        //3.最后要释放信号量
        semctl(semid,0,IPC_RMID);
        return 0;
        }
}

运行效果展示:
在这里插入图片描述
此时我们看到A、B是成双成对出现的说明同一个时刻父子进程只有一个在操作显示器(输出设备)。
通过对信号量的学习,我们就可以对上次共享内存的server&client案例进行改进了,这里就不在演示了,后面有机会在更新,这里关于信号量重点已经讲完。

猜你喜欢

转载自blog.csdn.net/FangXiaXin/article/details/84488321