IPC的七种方式

概念:
同步: 一个进程将数据写入, 然后就去睡眠等待, 直到读取进程将数据取走, 在去唤醒; 读进程与之类似
互斥: 一个进程对pipe进行I/O操作时,其他进程必须等待

pipe

feature: 容量有限,只用于父子进程通信

#include <unistd.h>

//	return:	成功0,	失败-1
int pipe(int fd[2]);

FIFO

feature: 适用于任何进程间通信

#include <sys/stat.h>

// return:	成功0,	失败-1
int mkfifo(const char* pathname, mode_t mode);

一旦创建了一个FIFO,就可以与一般的文件I/O函数操作; 参数中mode要与open函数中的mode相同

message queue

feature: 容量受到系统限制; 第一次读的时候,要考虑到上一次没有读完数据的问题

消息队列 信号灯 共享内存
头文件 <sys/msg.h > <sys/sem.h> <sys/shm.h>
创建/打开IPC函数 msgget semget shmget
控制IPC操作 msgctl semctl shmctl
IPC操作函数 msgsend msgrcv semop shmat shmdt

消息队列,是消息的连接表,存放再内核中; 一个消息队列由一个queue_id来标识

#include <sys/types.h>
#include <sys/ipc.h>

// 函数ftok把从pathname导出的信息与id的低序8位组合成一个整数IPC键,称为IPC键值
//							return: 成功: key_t			失败: -1
key_t ftok(const char* pathname,	int proj_id);

#include <sys/msg.h>

// 创建/打开message queue,	return: 成功: queue_id,		失败: -1			flag可以为IPC_CREAT|0777
int	msgget(key_t key,	int flag);
// 添加消息,					return: 成功: 0,				失败: -1
int	msgsend(int msqid,	const void *ptr,	size_t size,	int flag);
// 读取消息,					return: 成功: 消息数据长度,	失败: -1
int msgrcv(int msqid,	void *ptr,			size_t size,	long type,	int flag);
// 控制消息队列,				return: 成功: 0,				失败: -1
int msgctl(int msqid,	int cmd,			struct msqid_ds *buf);

ftok函数

semaphore

feature: 用于实现进程间同步与互斥, 而不是用于储存进程间通信数据

  • 用于进程间同步,若要再进程间传递数据需要结合共享内存
#include <sys/sem.h>
// 创建一个信号量组,		return: 成功: id,		失败: -1			sem_flags可以为IPC_CREAT|0666
int semget(key_t key,	int num_sems,	int sem_flags);
// 对信号量组operation,改变sem的值		return: 成功:0	失败: -1
int semop(int semid,	struct sembuf	semoparrary[],	sieze_t numops);
// 控制信号量
int semctl(int semid,	int sem_num,	int cmd, ...);
// cmd常用的由两个, 
// 1. SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量
// 2. IPC_RMID:rm ID 删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源

其中再semop函数中 semop中 struct sembuf 结构如下:

 struct sembuf 
	{
    
    
		short sem_num; // 信号量组中对应的序号,0~sem_nums-1
		short sem_op;  // 信号量值在一次操作中的改变量
		short sem_flg; // IPC_NOWAIT, SEM_UNDO
	}

sem_op是一次操作中信号量的改变量

伪代码说明:

if (sem_op > 0) 
{
    
    
	// 表示进程释放相应的资源数量,	该信号量的值 + sem_op的绝对值
}
else if (sem_op < 0) 
{
    
    	//sem_op<0, 表示请求相应的资源数, 该信号量的值 - sem_op的绝对值
	if ( 资源数大于请求资源数 ) {
    
    
		// 函数返回成功
	} 
	else {
    
    	//资源数不满足请求时, 这个操作与 sem_flags有关
		if ( sem_flg == IPC_NOWAIT ) {
    
    
			// semop函数出错返回 EAGAIN
		}
		else {
    
    	// sem_flg 没有指定IPC_NOWAIT, 为SEM_UNDO
			// 则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
			if ( 资源数大于请求资源数) {
    
    
				// 此信号量的semncnt值 -1;
				// 该信号量的值 -1;
				// 返回成功
			}
			else if (此信号量被删除) {
    
    
				// 函数semop出错,返回EIDRM;
			}
			else if (进程捕捉到信号,并从信号处理函数返回) {
    
    
				// 信号量的semncnt值 -1;
				// 函数semop出错,返回EINTR;
			}
		}
	}
}
else
{
    
    	//sem_op==0, 进程阻塞直到信号量的相应值为0
	if (信号量==0) {
    
    
		//函数立即返回
	}
	else {
    
     //信号量部位0, 依据sem_flag决定动作
		if ( sem_flg == IPC_NOWAIT ) {
    
    
			// semop函数出错返回 EAGAIN
		}
		else {
    
    	// sem_flg 没有指定IPC_NOWAIT
			// 则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
			if ( 资源数大于请求资源数) {
    
    
				// 此信号量的semncnt值 -1;
				// 该信号量的值 -1;
				// 返回成功
			}
			else if (此信号量被删除) {
    
    
				// 函数semop出错,返回EIDRM;
			}
			else if (进程捕捉到信号,并从信号处理函数返回) {
    
    
				// 信号量的semncnt值 -1;
				// 函数semop出错,返回EINTR;
		}
	}
}

  • sem_op>0, 表示进程释放相应的资源数量, 该信号量的值 + sem_op的绝对值
  • sem_op<0, 表示请求相应的资源数, 该信号量的值 - sem_op的绝对值
    1. if 资源数大于请求资源数时, 函数成功返回
  1. else 资源数不满足请求时, 这个操作与 sem_flags有关
    2.1. sem_flg 指定IPC_NOWAIT, semop函数 出错返回 EAGAIN
    2.2.

shared memeory

feature: 容量容易控制,速度快; 但要保持同步,

#include <sys/shm.h>

// 创建或获取一个共享内存:		成功返回共享内存ID,	失败返回-1
3 int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:		成功返回0,			失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:		成功返回0,			失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
// shmctl 中cmd常用的时IPC_RMID,删除该共享内存

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访
问普通内存一样对文件进行访问,不必再调用read(),write()等操作

void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset ) 

addr 指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。
len 是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC(可执行),PROT_NONE(不可访问)。
flags 由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。
fd 为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。
offset 参数一般设为0,表示从文件头开始映射

mmap的作用是映射文件描述符fd指定文件的 [off,off + len]区域至调用进程的[addr, addr + len]的内存区域, 如下图所示:
在这里插入图片描述
mmap参考

共享内存+信号量+消息队列 例子

server.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h>  // shared memory
#include<sys/sem.h>  // semaphore
#include<sys/msg.h>  // message queue
#include<string.h>   // memcpy

// 消息队列结构
struct msg_form {
    
    
    long mtype;
    char mtext;
};

// 联合体,用于semctl初始化
union semun
{
    
    
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
    
    
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
    
    
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P操作:
//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    
    
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
    
    
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    
    
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
    
    
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
    
    
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
    
    
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}

// 创建一个信号量集
int creat_sem(key_t key)
{
    
    
    int sem_id;
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
    
    
        perror("semget error");
        exit(-1);
    }
    init_sem(sem_id, 1);  /*初值设为1资源未占用*/
    return sem_id;
}


int main()
{
    
    
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    char data[] = "this is server";
    struct shmid_ds buf1;  /*用于删除共享内存*/
    struct msqid_ds buf2;  /*用于删除消息队列*/
    struct msg_form msg;  /*消息队列用于通知对方更新了共享内存*/

    // 获取key值
    if((key = ftok(".", 'z')) < 0)
    {
    
    
        perror("ftok error");
        exit(1);
    }

    // 创建共享内存
    if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
    {
    
    
        perror("Create Shared Memory Error");
        exit(1);
    }

    // 连接共享内存
    shm = (char*)shmat(shmid, 0, 0);
    if((int)shm == -1)
    {
    
    
        perror("Attach Shared Memory Error");
        exit(1);
    }


    // 创建消息队列
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
    
    
        perror("msgget error");
        exit(1);
    }

    // 创建信号量
    semid = creat_sem(key);
    
    // 读数据
    while(1)
    {
    
    
        msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
        if(msg.mtext == 'q')  /*quit - 跳出循环*/ 
            break;
        if(msg.mtext == 'r')  /*read - 读共享内存*/
        {
    
    
            sem_p(semid);
            printf("%s\n",shm);
            sem_v(semid);
        }
    }

    // 断开连接
    shmdt(shm);

    /*删除共享内存、消息队列、信号量*/
    shmctl(shmid, IPC_RMID, &buf1);
    msgctl(msqid, IPC_RMID, &buf2);
    del_sem(semid);
    return 0;
}

