Linux进程间通信2——信号量1

目录

1.信号量的引例

2.信号量的介绍

3.信号量的接口介绍

3.1 semget

3.2 semop

3.3 semctl

4.利用信号量解决引例中存在的问题

5.ipcs命令


1.信号量的引例

不加控制模拟使用打印机:

比如:进程a和进程b模拟访问打印机,进程a输出第一个字符‘a’表示开始使用打印机,输出第二个字符‘a’表示结束使用,b进程操作与a进程操作相同。

思路:

Linux进程间通信2——信号量1_第1张图片

具体代码如下:

//a.c
#include 
#include 
#include 
#include 

int main()
{
    int i=0;
    for(;i<5;i++)
    {   
        printf("A");//开始使用
        fflush(stdout);
        int n=rand()%3;
        sleep(n);//模拟使用打印机
        printf("A");//使用结束
        fflush(stdout);
        n=rand()%3;
        sleep(n);//使用结束后去做其他事情
    }   
    
}
//b.c
#include 
#include 
#include 
#include 

int main()
{
    int i=0;
    for(;i<5;i++)
    {   
        printf("B");//开始使用
        fflush(stdout);
        int n=rand()%3;
        sleep(n);//模拟使用打印机
        printf("B");//使用结束
        fflush(stdout);
        n=rand()%3;
        sleep(n);//使用结束后去做其他事情
    }   
    
}

Linux进程间通信2——信号量1_第2张图片

其实,正确的结果应该是:

由于打印机同一时刻只能被一个进程使用,所以输出结果不应该出现ABAB这样交替的结果;我们上面得到的都是错误的结果。

2.信号量的介绍

信号量就是控制某个进程能够对某个资源进行访问;保证同一时刻只能由一个进程对某个资源进程访问

信号量是一个特殊的变量,对信号量的操作都是一个原子操作(不能被打断的操作);

打印机

信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子减一,该操作被称为P操作。当信号量为0时,代表没有资源可用,P操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作称为V操作(V操作不会阻塞)。信号量主要用来同步进程。信号量的值如果只取0,1,将其称为二值信号量。如果信号量的值大于1,则称之为计数信号量。

注意:正数值;加1,减1是一个原子操作。

临界资源:是同一时刻,只允许被一个进程或者线程访问的资源(硬件,例如打印机);

临界区:访问临界资源的代码段(软件);

3.信号量的接口介绍

3.1 semget

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

创建或者获取一个已经存在的信号量;

key:两个进程使用相同的key值,就可以使用同一个信号量;

nsems:创建几个信号量;

semflg:标志位;如果为创建:IPC_CREAT;

如果为全新创建,也就是不知道是否有人创建过,则 IPC_CREAT|IPC_EXCL,就是如果没有则创建,如果有则创建失败;

返回值为信号量的id号;

3.2 semop

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

对信号量进行改变,做P操作或者V操作;

semid:信号量的id号,也就是刚才semget的返回值,说明对哪个信号量进行操作;

sops:结构体指针,指向sembuf的结构体指针,sembuf结构体有三个成员变量:sem_num表示信号量的编号(即指定信号量集中的信号量下标);sem_op表示是p操作还是v操作;1为v操作(加1),-1为p操作(减1);sem_flg为标志位;

nsops:结构体的大小;

3.3 semctl

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

对信号量进行控制:初始化/删除信号量

semid:信号量id;

semnum:信号量编号;

cmd:命令:SETVAL:初始化信号量;       IPC_RMID:删除信号量;

...:如果是初始化,是信号量的初始值;如果是删除信号量,是空,不写。

注意:联合体semun,这个联合体需要自己定义;

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:信号量的值;其它成员为进程间通信的一些信息。

4.利用信号量解决引例中存在的问题

思路:

Linux进程间通信2——信号量1_第3张图片

代码如下:

//sem.h
#include 
#include 
#include 

union semun{
    int val;
};

void sem_init();
void sem_p();
void sem_v();
void sem_destory();
//sem.c
#include "sem.h"

static int semid=-1;

void sem_init()
{
    semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
    if(semid==-1)//可能是信号量已经被创建过,此时只需要获取已创建的信号量id即可
    {   
        semid=semget((key_t)1234,1,0600);
        if(semid==-1)//真正的失败,发生概率较小
        {
            perror("semget error!\n");
        }
    }   
    else//全新创建成功,需要初始化
    {   
        union semun a;
        a.val=1;
        if(semctl(semid,0,SETVAL,a)==-1)
        {
            perror("semget error");
        }
    }   
}

void sem_p()
{
    struct sembuf buf;
    buf.sem_num=0;//信号量编号
    buf.sem_op=-1;//P操作
    buf.sem_flg=SEM_UNDO;//UNDO保证安全,记下p操作,如果发生异常自动释放,防止死锁
    if(semop(semid,&buf,1)==-1)
    {
        perror("p error");
    }
}


void sem_v()
{
    struct sembuf buf;
    buf.sem_num=0;//信号量编号
    buf.sem_op=1;//V操作
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        perror("v error");
    }
}

void sem_destory()
{
    if(semctl(semid,0,IPC_RMID)==-1)//0表示占位,创建时一起创建,销毁时一起销毁
    {
        perror("destory sem error!\n");
    }

}

//a.c
#include 
#include 
#include 
#include 
#include "sem.h"

int main()
{
    int i=0;
    sem_init();
    for(;i<5;i++)
    {   
        sem_p();
        printf("A");//开始使用
        fflush(stdout);
        int n=rand()%3;
        sleep(n);//模拟使用打印机
        printf("A");//使用结束
        fflush(stdout);
        sem_v();//PV操作之间为临界区,尽可能在满足要求的前提下使临界区小
        n=rand()%3;
        sleep(n);//使用结束后去做其他事情
    }   

    sleep(10);//保证a是最后一个需要使用资源的,使用完毕后,销毁信号量
    sem_destory();//销毁只能由一方销毁

    
}
//b.c
#include 
#include 
#include 
#include 
#include "sem.h"
int main()
{
    int i=0;
    sem_init();
    for(;i<5;i++)
    {   
        sem_p();
        printf("B");//开始使用
        fflush(stdout);
        int n=rand()%3;
        sleep(n);//模拟使用打印机
        printf("B");//使用结束
        fflush(stdout);
        sem_v();
        n=rand()%3;
        sleep(n);//使用结束后去做其他事情
    }   
    
}

 运行结果:

Linux进程间通信2——信号量1_第4张图片

5.ipcs命令

  • ipcs -s:只查看信号量
  • ipcs -m:只查看共享内存
  • ipcs -q:只查看消息队列
  • ipcrm  删除操作:例如:ipcrm -s semid;

重启一下,信号量等自动删除;

上述例子的信号量销毁前:

Linux进程间通信2——信号量1_第5张图片 信号量销毁后:

Linux进程间通信2——信号量1_第6张图片

 

你可能感兴趣的:(Linux,linux)