进程间通信

进程间通信 (ipc机制 ):管道,信号量,共享内存,消息队列,套接字。

管道

  1. 管道管道可以用来在两个进程之间通信(传递数据),如: ps -ef | grep “bash”, 其中‘|’就是管 道,其作用就是将 ps 命令的结果写入管道文件,然后 grep 再从管道文件中读出该数据进行 过滤。

  2. 管道文件:本身在磁盘上只保存其各种属性,打开其(open)会在内存中分配空间,即写入管道的数据存在于内存中,因此效率比较高,只存在一个读端,一个写端。

  3. 管道的通信方式半双工

有名管道

有名管道可以在任意两个进程之间通信 有名管道的创建

  • 命令创建: mkfifo FIFO

  • 系统调用创建

    lcx@lcx-virtual-machine:~/mycode/11.8$ mkfifo fifo
    lcx@lcx-virtual-machine:~/mycode/11.8$ ls
    fifo
    lcx@lcx-virtual-machine:~/mycode/11.8$ ls -l
    总用量 0
    prw-rw-r-- 1 lcx lcx 0 11月  8 20:10 fifo
    lcx@lcx-virtual-machine:~/mycode/11.8$ 

例如:一个进程写文件一个进程读文件。

lcx@lcx-virtual-machine:~/mycode/11.8$ vi a.c
  
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9     int fdw=open("fifo",O_WRONLY);
 10     assert(fdw!=-1);
 11     printf("fdw=%d\n",fdw);
 12     char buff[128]={0};
 13     printf("input:\n");
 14     while(1)
 15     {
 16       fgets(buff,128,stdin);
 17       if(strncmp(buff,"end",3)==0)
 18       {
 19         break;
 20       }
 21       write(fdw,buff,strlen(buff));
 22     }
 23     close(fdw);
 24     exit(0);
 25 }
~         
~                                                                               
~ 
lcx@lcx-virtual-machine:~/mycode/11.8$ gcc -o a a.c
lcx@lcx-virtual-machine:~/mycode/11.8$ ls
a  a.c  fifo
lcx@lcx-virtual-machine:~/mycode/11.8$ vi b.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9     int fdr=open("fifo",O_RDONLY);
 10     assert(fdr!=-1);
 11     printf("fdr=%d\n",fdr);
 12     while(1)
 13     {
 14       char buff[128]={0};
 15       int n=read(fdr,buff,127);
 16       if(n==0)//当写入程序关闭时,读取程序最后不会再阻塞住,而是返回为0
 17       {
 18           break;
 19       }
 20       printf("buff=%s\n",buff);
 21     }
 22     close(fdr);
 23     exit(0);
 24 }
~     
~                                                                               
~          
lcx@lcx-virtual-machine:~/mycode/11.8$ gcc -o b b.c
lcx@lcx-virtual-machine:~/mycode/11.8$ ls
a  a.c  b  b.c  fifo
​
lcx@lcx-virtual-machine:~/mycode/11.8$ ./a
fdw=3
input:
hello
//在另一终端运行:
lcx@lcx-virtual-machine:~/mycode/11.8$ ./b
fdr=3
buff=hello
n=6
​
​

注:

  • 要打开管道文件必须有读和写俩个进程同时存在才行,否则会阻塞。

  • 如果管道为空,读取便会阻塞。

  • 如果管道为满,写入便会阻塞。

  • 一个管道在传递数据是单向传递的。

  • 管道的写段关闭,读端最终返回值为0。

  • 管道的读端关闭,写段写入数据会触发异常,内核发出信号(SIGPIPE)处理写段。

无名管道

只能在父子进程间的通信

无名管道的创建:

