进程间通信

1.进程的创建

我们使用fork函数创建进程:

Fork()通过复制调用进程来创建一个新进程。被称为子进程的新进程与被称为子进程的调用进程完全相同

除以下几点外:
*子进程有它自己唯一的进程ID,并且这个PID不匹配任何现有进程组的ID (setpgid(2))。
*子进程的父进程ID与父进程ID相同。
*子进程没有继承父进程的内存锁(mlock(2), mlockall(2))。
*进程资源利用率(getrusage(2))和CPU时间计数器(times(2))在子进程中重置为零。
*子进程的挂起信号集最初是空的(sigpending(2))。
子节点不从父节点继承信号量调整(semop(2))。
*子节点不继承其父节点的记录锁(fcntl(2))。
*子节点不从父节点(setitimer(2), alarm(2), timer_create(2))继承计时器。
*子节点不会从父节点(aio_read(3), aio_write(3))继承出色的异步I/O操作,也不会继承任何异步I/O上下文

#include <unistd.h>
pid_t fork(void);

来一个实例以便于理解:

#include <unistd.h>
#include <stdio.h>

int count=0;

int main(void){

   pid_t pid;

   pid=fork();                //创建进程,子进程pid为0

   if(pid<0){

      printf("进程创建失败!\n");
   }
   else if(pid==0){           //子进程执行代码

      while(1){

           sleep(2);
           printf("子进程\n");
           printf("count的值为:%d\n",count);
           count++;

           //sleep(2);
      }
   }
   else{                      //父进程执行代码

      while(1){

           sleep(1);
           printf("父进程\n");
           printf("count的值为:%d\n",count);
           count+=2;

           //sleep(1);
      }
   }

   return 0;
}

在使用了fork函数之后系统就为我们创建了一个子进程,子进程执行的代码与父进程是一样的,两个进程之间是独立的,所有变量是独立不共享的,可以看成你先创建了一个程序,然后复制了一个子进程,子进程代码与变量的值与父进程是一样的(这点很重要)。父子进程在执行中不同的是pid,子进程pid为0,父进程pid大于0,依靠这一特点我们可以在代码中使用if-else语句让父子进程分别执行不同的代码,完成不同的功能。(关于父子进程其实还有很多知识点,这里只讲了大概,以支持完成下文的理解)

2.什么是进程间通信

进程间通信是指在并行计算过程中,各进程之间进行数据交互或消息传递,其通信量的大小主要取决于并行设计的粒度划分和各个执行进程之间的相对独立性。也就是在多进程环境下,使用的数据交互、事件通知等方法使各进程协同工作。

1.为什么要进行进程间的通信:

通俗的讲就是由于在多进程环境下,各个进程都是独立不互通的,但系统的协调运作需要各进程互通协作,交换信息。因此需要进行进程间通信。

3.信号

进程间通信方式之一:信号

1.什么是信号:

信号是给程序提供一种可以处理异步事件的方法,它利用软件中断来实现。不能自定义信号,所有信号都是系统预定义的。

信号可由shell终端根据当前发生的错误(段错误、非法指令等)Ctrl+c而产生,也可由kill或killall命令产生。

2.有哪些信号

在这里插入图片描述

3.对信号的处理

  1. 忽略此信号
  2. 捕捉信号,指定信号处理函数进行处理
  3. 执行系统默认动作,大多数都是终止进程

也就是说我们可以在程序中进行设置,设置忽略此信号,则收到此信号后程序会将其忽略,不做任何反应。

设置捕捉信号,指定信号处理函数进行处理则需要将信号与某个函数进行绑定,当收到该信号后,就会执行与该信号绑定的函数。

执行系统默认动作。就是执行信号原本对应的含义,见上图。

4.信号的捕获

信号的捕获,是指,指定接受到某种信号后,去执行指定的函数。

注意:SIGKILL和SIGSTOP不能被捕获,即,这两种信号的响应动作不能被改变。

1.signal函数的使用:

 sighandler_t signal(int signum, sighandler_t handler);
 
/*参数1是信号
参数2可以设置为要绑定函数名字
也可以设置以下特殊值:
        SIG_IGN     忽略信号
        SIG_DFL     恢复默认行为*/
        

绑定函数实例:

