Linux进程通信之信号量

Linux应用程序学习进程通信之互斥信号量

        这一节我学习了Linux进程通信中的互斥信号量的方式,相比于前面的学习来讲,信号量涉及的知识更多,也更复杂,因此在这一节我将尽量详细介绍信号量的知识。

在多进程操作编写程序时,我们总会发现有段临界代码,如果此时没有限制,任何进程都能访问这段区域,则会造成一种混乱,比方说有A和B两个进程,他们都想向一个文件写入内容,如果A写到一半睡眠了一会,B然后写,就会给文件内容造成混乱,因此信号量就是用来解决这类问题的。

        信号量类似于一种互斥锁,主要用途是保护临界资源,进程可以根据它来判定是否能够访问某些共享资源。举个例子来讲,有A和B两个同学,有一个座位,如果A先坐上了这个座位,B就不能坐,只能等待A走了才能坐,而如果B先坐了这个座位,则A必须等B走了才能坐。这是一个简单的二值信号量的例子,其中的A和B就相当于两个进程,座位就相当于一个信号量,每次只能有一个人能访问这个座位资源,因此是一种互斥的关系。当然我们还可能是计数信号量,也就是有多个信号,可由多个线程同时访问,但是在此我们只是重点说一下二值信号量。

我们通常采用下面的操作来获取共享资源:

(1)       测试控制该资源的信号量。

(2)       若此信号量的值为正,则该进程可以使用该资源。进程将信号量值减一,表示该进程占用一个资源单位。

(3)       若此次信号量的值为0,则进程进入休眠状态,直至信号量值大于0。

        当进程不再使用由一个信号量控制的共享资源时,该信号量值增1.如果有进程正在休眠等待此信号,则唤醒它们。

下面我们就来介绍一下linux中有关信号量的编程函数。

1)创建/获取一个信号量

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

        我们通常采用这个函数来创建一个新的信号量或者是获取一个已经存在的信号量。在介绍这个函数之前,我们首先了解一下IPC结构的特点,每个内核中的IPC结构都用一个非负整数的标识符加以引用,IPC标识符相当于引用ID号,要想使用IPC标识符来调用我们的IPC对象,就必须用IPC标识符来调用。常见的IPC结构有信号量,消息队列,共享存储段,但标识符是IPC对象的内部名,是内核动态提供的,用户无法让服务器和客户事先认可共同使用哪个描述符,所以还需要一个键值来作为该对象的外部名,好让多个合作进程能够在访问同一个IPC对象。说简单一点,就是当我们打开一个信号量操作的时候返回的是一个标识符,但是如何找到我们的信号量就需要通过我们的键值来完成了。那么我们就想问如何产生我们的键值?这里一般有两种方法:

1、 任意指定一个数。

    这种方法有个缺点就是这个数已经被别的IPC对象(消息队列,共享内存)所使用了,再与新建的信号量关联时就会失败。

2、 使用key_t  ftok(char *fname  , int  id)函数来产生。

         这个函数的第一个参数为一个路径名,在linux系统中,每一个文件都对应一个结构体,该结构体中包含了跟文件有关的数字,第二个参数为项目ID,取值在0~255之间。通过ftok函数,可将这两个值变换为一个键并返回。在ftok函数中,fname必须引用一个现存文件。但使用ftok函数也有一个问题就是如果服务进程使用ftok获取一个关键字后,该文件被删除,然后重建。此时客户进程以此重建后的文件来ftok获取关键字就喝服务进程关键字不一样。

 在我们的semget函数中,第一参数就是我们的键值,要打开一个信号量就必须指定一个键值。

 第二个参数意义是如果nsems=0,该函数就访问一个已存的信号量集合,如果nsems>0,则创建一个信号量集,表示指定集合中信号量的数量。

第三个参数是一个标记集合,表示信号的权限等,如果信号不存在可以指定为IPC_CREAT来创建一个信号量集。

该函可以获取信号量集合的标识符,但是当key参数所指定的信号量不存在的时候,并且semflg里面包含了IPC_CREAT时,就会创建一个信号量集合。

该函数成功则返回信号量集合的标识符,否则返回-1。

 

2)信号量操作函数:

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

       该函数用于操作信号量集合中的信号量,总共有三个参数,第一个参数就是我们前面由semget函数返回的信号量标识符,第二个参数为一个结构指针,我们知道一个信号量集合里面可能包含多个信号量,这个结构体就表示一个信号量的信息,第三个参数就指明了要操作的信号量,也就是第二个参数中结构体的个数,一个结构体对应一个信号量。该结构体原型如下:

  struct sembuf {

      unsigned short   sem_num;

      short           sem_op;

      short           sem_flg;

}

这个结构里面的成员的含义是:

   sem_num : 表示要操作的信号量在信号量集里的编号,其取值范围为0~nsems,其中nsems就是前面semget函数指定的创建信号量集的第二个参数。

   sem_op:    表示对信号量的操作,如果该信号为正数,则表示释放一个信号,如果为负数,则表示获取一个信号,如果此时信号不可利用,则需要等待。

   sem_flg : 通常设置为SEM_UNDO。表示操作系统会跟踪当前进程,如果进程终止信号却没有释放,这个标记就会使得操作系统自动释放这个信号量。

 

3)信号量控制函数:

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

这个函数是一个参数可变的函数,其第四个参数依赖于所请求的命令是可选的,如果使用该参数,其类型是semun,他是多个特定命令参数的联合,这个联合原型如下:

  union semun{

        int   val;

struct  semid_ds  *buf;

unsigned short    *array;

}