int pipe(int fds[2]);

  1. pipe()成功返回 0,失败返回-1

  2. fds[0]是管道读端的描述符

  3. fds[1]是管道写端的描述符

    例如:

    lcx@lcx-virtual-machine:~/mycode/11.8$ vi main.c
      
      1 #include<stdio.h>
      2 #include<stdlib.h>
      3 #include<unistd.h>
      4 #include<string.h>
      5 #include<assert.h>
      6 #include<fcntl.h>
      7 int main()
      8 {
      9     int fd[2];
     10     int res=pipe(fd);
     11     assert(res!=-1);
     12     pid_t pid=fork();
     13     assert(pid!=-1);
     14     if(pid==0)
     15     {
     16         close(fd[1]);//关掉写段
     17         char buff[128]={0};
     18         while(1)
     19         {
     20         int n=read(fd[0],buff,127);
     21         if (n==0)
     22         {
     23             break;
     24         }
     25         printf("child read:%s\n",buff);
     26         }
     27         close(fd[0]);//关掉读端
     28     }
     29     else
     30     {
     31         close(fd[0]);//关掉读端
     32         char buff[128]={0};
     33         while(1)
     34         {
     35         fgets(buff,128,stdin);
     36         if(strncmp(buff,"end",3)==0)
     37         {
     38             break;
     39         }
     40         write(fd[1],buff,strlen(buff));
     41         }
     42         close(fd[1]);//关掉写段
     43     }
     44     exit(0);
     45 }
    ~   
    ​
    lcx@lcx-virtual-machine:~/mycode/11.8$ gcc -o main main.c
    lcx@lcx-virtual-machine:~/mycode/11.8$ ./main
    as
    child read:as
    ​
    da
    child read:da
    ​
    fe
    child read:fe
    ​
    asfe
    child read:asfe
    ​
    qeertrhrg
    child read:qeertrhrg
    ​
    end
    ​

注:

  • 使用无名管道通信时,父子进程进入时,应先将不需要的读端或写段关闭,以保证仅一个读端一个写段。

通信方式

单工:单向传递,不可逆。(即收只能收,发只能发)

  • 半双工:单向传递,可逆。(即不能同时收和发)

  • 全双工:双向传递,收和发同时在进行。

信号量

信号量描述

  • 信号量主要用来同步进程。

  • 信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,

  • 信号量的值如果只取 0,1,将其称为二值信号量,0代表无资源可用,1代表有资源可用。

  • 获取资源 时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。

  • 释放资源时,需要对信号量的值进行原子加一,该操作被称为 V 操作。

  • 如果信号量的值大于 1,则称之为计数信号量。

  • 临界资源:同一时刻,只允许被一个进程或线程访问的资源 。

  • 临界区:访问临界资源的代码段。

信号量的封装:

  1. sem_init() -> 初始化

  2. sem_p() -> p操作

  3. sem_v() -> v操作

  4. sem_destroy() ->释放

linux提供的接口

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

  • semget()创建或者获取已存在的信号量的键;

  • semget()成功返回信号量的 ID, 失败返回-1 ;

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

  • nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号 量的个数 ;

  • semflg 可选: IPC_CREAT IPC_EXCL;

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

  • semctl()控制信号量 ,即初始化及释放

  • semctl()成功返回 0,失败返回-1

  • cmd 选项: SETVAL IPC_RMID

  • union semun {

    • int val;

    • struct semid_ds *buf;

    • unsigned short *array;

    • struct seminfo *_buf;};

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

  • semop()对信号量进行改变,做 P 操作或者 V 操作

  • semop()成功返回 0,失败返回-1

  • struct sembuf

  • {

    • unsigned short sem_num; //指定信号量集中的信号量下标

    • short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作

    • short sem_flg; //SEM_UNDO

  • };

信号量的应用

例:两个进程通过一个信号量访问一个临界资源:a,b

//sem.h 封装头文件:
​
lcx@lcx-virtual-machine:~/mycode/11.10$ vi sem.h
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<string.h>
  6 #include<sys/sem.h>
  7 
  8 union semun //由于该联合体未定义,需要自行进行定义
  9 {
 10 int val;
 11 };
 12 
 13 void sem_init();//初始化信号量
 14 void sem_p();//p操作
 15 void sem_v();//v操作
 16 void sem_destroy();//释放掉信号量
 17 
