进程之间是独立的,但是进程之间还是要相互协作的,这种协作叫通信。

本片博文非常的长~~~请按需求阅读

目录

1》》目的

2》》通信方式

3》》管道

4》》共享内存

5》》进程信号

1、Linux信号的种类:

2、信号的产生:

3、信号的注册:

4、信号的注销:

5、信号的处理:

6、信号的阻塞:

6》》可重入函数和不可重入函数

7》》volatile关键字



  • 1》》目的

数据传输:一个进程的数据发送给另一个进程
    数据共享:多个进程共享同样的进程
    通知事件:一个进程需要通知另一个进程发生了某种事件
    进程控制:一个进程完全控制    另一个进程的执行

  • 2》》通信方式

由于进程的独立性,通信需要双方拥有公共的媒介才能通信,而媒介由操作系统提供;由于通信的场景不同或者目的不同,操作系统提供了不同的进程间通信方式。
    a、管道
    b、共享内存
    c、消息队列
    d、信号量

ipcs    查看进程间通信方式(-m共享内存        -s信号量        -q消息队列)
     ipcrm 删除通信方式

  • 3》》管道

  • 概念:
    一个进程连接到另一个进程的数据流,称之为管道。这便可以达成一个目的——数据传输

  • 原理:
         
    操作系统在内核创建一块缓冲区,缓冲区便就是管道,并为用户提供管道的操作句柄----->传输的是字节流
           一共有两个句柄:fd[2]—— ( fd[0] 和 fd[1] ) fd[0]用于读取,fd[1]用于写入

  •     特性:

1>若管道中没有数据,read读取数据,会发生阻塞,直到读到数据返回。
         2>若管道中数据满了,如果继续写入数据,则会发生阻塞,直到能够写入数据或者写入完成。
         3>若所有读端关闭,继续写入数据会触发异常。SIGPIPE
         4>若所有写端关闭,进程会将管道数据全部读取完后退出程序。
         5>声明周期随进程,自带同步和互斥
                    (同步:对临界资源访问的时序可控    互斥:对资源的访问唯一)

  • 命名管道/匿名管道    ——都是半双工通信
  • 匿名管道:
    没有名字的缓冲区,因此在内核中,如果是两个独立的进程,即使其中一个进程创建了管道,另一个进程是找不到它的句柄的,所以匿名管道只能是用于具有亲缘关系的进程进行通信(fork()创建子进程,父子进程同时指向匿名管道)
     int pipe(int fd[2])————成功返回0,失败返回错误代码

1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5
  6 int main()
  7 {
  8     int pipefd[2];
  9     int ret = pipe(pipefd);
 10
 11     if(ret < 0)
 12     {
 13         printf("PIPE ERROR\n");
 14         return -1;
 15     }
 16
 17     int pid = fork();
 18     if(pid < 0)
 19     {
 20         printf("FORK ERROR\n");
 21         exit(-1);
 22     }
 23     else if(pid == 0)//子进程
 24     {
 25         char buff[1024];
 26         read(pipefd[0],buff,1023);
 27         printf("child readed:%s\n",buff);
 28     }
 29     else
 30     {
 31         char buff[1024] = {0};
 32         scanf("%s",buff);
 33         printf("father write:%s\n",buff);
 34         write(pipefd[1],buff,strlen(buff));
 35     }
 36     wait(NULL);
 37     return 0;
 38 }

  • 命名管道:
    文件系统的可见性,有文件可见于文件系统之中。
    创建一个带名字的缓冲区,操作和匿名管道相同。
    open打开管道文件的特性:

若管道文件以只读方式打开,若没有被其他进程以写的方式打开,则被阻塞,直到被其他进程以写的方式打开这个管道文件

若管道文件以只写方式打开,若没有被其他进程以读的方式打开,则被阻塞,直到被其他进程以读的方式打开这个管道文件

        管道的实例:管道符的实现 |
            主进程创建两个子进程,在两个中分别进行程序替换让两个子进程分别运行ls程序和grep程序
                ls 将结果写入到标准输出
                grep从标准输入读取数据

  • 4》》共享内存

进程间通信最快的一种

  • 概念:在物理内存中开辟一块空间,并将空间映射到各个进程的虚拟地址空间的共享区,其他进程可以通过虚拟地址直接对内存进行操作。

  • 共享内存的操作步骤:(shared memory------------>shm)