我们首先来介绍下控制函数的参数含义:

semid : 就是由semget函数返回的信号量标识符。

semun : 表示信号在信号集合中的序号。

cmd : 为要执行的命令,其决定了第四个参数是否需要。

cmd参数指定了10种命令中的一种,在semid指定的信号量集合上执行此命令,其中有5条命令是针对一个特定的信号量值的,用semnum指定该信号量集合中的一个成员。semnum值在0和nsums-1之间。有关cmd的命令取值我们可以在linux下面来进行查看。

在此我只简单介绍几个常用的命令:

    GETVAL  :   返回成员semnum的semval值。

    SETVAL  :    设置成员semnum的semval值,该值有上面的联合体中的val指定

 

以上便是信号量的一些相关的一些知识,感觉有些凌乱,笔者也是初学者,总是感觉词不达意,请原谅,下面就用一个程序来说明一下,或许会更好的理解。

这里用两个程序代码,也就是两个进程,要完成的操作是进程A向board.txt文件中写入“math class is cancle”,但是A不是一次写完,它先写了“math class”,然后休息了一段时间,再去写“is cancle”,B实现的是向该文件写入“Englishexam”。当A程序执行时,如果在其休眠期,B进程执行了,如果不加信号量保护临界区域,则会在board.txt文件中写入math class English exam is cancle.从而造成错误,之所以错误就是我们前面说到的,没有对共享代码段进行保护,导致任意进程都可以随时访问该区域,从而造成混乱,下面的程序就是采用了信号量的程序代码:

A进程代码
      #include <stdio.h>

#include <sys/types.h>

#include <fcntl.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <sys/sem.h>

 

void main()

{

    int fd;   

    int semid;

    key_t key;

    struct sembuf sops;

    int sem_value;

   

    /**************步骤一:创建信号量*****************/ 

   

      key = ftok("/home", 1);  //以/home/share路径配合ID1获取信号量的键值

      semid = semget(key , 1 ,IPC_CREAT);//创建包含一个信号量的信号量集

   

     // sem_value = semctl(semid , 0 ,GETVAL);

     //printf("the init value of sem is %d\n" , sem_value);

    

    semctl(semid , 0 , SETVAL , 1);

   

    /**************************************/

  

   

      fd = open("./board.txt" ,O_RDWR|O_APPEND); //打开一个文件

     

      /***************步骤二:获取信号量,需要操作信号量*****************/

       sops.sem_num = 0;

       sops.sem_op = -1;

       sops.sem_flg = SEM_UNDO;  //这三个参数表示获取编号为0的信号

       semop(semid , &sops , 1);

     /*************************************************/

       

     /*****  此即为临界代码段*****/             

      write(fd , "math class " ,11);

      sleep(15);

       

      write(fd , " is cancle ", 11);

     /*****/

     

      /***************步骤三:释放信号量,也需要操作信号量*****************/

       sops.sem_num = 0;

       sops.sem_op =  1;

       sops.sem_flg = SEM_UNDO;  //这三个参数表示释放编号为0的信号

       semop(semid , &sops , 1);

     /*************************************************/

         

      close(fd);

}

 

程序中标为红色的需要注意一下,在我的linux系统中,默认的信号量值是0,可以通过标红的一二句来进行打印查看,此时如果不设置信号量值,则任何进程都不能访问共享资源,需要我们自己通过程序设置,也就是使用标红的第三句话来进行设置,我这里将其值设置为1,作为一个二值信号量。

 

B进程代码

#include <stdio.h>

#include <sys/types.h>

#include <fcntl.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <sys/sem.h>

 

void main()

{

    int fd;

    int semid;

    key_t key;

    struct sembuf sops;

   

    /**************步骤一:创建信号量*****************/ 

   

     key =ftok("/home" , 1);  //由于与前一个进程要共享同一个资源,

                                    //因此键值必须相同才能访问到相同的信号量

     semid = semget(key , 1 ,IPC_CREAT);//由于上一个进程创建过,因此这里不会创建,不过第三个参数不影响

       

   /**************************************/

            

    fd =open("./board.txt" ,O_RDWR|O_APPEND);

         

    /***************步骤二:获取信号量,需要操作信号量*****************/

       sops.sem_num = 0;

       sops.sem_op = -1;

       sops.sem_flg = SEM_UNDO;  //这三个参数表示获取编号为0的信号

       semop(semid , &sops , 1);            

     /*************************************************/

     

    write(fd , "Englishexam " , 13);  //这就属于我们的临界代买段区域

        

    /***************步骤三:释放信号量,也需要操作信号量*****************/

       sops.sem_num = 0;

       sops.sem_op =  1;

       sops.sem_flg = SEM_UNDO;  //这三个参数表示释放编号为0的信号

       semop(semid , &sops , 1);

         

     /*************************************************/

    

    close(fd);                 

}

 

       我们可以先运行A进程,然后迅速运行B进程(防止A进程睡眠结束),此时应该是A和B两个进程都在等待,然后A运行结束,B也运行结束。之所以A和B进程都等待,相信看到此,我们也明白了,A等待是因为调用了sleep函数,自主睡眠,而B进程等待是因为A    进程占用了信号量,此时B不能访问临界资源,需要等待A释放该信号量之后才能继续运行,因此才会一直等待。

以上就是Linux进程通信中信号量的一些知识,希望得到指教。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值