~                                                                               
//sem.c 封装信号量:
​
lcx@lcx-virtual-machine:~/mycode/11.10$ vi sem.c
​
  1 #include"sem.h"  //引用头文件
  2 static int semid=-1;  //定义静态全局变量 semid 信号量的id ,仅本文件可见
  3 void sem_init()  //初始化
  4 {
  5     semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);  //创建信号量:1234为共同使用的的key,1为信号量数目,命令为创建一个全新的信号量并给权限0600
  6     if(semid==-1)//创建失败
  7     {
  8         semid=semget((key_t)1234,1,0600);//可能是1234被占用,获取被创建好的信号量 不用初始化
  9         if(semid==-1) //再次失败
 10         {
 11             printf("semget err\n"); //打印创建失败
 12             return ;
 13         }
 14     }
 15     else
 16     {
 17         union semun a;//初始化,定义联合体a
 18         a.val=1;  //初始化val的值为1
 19         if (semctl(semid,0,SETVAL,a)==-1)//初始化 semid信号量id号,第0个信号量,命令初始化信号量值为 a
 20         {
 21             printf("semctl err\n");//-1,初始化错误
 22         }
 23     }
 24 }
 25 void sem_p()//p操作
 26 {
 27     struct sembuf buf;//定义一个结构体变量
 28     buf.sem_num=0;//第0个信号量
 29     buf.sem_op=-1;//代表需要p操作
 30     buf.sem_flg=SEM_UNDO;//防止该程序在释放信号量前死掉,导致其他需要用该信号量的进程一直等待。命令由内核归还信号量
 31     if(semop(semid,&buf,1)==-1)//p操作:信号量id,结构体,对几个进行信号量进行操作,即结构体数目
 32     {
 33         printf("semop p err\n");
 34     }
 35 
 36 }
 37 void sem_v()
 38 {
 39     struct sembuf buf;
 40     buf.sem_num=0;
 41     buf.sem_op=1;//代表需要v操作
 42     buf.sem_flg=SEM_UNDO;
 43     if(semop(semid,&buf,1)==-1)//v操作
 44     {
 45         printf("semop v err\n");
 46     }
 47 }
 48 void sem_destroy()//释放信号量
 49 {
 50     if (semctl(semid,0,IPC_RMID)==-1)//semid信号量id号,第0个信号量 ,命令释放信号量
 51     {
 52         printf("semctl err\n");
 53     }
 54 }
​
​
//a进程
​
lcx@lcx-virtual-machine:~/mycode/11.10$ vi a.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include"sem.h"
  7 int main()
  8 {
  9     sem_init();//创建或获取并初始化信号量
 10     for(int i=0;i<5;i++)
 11     {
 12         sem_p();//p操作-1,此时为0,代表没有资源
 13         printf("A");//代表开始使用该临界资源
 14         fflush(stdout);
 15         int n=rand()%3;
 16         sleep(n);
 17         printf("A");//代表推出使用临界资源
 18         fflush(stdout);
 19         sem_v();//v操作+1,此时为1,代表有资源可用
 20         n=rand()%3;
 21         sleep(n);
 22     }
 23     sleep(10);//预测十秒后b也使用完了在进行释放信号量
 24     sem_destroy();//释放信号量
 25 }
~                                                                                                                     
~                                                                                                        
//b进程
​
lcx@lcx-virtual-machine:~/mycode/11.10$ vi b.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include"sem.h"
  7 int main()
  8 {
  9     sem_init();
 10     for(int i=0;i<5;i++)
 11     {
 12         sem_p();
 13         printf("B");
 14         fflush(stdout);
 15         int n=rand()%3;
 16         sleep(n);
 17         printf("B");
 18         fflush(stdout);
 19         sem_v();
 20         n=rand()%3;
 21         sleep(n);
 22     }
 23 }
~                                                                                                                     
~  
//运行进程
lcx@lcx-virtual-machine:~/mycode/11.10$ gcc -o a a.c sem.c
lcx@lcx-virtual-machine:~/mycode/11.10$ gcc -o b b.c sem.c
lcx@lcx-virtual-machine:~/mycode/11.10$ ./a& ./b&
[1] 17377
[2] 17378
lcx@lcx-virtual-machine:~/mycode/11.10$ AABBAABBAABBAABBAABB
//此时两个进程A,Bza
​

例2:三个进程:打印A,打印B,打印C,要求最后输出 ABCABCABC....

解析:因为有顺序的打印,则需要进行三个信号量进行控制:S1,S2,S3;

初始Sa 为1,Sb为0,Sc为0;解题如下:

//sem.h 封装头文件:
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<sys/sem.h>
  6 
  7 #define SEM_NUM 3 //需要三个信号量,定义宏
  8 #define SEM1 0  //第一个信号量,p v 操作需要传参
  9 #define SEM2 1  //第二个信号量,p v 操作需要传参  
 10 #define SEM3 2  //第三个信号量,p v 操作需要传参
 11 union semun  //定义联合体
 12 {
 13     int val;
 14 };
 15 int sem_init(); //创建初始化
 16 void sem_p(int index);  //P操作
 17 void sem_v(int index);  //V操作
 18 void sem_destroy();     //释放信号量