a、创建共享内存——指定标识符,大小,权限,并返回一个句柄    ---->shmget
        b、将共享内存映射到虚拟地址空间——对句柄操作-------------------->shmat
        c、对共享内存进行任意操作
        d、解除映射关系----------------------------------------------------------->shmdt
        e、删除共享内存----------------------------------------------------------->shmctl    
    int shmget(size_t key, size_t size, int shmflg)
        key:共享内存的标识符/名称
        size:共享内存的大小
        shmflg:与mode_t权限类似
        成功返回shmid,失败返回-1
    void* shmat(int shmid, const void* shmaddr, int shmflg)
        shmid:共享内存标识符
        shmaddr:指定连接的地址
        shmflg:SHM_RND\SHM_RDONLY
        成功返回共享内存首地址    失败返回 -1
     int shmdt(const void *shmaddr);
         shmaddr: 由shmat所返回的指针
        返回值:成功返回0;失败返回-1
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
        shmid:由shmget返回的共享内存标识码
        cmd:将要采取的动作(有三个可取值)
        buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
        返回值:成功返回0;失败返回-1

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/shm.h>
#define IPC_KEY 0x123456
#define SHM_SIZE 4096
  int main()
  {    //创建共享内存
       int shmid = shmget(IPC_KEY,SHM_SIZE,IPC_CREAT|0664);
        if(shmid < 0)
       {
             perror("CREATE ERROR\n");
              return -1;
        }
  //映射首地址
      char* address = (char*)shmat(shmid,NULL,0);
 //映射首地址
      char* address = (char*)shmat(shmid,NULL,0);
      if(address ==(void*)-1)
        {
             perror("CONECT ERROR\n");
              return -1;
        }
//进行内存操作
     int i = 0;
        while(1)
  {
        sprintf(address,"------%d",++i);

sleep(1);
    }
 //接触映射关系
       shmdt(address);
       shmctl(shmid,IPC_RMID,NULL);
     return 0;
 }

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/shm.h>
#define IPC_KEY 0x123456
#define SHM_SIZE 4096
  int main()
  {    //创建共享内存
       int shmid = shmget(IPC_KEY,SHM_SIZE,IPC_CREAT|0664);
        if(shmid < 0)
       {
             perror("CREATE ERROR\n");
              return -1;
        }
  //映射首地址
      char* address = (char*)shmat(shmid,NULL,0);
 //映射首地址
      char* address = (char*)shmat(shmid,NULL,0);
      if(address ==(void*)-1)
        {
             perror("CONECT ERROR\n");
              return -1;
        }
//进行内存操作
     int i = 0;
        while(1)
  {
        printf("收到:%s\n",address);
            sleep(1);
    }
 //接触映射关系
       shmdt(address);
       shmctl(shmid,IPC_RMID,NULL);
     return 0;
 }

第一个程序运行之后,运行第二个程序,便会得到下面的图1。如果第一个程序停止,第二个的数据不会变化,图2。第二个程序停止在运行,数据不会从停止的时候开始,因为共享区的数据一直在被改变图3。


ipcs -m

ipcrm:删除该id之后,我并没有结束我的进程,所以并不会真正的删除我们的共享内存,当我们后面的链接数nattch为0的时候,就真正的删除了

  • 5》》进程信号

信号:就像我们听到下课铃声,铃声响起(信号产生)--->我们听到,并且识别(信号注册)--->知道下课了,忘记这个提示(信号的注销)---->开始玩耍(信号的处理)/拖堂(信号的阻塞)

    作用:事件通知——实际是软中断(软件中断)

  • 1、Linux信号的种类

查看:kill -l    (1~31 非可靠信号,继承Unix    34~64    可靠信号(实时信号))
            

  • 2、信号的产生:

      就像下课铃声打了~~~~。信号产生。

  • 软件方式:

函数:

            int kill(pid_t pid, int sig):指定进程,指定信号

            int raise(int sig):给调用进程指定信号

            int abort(void):给调用进程发送SIGBART信号

size_t alarm(size_t second):second秒后发送SIGALRM信号
   
     我需要每次睡眠一秒,不然不知道打印会很多很多~


总的代码。

  • 硬件方式:

ctrl + c        ctrl + z(只是停止进程,并未退出)        ctrl + l
           (信号的到来会打断当前可中断睡眠状态)
   core dumped———核心转储:异常退出时保存程序运行信息--->默认关闭
             ulimit -a        查看转储文件大小
            
             ulimit -c        设置转储文件大小-----》只有设置了大小,才会生成core文件
             core文件命名方式:core.pid
            
             core文件的使用:
                gdb   可执行文件     --->        core-file    core.pid
            

  • 3、信号的注册:

下课铃声进入我们和老师的大脑。
      pcb中有 struct sigpending,该结构体中有sigset_t(信号集合——位图0\1)
    操作系统给一个进程发送信号,实际就是向进程的信号pending集合中添加信号(修改位图)
    位图只能标记信号是否存在,不能标记到来的信号的个数,这就牵出可靠信号和不可靠信号。
    pcb中有sigqueue链表,信号到来会组织一个结点添加到链表中
    可靠信号到来:修改位图,每个信号都组织结点添加到链表中
    非可靠信号到来:修改位图,若位图已经为一,修改位则什么也不做,否则添加一个结点。
    

  • 4、信号的注销:

     铃声响完之后,铃声不会一直在脑海里
    可靠信号:可能结点有多个,所以删除结点后判断是否有多个相同结点,判断是否修改位图
    非可靠信号:结点只有一个,删除结点位图修改为0

  • 5、信号的处理:

     准备下课~~~~
     当我们收到接收到一个信号,就需要来处理这个信号,也就是处理每个信号对应的操作。每个信号都有其默认的操作,但是操作系统也提供了修改其默认操作的接口。

  • 1) signal(int signum, sighander_t handler)

这是给信号修改一个处理函数,这个也可以当成是信号的注册,因为它就是给信号赋一个操作。当出现signum号信号,开始执行这个handler操作。
    signum:信号编号
    handler:操作,操作有一下三种:
             默认:每个结点对应了一个事件,每个事件在操作系统都有定义完毕的处理方式    SIG_DFL
             忽略:忽略信号    SIG_IGN
             自定义:用户自定义函数(回调函数),替换内核中的处理方式

我定义一个处理的函数,每当我发起SIGINT信号,就会去调我们的fun函数。

  •  int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;//通常为 0,调用第一个函数sa_handler,如果是SA_SIGINFO就调用第二个函数
               void     (*sa_restorer)(void);
           };

sigaction函数可以读取和修改与指定信号相关联的处理动作。signal(int signum, sighander_t handler)内部也是调用的这个函数。

参数:igno是指定信号的编号。
                     若act指针⾮空,则根据act修改该信号的处理动作。
                    若oact指针⾮空,则通 过oact传出该信号原来的处理动作。

void fun(int num)//自定义操作
  {
      printf("action !! %d\n",num);
  }
  int main()
  {
      struct sigaction act;
      struct sigaction old;
      sigemptyset(&act.sa_mask);
      act.sa_handler = fun;//自定义操作
      act.sa_flags = 0;
      sigaction(2,&act,&old);//不可靠信号
      sigaction(SIGRTMIN+5,&act,&old);//可靠信号
//下面是信号的阻塞,在下下一小节/
      sigset_t newset;
      sigset_t oldset;
      sigemptyset(&newset);
      sigfillset(&newset);
      sigprocmask(SIG_BLOCK,&newset,&oldset);//阻塞所有信号
      printf("bolck………………………………\n");
      getchar();
      sigprocmask(SIG_UNBLOCK,&newset,NULL);
  }
2号信号触发了4次,39号信号也调用了4次,但是不可靠信号之处理了一次。说明改信号只注册了一次,这就验证了前面两种信号的注册
 

  • 信号的捕捉

处理操作是我们用户自定义操作的时候,进程会从用户态和内核态相互切换。
     如果信号的处理动作是⽤户⾃定义函数,在信号递达时就调用这个函数,这称为捕捉信号

    进程从用户态到内核态三种形式:系统调用接口,程序异常,中断

  • 6、信号的阻塞:

就是说信号来了,但是暂时不处理这个信号,阻止信号的递达。就像下课铃声响了,老师拖堂,阻塞下课。
       (注意: 9信号和19信号不能被阻塞和自定义)

  1. int sigemptyset(sigset_t *set);                   清空信号集合
  2. int sigfillset(sigset_t *set);                          将所有信号添加到集合中
  3. int sigaddset (sigset_t *set, int signo);       将指定信号添加到集合中
  4. int sigdelset(sigset_t *set, int signo);         删除指定信号
  5. int sigismember(const sigset_t *set, in t signo);
  6. int sigprocmask(int how, const sigset_t *set, sigset_t *oset);     返回值:若成功则为0,若出错则为-1