因为将SIGINT信号与text函数绑定,所以ctrl+c不能终止程序。(注:有的信号会打断sleep函数的休眠

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void text(int sig){

     printf("Hello World\n");

     return;
}

int main(void){

    signal(SIGINT,text);       //信号函数,绑定信号SIGINT(终端中断信号)与text>函数

    while(1){
          sleep(1);
    }

    return 0;
}

特殊参数2实例:

程序执行后,第一次输入ctrl+c将执行text函数(然后将恢复信号的默认执行动作),第二次将终止程序

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void text(int sig){

     printf("Hello World\n");

     signal(SIGINT,SIG_DFL);           //调用该函数后将SIGINT信号的执行动作恢复默认

     return;
}

int main(void){

    signal(SIGINT,text);       //信号函数,绑定信号SIGINT(终端中断信号)与text>函数

    while(1){
          sleep(1);
    }

    return 0;
}

5.sigaction函数的使用

1.函数原型

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
/*参数1为信号
参数2为sigaction结构体
struct sigaction {
            void (*sa_handler)(int);      信号的响应函数 
            sigset_t   sa_mask;           屏蔽信号集                         
            int sa_flags; }               当sa_flags中包含 SA_RESETHAND时,接受到该信号并调用指定的信号处理函数执行之后,把该信号的响应行为重置为默认行为SIG_DFL
                                          当sa_mask包含某个信号A时,则在信号处理函数执行期间,如果发生了该信号A,则阻塞该信号A(即暂时不响应该信号),直到信号处理函数执行结束。即,信号处理函数执行完之后,再响应该信号A
 */

2.sigaction实例

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void text(int sig){

     printf("Hello World\n");

     return;
}

int main(void){

    struct sigaction act;

    act.sa_handler=text;               //设置绑定函数
    sigemptyset(&act.sa_mask);
    act.sa_flags=SA_RESETHAND;         //设置函数执行后恢复信号的默认行为

    sigaction(SIGINT,&act,0);

    while(1){
          sleep(1);
    }

    return 0;
}

6.kill函数的使用

1.函数原型

int kill(pid_t pid, int sig);
/* 参数1为进程pid
参数2为发送给进程的信号
*/

看一个例子:

创建一个子进程,子进程每秒中输出字符串“HELLO WORLD!",父进程等待用户输入,如果用户按下字符A, 则向子进程发信号SIGUSR1, 子进程的输出字符串改为大写; 如果用户按下字符a, 则向子进程发信号SIGUSR2, 子进程的输出字符串改为小写

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void text(int sig){                      //输出大写字符串

     printf("HELLO WORLD!\n");

     return;
}

void text1(int sig){                     //输出小写字符串

     printf("hello world!\n");

     return;
}

void text2(int sig){

}

int main(void){

   pid_t pd;

   char c;

   pd=fork();                            //创建子进程

   if(pd==-1){

     printf("进程创建失败!\n");

     exit(1);
   }
   else if(pd==0){                       //子进程执行代码

        struct sigaction act;

        act.sa_flags=0;
        act.sa_handler=text;
        sigemptyset(&act.sa_mask);
        sigaction(SIGUSR1,&act,0);       //将信号1与text绑定

        act.sa_handler=text1;
        sigaction(SIGUSR2,&act,0);       //将信号2与text1绑定

        while(1){
             sleep(3);
        }
   }
   else{                                 //父进程执行代码

       while(1){

              /* struct sigaction act1;

               act1.sa_flags=0;
               act1.sa_handler=text2;
               sigemptyset(&act1.sa_mask);
               sigaction(SIGUSR1,&act1,0);
               sigaction(SIGUSR2,&act1,0); */

               c=getchar();              //等待用户输入字符

               getchar();                //读取输入的回车

               if(c=='c'){

                  break;
               }
               else if(c=='A'){
                      kill(0,SIGUSR1);       //发送信号1
               }
               else if(c=='a'){
                      kill(0,SIGUSR2);       //发送信号2
               }

       }


   }

   return 0;

}

一开始我写的是上面的代码,但很奇怪的是父进程只能执行一次就会退出(上文提到信号SIGUSR1与SIGUSR2的默认行为都是终止进程)在这里插入图片描述

,后来我想到,kill函数发送信号给子进程的时候,父进程是否也会收到该信号。
于是我又写一版代码,这次在父进程中设置了信号与函数的绑定。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void text(int sig){                      //输出大写字符串

     printf("HELLO WORLD!\n");

     return;
}

void text1(int sig){                     //输出小写字符串

     printf("hello world!\n");

     return;
}

void text2(int sig){

}

int main(void){

   pid_t pd;

   char c;

   pd=fork();                            //创建子进程

   if(pd==-1){

     printf("进程创建失败!\n");

     exit(1);
   }
   else if(pd==0){                       //子进程执行代码

        struct sigaction act;

        act.sa_flags=0;
        act.sa_handler=text;
        sigemptyset(&act.sa_mask);
        sigaction(SIGUSR1,&act,0);       //将信号1与text绑定

        act.sa_handler=text1;
        sigaction(SIGUSR2,&act,0);       //将信号2与text1绑定

        while(1){
             sleep(3);
        }
   }
   else{                                 //父进程执行代码

       while(1){

               struct sigaction act1;

               act1.sa_flags=0;
               act1.sa_handler=text2;
               sigemptyset(&act1.sa_mask);
               sigaction(SIGUSR1,&act1,0);           //绑定信号与函数
               sigaction(SIGUSR2,&act1,0);

               c=getchar();              //等待用户输入字符

               getchar();                //读取输入的回车

               if(c=='c'){

                  break;
               }
               else if(c=='A'){
                      kill(0,SIGUSR1);       //发送信号1
               }
               else if(c=='a'){
                      kill(0,SIGUSR2);       //发送信号2
               }

       }


   }

   return 0;

}

这样就能正常运行了,后来我查了一下帮助发现确实如此(万能的man)。

在这里插入图片描述
用有道翻译了一下:

kill()系统调用可用于向任何进程组或进程发送任何信号。
如果pid为正,则信号sig被发送到pid指定ID的进程。
如果pid等于0,则sig被发送到调用进程的进程组中的每个进程。
如果pid = -1,则sig被发送到调用进程有权限发送信号的每个进程,除了进程1 (init),但是
见下文。
如果pid小于-1,则将sig发送给进程组中ID为-pid的所有进程。
如果sig为0,则没有信号发送,但仍然执行错误检查;这可用于检查进程ID或进程是否存在.

真相大白

7.alarm函数的使用

闹钟函数,一开始会休眠一段时间,休眠时间结束后,会向当前进程发送一个特定信号。

1.函数原型

unsigned int alarm(unsigned int seconds);
//参数为休眠的时间

实例:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>

int wakeflag = 0;

void wake_handle(int sig) 
{
	wakeflag = 1;
}

int main(void) 
{
	int ret;
	
	struct sigaction act;
	act.sa_flags = 0;
	act.sa_handler = wake_handle;
	sigemptyset(&act.sa_mask);
	sigaction(SIGALRM, &act, 0);
	
	printf("time =%ld\n", time((time_t*)0));

	ret = alarm(5);                //休眠5秒后发送信号
	if (ret == -1) {
		printf("alarm error!\n");
		exit(1);
	}
	pause();                       //挂起当前进程,直到收到任意一个信号

	if (wakeflag) {
		printf("wake up, time =%ld\n", time((time_t*)0));
	}

	return 0;
}


8.发送多个信号

某进程正在执行某个信号对应的操作函数期间(该信号的安装函数),如果此时,该进程又多次收到同一个信号(同一种信号值的信号),则:如果该信号是不可靠信号(<32),则只能再响应一次。如果该信号是可靠信号(>32),则能再响应多次(不会遗漏)。但是,都是都必须等该次响应函数执行完之后,才能响应下一次。

某进程正在执行某个信号对应的操作函数期间(该信号的安装函数),如果此时,该进程收到另一个信号(不同信号值的信号),则:如果该信号被包含在当前信号的signaction的sa_mask(信号屏蔽集)中,则不会立即处理该信号。直到当前的信号处理函数执行完之后,才去执行该信号的处理函数。

否则:

则立即中断当前执行过程(如果处于睡眠,比如sleep, 则立即被唤醒)而去执行这个新的信号响应。新的响应执行完之后,再在返回至原来的信号处理函数继续执行。

4.管道

man一下:

在这里插入图片描述

使用pipe函数后,会得到一个文件描述符数组fd,fd[0]对应管道的读端,fd[1]对应管道的写端。可以对fd[0]进行读操作,对fd[1]进行写操作(如果对fd[0]进行写操作,对fd[1]进行读操作,可能导致不可预期的错误)。

可以理解为使用pipe函数后,系统生成了一个多个进程可以访问到的文件(这个文件就是进程间通信的管道),只要取得了fd[]数组,就能往文件里写入数据,也可以读出数据。

放一张图片以便理解:

在这里插入图片描述

下面看一个实例:

在父进程中创建管道,再创建子进程,这样子进程也能取得fd数组,操作管道。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void){

   int fd[2];                 //文件描述符数组fd
   char buff[128];
   int _fd=0;                 //进程id

   int ret=pipe(fd);          //创建管道

   if(ret!=0){
      printf("管道创建失败!\n");
   }

   _fd=fork();                //创建进程

   if(_fd<0){
      printf("进程创建失败!\n");
   }
   else if(_fd==0){

        bzero(buff,sizeof(buff));              //清空buff
        read(fd[0],buff,sizeof(buff));         //子进程通过fd[0]读出buff中的数据
        printf("-------%s--------\n",buff);
   }
   else{

       strcpy(buff, "Hello World!");
       write(fd[1],buff,sizeof(buff));         //父进程通过fd[1]往buff写入数据
   }

   return 0;

}

