嵌入式软件开发 day24(进程间通信)

APUE

一、进程间通信

什么是进程间通信(ipc)

进程间通信
  • 数据传输(多个进程间进行数据传输)
  • 资源共享(不同进程间使用系统资源不同,eg:文件,内存)
  • 事件通知(当进程发生特定事件,希望把这些事件告诉给另外一些进程,让另外一些进程及时作出反应)
  • 进程控制(在一个进程里控制另一个进程的执行。eg:用调试工具进行程序调试,需要控制程序单步执行,在调试工具进程里对另一个进程进行控制)
Linux系统下的ipc
  • 早期unix系统ipc(Linux系统诞生之初已经存在的两个系统间通信)
    • 管道(数据传输)
    • 信号(事件通知)
    • fifo(数据传输)
  • system-v ipc(贝尔实验室在早期Linux系统优化来的)
    • system-v 消息队列(数据传输,进程控制)
    • system-v 信号量(资源共享,事件通知)
    • system-v 共享内存(数据传输,效率高)
  • socket ipc(BSD)(允许不同机器间进行系统之间通信)
  • posix ipc(IEEE)
    • posix 消息队列
    • posix 信号量
    • posix 共享内存

1.1 管道

单工:类似队列(先进先出)

注意: 管道天生实现阻塞,管道必须要凑齐读写双方才能建立

匿名管道:piepe(),磁盘文件系统上看不到文件(ls不显示),相当于直接给了我们一个文件描述符或者FILE* 。

注意: 如果两个进程之间没有血缘关系,是不能用匿名管道进行通信的,因为另一个进程找不到文件位置。

命名管道:mkfifo(), 从磁盘文件系统上看到的(ls文件类型为P 的文件),本质上就是当前磁盘上存在的一个P类型的文件,没有血缘关系的进程之间 可以用命名管道进行通信

1.1.1 函数pipe()

描述:匿名管道的创建

手册man pipe

头文件

#include <unistd.h>

函数原型

int pipe(int pipefd[2]);
  • pipe()返回两个文件描述符填充到 pipefd[]数组中( pipefd[0]为读端,pipefd[1]为写端 )

返回值

成功:0

失败:-1,errno

特点

  • 特殊文件(没有名字),无法使用open,但是可以使用close。
  • 只能通过子进程继承文件描述符的形式来使用
  • write和read操作可能会阻塞进程
  • 所有文件描述符被关闭之后,无名管道被销毁

使用步骤

  • 父进程pipe无名管道
  • fork子进程
  • close无用端口
  • write/read读写端口
  • close读写端口
       #include <sys/types.h>
       #include <sys/wait.h>
       #include <stdlib.h>
       #include <unistd.h>
       #include <stdio.h>
       #include <string.h>
       #include <errno.h> //查看错误代的erron,调试程序的一个重要方法

int main(int argc,char *argv[])
{
	pid_t pid;
	const char date[]="pipe test program";
	char buf[256];
	/*定义管道描述符数组,定义了2个管道,一般pipe_fd[0]是接收管道描述符,pipe_fd[1]是写管道描述符*/
	int pipe_fd[2];
	int status;
	int real_read,real_write;
	/*向buf内的256byte位填充“0”*/
	memset((void*)buf,0,sizeof(buf));

	if(pipe(pipe_fd) < 0){
		printf("pipe creat error!\n");
		exit(1);
	}
	if((pid=fork())  == 0){
		close(pipe_fd[1]);
		if((real_read=read(pipe_fd[0],buf,256))>0){
			printf("%d bytes read from the pipe is : %s\n",real_read,buf);
		}
		close(pipe_fd[0]);
		exit(0);
	}
	else if(pid > 0){
		close(pipe_fd[0]);
		if((real_write=write(pipe_fd[1],date,strlen(date)))!= -1){
			printf("parent write %d bytes : %s\n",real_write,date);
		}
		close(pipe_fd[1]);
		wait(&status);
		exit(0);
	}
}


在这里插入图片描述

代码演示

功能:父子进程 父写子读


1.1.2 函数mkfifo()

描述:命名管道的创建

手册man 3 mkfifo

头文件

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

函数原型

int mkfifo(const char *pathname, mode_t mode);
  • pathname – 指定我们所创建的命名管道的名字
  • mode – 指定操作权限

返回值

成功:0

失败:-1,errno

特点

  • 有文件名,可以使用open函数打开
  • 任意进程间数据传输
  • write和read操作可能会阻塞进程
  • write具有"原子性"

使用步骤

  • 第一个进程mkfifo有名管道
  • open有名管道,write/read数据
  • close有名管道
  • 第二个进程open有名管道,read/write数据
  • close有名管道

代码演示

fifo_read

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