SIG_BLOCK :    阻塞set集合中的信号,将原有阻塞放入oset
        SIG_UNBLOCK:    对set集合中的信号解除阻塞
        SIG_SETMASK:    将set集合中的信号添加到阻塞集合中

如果oset是⾮空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
          如果set是⾮空指针,则 更改进程的信号屏蔽字,参数how指⽰如何更改。
          如果oset和set都是⾮空指针,则先将原来的信号 屏蔽字备份到oset⾥,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

//如果oset和set都是⾮空指针,则先将原来的信号 屏蔽字备份到 oset⾥,然后根据set和how参数更改信号屏蔽字
  /*实现的过程:
   *1、定义一个集合
   *2、向集合哄添加要阻塞的信号
   *3、阻塞这个集合中的所有信号
   *4、获取换行getchar()
   *5、对集合中的信号进行解除阻塞
   */
   #include<stdio.h>
   #include<unistd.h>
   #include<signal.h>
  int main()
  {
      sigset_t newset;
      sigset_t oldset;
      sigemptyset(&newset);
      sigfillset(&newset);
      sigprocmask(SIG_BLOCK,&newset,&oldset);
      printf("bolck………………………………\n");
      getchar();
      sigprocmask(SIG_UNBLOCK,&newset,NULL);
      return 0;
 }

//如果oset是⾮空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是⾮空指针,则 更改进程 的信号屏蔽字,参数how指⽰如何更改。。
void print(sigset_t* p)
 {
      int i = 1;//信号的集合是从1开始的
      for(i; i < 32; ++i)
      {
          if(sigismember(p,i))//判断指定信号是否在目标信号集中
          {
              printf("1");
          }
          else
          {
              printf("0");
          }
      }
      printf("\n");
  }
  int main()
  {
     sigset_t newset,oldset,pending;
     sigemptyset(&newset);
      sigfillset(&newset);
      sigprocmask(SIG_BLOCK,&newset,NULL);
      raise(5);//给调用进程发送5号信号
      raise(6);//给调用进程发送6号信号
      raise(7);//给调用进程发送7号信号
      raise(8);//给调用进程发送8号信号
      getchar();
      sigpending(&pending);
      print(&pending);//打印我们的未决集合
      sigprocmask(SIG_UNBLOCK,&newset,&oldset);//解除阻塞
      return 0;
}
//由于阻塞了所有信号,所以2号和3号也阻塞着。未决集合,注册了,但是没有处理的信号。
   所以23 5678号信号都打印了

  • 6》》可重入函数和不可重入函数

:重入,我们可以理解为多个执行流可以进入。函数被不同的控制流程调⽤,有可能在第⼀次调⽤还没返回时就再次进⼊该函数, 这称为重⼊。那么这样我们得考虑数据安全的问题。所以能否在多个时序操作中对全局数据能时序性的访问,也就是全局数据是正常的改变就是重入,反之就是不可重入。看下面的例子

