2020-11-07

关于消息队列/内存共享/信号/信号量那点事。

一、消息队列

1.消息队列key的获取
在程序中若要使用消息队列,必须要能够知道消息队列key,因为应用进程无法直接访问内核消息队列中的数据结构,因此需要一个消息队列的标识,让应用进程知道当前操作的是哪一个消息队列,同时也要保证每一个消息队列key值的唯一性。

2.通过 ftok函数获取
key_t ftok(const char *pathname,int id);
key_t key;
key=ftok(".",1);
该函数通过一个路径名称映射出一个消息队列key(我的理解是使用路径映射的方式比较容易获取一个唯一的消息队列key)当然也可以直接使用0x1234 代替key (这里看自己怎样写就好了没有固定的写法)。
IPC_CREAT:若消息队列不存在创建一个新的消息队列,若存在则返回存在的消息队列。对于(msgflg)其他的用法大家可以根据man msgflg手册里面看看就好了自己会怎么用就行不需要刻意去背。
对于消息队列的操作主要就是 对 msgget msgrcv msgsnd 的操作
我就把自己的一些理解写出来帮助自己学习,希望也能帮助到更多的伙伴(上代码吧 哈哈)。

//接受消息
#include
#include 
#include 
#include 
#include
//int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

struct msgbuf
 {
     
          long mtype;       /* message type, must be > 0 */
          char mtext[128];    /* message data */
  };

int  main()

{
     
      struct  msgbuf  readbuf;
      int msgid;
      key_t key;
      key=ftok(".",1);
      msgid= msgget(key,IPC_CREAT|0777);//打开权限
      if(msgid==-1){
     

           printf("Creat failed\n");
      }
      msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);
      printf("read :%s\n",readbuf.mtext);
       return 0;
}
-- INSERT --                             

//发送消息
#include
#include 
#include 
#include 
#include

//int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

struct msgbuf
 {
     

          long mtype;       /* message type, must be > 0 */
          char mtext[128];    /* message data */
  };


int  main()

{
     
      struct  msgbuf  sendbuf={
     888,"thi is  message"};
      int msgid;
      key_t key;
      key=ftok(".",1);
      msgid= msgget(key,IPC_CREAT|0777);

      if(msgid==-1){
     

           printf("Creat failed\n");
       }

      msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
       return 0;
 }

      
                                                          

运行结果
在这里插入图片描述

##二、 内存共享

1.实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号量之类的手段来配合。
在对内存操作过程中就不仔细写了总结的话就是
a. shmget()创建共享内存 失败返回-1,成功返回共享内存的ID
b. shmat() 映射把该共享内存连接到进程上,即要把待使用的共享内存映射到进程空间。
c. 数据区域 这里可以通过strcpy()函数,该我们要输入的数据写入到该进程中 在之前可以通过一个char *shmaddr 获取我们输入的数据(在后面代码中可以体会到)。
d.shmdt() 释放共享内存 断开共享内存与进程的连接
e.shmctl()因为内存不会随着程序的结束而自动消除,要么调用shmctl函数删除,要么手动使用ipcrm -m shmid 删除,否则会一直保存在系统中。

代码展示(啦啦啦啦)。

//创建写入消息
#include
#include 
#include 
#include
  //int shmget(key_t key, size_t size, int shmflg);
  
int  main()

{
     
    int shmid;
    char *shmaddr;
    key_t  key;
    key=ftok(".",1);

   shmid= shmget(key,1024*4,IPC_CREAT|0666);//ke du ke xie

   if(shmid==-1){
     

          printf("shmget  error\n");
          exit(-1);
       }

   shmaddr= shmat(shmid,0,0);//ying shen
   printf("shmat ok\n");
   strcpy(shmaddr,"xiao yin");

   sleep(5);
   shmdt(shmaddr);

   shmctl(shmid,IPC_RMID,0);

   printf("quit\n");

   return 0;
}

//读取 数据
#include
#include 
#include 
#include
  //int shmget(key_t key, size_t size, int shmflg);

int  main()

{
     
    int shmid;
    char *shmaddr;
    key_t  key;
    key=ftok(".",1);
   shmid= shmget(key,1024*4,0);
   if(shmid==-1){
     

          printf("shmget  error\n");
          exit(-1);
       }

   shmaddr= shmat(shmid,0,0);//ying shen
   printf("shmat ok\n");
   printf("data:%s\n",shmaddr);

   sleep(5);
   shmdt(shmaddr);
   printf("quit\n");

   return 0;
}

运行结果
2020-11-07_第1张图片

三、信号

1.信号的概念
信号是linux系统为例响应一些状况而产生的事件,进程收到信号后应该采取相应的动作。

2.哪些情况会引发信号
1.键盘事件 ctrl +c ctrl +
2.非法内存 如果内存管理出错,系统就会发送一个信号进行处理
3.硬件故障 同样的,硬件出现故障系统也会产生一个信号
4.环境切换 比如说从用户态切换到其他态,状态的改变也会发送一个信号,这个信号会告知给系统

1.单个信号处理

#include
#include 

    // typedef void (*sighandler_t)(int);

    //   sighandler_t signal(int signum, sighandler_t handler);