~                                                                               
~      
                                                                          
//sem.c 封装信号量:
​
  1 #include"sem.h"
  2 static int semid=-1;
  3 int sem_init()
  4 {
  5     semid=semget((key_t)1234,SEM_NUM,IPC_CREAT|IPC_EXCL|0600);//与上道题不同的是,这道题中信号量数目为3
  6     if (semid==-1)
  7     {
  8         semid=semget((key_t)1234,SEM_NUM,0600);
  9         if (semid==-1)
 10         {
 11             printf("semget err\n");
 12             return -1;
 13         }
 14     }
 15     else
 16     {
 17         int arr[SEM_NUM]={1,0,0};   //初始化信号量准备工作:A为1,B为0,C为0
 18         for (int i=0;i<SEM_NUM;i++) //用循环进行初始化
 19         {
 20             union semun a;
 21             a.val=arr[i];           //获得信号量初始化值
 22             if(semctl(semid,i,SETVAL,a)==-1) //初始化:id,第i个信号量,赋值a
 23             {
 24                 printf("semctl err\n");
 25                 return -1;
 26             }
 27         }
 28     }
 29 }
 30 void sem_p(int index)
 31 {
 32     if (index<0||index>=SEM_NUM) //判断参数合法性
 33     {
 34         return;
 35     }
 36     struct sembuf buf;
 37     buf.sem_num=index;  //几号信号量
 38     buf.sem_op=-1;      //需要进行P操作
 39     buf.sem_flg=SEM_UNDO;
 40 
 41     if (semop(semid,&buf,1)==-1);  //p操作,只操作一个信号量
 42     {
 43         return;
 44     }
 45 }
 46 void sem_v(int index)
 47 {
 48     if (index<0||index>=SEM_NUM)
 49     {
 50         return;
 51     }
 52     struct sembuf buf;
 53     buf.sem_num=index;
 54     buf.sem_op=1;
 55     buf.sem_flg=SEM_UNDO;
 56 
 57     if (semop(semid,&buf,1)==-1);
 58     {
 59         return;
 60     }
 61 }
 62 void sem_destroy()
 63 {
 64     if(semctl(semid,0,IPC_RMID)==-1)//直接进行释放,0此时只是占位。
 65     {
 66         printf("semctl err\n");
 67         return ;
 68     }
 69 }
      
​
​
​
​
//a进程
lcx@lcx-virtual-machine:~/mycode/11.13$ vi a.c
​
  1 #include"sem.h"
  2 
  3 int main()
  4 {
  5     int res=sem_init();//获取返回值,如果为-1,则失败
  6     if(res==-1)
  7     {
  8         return 0;
  9     }
 10     for(int i=0;i<5;i++)//打印5次
 11     {
 12         sem_p(SEM1);//对S1 p操作,a进程开始执行
 13         printf("A");
 14         fflush(stdout);
 15         sleep(1);
 16         sem_v(SEM2);//对S2 v操作,此时S2为1代表可用
 17     }
 18 }
~                                                                               
~                                                                                                                      
~                                                                                                        
//b进程
lcx@lcx-virtual-machine:~/mycode/11.13$ vi a.c
​
  1 #include"sem.h"
  2 
  3 int main()
  4 {
  5     int res=sem_init();
  6     if(res==-1)
  7     {
  8         return 0;
  9     }
 10     for(int i=0;i<5;i++)
 11     {
 12         sem_p(SEM2);//对S2 P操作,执行B
 13         printf("B");
 14         fflush(stdout);
 15         sleep(1);
 16         sem_v(SEM3);//对S3 v操作,此时S3为1代表可用
 17     }
 18 }
~    
 
 
 //c进程   
 lcx@lcx-virtual-machine:~/mycode/11.13$ vi a.c
 
  1 #include"sem.h"
  2 
  3 int main()
  4 {
  5     int res=sem_init();
  6     if(res==-1)
  7     {
  8         return 0;
  9     }
 10     for(int i=0;i<5;i++)
 11     {
 12         sem_p(SEM3);对S3 P操作,执行C
 13         printf("C");
 14         fflush(stdout);
 15         sleep(1);
 16         sem_v(SEM1);
 17     }
 18     sem_destroy();对S1 v操作,此时S1为1代表可用
 19 }