//C语言
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int a = 1;
int b = 1;
int test()
{++a;sleep(4);++b;return a+b;}void sigcb(int num){printf("signal-----> %d\n",test());}int main(){signal(SIGINT,sigcb);//CTRL + C 触发printf("main-----> %d\n",test());return 0;}
//如果我们的主控流程先执行,即信号后执行,我们预期结果是不是应该是
signal----->6
main----->4

但是………………4秒类按CTRL + C,不然触发不了信号。

为什么和预期结果不一样呢?
这就是因为这个test是不可重入的函数,也就是说如果有其他执行流进入,会造成数据错误,造成数据不安全问题。在4秒类,a先++、a==2,等四秒,这时信号进来,a++、a==3、,b++,b==2,然后主控流程的printf最后执行的时候,b++、b==3,得到上面的结果。

  • 7》》volatile关键字

保持内存可见性,防止编译器对代码过度优化。看下面的例子

//C语言#include<stdio.h>#include<signal.h> int a = 1;void sigcb(int num){printf("signal-----》%d\n",num);//CTRL+C,触发这个操作,改变a的值a = 0;}int main(){signal(SIGINT,sigcb);while(a)//值改变之后,while退出循环{ }return 0;}
//真的是这样的吗??

有没有发现我编译的时候加了-O2,这就是让编译器对代码进行优化。对于上面的代码,如果a这个数据用的频率偏高,而且值一直没有变化,所以它就将a的值放入寄存器中,那么CPU在寄存器中拿数据就不去内存中了,但是我们改变的是内存中的a的值,所以a的值并没有改变。这时我们对 a 加个关键字volatile。

//C语言
//C语言#include<stdio.h>#include<signal.h> volatile int a = 1;void sigcb(int num){printf("signal-----》%d\n",num);//CTRL+C,触发这个操作,改变a的值a = 0;}int main(){signal(SIGINT,sigcb);while(a)//值改变之后,while退出循环{ }return 0;}
//真的是这样的吗??

这时一下就退出了,加这个关键字的时候,编译器就不会把a加入到寄存器中。

Linux征途——进程间通信相关推荐

  1. linux c 进程间通信

    进程间通信概述 进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息. Linux的进程间通信方法有管道(Pipe)和有名管道(FIFO).信号( ...

  2. Linux的进程间通信-消息队列

    Linux的进程间通信-消息队列 微博ID:orroz 微信公众号:Linux系统技术 前言 Linux系统给我们提供了一种可以发送格式化数据流的通信手段,这就是消息队列.使用消息队列无疑在某些场景的 ...

  3. Linux环境进程间通信(二): 信号--转载

    http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html http://www.ibm.com/developerworks ...

  4. Linux下进程间通信的六种机制详解

    linux下进程间通信的几种主要手段:        1.管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具 ...

  5. Linux下进程间通信概述

    1. Linux下进程间通信概述 P83-P84 将第一页和第二页合并起来讲了 引言:前面我们学习了一下进程,我们知道多,进程间的地址空间相对独立.进程与进程间不能像线程间通过全局变量通信. 如果想进 ...

  6. Linux环境进程间通信 信号量

    信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制.相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志.除了用于访问控制外,还可用于进程 ...

  7. Linux进程+进程间通信IPC

    一 Linux进程 1) 进程的内存映像 2)解释 BSS段:在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Bloc ...

  8. 通信开源linux,Linux环境进程间通信

    Linux环境进程间通信(一) http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/index.html Linux环境进程间通信(二): 信 ...

  9. Linux环境进程间通信(五): 共享内存(上)

    Linux环境进程间通信(五): 共享内存(上) 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间. ...

最新文章

  1. [概统]本科二年级 概率论与数理统计 第七讲 期望、方差与相关性
  2. PyCairo 中的文本
  3. 14行代码AC——习题5-4 交换学生(Foreign Exchange, UVa 10763)——解题报告
  4. SAP CRM产品主数据无法根据产品描述字段进行搜索的原因
  5. 群签名和环签名的区别_环签名方案的研究
  6. mysql 同一天多条记录只取第一条_MySQL面试高频100问(二)
  7. java中操作时间的常用工具类
  8. OSI七层、TCP/IP五层、UDP、TCP的socket编程(服务端及客户端)、字节序转换、多进程以及多线程服务端的实现
  9. 【ElasticSearch】深入理解 ElasticSearch Doc Values
  10. 输出大写字母矩阵c语言,寻找C语言大神!!从键盘输入任意一个字母,如果其为大写字母,则输出“capital letter”;如果其为小写...
  11. SWPU ROUND #6(DIV.3)
  12. 学术族谱典型用户及典型场景模拟
  13. 泰勒公式的计算机应用,泰勒公式应用
  14. 谷歌学术打不开的解决办法
  15. 自我鉴定计算机专业大学,大学生计算机专业的自我鉴定书
  16. 微信小程序后台销毁时间 演变和总结(热启动时间限制)
  17. PCM1863应用笔记
  18. HTML实现A4模板
  19. Kali渗透测试(四)——无线网络WPA攻击(PSK破解、AIROLIB、JTR、cowpatty、pyrit)
  20. 创建一个docker容器

热门文章

  1. ZCuSn10Pb1铸造锡青铜套ZCuSn10Pb1力学性能
  2. HTTP Catcher(网球)使用教程【五】开启DNS劫持
  3. RPC分布式网络通信框架(三)—— 服务配置中心Zookeeper模块
  4. 【zzulioj 1897 985的红绿灯难题】
  5. SystemTap编译安装
  6. 03-学习笔记(HTML创建表格并通过for循环将数组内数据插入表格-vue)【新手上路,多多关照】
  7. 西虹市首富:在币圈赔光10亿的一万种办法
  8. 如何增强宝宝抵抗力和免疫力,宝宝抵抗力差怎么调理
  9. datagram和packet的区别
  10. 游戏服务器后台的快速开发