值得注意的是:

对管道进行read时,如果管道中已经没有数据了,此时读操作将被“阻塞”。
如果此时管道的写端已经被close了,则写操作将可能被一直阻塞!
而此时的阻塞已经没有任何意义了。

如果不准备再向管道写入数据,则把该管道的所有写端(包括父子进程两个写端)都关闭,
则,此时再对该管道read时,就会返回0,而不再阻塞该读操作。(管道的特性)
注意,这是管道的特性。
如果有多个写端口,而只关闭了一个写端(只关闭父进程写端或子进程写端),那么无数据时读操作仍将被阻塞。

因为管道的这种特性,我们通常在只需读的一方关闭写通道(fd[1]),在只需写的一方关闭读通道(fd[0])。在一方完成写操作时关闭所有写端。

比如在上述实例中父子进程各有一个管道的读端和写端:把父进程的读端(或写端)关闭,把子进程的写端(或读端)关闭。则管道变为:

在这里插入图片描述

结合上述情况完成一个小练习:
创建一个子进程,父进程通过管道向子进程发送数据(字符串),该字符串由用户输入。当用户输入”exit”时, 就不再向子进程发送数据,并关闭该端的管道。
子进程从管道读取数据,并输出。直到父进程关闭了管道的写端。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void){

   int fd[2];                 //文件描述符数组fd
   char buff[128];
   int _fd=0;                 //进程id

   int ret=pipe(fd);          //创建管道

   if(ret!=0){
      printf("管道创建失败!\n");
   }

   _fd=fork();                //创建进程

   if(_fd<0){
      printf("进程创建失败!\n");
   }
   else if(_fd==0){

        close(fd[1]);                                //子进程关闭写端
        while(1){

              bzero(buff,sizeof(buff));              //清空buff

              if(read(fd[0],buff,sizeof(buff))==0){           //父进程写端关闭,跳出循化

                 close(fd[0]);
                 break;
              }

              printf("-------%s--------\n",buff);

        }
   }
   else{

       close(fd[0]);                                //父进程关闭读端
       while(1){

               //gets(stdin);
               scanf("%s",buff);
               //getchar();

               if(strcmp(buff, "exit")==0){         //如果输入exit则退出,关闭父进程写端

                  close(fd[1]);
                  break;
               }

               write(fd[1],buff,sizeof(buff));         //往buff写入数据
       }
   }

   return 0;

}
~

