进程控制理论<四>--消息队列和信号量

来源:互联网 发布:打击网络谣言法律依据 编辑:程序博客网 时间:2024/06/06 12:29

该文章参考于http://blog.csdn.net/yx_l128125/article/details/7688312,中间增加信号灯内容,在此先感谢这位大哥。

一、消息队列简介

      1、 定义unix早期通信机制之一的信号能够传送的信息量有限,管道则只能传送无格式的字节流,这无疑会给应用程序开发带来不便。消息队列,则克服了这些缺点。

 

       2、发展:消息队列,就是一个消息的链表。可以把消息看做一个记录,具有特定的格式。进程可以向其中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息。

 

        3、分类:

              主要有两种类型的消息队列:

              (1)POSIX消息队列;

              (2)系统V消息队列;(目前系统V消息队列目前被大量使用)

               系统V消息队列是随内核持续的,只有在内核重启或者人工删除时,该消息队列才会被删除。

二、消息队列函数

2.1键值

       消息队列的内核持续性要求每一个消息队列都在系统范围内对应一个唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值。

       头文件:   #include <sys/types.h>

                           #include <sys/ipc.h>

      函数原型: key_t  ftok(char*pathname,  char proj)

      参数:        1、Pathname:文件名

                          2、Proj:项目名(不为0即可)

      返回值: 成功,返回文件对应的键值。失败,返回-1。

2.2打开/创建(消息队列)

     头文件:     #include <sys/types.h>

                          #include <sys/ipc.h>

                         #include <sys/msg.h>

      函数原型:int  msgget(key_t key ,int msgflg)

      参数:       1、key:消息队列的键值,由ftok获得。

                         2、msgflg:操作类型,也可用于设置消息队列的访问权限。两者通过 '|'连接表示。

                                             例如:IPC_CREAT |0x777  表示创建新的队列 并且设置消息队列文件的属性0x777(对usr文件属主、group用户组、other其他用户都可读、可写、可执行)。

      返回值:   成功,返回与键值key相对应的消息队列描述符。失败,返回-1。

      操作类型有下面几种情况:

       (1)IPC_CREAT :创建新的消息队列

       (2)IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。

       (3)IPC_NOWAIT:读写消息队列要求无法得到满足时(如:队列已经满了,没法写进去;或者队列已经空了,没法在读时),不阻塞和。

      注:在以下两种情况下,将创建一个新的消息队列:

               (1)如果没有与键值key相对应得消息队列,并且msgflg中包含了IPC_CREAT标志位。

               (2)Key参数为IPC_PRIVATE

  创建消息队列的例程:

int open_queue(key_t keyval)

{

     int qid;

     if((qid=msgget(keyval,IPC_CREAT))==-1) //若找不到与键值keyval对应的消息队列,则创建一个新的消息队列

     {

          return(-1);

      }

    return(qid); //把消息队列返回回来

}

2.3发送消息

      功能:向消息队列发送一条消息

      头文件:     #include <sys/types.h>

                           #include <sys/ipc.h>

                           #include <sys./msg.h>

       函数原型:int msgsnd(int msqid,struct msgbuf *msgp,int msgsz ,int msgflg)

       参数: 

                           1、msqid :已打开的消息队列描述符ID

                           2、msgp :存放消息的结构指针

                           3、msgsz :消息数据长度

                           4、msgflg:处理方式,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够的空间容纳要发送的消息时,msgsnd是否等待。

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

       消息格式:

Struct msgbuf

{

    long   mtype;   //消息类型>0

     char  mtext[1];  //消息数据的首地址

}

发送程序:

1、获取消息队列的键值

        key=ftok("/tmp/2",'a');
 2、创建消息队列

        /*用msgget去打开消息队列key,但消息队列/tmp/2不存在,所以IPC_CREAT会去创建这个文件*/
        msgid=msgget(key,IPC_CREAT|0666); /*通过文件对应*/
         if(msgid==-1)/*创建失败*/
          {
                 printf("create error\n");
                 return -1;
           }
        msgbuf.mtype = getpid();/*为这条消息去设置类型,类型编号=进程的ip*/
        strcpy(msgbuf.data,"test haha");/*往数据部分msgbuf.data去拷入"test haha"*/

