• 20155301 滕树晨linux基础——linux进程间通信(IPC)机制总结


    20155301 滕树晨linux基础——linux进程间通信(IPC)机制总结

    共享内存

    • 共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的地址空间(这里的地址空间具体是哪个地方?)中。其他进程可以将同一段共享内存连接到自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是malloc分配的一样。如果一个进程向共享内存中写入了数据,所做的改动将立刻被其他进程看到

    • 共享内存是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换。共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅映射到各进程的地址不同而已,因此不需要进行复制,可以直接使用此段空间。

    • 注意:共享内存本身并没有同步机制,需要程序员自己控制。

    • 共享内存的头文件

    #include <sys/types.h>
    
    #include <sys/stat.h>
    
    #include <sys/shm.h>
    
    • 结构shmid_ds结构体
    strcut shmid_ds{
    
      struct ipc_perm shm_perm;
    
      size_t shm_segsz;
    
      time_t shm_atime;
    
      time_t shm_dtime;
    
    ......
    }
    
    • 共享内存函数定义:

      int shmget(key_key,size_t size,int shmflg); //shmget函数用来创建一个新的共享内存段,或者访问一个现有的共享内存段(不同进程只要key值相同即可访问同一共享内存段)。第一个参数key是ftok生成的键值,第二个参数size为共享内存的大小,第三个参数sem_flags是打开共享内存的方式。

      eg.int shmid = shmget(key, 1024, IPC_CREATE | IPC_EXCL | 0666);//第三个参数参考消息队列int msgget

      (key_t key,int msgflag);

      void *shmat(int shm_id,const void *shm_addr,int shmflg); //shmat函数通过shm_id将共享内存连接到进程的地址空间中。第二个参数可以由用户指定共享内存映射到进程空间的地址,shm_addr如果为0,则由内核试着查找一个未映射的区域。返回值为共享内存映射的地址。  
      eg.char *shms = (char *)shmat(shmid, 0, 0);//shmid由shmget获得

      int shmdt(const void *shm_addr); //shmdt函数将共享内存从当前进程中分离。 参数为共享内存映射的地址。  
      eg.shmdt(shms);

      int shmctl(int shm_id,int cmd,struct shmid_ds *buf);//shmctl函数是控制函数,使用方法和消息队列msgctl()函数调用完全类似。参数一shm_id是共享内存的句柄,cmd是向共享内存发送的命令,最后一个参数buf是向共享内存发送命令的参数。

    管道

    管道实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。

    • 管道的特点:

      1、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

      2、只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。比如fork或exec创建的新进程,在使用exec创建新进程时,需要将管道的文件描述符作为参数传递给exec创建的新进程。当父进程与使用fork创建的子进程直接通信时,发送数据的进程关闭读端,接受数据的进程关闭写端。

      3、单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

      4、数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

    • 管道的实现机制:

      管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

      管道只能在本地计算机中使用,而不可用于网络间的通信。

      pipe函数原型:

    #include <unistd.h>   
    int pipe(int file_descriptor[2]);//建立管道,该函数在数组上填上两个新的文件描述符后返回0,失败返回-1。  
    eg.int fd[2]  
    int result = pipe(fd);  
    

    通过使用底层的read和write调用来访问数据。 向file_descriptor[1]写数据,从file_descriptor[0]中读数据。写入与读取的顺序原则是先进先出

    • 管道读写规则

      当没有数据可读时:

      O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

      O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

      当管道满的时候

      O_NONBLOCK disable:write调用阻塞,直到有进程读走数据

      O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

    如果所有管道写端对应的文件描述符被关闭,则read返回0

    如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE

    当要写入的数据量不大于PIPE_BUF(Posix.1要求PIPE_BUF至少512字节)时,linux将保证写入的原子性。

    当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

    命名管道(FIFO)

    命名管道是一种特殊类型的文件,它在系统中以文件形式存在。这样克服了管道的弊端,他可以允许没有亲缘关系的进程间通信。

    创建管道的两个系统调用原型:

    #include <sys/types.h>   
    #include <sys/stat.h>   
    int mkfifo(const char *filename,mode_t mode); //建立一个名字为filename的命名管道,参数mode为该文件的权限(mode%~umask),若成功则返回0,否则返回-1,错误原因存于errno中。  
    eg.mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );  
    

    具体操作方法只要创建了一个命名管道然后就可以使用open、read、write等系统调用来操作。创建可以手工创建或者程序中创建。

    int mknod(const char *path, mode_t mode, dev_t dev); //第一个参数表示你要创建的文件的名称,第二个参数表示文件类型,第三个参数表示该文件对应的设备文件的设备号。只有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0即可。  
    eg.mknod(FIFO_FILE,S_IFIFO|0666,0);    
    
    • 管道和命名管道的区别:

      对于命名管道FIFO来说,IO操作和普通管道IO操作基本一样,但是两者有一个主要的区别,在命名管道中,管道可以是事先已经创建好的,比如我们在命令行下执行

      mkfifo myfifo
      

      就是创建一个命名通道,我们必须用open函数来显示地建立连接到管道的通道,而在管道中,管道已经在主进程里创建好了,然后在fork时直接复制相关数据或者是用exec创建的新进程时把管道的文件描述符当参数传递进去。

      一般来说FIFO和PIPE一样总是处于阻塞状态。也就是说如果命名管道FIFO打开时设置了读权限,则读进程将一直阻塞,一直到其他进程打开该FIFO并向管道写入数据。这个阻塞动作反过来也是成立的。如果不希望命名管道操作的时候发生阻塞,可以在open的时候使用O_NONBLOCK标志,以关闭默认的阻塞操作。

    信号

    信号机制是unix系统中最为古老的进程之间的通信机制,用于一个或几个进程之间传递异步信号。信号可以有各种异步事件产生,比如键盘中断等。shell也可以使用信号将作业控制命令传递给它的子进程。

    • 使用方法定义:
    #include <sys/types.h> 
    #include <signal.h> 
    void (*signal(int sig,void (*func)(int)))(int); //用于截取系统信号,第一个参数为信号,第二个参数为对此信号挂接用户自己的处理函数指针。返回值为以前信号处理程序的指针。
    eg.int ret = signal(SIGSTOP, sig_handle);
    

    由于signal不够健壮,推荐使用sigaction函数。

    int kill(pid_t pid,int sig); //kill函数向进程号为pid的进程发送信号,信号值为sig。当pid为0时,向当前系统的所有进程发送信号sig。  
    int raise(int sig);//向当前进程中自举一个信号sig, 即向当前进程发送信号。  
    #include <unistd.h>   
    unsigned int alarm(unsigned int seconds); //alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。使用alarm函数的时候要注意alarm函数的覆盖性,即在一个进程中采用一次alarm函数则该进程之前的alarm函数将失效。  
    int pause(void); //使调用进程(或线程)睡眠状态,直到接收到信号,要么终止,或导致它调用一个信号捕获函数。  
    

    消息队列

    消息队列是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用IPC标识符唯一地进行识别。内核中的消息队列是通过IPC的标识符来区别,不同的消息队列直接是相互独立的。每个消息队列中的消息,又构成一个独立的链表。

    消息队列克服了信号承载信息量少,管道只能承载无格式字符流。

    • 消息队列头文件
    #include <sys/types.h>   
    #include <sys/stat.h>   
    #include <sys/msg.h>   
    
    • 消息缓冲区结构
    struct msgbuf{  
        long mtype;  
        char mtext[1];//柔性数组  
    }  
    

    在结构中有两个成员,mtype为消息类型,用户可以给某个消息设定一个类型,可以在消息队列中正确地发送和接受自己的消息。mtext为消息数据,采用柔性数组,用户可以重新定义msgbuf结构。例如:

    struct msgbuf{  
        long mtype;  
        char mtext[1];//柔性数组  
    }  
    

    当然用户不可随意定义msgbuf结构,因为在linux中消息的大小是有限制的,在linux/msg.h中定义如下:

    define MSGMAX 8192

    消息总的大小不能超过8192个字节,包括mtype成员(4个字节)。

    • msqid_ds内核数据结构。
    <div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">struct msgid_ds{</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">   struct ipc_perm msg_perm{</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">   time_t msg_stime;</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">   time_t msg_rtime;</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">   time_t msg_ctime;</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">   unsigned long _msg_cbuyes;</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">    ..........</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">   };</div>
    

    Linux内核中,每个消息队列都维护一个结构体,此结构体保存着消息队列当前状态信息,该结构体在头文件linux/msg.h中定义。

    • ipc_perm内核数据结构
    struct ipc_perm{  
      key_t key;  
      uid_t uid;  
      gid_t gid;  
      .......  
    };  
    

    结构体ipc_perm保存着消息队列的一些重要的信息,比如说消息队列关联的键值,消息队列的用户id组id等。它定义在头文件linux/ipc.h中。

    常用函数:

    key_t ftok( const char * fname, int id );//参数一为目录名称, 参数二为id。如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
    eg.key_t key = key =ftok(".", 1);
    int msgget(key_t key,int msgflag); //msgget用来创建和访问一个消息队列。程序必须提供一个键值来命名特定的消息队列。
    eg.int msg_id = msgget(key, IPC_CREATE | IPC_EXCL | 0x0666);//根据关键字创建一个新的队列(IPC_CREATE),如果队列存在则出错(IPC_EXCL),拥有对文件的读写执行权限(0666)。
    int msgsnd(int msgid,const void *msgptr,size_t msg_sz,int msgflg); //msgsnd函数允许我们把一条消息添加到消息队列中。msgptr只想准备发送消息的指针,指针结构体必须以一个长整型变量开始。 
    eg.struct msgmbuf{
        int mtype;
        char mtext[10];
    };
    struct msgmbuf msg_mbuf;
    msg_mbuf.mtype = 10;//消息大小10字节
    memcpy(msg_mbuf.mtext, "测试消息", sizeof("测试消息"));
    int ret = msgsnd(msg_id, &msg_mbuf, sizeof("测试消息"), IPC_NOWAIT);
    int msgrcv(int msgid, void *msgptr, size_t msg_sz, long int msgtype, int msgflg); //msgrcv可以通过msqid对指定消息队列进行接收操作。第二个参数为消息缓冲区变量地址,第三个参数为消息缓冲区结构大小,但是不包括mtype成员长度,第四个参数为mtype指定从队列中获取的消息类型。
    eg.int ret = msgrcv(msg_id, &msg_mbuf, 10, 10, IPC_NOWAIT | MSG_NOERROR);
    int msgctl(int msqid,int cmd,struct msqid_ds *buf); //msgctl函数主要是一些控制如删除消息队列等操作。 cmd值如下:
    IPC_STAT:获取队列的msgid_ds结构,并把它存到buf指向的地址。
    IPC_SET:将队列的msgid_ds设置为buf指向的msgid_ds。
    IPC_RMID:内核删除消息队列,最后一项填NULL, 执行操作后,内核会把消息队列从系统中删除。
    
    • 消息队列的本质

      Linux的消息队列(queue)实质上是一个链表,它有消息队列标识符(queue ID)。 msgget创建一个新队列或打开一个存在的队列;msgsnd向队列末端添加一条新消息;msgrcv从队列中取消息, 取消息是不一定遵循先进先出的, 也可以按消息的类型字段取消息。

  • 相关阅读:
    给有C或C++基础的Python入门 :Python Crash Course 4 操作列表 4.4 -- 4.5
    给有C或C++基础的Python入门 :Python Crash Course 4 操作列表 4.1--4.3
    因数表进阶:1--x的因数和
    【python】接口自动化初识第三站
    【python】接口自动化初识第二站
    【python】接口自动化初识第一站
    【pycharm】无法自动补齐代码
    【python】UI自动化框架搭建
    【jmeter】B接口通过正则表达式引用A接口的参数
    测试流程测试方法
  • 原文地址:https://www.cnblogs.com/fengxingck/p/8012620.html
Copyright © 2020-2023  润新知