#define MYFIFO "/home/ldy/野火嵌入式学习/SYS_Program/namefifo"
int main(int argc,char *argv[])
{
	char buff[256];
	int fd;
	int real_read;
	/* access函数中的F_OK参数用来检测当前管道是否存在 */
	/* errno函数中的EEXIST参数用来检测文件是否存在 */
	if(access(MYFIFO,F_OK)==-1){ 
		if((mkfifo(MYFIFO,0666)<0) && (errno != EEXIST)){
			printf("creat fifo error!\n");
			exit(1);
		}
	}
	fd =  open(MYFIFO,O_RDONLY);
	if(fd==-1){
		printf("open error!\n");
		exit(1);
	}
	while(1){
		memset(buff,0,sizeof(buff));
		if((real_read=read(fd,buff,256))>0){
			printf("read %s from fifo\n",buff);
		}
	}
	close(fd);
	exit(0);

}

fifo_write

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

#define MYFIFO "/home/ldy/野火嵌入式学习/SYS_Program/namefifo"
int main(int argc,char *argv[])
{
	int fd;
	char buff[256];
	int real_write;
	if(argc<=1){
		printf("usage:./fifo_write string\n");
		exit(1);
	}
	sscanf(argv[1],"%s",buff);
	fd=open(MYFIFO,O_WRONLY);
	if(fd==-1){
		printf("open error!\n");
		exit(1);
	}
	if((real_write=write(fd,buff,256))>0){
		printf("write '%s' to fifo\n",buff);
	}
	close(fd);
	exit(0);	
}

1.2 XSI IPC

命令

ipcs – 查看当前IPC(msg,sem,shm状态)

ipcrm – 删除指定IPC

msg(消息队列) sem(信号量) shm(共享内存)

xxxget:创建

xxxop:操作控制

xxxctl:打杂的(初始化,销毁)

主动端:先发包的一方(后运行起来)

被动端:先收包的一方(先运行起来)

区分主动被动:不管后边谁收谁发,只看第一个谁先发,谁是主动端

1.2.1 标识符和键

有亲缘

无亲缘关系进程通信 – 协议:双方约定对话格式

C/S – Client/Server

在这里插入图片描述

1.2.1.1 ftok()

描述:利用给定的路径名(inode号)和一个项目ID 产生一个键(key值)

产生IPC Key值,将路径名和项目标识符转换为System V IPC密钥

手册man ftok

头文件

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

函数原型

key_t ftok(const char *pathname, int proj_id);
  • pathname – 一个现有的文件,产生键时,只使用id参数的低八位。
  • proj_id – 项目ID 一个大于0的整型

返回值

成功:该消息队列的Key值

失败:-1,并且设置errno

1.3 msg消息队列

1.3.1 msgget()

描述:用来创建新的消息队列或获取已有的消息队列

手册man msgget

头文件

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

函数原型

int msgget(key_t key, int msgflg);
  • key – 消息队列key值,用来产生唯一的msgid的
  • msgflg – 创建当前消息队列的特殊要求

IPC_CREAT:如果消息队列对象不存在,则创建之,否则则进行打开操作;
IPC_EXCL:和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建,否则产生一个错误并返回。

返回值

成功:消息队列的msgid

失败:-1,errno

1.3.2 msgsnd() & msgrcv()

描述:发送消息队列 & 接收消息队列

手册man msgop

头文件

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

函数原型

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);

//结构体定义注意:
           struct msgbuf {
               long mtype;       /* message type, must be > 0 结构体开始必须要包含此成员*/
               char mtext[1];    /* message data */
           };
  • msqid – 消息队列ID

  • msgp – 待发送/接收数据首地址

  • msgsz – 待发送/接收数据信息的大小,而不是全部的数据信息大小:sizeof(buf)-sizeof(long)

  • msgtyp – 是否挑选消息来接收,比如接收第几个包

    可以实现多个不同需求的进程通过同一个msg消息队列通信

  • msgflg – 特殊要求

返回值

成功:msgsnd返回0,msgrcv返回

失败:-1,errno

1.3.3 msgctl()

描述:控制一个消息队列,如销毁,设置等等

手册man msgctl

头文件

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

函数原型

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • msqid – 消息队列ID

  • cmd – 对消息队列msqid 执行 cmd命令

    IPC_STAT
    IPC_SET
    IPC_RMID  删除当前消息队列
    IPC_INFO (Linux-specific)
    MSG_INFO (Linux-specific)
    MSG_STAT (Linux-specific)
    
  • buf – 传参

返回值

成功: cmd为IPC_STAT, IPC_SET, IPC_RMID 返回 0

失败:-1,errno

1.3.4 代码演示

过程:

  1. 拟定协议(.h文件)
  2. 获取键值(ftok)
  3. 发送端:获取msgid,接收端:获取msgid并创建消息队列 (msgget)
  4. 发送端:发送消息,接收端:接收消息 (msgsnd/msgrcv)
  5. 接收端销毁消息队列(msgctl)