3、发送信息
    ret=msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);/*往msgid*发送&msgbuf中的内容*/
    if(ret==-1)
    {
         printf("send message err\n");
         return -1;
   }

 

2.4接收消息

        功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除

        头文件:   #include <sys/types.h>

                           #include <sys/ipc.h>

                           #include <sys/msg.h>

        函数原型:int msgrcv(int msqid,struct msgbuf *msgp, int msgsz,long  msgtyp, int  msgflg)

         参数:

                           1、msqid :已打开的消息队列返回值id

                           2、msgp :存放消息的结构指针

                           3、msgsz :消息数据长度

                           4、msgtyp:接收的消息的类型

                           4、msgflg:处理方式,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列中没有指定类型的消息,函数不会阻塞,而是直接返回。(下面测试一下:1、设置处理方式msgflg不是IPC_NOWAIT,同时设置一个与发送不同类型。验证函数是否阻塞?)

        int  read_message(int qid,long type,struct mymsgbuf *qbuf)

       {

               int  result, length;

              length=sizeof(struct  mymsgbuf)-sizeof(long);

             if ((result=msgrcv(qid, qbuf, length, type, 0))==-1)/*接收消息*/

           {

               return (-1);

            }

               return(result);

        }

源码:

#include <sys/types.h>#include <sys/msg.h>#include <unistd.h>struct msg_buf{int mtype;char data[255];};int main(){key_t key; int msgid;  int ret;     struct msg_buf msgbuf;       key=ftok("/tmp/2",'a');/*获取消息队列的键值*/    printf("key =[%x]\n",key);    /*用msgget去打开消息队列key,但消息队列/tmp/2不存在,所以IPC_CREAT会去创建这个文件*/ msgid=msgget(key,IPC_CREAT|0666); /*文件属性对应666即usr、group、other都是可读、可写*/  if(msgid==-1)/*创建失败*/{printf("create error\n");return -1;  }   msgbuf.mtype = getpid();/*为这条消息去设置类型,类型编号=进程的ip*/    strcpy(msgbuf.data,"test haha");/*往数据部分msgbuf.data去拷入"test haha"*/    ret=msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);/*往msgid*发送&msgbuf中的内容*/    if(ret==-1){printf("send message err\n");return -1;} memset(&msgbuf,0,sizeof(msgbuf));/*清空&msgbuf为了,接收这个数据,然后显示这个数据*/  ret=msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),getpid(),IPC_NOWAIT);     if(ret==-1)  {printf("recv message err\n");return -1;}printf("recv msg =[%s]\n",msgbuf.data);}

执行结果:

                   key =[ffffffff]
                   recv msg =[test haha]

   

              1、由上面执行结key =[ffffffff],可知未成功获取消息队列的键值。原因:在key=ftok("/tmp/2",'a'),/tmp目录下没有2/a文件。
            验证办法:增加/tmp/2/a文件,观察key值输出。

                      key =[61010025]       说明成功获取消息队列键值
                      recv msg =[test haha]
           2、验证IPC_NOWIAT是否有效?验证思路:

                 1、先建立消息队列,并向

                     msgid=msgget(key,IPC_CREAT|0666);

                  2、消息队列写入数据(设置消息队列信息类型及数据)

                     msgbuf.mtype = getpid();/*为这条消息去设置类型,类型编号=进程的ip*/
                     strcpy(msgbuf.data,"test haha");/*往数据部分msgbuf.data去拷入"test haha"*/

                     msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);

                 3、读取数据设置以下参数

                     1、设置msgtyp   即读取消息类型同写入的消息类型不一致

                     2、设置msgflg     即处理方式不设置IPC_NOWIAT(即在消息队列中读取不到指定类型消息时,函数阻塞。)

                            msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),10);

               执行结果(可知IPC_NOWIAT有作用):

                

