Linux学习_IPC进程间通信

Posted Leslie X徐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux学习_IPC进程间通信相关的知识,希望对你有一定的参考价值。

IPC进程间通信

进程之间数据通信的手段:
一、管道

  1. 匿名管道:只允许亲缘关系进程使用
    - 使用pipe创建匿名管道
		#include <unistd.h>
		int pipe(int pipefd[2]);
		成功返回0,失败返回-1
		pipe执行后,将管道读端与pipe[0]绑定,写端与pipe[1]绑定。
		注意pipe()在fork()前使用保证两个进程共用一根管道。
  1. 命名管道:任何进程都可以使用
    - 使用mkfifo创建命名管道
		#include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);

注意:管道文件不能使用fopen打开,FILE*只能对于普通文件使用。

二、信号量

  • 进程间的信号量,需要在内核上创建,由两个不同的进程共同访问,创建步骤如下:
  1. 在硬盘上创建文件,两个进程通过该文件密钥,在内核上访问同一个信号量。
  2. 获取文件密钥:使用函数ftok()
		#include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);
	返回:密钥值
	参数:
		pathname: 准备创建密钥的文件名路径,此文件必须存在
		proj_id: 范围0~255	
		
The ftok() function uses the identity of the file named  by  the  given
       pathname  (which  must  refer  to an existing, accessible file) and the
       least significant 8 bits of proj_id (which must be nonzero) to generate
       a  key_t  type  System  V  IPC  key,  suitable  for use with msgget(2),
       semget(2), or shmget(2).

  1. 根据密钥创建或者访问信号量
		#include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semget(key_t key, int nsems, int semflg);
		
	返回:成功返回信号量id,失败返回-1
	参数:
		nsems:准备创建的信号量文件数组容量
		semflag:默认IPC_CREAT | 0664
  1. 初始化信号量:使用函数semctl
		#include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semctl(int semid, int semnum, int cmd, ...);
	返回:成功返回信号量id,失败返回-1
	参数:
		semid:信号量id
		semnum:等待操作的信号量的下标
		cmd:具体操作
			IPC_SET:
			IPC_RMID:

注意:可变参,如果cmd传的是IPC_SET ,SETVAL就需要传入具体的初始化值,是一个联合体,需要自己声明。

		 union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

  1. 操作信号量,可以随便加减,需要使用semop函数
		#include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semop(int semid, struct sembuf *sops, size_t nsops);
       返回:成功返回信号量id,失败返回-1
		参数:
			nsops:等待操作的信号量的数量
			sops:信号量结构体指针,信号量加减完全取决于此结构体。
			该结构体成员如下
				unsigned short sem_num;  /*  信号量下标 */
			   short          sem_op;   /* +n -n  */
			   short          sem_flg;  /* operation flags信号量属性,默认SEM_UNDO,代表程序结束信号量由内核自动回收 */

注意:具体使用时需要根据结构体自己封装一个post wait操作。

三、消息队列
四、共享内存

  • 需要通过一个文件创建密钥,再通过密钥访问内核中的一个共享内存
  1. 创建文件
  2. 获取密钥ftok
  3. 通过密钥获取共享内存id,使用shmget函数
 #include <sys/ipc.h>
#include <sys/shm.h>

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

	key:密钥
	size:准备创建的共享内存大小
	shmflg:写 IPC_CREAT | 0664
  1. 将内核中的共享内存映射到内存中来,使用函数shmat
#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:
	shmid:共享内存id
	shmaddr:0
		想要映射的具体地址,可以传个数组首地址,也可以传一个堆空间的首地址。通常情况下,直接传0,代表内存地址由系统自己决定
	shmflg:内存标记位,如果第二个参数传0的话,该参数直接传0即可,代表降低程序对硬件的耦合度
返回:
	该函数将映射好的地址的首地址返回,返回的是一个void*
  1. 直接向共享内存所映射内存中写入数据即可
  2. 当共享内存不再使用时,注意删除共享内存,使用shmctl函数
#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

	cmd:IPC_RMID
	buf:0
	

注意在销毁共享内存之前,先要解除共享内存的映射
使用函数shmdt

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

int shmdt(const void *shmaddr);

题1

要求:

  • 创建两个文件
  • 文件1:输入三角形/长方形的三条边长
  • 文件2:根据文件1输入的数据数量,计算三角形/长方形的面积。
    文件1:
/*
 * 1.c
 * 输入三角形/长方形的边长
 */


#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int pipefd[2]={(int)*argv[1],(int)*argv[2]};
	printf("pipefd: %d %d\\n",pipefd[0],pipefd[1]);
	int i=4,arr[4];
	printf("请输入边长(三角形则最后一条边长为0):\\n");
	while(i--){
		printf("请输入第%d条边长:",4-i);
		scanf("%d",&arr[3-i]);
	}
	write(pipefd[1],arr,sizeof(arr));
	return 0;
}


文件2:

/*
 * 2.c
 * 创建两个文件
 * 文件1:输入三角形/长方形的三条边长
 * 文件2:根据文件1输入的数据数量,计算三角形/长方形的面积。
 */


#include <stdio.h>
#include <unistd.h>
#include <wait.h>

char* path="./1";