5.消息队列

1.什么是消息队列:

“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。

2.消息队列的创建:

int  msgget(key_t key,  int msgflg);
   /*功能:获取或创建一个消息队列
   参数:与共享内存相似。
            msgflag可使用IPC_CREAT
   返回值:成功,返回正整数,即“消息队列标识符”
              失败,返回-1  */

在这里插入图片描述

3.消息的发送:

在这里插入图片描述

int  msgsnd(int msqid,   const void *msgp,   size_t msgsz,   int msgflg);
   /* 功能:发送一个消息,即把消息添加到消息队列中
   参数:msgid  消息队列标识符
         msgp    消息指针
           
注:消息的类型需要自己定义。但要求其第一个结构成员为long int
     例: struct  my_msg_st {
             long  int  msg_type;    // 消息的类型,取>0, 接收消息时可使用该值 
                      				//other info
        } 
    msgsz  消息的长度(不包含第一个成员msg_type)
    msgflg  如果包含:  IPC_NOWAIT, 则消息队列满时,不发送该消息,而立即返回-1
            如果不包含:IPC_NOWAIT,则消息队列满时,挂起本进程,直到消息队列有空间可用。
   返回值:成功,返回0
           失败,返回-1           */

4.消息的接收:

 ssize_t msgrcv (int msqid,  void *msgp,   size_t msgsz,   long msgtype,   int msgflg);
    /*功能:从消息队列中接收一条消息。
    参数:msgid  消息队列标识符
            msgp    用于接收消息的缓存
            msgsz   要接收的消息的长度(不包括其第一个成员)
            msgtype  指定接收消息的类型
                  0: 从消息队列中获取第一个消息,以实现顺序接受(先发先收) */