三、信号灯

3.1概念

            信号灯:又被称为信号量,是IPC(进程通信)的方式之一。

            主要作用:由于进程之间的控制,而其他进程通信方式(管道、信号处理、共享内存、消息队列、socket)作用:数据传输。

           信号灯控制进程方式有:1、控制进程间对临界资源的互斥访问 2、控制进程同步即执行顺序

 

           信号灯由二值信号灯和计数信号灯两类组成。其中:

            二值信号灯:信号灯的值只能取0或1。类似互斥锁

            计数信号灯:信号灯的值可以取任意非负值。


          下面以二值信号灯为例(进程A和进程B对临界资源访问中),使用信号灯的的工作流程:

                   

            由上图可知:

              1、第一步:进程A访问临界资源前,首先获取信号量semval值(初始值为1)。获取过程

                    1、判断semval是否为0(若为0.表示临界资源被占用,进程A无法访问,只有等到占有的临界资源的进程释放信号量即semva+1,才允许进程A访问占有)

                    2、semval不为0时,semval-1操作

               2、第二步:成功获取信号量值即 获取占有临界资源的的通行证后,进程A占用并访问临界资源

               3、第三步:访问结束后,进程A释放信号量(即注销通行证),semval+1。此时semval=1,进程B可以获取访问临界资源的通行证了。呵呵

      注:1、何为临界资源?操作系统中任意时刻一次 只允许一个进程访问资源称为临界资源。

              2、为什么semval初始值为1?因为访问的是临界资源,任意时候只允许一个进程访问,所以smval初始值1即二值信号灯。

              3、进程B访问临界资源过程等同进程A访问过程。

 

3.2 信号灯的用法

           1、建立信号灯集:进程通过调用semget来创建或者获取一个信号灯集

           2、初始化semval:通过semctl使用SETVAL命令来初始化信号灯的值(semval)

           3、获取和释放semval:通过函数semop来获取或者释放信号灯,其中获取信号灯对应 semval-1,而释放信号灯对应semval+1

           4、删除信号灯集:当进程结束使用信号灯时,使用semctl通过IPC_RMID命令删除。


3.3 函数原型

        1、获取system V  IPC键值(同创建消息队列,共享内存所用键值一样)
               函数原型: int  key_t  ftok(const char  *pathname ,int  proj_id)
               参数说明: pathname:   指定的带路径的文件名
                                    proj_id:           工程id。
                返回值:   成功返回system V  IPC的键值,失败返回-1,errno存放错误原因。

          2、创建或打开信号灯集
                函数原型:  int   semget(key_t key,   int nsems,   int semflgconst )
                参数说明:  key:           信号灯集的键值。当key取值IPC_PRIVATE,则函数semget()将创建新的信号灯集。
                                      nsems:         创建的信号灯集中信号灯个数。例如二值信号灯,nsems=1。
                                      semflgconst:  操作类型和访问权限。通过'|'连接表示
                返回值:   成功,返回信号灯的IPC的标识符,失败返回-1,errno存放错误原因。
                                   
           3、信号灯操作函数
                函数原型:  int  semop(int semid,     struct  sembuf  *sops,    unsigned  nsops)
                参数说明:  semid:        待操作的信号灯标识符ID。
                                      sops:             指向待操作的信号灯结构体(数组)。sembuf结构体包含了对于某个信号灯操作方法的信息,其成员如下:            struct sembuf
                                 {
                                       unsigned  short   sem_num; /* 信号灯编号,从0开始*/
                                       short                     sem_op; /*信号灯操作,为正时,代表释放信号灯值;为负,代表获取信号灯值*/ 
                       short                      sem_flg; /*操作的标识*/
                                  }
                                      nsopst:          操作类型和访问权限。通过'|'连接表示

                返回值:   成功,返回信号灯的IPC的标识符,失败返回-1,errno存放错误原因。

           4、信号灯控制函数
                函数原型:semctl(int semid,    int  semnum,   int cmd,    union semun  arg)
                参数说明:  semid:        待操作的信号灯标识符ID。
                                      semnum:        操作的信号灯编号
      cmd:            控制命令,常用命令有:
                                                             1、IPC_RMID:将信号灯集从内存中删除