int main(int argc, char **argv)
{
	int pid, pipefd[2];
	pipe(pipefd);
	pid = fork();
	if(pid==0){
		execlp(path,path,&pipefd[0],&pipefd[1],NULL);
	}
	if(pid>0){
		wait(0);
		int arr1[4]={0};
		read(pipefd[0],arr1,sizeof(arr1));
		if(((arr1[3]==0)&&
			(arr1[0]+arr1[1]>arr1[2] ||
			arr1[0]+arr1[2]>arr1[1] ||
			arr1[1]+arr1[2]>arr1[0])))
			{printf("\\n三角形周长为:%d\\n",arr1[0]+arr1[1]+arr1[2]);}
		else if
			((arr1[3]!=0)&&
			((arr1[0]==arr1[1] &&arr1[2]==arr1[3]) ||
			(arr1[0]==arr1[3] &&arr1[1]==arr1[2]) ||
			(arr1[0]==arr1[2] &&arr1[1]==arr1[3]) ))
		{printf("\\n长方形周长为:%d\\n",arr1[0]+arr1[1]+arr1[2]+arr1[3]);}
		else if(!(arr1[0]&arr1[1]&arr1[2]&arr1[3])){
			printf("输入错误!\\n");
		}
		else{
			printf("无法识别形状\\n");
		}
	}
	return 0;
}


题目2:聊天

要求:使用命名管道实现进程间聊天
文件1_聊天:

/*
 * 1_聊天.c
 * 
 */


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

char* rfifo = "to1";
char* wfifo = "to2";
int rfd,wfd;
pid_t 	pid1,pid2;
pthread_t th;
char recv[128]={'\\0'};
char send[128]={'\\0'};

void sig_handler(int signum)
{
	if(signum==SIGINT){
		close(rfd);
		close(wfd);
		kill(pid2,SIGINT);
		pthread_cancel(th);
		printf("\\n1程序结束\\n");
		exit(0);
	}
}

void* th_fn(void* arg)
{
	while(1){
		if(read(rfd,recv,sizeof(recv))==0)break;
		printf("\\n接收到内容:%s\\n",recv);
		printf("请输入发送内容:");
		fflush(stdout);
		memset(recv,0,sizeof(recv));
	}
	return NULL;
}

int main(int argc, char **argv)
{
	printf("按ctl+C结束聊天\\n");
	mkfifo(rfifo,0666);
	mkfifo(wfifo,0666);
	
	wfd = open(wfifo,O_WRONLY);
	rfd = open(rfifo,O_RDONLY);
	
	pid1=getpid();
	write(wfd,&pid1,sizeof(pid1));
	read(rfd,&pid2,sizeof(pid2));
	printf("与2号聊天: %d\\n" ,pid2);
	
	pthread_create(&th,0,th_fn,0);
	pthread_detach(th);
	
	while(1){
		printf("请输入发送内容:");
		scanf("%s",send); while(getchar()!=10);
		write(wfd,send,strlen(send));
		memset(send,0,sizeof(send));
		signal(SIGINT,sig_handler);
	}
	
	return 0;
}


文件2_聊天:

/*
 * 2_聊天.c
 * 
 */


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


char* 	rfifo = "to2";
char* 	wfifo = "to1";
int 		rfd,wfd;
pid_t 	pid1,pid2;
pthread_t th;
char 	recv[128]={'\\0'};
char 	send[128]={'\\0'};

void sig_handler(int signum)
{
	if(signum==SIGINT){
		close(rfd);remove(rfifo);
		close(wfd);remove(wfifo);
		pthread_cancel(th);
		printf("\\n2程序结束\\n");
		exit(0);
	}
}

void* th_fn(void* arg)
{
	
	while(1){
		printf("请输入发送内容:");
		scanf("%s",send); while(getchar()!=10);
		write(wfd,send,strlen(send));
		memset(send,0,sizeof(send));
	}
}

int main(int argc, char **argv)
{
	mkfifo(rfifo,0666);
	mkfifo(wfifo,0666);
	
	rfd = open(rfifo,O_RDONLY);
	wfd = open(wfifo,O_WRONLY);
	
	pid2=getpid();
	write(wfd,&pid2,sizeof(pid2));
	read(rfd,&pid1,sizeof(pid1));
	printf("与1号聊天: %d\\n" ,pid1);

	pthread_create(&th,0,th_fn,0);
	pthread_detach(th);
	
	while(1){
		if(read(rfd,recv,sizeof(recv))==0)break;
		printf("\\n接收到内容:%s\\n",recv);
		printf("请输入发送内容:");
		fflush(stdout);
		memset(recv,0,sizeof(recv));
		signal(SIGINT,sig_handler);
	}
	return 0;
}


题目3:进程间信号量

要求:

  • 创建一对父子进程
  • 使用进程间信号量实现
  • A-B-A-B交替打印
  • 代码1:使用一个信号量
/*
 * sem_test1.c
 * 
 * 创建一对父子进程
 * 使用进程间信号量实现
 * A-B-A-B交替打印
 */


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdlib.h>

char* keysem = "./sem_test1";
int id;

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

void sig_handler(int signum){
	switch(signum){
		case SIGINT: 
		semctl(id,0,IPC_RMID,0);
			printf("程序结束!\\n");
			sleep(2);
			exit(0);
		default :break;
	}
}

void semPost(int semid,int sempos,int val){
	struct sembuf buf = {0};
	buf.sem_num = sempos;
	buf.sem_op = (val>=0)?val:(-val);
	buf.sem_flg = SEM_UNDO;
	semop(semid,&buf,1);
}

void semWait(int semid,int sempos,int val){
	struct sembuf buf = {0};
	buf.sem_num = sempos;
	buf.sem_op = (val<0)?val:(-val);
	buf.sem_flg = SEM_UNDO;
	

以上是关于Linux学习_IPC进程间通信的主要内容,如果未能解决你的问题,请参考以下文章

Linux间进程通信--消息队列

Linux进程间通信--共享内存

Linux间进程通信--消息队列

Linux 进程间通信(IPC)

Linux进程间通信---管道

深刻理解Linux进程间通信(IPC)