client.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h>  // shared memory
#include<sys/sem.h>  // semaphore
#include<sys/msg.h>  // message queue
#include<string.h>   // memcpy

// 消息队列结构
struct msg_form {
    
    
    long mtype;
    char mtext;
};

// 联合体,用于semctl初始化
union semun
{
    
    
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// P操作:
//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    
    
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
    
    
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    
    
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
    
    
        perror("V operation Error");
        return -1;
    }
    return 0;
}


int main()
{
    
    
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    struct msg_form msg;
    int flag = 1; /*while循环条件*/

    // 获取key值
    if((key = ftok(".", 'z')) < 0)
    {
    
    
        perror("ftok error");
        exit(1);
    }

    // 获取共享内存
    if((shmid = shmget(key, 1024, 0)) == -1)
    {
    
    
        perror("shmget error");
        exit(1);
    }

    // 连接共享内存
    shm = (char*)shmat(shmid, 0, 0);
    if((int)shm == -1)
    {
    
    
        perror("Attach Shared Memory Error");
        exit(1);
    }

    // 创建消息队列
    if ((msqid = msgget(key, 0)) == -1)
    {
    
    
        perror("msgget error");
        exit(1);
    }

    // 获取信号量
    if((semid = semget(key, 0, 0)) == -1)
    {
    
    
        perror("semget error");
        exit(1);
    }
    
    // 写数据
    printf("***************************************\n");
    printf("*                 IPC                 *\n");
    printf("*    Input r to send data to server.  *\n");
    printf("*    Input q to quit.                 *\n");
    printf("***************************************\n");
    
    while(flag)
    {
    
    
        char c;
        printf("Please input command: ");
        scanf("%c", &c);
        switch(c)
        {
    
    
            case 'r':
                printf("Data to send: ");
                sem_p(semid);  /*访问资源*/
                scanf("%s", shm);
                sem_v(semid);  /*释放资源*/
                /*清空标准输入缓冲区*/
                //注意:当scanf()输入字符或字符串时,缓冲区中遗留下了\n,所以每次输入操作后都需要清空标准输入的缓冲区。但是由于 gcc 编译器不支持fflush(stdin)(它只是标准C的扩展),所以使用了一下替代方案
                while((c=getchar())!='\n' && c!=EOF);
                msg.mtype = 888;  
                msg.mtext = 'r';  /*发送消息通知服务器读数据*/
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                break;
            case 'q':
                msg.mtype = 888;
                msg.mtext = 'q';
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                flag = 0;
                break;
            default:
                printf("Wrong input!\n");
                /*清空标准输入缓冲区*/
                while((c=getchar())!='\n' && c!=EOF);
        }
    }

    // 断开连接
    shmdt(shm);

    return 0;
}

signal

feature: 进程间唯一的异步通信方式,用于通知接收进程某个事件已经发生, 又名中断
局限性: 不能传递复杂,有效,具体的数据

#include <signal.h>

//向执行的进程发送信号		return	成功:0	返回:-1
int kill(pid_t pid,	int sig);

//接收信号
int signal(int sig, 函数指针);

pid < -1; 发送当前的进程id
pid = -1: 发送给所有进程, 除了init(1号进程)
pid = 0: 发送给同组下所有进程
pid > 0: 发送给指定pid进程

socket

网络间不同进程通信
基于文件型: 当通信进程都在同一台服务器中, 其原理类似于管道
基于网络型: 通信双方的进程运行在不同的主机环境下被分配了一对socket, 一个发送进程,一个接收进程. 非对称方式通信, 发送者需要提供接收者姓名;

七种通信方式实例

猜你喜欢

转载自blog.csdn.net/weixin_44280688/article/details/103295996