2、GETPID:获得smpid
3、GETVAL:获得semval
4、SETVAL:设置semval
                                                                5、IPC_STAT:获取信号灯信息,信息由arg.buf返回
6、IPC_SET:设置信号灯信息
                                                      
     arg:              用于设置或返回信号灯信息。是一个联合体的类型的副本。
                返回值:   成功,返回与cmd相关正数(例如:GETPID,返回sempid;GETVAL,返回semval),失败返回-1,errno存放错误原因。

3.4源码

/********************************************************** *实验要求:   创建两个进程,通过信号灯控制进程中一段代码的执行循序。 *功能描述:   本程序通过使用信号灯,实现了一种基于父子进程的简单的进程同步。 *日    期:   2010-9-17 *作    者:   国嵌 **********************************************************/#include<stdio.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>#include<stdlib.h>#include<math.h>#include<errno.h>/* * 函数入口 * */int main(){    int pid,semid;    key_t semkey;    if((semkey=ftok("./sem.c",1))<0)//将文件路径和项目ID(后面那个“1“)转换为System V IPC Key    {        printf("ftok failed\n");        exit(EXIT_FAILURE);    }    printf("ftok ok, semkey=%d\n",semkey);           if((semid=semget(semkey,1,IPC_CREAT|IPC_EXCL|0700))<0)//创建信号灯集,其中包含1个信号灯    {        printf("semget failed\n");exit(EXIT_FAILURE);    }    printf("semget ok, semid=%d\n",semid);    if((semctl(semid,0,SETVAL,1))<0)//设置semid对应的信号集中第一个信号灯的semval为1。    {        printf("semctl set semval failed\n");exit(EXIT_FAILURE);    }    printf("semctl set semval ok\n");    if((pid=fork())<0)    {        printf("fork failed\n");        exit(EXIT_FAILURE);    }    else if(pid>0)//父进程,先索取共享资源,而后释放    {        struct sembuf p_op_buf,v_op_buf;        p_op_buf.sem_num=0;        p_op_buf.sem_op=-1;        if(semop(semid,&p_op_buf,1)<0)//以上三行向semid代表的信号灯集的第一个信号灯申请一个资源,即使semval减1        {            printf("semop failed\n");            exit(EXIT_FAILURE);        }        printf("father get the semaphore\n");        sleep(3);        printf("father release the semaphore\n");        v_op_buf.sem_num=0;        v_op_buf.sem_op=1;        v_op_buf.sem_flg=0;        if(semop(semid,&v_op_buf,1)<0)//以上三行将释放一个资源给semid代表的信号灯集第一个信号灯        {            printf("semop release semaphore failed\n");            exit(EXIT_FAILURE);        }    }    else//子进程不断申请共享资源,申请到后声明一下,然后释放    {        struct sembuf p_op_buf,v_op_buf;        sleep(1);//等待父进程将唯一的资源占用        printf("child wait for the semaphore\n");        p_op_buf.sem_num=0;        p_op_buf.sem_op=-1;        p_op_buf.sem_flg=0;//该标志位不用要清零,此处不清零将出现错误        if(semop(semid,&p_op_buf,1)<0)//向semid代表的信号灯集的第一个信号灯申请一个资源,申请不到就会阻塞,直到有了资源        {            printf("semop get semaphore failed\n");            exit(EXIT_FAILURE);        }        printf("child get the semaphore\n");        if(semctl(semid,0,IPC_RMID,0)<0)        {            printf("semctl remove semaphore set failed\n");            exit(EXIT_FAILURE);        }    }}

运行结果:
       


 

           

 

0 0
原创粉丝点击