Linux进程间通信_IPC机制

来源:互联网 发布:mac的iphone在哪 编辑:程序博客网 时间:2024/05/17 22:03

刚开始接触IPC机制时,感觉这个知识点真的时晦涩难懂,因此自己将自己对IPC机制的理解做下总结。

Linux中的IPC机制:信号量, 共享内存, 消息队列。

注意这里的IPC机制的通信只能在一台主机上的多个进程线程之间进行通信,而跨主机之间的通信用socket, poll, epoll机制。

一:信号量

    计数信号量,其值在0和某个限制值之间,该值最大为32767, 这里信号量的值就是可用资源数。

    System V对计数信号量又加了一级复杂度,System V 信号量是由一个或多个计数信号量构成的集合。

信号量是什么:

       信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。

       信号量主要是对临界资源进行保护的机制,通过p,v操作使信号量结构体内部的成员val数值的变化来标示有没有可用资源,通常情况下信号量接合共享内存一起使用。

信号量集在系统中的数据结构,在/usr/include/linux/sem.h中可以找到。

内核维护的信号量集的结构体

struct semid_ds {
    struct ipc_perm    sem_perm;               //这个ipc_perm结构含有当前这个信号量的访问权限     
    __kernel_time_t    sem_otime;       
    __kernel_time_t    sem_ctime;      
    struct sem    *sem_base;                       //sem_base是一个指向信号量结构数组的指针     
    struct sem_queue *sem_pending;     
    struct sem_queue **sem_pending_last;   
    struct sem_undo    *undo;          
    unsigned short    sem_nsems;    
};

内核管理的信号量集如图所示


系统调用函数semop()对这个结构体成员数据进行操作,来实现对val值的改变。

struct sembuf {
    unsigned short  sem_num;    
    short        sem_op;       
    short        sem_flg;   
};

信号量结构体

union semun {
    int val;           
    struct semid_ds *buf;   
    unsigned short *array;   
    struct seminfo *__buf;   
    void *__pad;
};

通常情况下,通过对信号量进行p,v操作使得信号量的数据成员val的变化,来标示资源是否可用。val值表示几个进程可以访问这个可用资源。例如val值设为2, 现在有个进程想访问这个资源,则进行p操作是的val值减一,如果令个进程访问这个临界资源,在进行p操作,此时val值变为0,表示没有可用资源可以访问,这时如果有个进程访问这个资源,那么进程会被阻塞。直到使用资源的进程放弃继续访问资源。如果进程放弃继续访问资源,则进行v操作,使得val值加一。

二:共享内存

        通常大量数据之间的通信用共享内存,小数据用消息队列。利用共享内存机制进行进程间的通信效率比较快,因为进程可以直接读取内存,而不需要任何数据的拷贝,例如管道,消息队列等通信方式,需要在内核空间和用户空间进行四次的数据拷贝,而共享内存则拷贝两次数据即可, 一次从输入文件到共享内存区,另一次从共享内存到输出文件。

    如图所示,进程间需要共享的数据放入内核的共享内存区,进程可以把共享内存映射到自己进程的地址空间去,所以进程可以直接读取内存,不需要任何数据的拷贝。

再来看看这个图,共享内存机制只需要进行两次的数据拷贝,一次从输入文件到共享内存区,另一次从共享内存到输出文件。


    共享内存机制没有同步机制,因此可能会导致多个进程对共享内存中的数据进行修改,这样会时数据发生错误。因此利用信号量来解决这个问题。

    为了解决这个问题设置两个信号量,sem1, sem2. sem1信号量是对服务器给客户端发消息进行同步,sem2是对客户端对服务器发消息进行同步。服务器给共享内存区写数据,写完后设置sem1信号量进行V操作使val变为1,此时表示有可用资源等待进程访问。此时客户端先对sem1信号量进行P操作,使得val变为0,然后客户端读共享内存区的数据。这样做是为了防止多个进程对资源访问。同理操作sem2即可

下面的程序实现服务器,客户端之间相互进行收发消息

ser.cpp

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/sem.h>
using namespace std;

union semun{
    int val;
    struct semid_ds *buf;
    ushort *array;
    struct seminfo *__buf;
    void *__pad;
};
#define IPCMODE IPC_CREAT|IPC_EXCL|0666