void   handler(int signum)
{
     

     printf("get signum ;%d\n",signum);

     printf("never  quit\n");

}
int   main()
{
     

    signal(SIGINT,handler);
    while(1);
    return 0;
}

运行结果

CLC@Embed_Learn:~$ gcc  f9.c
CLC@Embed_Learn:~$ 
CLC@Embed_Learn:~$ ./a.out
^Cget signum ;2
never  quit
^Cget signum ;2
never  quit
^Cget signum ;2
never  quit
^Z
[2]+  Stopped                 ./a.out
CLC@Embed_Learn:~$ 


2.多个信号

#include
#include 

    // typedef void (*sighandler_t)(int);

    //   sighandler_t signal(int signum, sighandler_t handler);

void   handler(int signum)
{
     

     printf("get signum ;%d\n",signum);
     switch(signum)
    {
     
        case 2:
              printf("SIGINT\n");
              break;
        case 9:
              printf("SIGKILL\n");
              break;
        case 10:
              printf("SIGUSR1\n");
              break;
     }

     printf("never  quit\n");

}
int   main()
{
     

    signal(SIGINT,handler);
    signal(SIGKILL,handler);
    signal(SIGUSR1,handler);

    while(1);
    return 0;
}
~  

通过发送指令发杀死进程
查看运行的进程号

 ps -aux|grep a.out

2020-11-07_第2张图片
通过kill(signum,pid)来发送消息
注意在执行这个代码时将上一个同时运行,同样先查看运行进程号 ps -aux|grep a.out

#include
#include
#include

int  main(int argc,char **argv)
{
     

       int signum;
       int  pid;
       signum=atoi(argv[1]);//atoi将字符转换成整型
       pid=atoi(argv[2]);

       printf("send %d pid %d\n",signum,pid);
       kill(signum,pid);
       printf("send signal ok\n");
       return 0;
}


// 发送指令
CLC@Embed_Learn:~$ ./a.out 9 10209

四、信号量

1、创建:semget函数
函数原型:int semget(key_t key, int num_sems:, int sem_flags);
作用:第一次使用时创建信号量,以后使用时获取信号量。
参数:
key:一个整型值对应内核中一个信号量对象,可以自己指定,不同信号量的key值不一样。不相关的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个响应的信号标识符。
num_sems:信号量的数目。
sem_flags:设置一组标志,与open函数的标志非常相似,包括信号量的权限等。IPC_CREAT标志是创建或者使用已有的信号量。
而IPC_CREATE和IPC_EXCL结合使用可以确保创建出的是一个新的、唯一的信号量,如果该信号量已存在,它将返回一个错误。
创建时给出的权限可以是:0600。
2、初始化、删除 :semctl(cmd)
作用:对信号量值进行修改。
函数原型:int semctl(int sem_id, int sem_num, int command, …);
sem_id:信号量id。
sem_num:信号量的下标,从0开始。
command:具体的操作命令,有SETVAL(设置初值)、IPC_RMID(移除信号量)
最后一个参数可以有也可以没有,如果有的话。它将会是一个union semun结构,包含以下几个成员:

union semun
{
int val;
struct semid_ds *buf;
usigned short *array;
}

一般只使用val这个成员,来为信号量赋初值。当信号量值为0时,进程会阻塞运行。
看一下信号量实现的具体代码操作吧(冲 鸭)

#include
#include
#include
#include
#include
#include


union  semun{
     


        int  val;
        struct  semid_ds   *buf;
        unsigned  short    *array;
        struct seminfo     *_buf;
};

void  pGetKey(int id)
{
     
   struct  sembuf set;
   set.sem_num=0;
   set.sem_op=-1;
   set.sem_flg=SEM_UNDO;
   semop(id,&set,1);
   printf("getkey\n");

}
void  PutBackKey(int id)
{
     

    struct sembuf  set;
    set.sem_num=0;
    set.sem_op=1;
    set.sem_flg=SEM_UNDO;
    semop(id,&set,1);
    printf("putbackkey\n");
}


int  main(int  argc,char const *argv[])

{
     


    key_t  key;
    int   semid;

    key=ftok(".",2);
                    // 只传入一个信号量
    semid=semget(key,1,IPC_CREAT|0666);//获取信号量

    union semun initsem;

    initsem.val=0;
               //操作第0个信号量
    semctl(semid,0,SETVAL,initsem);//初始化 
              //SETVAL  设置信号量的值 initsem
    int pid=fork();

   if(pid>0){
     
          // 去拿锁
          pGetKey(semid);
          printf("this  is father\n");
          PutBackKey(semid);//放锁
          //销毁
          semctl(semid,0,IPC_RMID);

    }

   else if(pid==0){
     

         printf("this  is  child\n");
         PutBackKey(semid);
    }

   else{
     

        printf("fork  error\n");

        }
}                                                                   105,0-1       Bot

运行结果
对于拿锁和放锁在父子进程中的具体操作大家可以将其位置调换在看一下运行结果。

this  is  child
putbackkey
getkey
this  is father
putbackkey

好了到这里关于linux下的通信进程的基本操作就学完了对于管道之间的通信在前面写过了需要的伙伴可以去看看(记录自己的学习过程)加油。

你可能感兴趣的:(linux)