weiqi7777

进击吧,linux(十五) 信号量互斥编程

0
阅读(2813)

 

           Linux进程和进程之间有多种通讯方式。Linux进程间通讯的主要方式有:

1、  无名管道

2、  有名管道

3、  信号

4、  消息队列

5、  共享内存

6、  信号量

7、  套接字

信号通讯包括有信号量互斥。信号量可以认为是一个资源,当一个进程使用该信号量后,如果信号量互斥的话,那么其他进程就不能使用该信号量,要直到等待使用该信号量的进程结束后,才能使用该信号量。

信号量编程主要是有三个函数。

8.1 创建/打开信号量集合

8.1.1 函数名

    semget

8.1.2 函数原形

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

8.1.3 函数功能

    获取一个信号量的集合标识符,当key指定的信号量不存在的时候并且semflg包含了IPC_CREAT,就会创建一个信号量集合。

8.1.4 所属头文件

    <sys/types.h> <sys/ipc.h>  <sys/sem.h>

8.1.5 返回值

    成功: 信号量集合标识符

    失败: -1

8.1.6 参数说明

    key 打开的信号量的键值,使用ftok(char *,int )创建的键值

nsems:指定信号量集合包含的信号量个数

semflg:标志

-IPC_CREAT:KEY所关联的信号量不存在,就创建信号量

8.2 操作信号量

8.2.1 函数名

    semop

8.2.2 函数原形

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

8.2.3 函数功能

    对信号量操作,申请或者释放信号量

8.2.4 所属头文件

    <sys/types.h> <sys/ipc.h> <sys/sem.h>

8.2.5 返回值

    成功: 0

    失败: -1

8.2.6 参数说明

semid:  要操作的信号量集合的标识符

sops:      信号量操作的数组,每一个数组元素对应信号量的操作

struct sembuf{

    unsigned short sem_num; /*semaphore number*/

    short sem_op;      /*semaphore operation*/

    short sem_flg; /*operation flags*/

}

sem_num: 对信号量集合里面的哪些信号操作

sem_op: 信号量的操作,正数表示释放信号量,负数表示请求信号量,如果没有请求到信号量,那么进程就阻塞,等到有信号量为止。

sem_flg:

-SEM_UNDO:当信号操作失败,系统会自动的释放掉信号量

-SEM_NOWAIT:当信号操作造成进程阻塞,不阻塞程序,继续执行

nsops: 要操作多少个信号量

8.3 控制信号量

8.3.1 函数名

           semctl

8.3.2 函数原型

       int semctl(int semid, int semnum, int cmd, …)

8.3.3 函数功能

       cmd决定对信号量集合的操作

8.3.4所属头文件

   <sys/types.h> <sys/ipc.h> <sys/sem.h>

8.3.5返回值

    失败:-1

    成功:根据不同的命令返回不同的值

       GETVAL: 返回信号量的值

8.3.6 参数说明

    semid:要操作的信号量集合的标识符

    semnum 命令的参数

clip_image002

可以看出,这个参数就是一个联合体。4个字节大小。根据不同的命令,填入不同的值。

cmd 命令

SETVAL:   设置信号量的值

GETVAL:       获取信号量的值

可选参数,根据命令,有不同的参数


在信号量中,有一个很重要的东西,就是键值。Linux将信号量集合和一个键值给关联起来,键值其实就是一个整数。当键值确定了,那么对应的信号量集合就知道了,就可以打开、创建或者操作信号量集合了。一个信号量集合可以包含多个信号量。

对于这个键值,有两种方法来指定:

1、  任意指定一个数

如果这个数已经被其他的IPC (Internet Process Connection)所使用,那么就会在关联信号量集合时失败,所以这种方法不推荐

    2、使用key_t ftok( char * fname, int id )来创建一个键值

参数第一个是一个目录的路径,第二个是一个任意的数字

以下就写程序来学习一下怎么使用信号量互斥。这里,假设有两个进程AB都想向屏幕上打印数据,但是B进程必须要等待A进程打印完后才能打印。