~      
 
~  
//运行进程
lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o a a.c sem.c
lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o b b.c sem.c
lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o c c.c sem.c
lcx@lcx-virtual-machine:~/mycode/11.13$ ls
a  a.c  b  b.c  c  c.c  sem.c  sem.h  sem.o
lcx@lcx-virtual-machine:~/mycode/11.13$ ./a& ./b& ./c&
[1] 12031
[2] 12032
[3] 12033
lcx@lcx-virtual-machine:~/mycode/11.13$ ABCABCABCABCABC
​
​

共享内存

概念:共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理 内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了 数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供 同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。

注:

  1. 共享内存由内核管理,如果不给内核释放共享内存的信息,程序终止时,只会自动断开映射,并不会收回这块共性内存,该共享内存仍然存在。

  2. 由于使用共享内存传递数据时存在缺陷,因此通常在使用共享内存时必定伴随着使用信号量,用来同步两个进程。

使用方法:

  1. 创建共享内存

    int shmget(key_t key, size_t size, int shmflg);

    • shmget()用于创建或者获取共享内存

    • shmget()成功返回共享内存的 ID, 失败返回-1

    • key: 不同的进程使用相同的 key 值可以获取到同一个共享内存

    • size: 创建共享内存时,指定要申请的共享内存空间大小

    • shmflg: IPC_CREAT IPC_EXCL

  2. 将共享内存映射到进程中

    void shmat(int shmid, const void * shmaddr, int shmflg);

    • shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上

    • shmat()成功返回返回共享内存的首地址,失败返回 NULL

    • shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间

    • shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写

  3. 断开映射

    int shmdt(const void *shmaddr);

    • shmdt()断开当前进程的 shmaddr 指向的共享内存映射

    • shmdt()成功返回 0, 失败返回-1

  4. 删除共享内存

    int shmctl(int shmid, int cmd, struct shmid_ds *buf);

    • shmctl()控制共享内存

    • shmctl()成功返回 0,失败返回-1

    • cmd: IPC_RMID

共享内存的应用

例:A进程从键盘获取数据写入共享内存,B进程输出。

//信号量封装  //由于使用共享内存存在缺陷,需要通过信号量来同步进程
lcx@lcx-virtual-machine:~/mycode/11.13$ vi sem.h
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<sys/sem.h>
  6 
  7 #define SEM_NUM 2
  8 #define SEM1 0
  9 #define SEM2 1
 11 union semun
 12 {
 13     int val;
 14 };
 15 int sem_init();
 16 void sem_p(int index);
 17 void sem_v(int index);
 18 void sem_destroy();
~                                                                               
lcx@lcx-virtual-machine:~/mycode/11.13$ vi sem.c
​
  1 #include"sem.h"
  2 static int semid=-1;
  3 int sem_init()
  4 {
  5     semid=semget((key_t)1234,SEM_NUM,IPC_CREAT|IPC_EXCL|0600);//与上道题不同的是,这道题中信号量数目为2
  6     if (semid==-1)
  7     {
  8         semid=semget((key_t)1234,SEM_NUM,0600);
  9         if (semid==-1)
 10         {
 11             printf("semget err\n");
 12             return -1;
 13         }
 14     }
 15     else
 16     {
 17         int arr[SEM_NUM]={1,0};   //初始化信号量准备工作:A为1,B为0
 18         for (int i=0;i<SEM_NUM;i++) //用循环进行初始化
 19         {
 20             union semun a;
 21             a.val=arr[i];           //获得信号量初始化值
 22             if(semctl(semid,i,SETVAL,a)==-1) //初始化:id,第i个信号量,赋值a
 23             {
 24                 printf("semctl err\n");
 25                 return -1;
 26             }
 27         }
 28     }
 29 }
 30 void sem_p(int index)
 31 {
 32     if (index<0||index>=SEM_NUM) //判断参数合法性
 33     {
 34         return;
 35     }
 36     struct sembuf buf;
 37     buf.sem_num=index;  //几号信号量
 38     buf.sem_op=-1;      //需要进行P操作
 39     buf.sem_flg=SEM_UNDO;
 40 
 41     if (semop(semid,&buf,1)==-1);  //p操作,只操作一个信号量
 42     {
 43         return;
 44     }
 45 }
 46 void sem_v(int index)
 47 {
 48     if (index<0||index>=SEM_NUM)
 49     {
 50         return;
 51     }
 52     struct sembuf buf;
 53     buf.sem_num=index;
 54     buf.sem_op=1;
 55     buf.sem_flg=SEM_UNDO;
 56 
 57     if (semop(semid,&buf,1)==-1);
 58     {
 59         return;
 60     }
 61 }
 62 void sem_destroy()
 63 {
 64     if(semctl(semid,0,IPC_RMID)==-1)//直接进行释放,0此时只是占位。
 65     {
 66         printf("semctl err\n");
 67         return ;
 68     }
 69 }
      
      