5.消息的控制:

int  msgctl(int msqid,  int cmd,  struct msqid_ds *buf);
    /*功能:与shmctl类似
    参数:cmd 常用命令:
             IPC_RMID   删除消息队列
    返回值:成功, 返回 0
               失败,返回-1 */

6.实例:

in.c:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MSG_SIZE 80

struct buffs{

      long int type;
      char buff[MSG_SIZE];
};

int main(void){

   struct buffs msg;                                 //用于储存数据的结构体
   int msg_fd;
   msg_fd=msgget((key_t)1235, 0666|IPC_CREAT);       //创建消息队列

   if(msg_fd==-1){

      printf("消息队列创建失败!\n");
      return 0;
   }

   msg.type=1;
   strcpy(msg.buff,"Hello World!");

   int ret=msgsnd(msg_fd,&msg,MSG_SIZE,0);          //将消息送入消息队列

   if(ret==-1){
      printf("消息队列消息入队失败!\n");
   }

   return 0;

}
~

on.c:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#define MSG_SIZE 80

struct buffs{

       long int type;
       char buff[MSG_SIZE];
};

int main(void){

   struct buffs msg;
   int msg_fd=msgget((key_t)1235, 0666|IPC_CREAT);               //获取消息队列,是获取不是创建

   if(msg_fd==-1){

     printf("获取消息队列失败!\n");
     exit(0);
   }

   msg.type=0;
   int ret=msgrcv(msg_fd,&msg,MSG_SIZE,0,0);                     //取得消息队列中的消息

   if(ret==-1){
      printf("获取消息失败!\n");
   }

   printf("----------%s-----------\n",msg.buff);

   msgctl(msg_fd, IPC_RMID, 0);                                 //销毁消息队列

   return 0;

}

分别执行程序in.c与on.c。
程序in.c创建消息队列,并将"Hello World!“送入消息队列,on.c取得消息队列描述符,取出消息,打印出"Hello World!”。

7.练习:

程序1, 循环等待用户输入字符串,
每收到一个字符串,就把它发送给进程2
直到用户输入exit
程序2, 接受进程1发过来的信息,并打印输出。
直到接受到exit。

in.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MSG_SIZE 80

struct buffs{

      long int type;
      char buff[MSG_SIZE];
};

int main(void){

   struct buffs msg;                                 //用于储存数据的结构体
   int msg_fd;
   msg_fd=msgget((key_t)1235, 0666|IPC_CREAT);       //创建消息队列

   if(msg_fd==-1){

      printf("消息队列创建失败!\n");
      return 0;
   }

   msg.type=1;
   int ret=0;
   while(1){

        bzero(msg.buff,sizeof(MSG_SIZE));               //清空msg.buff
        printf("请输入你要输入的字符串!\n");
        scanf("%s",msg.buff);

        if(strcmp(msg.buff, "exit")==0){

           ret=msgsnd(msg_fd,&msg,MSG_SIZE,0);          //将消息送入消息队列

           if(ret==-1){
              printf("消息队列消息入队失败!\n");
           }

           exit(1);
        }

       ret=msgsnd(msg_fd,&msg,MSG_SIZE,0);          //将消息送入消息队列

       if(ret==-1){
         printf("消息队列消息入队失败!\n");
       }

    }

   return 0;

}
~