int main(int argc, char *argv[])
{
    key_t key_id;
    key_id = ftok(argv[1], ID);
    if(key_id == -1)
    {
        printf("ftok error.\n");
        exit(1);
    }
    printf("key_id = 0x%x\n", key_id);

    key_t shm_id;
    shm_id = shmget(key_id, 1024, IPC_CREAT|IPC_EXCL|0666);
    if(shm_id == -1)
    {
        printf("shmget error.\n");
        exit(1);
    }


    char *addr;
    addr = (char *)shmat(shm_id,NULL, 0);
    if(addr == (void*)-1)
    {
        printf("shmat error.\n");
        shmctl(shm_id, IPC_RMID, NULL);
        exit(1);
    }

    //////////////////////////////////////////////////////////
    key_t sem_key;
    sem_key = ftok(argv[1],ID1);
    if(sem_key == -1)
    {
        printf("sem ftok error.\n");
        shmctl(shm_id, IPC_RMID, NULL);
        exit(1);
    }
    key_t sem_id = semget(sem_key, 2, IPC_CREAT|IPC_EXCL|0666);
    if(sem_id == -1)
    {
        printf("semget error.\n");
        shmctl(shm_id, IPC_RMID, NULL);
        exit(1);
    }

    union semun init;
    init.val = 0;
    semctl(sem_id, 0, SETVAL, init);
    semctl(sem_id, 1, SETVAL, init);

    struct sembuf p = {0,-1,0};
    struct sembuf v = {1,1,0};
    //////////////////////////////////////////////////////////

    while(1)
    {
        //write
        printf("Ser:>");
        scanf("%s",addr);
        if(strncmp(addr,"quit",4) == 0)
        {
            shmdt(addr);
            shmctl(shm_id, IPC_RMID, NULL);
            semctl(sem_id,0,IPC_RMID);
            semctl(sem_id,1,IPC_RMID);
            break;
        }
        semop(sem_id, &v, 1);   //服务器端写数据,然后对sem1进行V操作

        //read
        semop(sem_id, &p, 1);   //服务器读数据之前,先对sem0进行P操作
        printf("Cli:>%s\n",addr);
    }
    return 0;
}


cli.cpp

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/sem.h>
using namespace std;

union semun{
    int val;
    struct semid_ds *buf;
    ushort *array;
    struct seminfo *__buf;
    void *__pad;
};
#define IPCMODE IPC_CREAT|IPC_EXCL|0666

int main(int argc, char *argv[])
{
    key_t key_id = ftok(argv[1],ID);
    if(key_id == -1)
    {
        printf("ftk error.\n");
        exit(1);
    }

    key_t shm_id = shmget(key_id,0,0);
    if(shm_id == -1)
    {
        printf("shmget error.\n");
        exit(1);
    }

    char *addr = (char *)shmat(shm_id,NULL,0);
    if(addr == (void *)-1)
    {
        printf("shmat error.\n");
        exit(1);
    }


    ////////////////////////////////////////////////////////
     key_t sem_key;
     sem_key = ftok(argv[1],ID1);
     if(sem_key == -1)
     {
         printf("sem ftok error.\n");
         exit(1);
     }   
     key_t sem_id = semget(sem_key, 0, 0);
     if(sem_id == -1)
     {   
         printf("semget error.\n");
         exit(1);
     }

     struct sembuf p = {1,-1,0};
     struct sembuf v = {0,1,0};
    ////////////////////////////////////////////////////////

    while(1)
    {
        semop(sem_id, &p, 1);   //客户端进行读数据之前对sem1信号量进行P操作
        printf("Ser:>%s\n",addr);

        printf("Cli:>");
        scanf("%s",addr);
        if(strcmp(addr,"quit") == 0)
        {
            shmdt(addr);
             break;
        }
        semop(sem_id, &v, 1);    //客户端进行写数据之后对sem0信号量进行V操作
   }
    return 0;
}

三:消息队列

    当初发明消息队列的这种IPC机制的时候是为了克服如果客户端和服务器之间相互进行数据交互的时候(双向数据流)这种情况就要用到两个管道进行通信,如果用消息队列则可以解决这种双向数据流交互的问题。

 内核中关于消息队列的几个结构体 

在/usr/include/linux/msg.h文件中

struct msqid_ds {
    struct ipc_perm msg_perm;
    struct msg *msg_first;        /* first message on queue,unused  */
    struct msg *msg_last;        /* last message in queue,unused */
    __kernel_time_t msg_stime;    /* last msgsnd time */
    __kernel_time_t msg_rtime;    /* last msgrcv time */
    __kernel_time_t msg_ctime;    /* last change time */
    unsigned long  msg_lcbytes;    /* Reuse junk fields for 32 bit */
    unsigned long  msg_lqbytes;    /* ditto */
    unsigned short msg_cbytes;    /* current number of bytes on queue */
    unsigned short msg_qnum;    /* number of messages in queue */
    unsigned short msg_qbytes;    /* max number of bytes on queue */
    __kernel_ipc_pid_t msg_lspid;    /* pid of last msgsnd */
    __kernel_ipc_pid_t msg_lrpid;    /* last receive pid */
}; 

内核中消息队列的结构如图

msg_ids{}结构用来方便内核管理消息队列,由此可见消息队列是以链表的型式进行连接的。下图中应该还得画出在

msg_ids{}结构中有msg_first和msg_last两个msg类型的指针分别指向消息队列的头和尾。

 

    消息队列的特点:消息队列传送的消息不仅是字符类型,也可以是其他类型,消息队列发送的消息是个结构体

msgbuf{}这个结构体包含消息类型,消息数据

struct msgbuf {
    long mtype;         /* type of message */
    char mtext[1];      /* message text */
};

由于消息队列发送的消息都带有消息类型,所以允许多个进程共同的使用同一个消息队列进行数据的交互。

注意:消息队列的读取不一定按照先进先出的方式。例如,服务器发送的消息设type尾1,客户端发送的消息的type设尾2,那么如果显现一个消息队列里面的消息情况为1,1,1,2,2,1,1这个队列。那么客户端要读取服务器发送的消息则先出队列的是最右边的第一个1,客户端继续读取服务器发来的消息,则出队列的是右边第二个1,接下来出队列的是右边第五个1,,由此可以看出消息的读取不一定按照先进先出的方式。


0 0
原创粉丝点击