​
//a进程
​
lcx@lcx-virtual-machine:~/mycode/11.13$ vi shma.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<sys/shm.h>
  7 #include"sem.h"
  8 int main()
  9 {
 10     int shmid =shmget((key_t)1234,128,IPC_CREAT|0600); //创建共享内存:键值,字节数,命令|权限
 11     assert(shmid!=-1);
 13     char* s=(char*)shmat(shmid,NULL,0); //将共享内存映射过来
 14     sem_init();//创建初始化信号量
 15     while(1)
 16     {
 17         printf("input:\n");
 18         char buff[128]={0};//定义字符数组用来存放键盘输入的字符
 19         fgets(buff,128,stdin);//获取键盘输入字符
 20         sem_p(SEM1);//对S1 P操作,A进程执行
 21         strcpy(s,buff);//将字符数组拷贝入共享内存中
 22         sem_v(SEM2);//对S2 V操作,此时S2为1,B进程可使用
 23         if(strncmp(buff,"end",3)==0)//注:1.在拷贝后进行判断,因为B进程需要end标志;
 24         {                           //   2.在V操作后判断,如果在V前,end结束后,B进程没资源可用一直堵塞住
 25             break;
 26         }
 27     }
 28     shmdt(s);//断开映射:共享内存地址
 29 }
     
     
//b进程
​
lcx@lcx-virtual-machine:~/mycode/11.13$ vi shmb.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<sys/shm.h>
  7 #include"sem.h"
  8 int main()
  9 {
 10     int shmid =shmget((key_t)1234,128,IPC_CREAT|0600);//创建共享内存
 11     assert(shmid!=-1);
 12 
 13     char* s=(char*)shmat(shmid,NULL,0);//将共享内存映射过来
 14     sem_init();//创建获取初始化信号量
 15     while(1)
 16     {
 17         sem_p(SEM2);//S2 P操作,B进程开始执行
 18         if(strncmp(s,"end",3)==0)//判断退出循环条件
 19         {
 21             break;
 22         }
 23         printf("read:%s\n",s);//打印共享内存中的数据
 24         sem_v(SEM1);//S1 V操作,此时S1为1,A进程可使用
 25     }
 26     shmdt(s);//断开映射
 27     shmctl(shmid,IPC_RMID,NULL);//释放信号量
 28     sem_destroy();//s
 29 }
 
​
//执行
lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o shma shma.c sem.c
lcx@lcx-virtual-machine:~/mycode/11.13$ gcc -o shmb shmb.c sem.c
lcx@lcx-virtual-machine:~/mycode/11.13$ ./shma
​
​
//另一个终端中lcx@lcx-virtual-machine:~/mycode/11.13$ ./shmb
​
​

消息队列

在内存中开辟空间用来:

  • 发送消息

    1. 数据类型

    2. 数据

  • 获取消息

    1. 数据类型

    2. 数据

使用方法:

  1. msgget()创建或者获取一个消息队列

    int msgget(key_t key, int msqflg);

    • msgget()成功返回消息队列 ID,失败返回-1

    • msqflg: IPC_CREAT

  2. msgsnd()发送一条消息

    int msgsnd(int msqid, const void *msqp, size_t msqsz, int msqflg);

    • msqp消息结构为:

      {

      long mtype; 消息类型, 必须大于 0

      char mtext[1]; // 消息数据

      };

    • msgsnd()成功返回 0, 失败返回-1

    • msqsz: 指定 msqp 中数据的大小 。注:是结构体数据元素的大小,不是结构体的大小。

    • msqflg:一般设置为 0 可以设置 IPC_NOWAIT

  3. msgrcv()接收一条消息

    ssize_t msgrcv(int msqid, void *msqp, size_t msqsz, long msqtyp, int msqflg);

    • msgrcv()接收一条消息

    • msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1

    • msqid:消息队列id

    • msqp:用来存放获取到的消息的空间

    • msqsz:用来存放获取到的消息的空间大小

    • msqtyp: 指定接收的消息类型,类型可以为 0

    • msqflg: 一般设置为 0 可以设置 IPC_NOWAIT

  4. msgctl()控制消息队列

    int msgctl(int msqid, int cmd, struct msqid_ds *buf);

    • msgctl()控制消息队列

    • msgctl()成功返回 0,失败返回-1

    • cmd: IPC_RMID