on.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#define MSG_SIZE 80

struct buffs{

       long int type;
       char buff[MSG_SIZE];
};

int main(void){

   struct buffs msg;
   int msg_fd=msgget((key_t)1235, 0666|IPC_CREAT);               //获取消息队列,是获取不是创建

   if(msg_fd==-1){

     printf("获取消息队列失败!\n");
     exit(0);
   }

   msg.type=0;
   int ret=0;

   while(1){

         bzero(msg.buff,sizeof(MSG_SIZE));
         ret=msgrcv(msg_fd,&msg,MSG_SIZE,0,0);                     //取得消息队列中的消息

         if(ret==-1){
            printf("获取消息失败!\n");
         }

         if(strcmp(msg.buff, "exit")==0){
            msgctl(msg_fd, IPC_RMID, 0);                                 //销毁消息队列
            exit(1);
         }

         printf("----------%s-----------\n",msg.buff);

   }

   return 0;

}

6.共享内存

摘自搜狗百科

共享内存是Unix下的多进程之间的通信方法 ,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。
所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。所有进程都能访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会即时被有访问同一段共享内存的其他进程看到。共享内存的使用大大降低了在大规模数据处理过程中内存的消耗,但是共享内存的使用中有很多的陷阱,一不注意就很容易导致程序崩溃。

1.System V版本的共享内存 shmm

原理:

多个进程都通过虚拟地址空间到用户页表,然后通过用户级页表映射到物理内存的相同一块内存区域

在这里插入图片描述

虚拟地址空间(摘自《深入理解计算机系统》):

虚拟存储器是一个抽象概念,它为每个进程提供了一个假象,好像每个进程都在独占地使用主存。每个进程看到的存储器都是一致的,称之为虚拟地址空间。下图所示的是Linux进程的虚拟地址空间,在Linux中,最上面的四分之一的地址空间是预留给操作系统中的代码和数据的,这对所有进程都一样。底部的四分之三的地址空间用来存放用户进程定义的代码和数据。请注意,图中的地址是从下往上增大的。

在这里插入图片描述

通俗的讲就是,某一进程通过某些接口分配获取一块内存,各进程获取内存首地址后对该内存进行操作,或储存数据或读取数据,因为使用的是同一块内存所以就实现了进程间的通信。

接口实现:

  • ftok函数生成key标识符
key_t ftok(const char *pathname,int proj_id)
  • 创建一个共享内存块
int shmget(key_t key,size_t size,int shmflg)
/* 参数说明:size   - 申请的共享内存的大小,为4k的整数倍;
          shmflg - IPC_CREAT 创建新的共享内存,已存在 使用IPC_EXCL */
  • 挂接共享内存
void *shmat(int shmid,const void *shmaddr, int shmflg)
/* 参数说明:shmid   - 挂接的共享内存ID.
          shmaddr - 一般为0,表示连接到由内核选择的第一个可用地址上
          shmflg  - 一般为0   */
  • 取消共享内存映射
int shmdt(const void *shmaddr);
/* shmaddr  - 地址 */
  • 用于控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds  *buf);
/* 参数   shmid - 由shmget返回的共享内存标识码
       cmd   - 将要采取的动作(可取值:IPC_STAT、IPC_SET、IPC_RMID)
     buf   - 指向一个保存着共享内存的模式状态和访问权限的数据结构  */

实例:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>

struct share_mem{

      int data;
      char buff[64];
};