prtot.h

#ifndef PROTO_H__
#define PROTO_H__

#define KEYPATH		"/etc/services"
#define KEYPROJ		'A'			//		12   "12"

#define NAMESIZE	32

struct msg_st
{
	long mtype;
	char name[NAMESIZE];
	int math;
	int chinese;
};


#endif

snder.c

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

#include "proto.h"


int main()
{
	key_t key;
	int msgid;
	struct msg_st sbuf;
	
	key = ftok(KEYPATH,KEYPROJ);
	if(key < 0)
	{
		perror("fork()");
		exit(1);
	}

	msgid = msgget(key,0);
	if(msgid < 0)
	{
		perror("msgget()");
		exit(1);
	}

	sbuf.mtype = 1;
	strcpy(sbuf.name,"Alan");
	sbuf.math = rand()%100;
	sbuf.chinese = rand()%100;
	if(msgsnd(msgid,&sbuf, sizeof(sbuf)-sizeof(long),0) < 0)
	{
		perror("msgsnd()");
		exit(1);
	}
	
	puts("OK");

	exit(0);
}

rcver.c

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

#include "proto.h"


int main()
{
	key_t key;
	int msgid;
	struct msg_st rbuf;
	
	key = ftok(KEYPATH,KEYPROJ);
	if(key < 0)
	{
		perror("fork()");
		exit(1);
	}

	msgid = msgget(key, IPC_CREAT|0600);
	if(msgid < 0)
	{
		perror("msgget()");
		exit(1);
	}

	while(1)
	{
		if(msgrcv(msgid, &rbuf, sizeof(rbuf)-sizeof(long),0,0) < 0)
		{
			perror("msgrcv()");
			exit(1);
		}
	
		printf("NAME:%s\n",rbuf.name);
		printf("MATH:%d\n",rbuf.math);
		printf("CHINESE:%d\n",rbuf.chinese);
	}
	
	msgctl(msgid,IPC_RMID,NULL);

	exit(0);
}

1.4 shm共享内存

1.4.1 shmget()

描述: 分配一个System V共享内存段

手册man shmget

头文件

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

函数原型

int shmget(key_t key, size_t size, int shmflg);
  • key –

    如果没有亲缘关系的进程中 创建和获取 和之前 消息队列是一样的

    如果在有亲缘关系的进程中,fork()之后,每一个子进程都可以拿到父进程创建的 key值,此时不再关心key值,此时可以设置为 IPC_PRIVATE,表示该IPC 为 匿名IPC 不需要ftok

  • size – 要设置的共享内存的大小

  • shmflg – 特殊要求

返回值

成功:返回有效的共享内存标识符

失败:-1,errno

1.4.2 shmat() & shmdt()

描述: 把共享内存映射过来 & 把共享内存进行解除映射
手册man shmop

头文件

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

函数原型

void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
  • shmid – 共享内存ID

  • shmaddr – 需要映射到我当前空间的具体位置(NULL:表示函数帮忙在当前进程空间中寻找一块可用的内存地址)

  • shmflg – 特殊要求(0:没有特殊要求)

返回值

成功:shmat返回共享内存的首地址,shmdt返回0

失败:shmat返回(void *) -1设置errno,shmdt返回-1,设置errno

1.4.3 shmctl()

描述: System V共享内存控制 如销毁

手册man shmctl

头文件

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

函数原型

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • shmid – 共享内存ID

  • cmd – 命令操作 (如:IPC_RMID 销毁 )

  • buf – 是否需要传参,需要传递的参数

返回值

成功:0

失败:-1,errno

1.4.4 代码演示

功能:父子进程进 使用 共享内存 进行通信

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>



#define MEMSIZE		1024

int main(int argc,char *argv[])
{
	char *str;
	pid_t pid;
	int shmid;	

	shmid = shmget(IPC_PRIVATE, MEMSIZE,0600);	
	if(shmid < 0)
	{
		perror("shmget()");
		exit(1);
	}
	
	pid = fork();
	if(pid < 0)
	{
		perror("fork()");
		exit(1);
	}
	if(pid == 0) // child write
	{
		str = shmat(shmid,NULL,0);
		if(str == (void *) -1)
		{
			perror("shmat()");
			exit(1);
		}
		strcpy(str,"HELLO");
		shmdt(str);
		exit(0);
	}
	else			//parent read
	{
		wait(NULL);
		str = shmat(shmid,NULL,0);
		if(str == (void *) -1)
		{
			perror("shmat()");
			exit(1);
		}
		puts(str);
		shmdt(str);
		shmctl(shmid,IPC_RMID,NULL);
		exit(0);
	}


	exit(0);
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值