消息队列的应用:

创建一个消息队列,发送一条消息,接收一条消息

//写入消息
lcx@lcx-virtual-machine:~/mycode/11.22$ vi a.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<sys/msg.h>
  7 struct mess  //定义消息结构体
  8 {
  9     long type;
 10     char buff[32];
 11 };
 12 int main()
 13 {
 14     int msgid=msgget((key_t)1234,IPC_CREAT|0600);//创建消息队列
 15     assert(msgid!=-1);
 16     struct mess dt;//定义一个消息变量
 17     dt.type=2;//消息类型赋为2
 18     strcpy(dt.buff,"hello2");//消息数据为hello2
 19     msgsnd(msgid,(void *)&dt,32,0);//写入消息队列
 20 
 21 }
~                                                                                
~  
//读取消息
lcx@lcx-virtual-machine:~/mycode/11.22$ vi b.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<sys/msg.h>
  7 struct mess//定义消息结构体
  8 {
  9     long type;
 10     char buff[32];
 11 };
 12 int main()
 13 {
 14     int msgid=msgget((key_t)1234,IPC_CREAT|0600);//创建或打开消息队列
 15     assert(msgid!=-1);
 16     struct mess dt;//定义消息变量用来存放读出的消息
 17     msgrcv(msgid,&dt,32,0,0);//读取消息:32为消息中数据的大小,0为消息类型:可读任意消息类型消息,写消息时不能为0 ,若为1,2,等等,只能读对应类型的消息
 18     printf("read msg:%s\n",dt.buff);//打印读出的消息
 19 }
~            
lcx@lcx-virtual-machine:~/mycode/11.22$ gcc -o b b.c
lcx@lcx-virtual-machine:~/mycode/11.22$ gcc -o a a.c
lcx@lcx-virtual-machine:~/mycode/11.22$ ./a       //写入一次
lcx@lcx-virtual-machine:~/mycode/11.22$ ./a       //写入第二次
lcx@lcx-virtual-machine:~/mycode/11.22$ ipcs -q   //此时占用字节为64
​
--------- 消息队列 -----------
键        msqid      拥有者      权限      已用字节数     消息      
0x000004d2 0          lcx        600        64           2       
​
lcx@lcx-virtual-machine:~/mycode/11.22$ ./b       //读出第一次
read msg:hello2
lcx@lcx-virtual-machine:~/mycode/11.22$ ./b       //读出第二次
read msg:hello2
lcx@lcx-virtual-machine:~/mycode/11.22$ ipcs -q   //此时消息队列中无可读消息
​
--------- 消息队列 -----------
键        msqid      拥有者  权限     已用字节数 消息      
0x000004d2 0          lcx        600        0            0   

注:当消息队列为空时,读消息会被阻塞。

观察

观察方法:ipcs

//终端1:运行三个进程
lcx@lcx-virtual-machine:~/mycode/11.13$ ./a& ./b& ./c&
​
//终端2:观察信号量
lcx@lcx-virtual-machine:~/mycode/11.13$ ipcs
​
--------- 消息队列 -----------
键        msqid      拥有者  权限     已用字节数 消息      
​
------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态      
0x00000000 9          lcx        600        524288     2          目标       
0x00000000 16         lcx        600        4194304    2          目标       
0x00000000 19         lcx        600        524288     2          目标       
0x00000000 20         lcx        600        524288     2          目标       
​
--------- 信号量数组 -----------
键        semid      拥有者  权限     nsems     
0x000004d2 1          lcx        600        3   

观察信号量: ipcs -s

观察共享内存:ipcs -m

观察消息队列:ipcs -q

删除:

  • ipcrm -s +id :删除id信号量

  • ipcrm -m +id :删除id共享内存

  • ipcrm -q +id :删除消息队列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值