int main(void){

   int shmem=0;

   pid_t pid=0;

   pid=fork();                    //创建一个子进程,用于与父进程通信
   if(pid==-1){

      printf("进程创建失败!\n");
      exit(1);
   }
   else if(pid>0){                //父进程执行代码

           shmem=shmget((key_t)1234,sizeof(struct share_mem),0666|IPC_CREAT);         //获取共享内存标识,如果不存在,则创建一个
           if(shmem==-1){

              printf("共享内存获取失败!\n");
              exit(1);
           }

           struct share_mem* tmp=(struct share_mem*)(shmat(shmem,(void*)0,0));        //获取共享内存分配的地址
           if(tmp==(void*)-1){

              printf("地址获取失败!\n");
              exit(2);
           }

           tmp->data=0;                                  //设置结构体数据
           strcpy(tmp->buff,"Hello World!");
           int count=0;
           for(;count<10;count++){

               tmp->data++;                             //结构体内的变量++
               sleep(1);
           }

           shmctl(shmem, IPC_RMID, 0);                  //销毁共享内存
   }
   else{                                                //子进程执行代码

       shmem=shmget((key_t)1234,sizeof(struct share_mem),0666|IPC_CREAT);
       if(shmem==-1){

          printf("共享内存获取失败!\n");
          exit(1);
       }

       struct share_mem* tmp=(struct share_mem*)(shmat(shmem,(void*)0,0));
       if(tmp==(void*)-1){

          printf("地址获取失败!\n");
          exit(2);
       }

       int count=0;
       for(;count<5;count++){                     //输出共享内存内结构体数据

           sleep(1);
           printf("----------%s-------------\n",tmp->buff);
           printf("----------%d-------------\n",tmp->data);
       }

   }

   return 0;
}

2.存储映射共享I/O (mmap函数)

原理:将一个文件或者其它对象映射进内存:
1.使用普通文件提供的内存映射
2.使用特殊文件提供匿名内存映射

在这里插入图片描述

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
       int munmap(void *addr, size_t length); 

/* 参数addr:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选
            定地址,映射成功后返回该地址。

参数length:代表将文件中多大的部分映射到内存。

参数prot:映射区域的保护方式。

参数flags:影响映射区域的各种特性。必须要指定MAP_SHARED 或MAP_PRIVATE。

参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。

参数offset:文件映射的偏移量,通常设置为0。  */  

看一个例子:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>

struct share_mem{

      int data;
      char buff[64];
};

int main(void){

   int shmem=0;

   int fd =open("tmp.txt",O_RDWR|O_CREAT|O_TRUNC,0644);                //用于保存要打开文件的文件描述符
   if(fd<0){

      printf("文件打开失败!\n");
      exit(3);
   }

   ftruncate(fd,sizeof(struct share_mem));         //将fd引用的常规文件截断为精确的长度字节。

   pid_t pid=0;

   pid=fork();                    //创建一个子进程,用于与父进程通信
   if(pid==-1){

      printf("进程创建失败!\n");
      exit(1);
   }
   else if(pid>0){                //父进程执行代码

           /* shmem=shmget((key_t)1234,sizeof(struct share_mem),0666|IPC_CREAT);         //获取共享内存标识,如果不存在,则创建一个
           if(shmem==-1){

              printf("共享内存获取失败!\n");
              exit(1);
           } */

           struct share_mem* tmp=(struct share_mem*)(mmap(NULL,sizeof(struct share_mem),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0));       //获取共享内存分配的地址
           if(tmp==(void*)-1){

              printf("地址获取失败!\n");
              exit(2);
           }

           tmp->data=0;                                  //设置结构体数据
           strcpy(tmp->buff,"Hello World!");
           int count=0;
           for(;count<10;count++){

               tmp->data++;                             //结构体内的变量++
               sleep(1);
           }

           munmap(tmp,sizeof(struct share_mem));                  //销毁共享内存
   }
   else{                                                //子进程执行代码

     /*  shmem=shmget((key_t)1234,sizeof(struct share_mem),0666|IPC_CREAT);
       if(shmem==-1){

          printf("共享内存获取失败!\n");
          exit(1);
       } */

       struct share_mem* tmp=(struct share_mem*)(mmap(NULL,sizeof(struct share_mem),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0));
       if(tmp==(void*)-1){

          printf("地址获取失败!\n");
          exit(2);
       }

       int count=0;
       for(;count<5;count++){                     //输出共享内存内结构体数据

           sleep(1);
           printf("----------%s-------------\n",tmp->buff);
           printf("----------%d-------------\n",tmp->data);
       }

   }

   return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值