首先是A进程

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<stdio.h>
void main()
{
    key_t key;
    int semid;
    int n;
    struct sembuf sops[2];
    /*创建键值*/
    key = ftok("/home",3); 
    /*创建并打开信号量*/
    semid = semget(key,2,IPC_CREAT);
    /*设置信号量*/
    semctl(semid,0,SETVAL,1);
    semctl(semid,1,SETVAL,1);
    /*读取信号量的值*/
    n = semctl(semid,0,GETVAL);
    printf("%d\n",n);
    n = semctl(semid,1,GETVAL);
    printf("%d\n",n);
    /*获取信号量*/
    sops[0].sem_num = 0;
    sops[0].sem_op = -1;
    sops[0].sem_flg = SEM_UNDO;
    sops[1].sem_num = 1;
    sops[1].sem_op = -1;
    sops[1].sem_flg = SEM_UNDO;
    semop(semid,&sops[0],2);
    /*打印*/
    printf("hello ");
    /*暂停,休眠8秒钟*/
    sleep(4);
    /*打印*/
    printf("weiqi7777\n"); 
    /*释放信号量*/
    sops[0].sem_num = 0;
    sops[0].sem_op = 1;
    sops[0].sem_flg = SEM_UNDO;
    sops[1].sem_num = 1;
    sops[1].sem_op = 1;
    sops[1].sem_flg = SEM_UNDO;
    semop(semid,&sops[0],2);   
}

在程序中,首先使用ftok创建了一个键值。使用的第一个参数是/home目录,第二个是随意一个数字。

然后创建一个信号量集合。信号量集合包含了2个信号量。信号量分别对应struct sembuf sops[0]struct sembuf sops[1]

semctl,是一个控制信号量函数,这里是设置信号量集合中信号量的值,因为信号量集合是包括2个信号量的。所以就是依次设定信号量1和信号量2的值都是1。然后再对信号量的值读取,打印出来。对于信号量互斥,信号量的值要设置为1,表示同一时刻只有一个进程可以申请到资源。

然后是对信号量进行获取,其实就是填写结构体,因为是对2个信号量的操作,所以要填写两个结构体的值。

然后调用semop函数,进行信号量获取。这里要注意,第二个参数要用&sops[0]或者是sops,不能是&sops。这个是牵涉到C语言指针的知识。

打印数据,最后释放信号量。

然后是B进程:

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
void main()
{
    key_t key;
    int semid;
    int n;
    struct sembuf sops[2];
    /*创建键值*/
    key = ftok("/home",3);
    /*打开信号量*/
    semid = semget(key,2,IPC_CREAT);
    /*读取信号量值*/
    n = semctl(semid,0,GETVAL);
    printf("%d\n",n);
    n = semctl(semid,1,GETVAL);
    printf("%d\n",n);
    /*获取信号量*/
    sops[0].sem_num = 0;
    sops[0].sem_op = -1;
    sops[0].sem_flg = SEM_UNDO;
    sops[1].sem_num = 1;
    sops[1].sem_op = -1;
    sops[1].sem_flg = SEM_UNDO;
    semop(semid,&sops[0],2);   
    /*打印*/
    printf("http://blog.chinaaet.com/weiqi7777\n");
    /*释放信号量*/
    sops[0].sem_num = 0;
    sops[0].sem_op = 1;
    sops[0].sem_flg = SEM_UNDO;
    sops[1].sem_num = 1;
    sops[1].sem_op = 1;
    sops[1].sem_flg = SEM_UNDO;
    semop(semid,&sops[0],2);
}

对于B进程,要注意的是,创建的键值要和A进程创建的进程一致,这样,两个进程才会关联到同一个信号量集合,才能对信号量集合进行一致操作。

B进程获取信号量,因为A进程已经获取信号量了,所以B进程就会获取失败,然后B进程就会被阻塞掉。直到能获取到信号量为止。当A进程释放信号量后,B进程就获取到信号量了,然后执行自己的程序,最后释放信号量。

程序执行效果:

clip_image004

A进程首先执行,创建信号量并设置信号量,然后获取信号量。B进程再执行,但是获取信号量失败,所以B进程就阻塞。

clip_image006

A进程释放信号量后,B进程就会获取到信号量,然后B进程继续执行。这样,就实现了信号量的互斥操作。

互斥操作,主要协调多个进程对同一个资源的使用。比如对于串口,只有1个,有多个进程都要访问串口,那肯定不能大家同时去操作串口,所以就可以用信号量同步,给串口设置一个互斥信号量,只有获取到这个信号量的进程,才能对串口进行操作。这样,就防止了多个进程同时对一个资源进行控制了。