进程间通信(IPC)

概念

调用fork()之后创建子进程,子进程之间相互独立,每个进程都有自己的用户地址
空间,也就说进程之间的变量不共享,如果多个进程需要相互协作完成一项任务
时,必然会进行进程的同步和数据的交换,所以实现进程之间数据的交换必须通
过内核,在内核中开辟出一块缓冲区。一个进程把自己的数据从用户空间拷贝到
内核缓冲区,另一个进程再从内核缓冲区把数据读走。内核提供的这种机制称为
进程间通信机制(IPC)。

进程间通信的7种方式

1、信号

Linux下信号机制类似于单片机中的中断机制,信号是对硬件层中断的一种异步通信机制的模拟,无需CPU轮询等待。linux下kill命令可以查看系统所支持的信号。

  • Linux下信号机制模型
    在这里插入图片描述
    对上图的解释:
    每个进程都会维护着一个进程控制块(PCB),在这个PCB中保留着信号位图信息,当进程调用signal()、sigaction()安装信号或者其他进程给本进程发送信号的时候,PCB中的信号位图对应的信号标志位会被置1,表示内核正在监测这个信号。

    当硬件中断或者某种信号被触发时,内核会暂停当前正在运行的进程,并将此时程序的位置(中断点)压入栈保存起来,进程从用户态切换为内核态并检查信号位图究竟是哪个信号被触发,然后返回用户态开始执行信号处理函数,信号处理结束后,中断点出栈,程序从该位置继续运行。

    当然内核态的进程对信号还有两种处理方式,比如选择忽略该信号、执行默认动作。

  • 一个父子进程通过信号通信的例子

    程序思路:父子进程的运行分别由运行标志变量控制,父子进程通过kill函数发信号改变运行标志变量的值。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

//定义全局变量
int			child_run = 0;//子进程运行标志默认0
int			parent_run = 0;//父进程运行标志默认0

void sig_child(int signum)
{
	if(SIGUSR1 == signum)
	{
		child_run = 1;//子线程运行标志置1
	}
}

void sig_parent(int signum)
{
	if(SIGUSR2 == signum)
	{
		parent_run = 1;//父进程运行标志置1
	}
}


int main(int argc, char **argv)
{
	pid_t				pid = -1;//定义进程id

	signal(SIGUSR1,sig_child);//注册信号和指定信号处理程序
	signal(SIGUSR2,sig_parent);

	pid = fork();//创建子进程
	if(pid < 0)
	{
		printf("进程创建失败\n");
		exit(0);
	}
	else if(pid == 0)//子进程
	{
		printf("子进程创建成功,等待父进程发运行信号\n");

		while(!child_run)//子进程在此等信号
		{
		}

		printf("信号到达,子进程开始运行\n");
		
		kill(getppid(),SIGUSR2);//子进程给父进程发运行信号
		
		return 0;//子进程退出

	}
	else
	{
		printf("父进程等待子进程发运行信号\n");

		kill(pid,SIGUSR1);//给子进程发运行信号

		while(!parent_run)//父进程在此等待运行信号
		{
		}

		printf("父进程开始运行\n");

		wait(NULL);//父进程等待子进程退出,避免僵死进程
	}

	return 0;
}

首先先将程序中kill(pid,SIGUSR1)注释掉,父子进程分别阻塞…
在这里插入图片描述
再将注释取消,父子进程正常运行…
在这里插入图片描述

  • 信号只能被当前运行的进程所接收,信号发送但没有被接收称为挂起信号。
  • 进程可以发送多个信号给一个进程,只不过内核帮接收信号的进程做信号排队处理。
  • 以下给出几个关于信号的系统调用
kill()					向进程发送一个信号
sigaction()				注册信号
signal()				也是注册信号,两者差别后面讲
sigpending()			检查是否有挂起的信号
sigprocmask()			修改阻塞信号屏蔽码
sigsuspend()			等待一个信号
  • signal()和sigaction
    1、signal()
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

这个函数相对简单,没有阻塞信号的功能,当多个信号发送过来时,进程来不及处理,只能丢弃。

2、sigaction()


    stuct sigaction  
    {  
          void (*)(int) sa_handle;  //信号处理函数
          sigset_t sa_mask;  //信号屏蔽集
          int sa_flags;  //SA_NODEFER,如果设置来该标志,则不进行当前处理信
          				//号到阻塞;SA_RESETHAND,如果设置来该标志,则处理完
          				//当前信号后,将信号处理函数设置为SIG_DFL行为
    }  

int sigaction(int sig, const struct sigaction *act, struct sigaction *oact)

这个函数会对多个信号做排队处理。

  • 信号分类
    1、规则信号(regular signal,编号1-31),也称为不可靠信号:无论发送多少次,在接收进程处理之前,重复的信号会被合并为一个,信号可能会丢失。

    2、实时信号(real-time signal,编号32-63),也称为可靠信号:发送多次,就会在接收进程的信号队列中出现多少次。

2、管道

管道实质是一段内核缓冲区,这一段缓冲区类似队列,进程写入的数据由内核定向给另一进程,另一进程读出数据,这种数据流向也称单工。此管道只能在具有亲缘关系的进程才能通信,比如父子进程。

图解:
在这里插入图片描述

  • pipe()系统调用在内核空间创建管道,并返回两个文件描述符fd[0]、fd[1],fd[0]是只读描述符,fd[1]是只写描述符。

  • fork()系统调用创建子进程,子进程继承父进程中的文件描述符,所以父子进程用户空间中都存在对管道操作的两个文件描述符,如上图所示。那么父子进程都有各自的写端和读端,父进程将如何实现数据的交流呢。

  • 很简单,比如父进程向子进程发送数据,我们只需将父进程的读端fd[0]关闭,将子进程写端fd[1]关闭,这样就可以实现数据的交流。如下图所示:
    在这里插入图片描述

  • 就上面的陈述,将其用代码实现如下

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

int main(int argc, char **argv)
{
	int				fd[2];//管道描述符数组
	int				rv = -1;//返回值
	pid_t			pid = -1;//进程id
	char			buff[30];

	memset(buff,0,sizeof(buff)); 

	if(pipe(fd) < 0)//创建管道
	{
		printf("创建管道失败:%s\n",strerror(errno));
	}
	
	pid = fork();//创建进程
	if(pid < 0)
	{
		printf("创建进程失败:%s\n",strerror(errno));
		return 0;
	}
	else if(pid == 0)
	{
		printf("子进程开始运行并开始从管道读数据...\n");
		close(fd[1]);//子进程关闭写端

		rv = read(fd[0],buff,sizeof(buff));//开始读数据
		if(rv < 0)
		{
			printf("子进程读数据失败:%s\n",strerror(errno));
			return -1;
		}
		else
		{
			printf("子进程读到[%d]字节:%s\n",rv,buff);
		}
		close(fd[0]);//子线程关闭读端

		printf("子进程已退出\n");

		return 0;

	}
	
	printf("父进程开始运行并开始向子进程写数据...\n");
	close(fd[0]);//父进程关闭读端

	rv = write(fd[1],"hi,my child,I am your father!",30);//父进程向子进程写数据
	if(rv < 0)
	{
		printf("父进程写数据失败:%s\n",strerror(errno));
	}

	printf("父进程正在等待子进程退出...\n");

	wait(NULL);
	close(fd[1]);//父进程关闭写端
	printf("父进程退出!\n");

	return 0;
}

运行上面代码
在这里插入图片描述

3、命名管道(FIFO)

FIFO相对于管道,其最大的优点是任意进程之间可以相互通信,而不仅仅局限于亲缘进程,创建FIFO时,像无名管道一样在内核中创建了一个缓冲区,除此之外,FIFO在用户空间层以文件的形式存在,它有文件该有的属性,比如i节点,但是它没有数据块,FIFO的文件名相当于一个链接,其真正的数据存储在内核缓冲区中,正因为FIFO以文件的形式存在,多个任意进程可以访问。有因为没有数据块的原因,FIFO的访问速度比访问一般文件要快很多。

  • 创建并打开一个FIFO
    进程通过mknod()或者mkfifo系统调用创建一个FIFO,FIFO创建之后可以使用一般IO系统调用进行访问,比如open、read、write、close等,顶层操作与一般文件类似,对于内核,由于FIFO是一类特殊的文件,内核操作还是有区别的。

  • 一个例子
    应用多路复用epoll管理文件描述符,从而实现两个进程之间对话:
    在这里插入图片描述
    程序实现上图所示的一个两个进程之间全双工通信机制:

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

int					g_stop = 0;

//管道破裂处理程序
void sig_pipe(int signum)
{
	if(SIGPIPE == signum)
	{
		printf("get pipe broken signal and let programe exit\n");
		g_stop = 1;
	}
}

int main(int argc, char **argv)
{
	int					fdr_fifo;
	int					fdw_fifo;
	char				buf[1024];
	struct epoll_event  ctl_event;
	struct epoll_event  rv_event[2];
	int					rv = -1;
	int					rvctl = -1;
	int					rvwait = -1;
	int					epollfd;
	int					i;

	//判断文件是否存在
	if(access(".fifo1",F_OK) < 0)
	{
		printf(".fifo1不存在,将要被创建!\n");
		mkfifo(".fifo1",0777);
	}
	if(access(".fifo2",F_OK) < 0)
	{
		printf(".fifo2不存在,将要被创建!\n");
		mkfifo(".fifo2",0777);
	}

	signal(SIGPIPE, sig_pipe);

	//打开管道,通过终端命令行控制应该打开哪一个管道
	if(0 == atoi(argv[1]))
	{
		
		fdr_fifo = open(".fifo1",O_RDONLY);//另外一个进程要首先以写模式打开FIFO1,否则两个进程都会被阻塞
		if(fdr_fifo < 0)
		{
			printf("fifo1读模式打开失败:%s\n",strerror(errno));
			exit(0);//程序退出
		}
		
		printf("成功打开fifo1的读模式!\n");

		fdw_fifo = open(".fifo2",O_WRONLY);
		if(fdw_fifo < 0)
		{
			 printf("fifo2写模式打开失败:%s\n",strerror(errno));
			 exit(0);//程序退出
		}
		
		printf("成功打开fifo2的写模式!\n");
	}
	else
	{
		
		fdw_fifo = open(".fifo1",O_WRONLY);//另外一个进程要首先以写模式打开FIFO1,否则两个进程都会被阻塞
		if(fdw_fifo < 0)
		{
			printf("fifo1写模式打开失败:%s\n",strerror(errno));
			exit(0);//程序退出
		}

		printf("成功打开fifo1的写模式!\n");

		fdr_fifo = open(".fifo2",O_RDONLY);
		if(fdr_fifo < 0)
		{
			 printf("fifo2读模式打开失败:%s\n",strerror(errno));
			 exit(0);//程序退出
		}

		printf("成功打开fifo2的读模式!\n");
		
	}
	
	epollfd = epoll_create(2);//创建epoll实例
	if(epollfd < 0)
	{
		printf("epoll create error:%s\n",strerror(errno));
		goto Cleanup;
	}

	//初始化注册标准输入
	ctl_event.events = EPOLLIN;//设置为读
	ctl_event.data.fd = STDIN_FILENO;
	rvctl = epoll_ctl(epollfd,EPOLL_CTL_ADD,STDIN_FILENO,&ctl_event);
	if(rvctl < 0)
	{
		printf("ctl stdin 失败:%s\n",strerror(errno));
		close(epollfd);
		goto Cleanup;
	}
	//初始化注册fdr_fifo
	ctl_event.events = EPOLLIN;//设置为读
	ctl_event.data.fd = fdr_fifo;
	rvctl = epoll_ctl(epollfd,EPOLL_CTL_ADD,fdr_fifo,&ctl_event);
	if(rvctl < 0)
	{
		printf("ctl fdr_fifo 失败:%s\n",strerror(errno));
		close(epollfd);
		goto Cleanup;
	}
	

	//epoll处理两个文件描述符:标准输入和fdr_fifo
	while(!g_stop)
	{
		printf("开始监听文件描述符...\n");
		rvwait = epoll_wait(epollfd,rv_event,2,-1);//-1:永远阻塞直到有文件描述被触发时返回
		if(rvwait < 0)
		{
			printf("epoll_wait error:%s\n",strerror(errno));
			close(epollfd);
			goto Cleanup;
		}
		else if(rvwait == 0)
		{
			printf("wait timeout!\n");
			continue;
		}

		printf("监听返回!rvwait=%d\n",rvwait);
		if(rvwait > 0)
		{

			for(i = 0; i < rvwait; i++)
			{
				if(rv_event[i].data.fd == STDIN_FILENO)//标准输入有数据
				{
					memset(buf, 0, sizeof(buf));
					fgets(buf, sizeof(buf), stdin);
					rv = write(fdw_fifo, buf, strlen(buf));
					if(rv < 0)
					{
						printf("向管道写入数据错误:%s\n",strerror(errno));
						close(rv_event[i].data.fd);
						goto Cleanup;
					}

				}
				else //fdr_fifo文件描述可读
				{

					
					printf("文件描述[%d]状态是[%d]!\n",rv_event[i].data.fd,rv_event[i].events);
					if(rv_event[i].events & EPOLLIN)
					{
						printf("进入if...\n");
						memset(buf, 0, sizeof(buf));
						rv = read(fdr_fifo, buf, sizeof(buf));
						if( rv < 0)
						{
							printf("从fdr_fifo读数据失败: %s\n", strerror(errno));
							close(rv_event[i].data.fd);
							goto Cleanup;
						}
						else if( 0 == rv ) /* 如果从管道上读到字节数为0,说明管道的写端已关闭 */
						{
							printf("管道另一端已关闭\n");
							close(rv_event[i].data.fd);
							goto Cleanup;

						}
						printf("从管道读到[%d]字节数据:%s",rv, buf);

					
					}
					else if(rv_event[i].events & EPOLLHUP)//文件描述被挂断
					{

					   printf("管道另一端已关闭\n");
					   close(rv_event[i].data.fd);
					   goto Cleanup;
					}
				}

			}
		
		}
	}
Cleanup:
	exit(0);
}

在不同的终端运行上述程序:
在这里插入图片描述
在这里插入图片描述

4、命名socket

  • 命名socket用于同一主机中不同进程间的通信,网络socket基于TCP/IP协议栈用于不同主机、不同网络间的通信。它们在实现上的主要区别在于网络socket绑定IP地址和端口号,命令socket只需要一个特定的路径就可以进行通信,免去了网络协议的封装,在数据传输速率上远超于网络socket。

  • 命名socket又称unix域socket,提供了类似于TCP的字节流套接字和类似于UDP的数据包套接字,数据报服务是稳定可靠的,不会丢失数据。

  • socket结构体不再是struct sockaddr_in,而是:

struct sockaddr_un {
 sa_family_t sun_family; /* AF_UNIX */
 char sun_path[UNIX_PATH_MAX]; /* 路径名 */
};
  • 命名socket客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "socket_path"//命名socket通信路径
#define MSG_STR "Hello,I am client, nice to meet you!"
#define SIZE 50
int main(int argc, char **argv)
{

	int						sockfd = -1;
	struct sockaddr_un	server_addr;
	char					buff[SIZE];
	int						rv = -1;

	sockfd = socket(AF_UNIX, SOCK_STREAM, 0);//创建套接字,第三参数0会自动匹配与参数二适应的协议
	if(sockfd < 0)
	{
		printf("socket error:%s\n",strerror(errno));
		return 0;
	}

	//初始化服务器
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sun_family = AF_UNIX;
	strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(SOCKET_PATH)-1);//减一是因为去掉字符串中‘\0’
	
	if( connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)//连接服务器
	{
		printf("connect error:%s\n",strerror(errno));
		goto Cleanup;
	}

	if( (rv = write(sockfd, MSG_STR, strlen(MSG_STR))) < 0 )//向服务器写数据
	{
		printf("write error:%s\n",strerror(errno));
		goto Cleanup;
	}
	printf("Send [%d] bytes to named_server successfully!\n",rv);

	memset(buff, 0, sizeof(buff));
	if((rv = read(sockfd, buff, sizeof(buff))) < 0)
	{
		printf("read error:%s\n",strerror(errno));
		goto Cleanup;
	}
	else if(rv == 0)
	{
		printf("named_server disconnct!\n");
	}
	printf("%s\n",buff);

Cleanup:
	close(sockfd);
	return 0;
}

  • 命名socket服务端代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/un.h>

#define SOCKET_PATH "socket_path"//命名socket通信路径
#define LINK_NUMS 5//连接数
#define SIZE	50

int main(int argc, char **argv)
{
	int					sockfd		= -1;
	int					clientfd	= -1;
	struct sockaddr_un	server_addr;//服务器配置结构体
	struct sockaddr_un	client_addr;//缓存客户端信息结构体
	int					client_len;
	int					rv = -1;
	char				buff[SIZE];

	sockfd = socket(AF_UNIX, SOCK_STREAM, 0);//创建套接字,第三参数0会自动匹配与参数二适应的协议
	if(sockfd < 0)
	{
		printf("socket error:%s\n",strerror(errno));
		exit(0);
	}

	//判断socket文件是否存在
	if(!access(SOCKET_PATH, F_OK))
	{
		unlink(SOCKET_PATH);
	}

	//初始化服务器
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sun_family = AF_UNIX;
	strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(SOCKET_PATH)-1);//减一是因为去掉字符串中‘\0’

	if(bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
	{
		printf("bind error:%s\n",strerror(errno));
		goto Cleanup;
	}

	listen(sockfd, LINK_NUMS);


	printf("Starting to wait client to link...\n");

	clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);//开始等待连接
	if(clientfd < 0)
	{
		printf("accept error:%s\n",strerror(errno));
		goto Cleanup;
	}

	//读数据
	memset(buff, 0, sizeof(buff));
	rv = read(clientfd, buff, sizeof(buff));
	if(rv < 0)
	{
		printf("named_server read error:%s\n",strerror(errno));
		goto Cleanup;
	}
	else if(rv == 0)
	{
		printf("named_client disconnect!\n");
		goto Cleanup;
	}
	printf("named_server received [%d] bytes from named_client:%s\n",rv,buff);

	//回显
	rv = write(clientfd, "named_server received over!", 28);
	if(rv < 0)
	{
		printf("named_server write error:%s\n",strerror(errno));
		goto Cleanup;
	}

Cleanup:
	close(sockfd);
	close(clientfd);
	unlink(SOCKET_PATH);
	exit(0);
}

在这里插入图片描述
在这里插入图片描述

5、信号量

  • 信号量就是我们在操作系统系统中学过的,用于进程之间同步的一种操作,这种操作称为PV操作。信号量和信号都可以实现进程间的同步,但是实现的机制不同,信号(signal)通过信号处理器来操作的,而信号量通过PV操作实现。
  • PV操作的实质其实就是对临界资源的控制,让不同的进程能够有序充分地访问临界资源,避免产生死锁和资源短缺问题。
  • P操作:进程进入临界资源时,信号量值减一,信号量值为0说明可用资源已经没有了,进程将进入等待队列。
  • V操作:进程使用完资源后,出临界区,信号量值加一,此时正在等待的进程可以进入临界区使用临界资源。
  • linux下PV操作的函数简单总结,具体使用方法man一下就可以

ftok()获取IPC关键字,在共享内存、消息队列中都需要用到键值,这个键值独一无二,直接赋值可能与其他冲突,所以我们最好使用这个函数生成。

semget()创建或获取信号量

semctl()初始化信号集或删除信号集

semop()PV操作函数

  • 一个父子进程同步的例子,如果子进程线运行,父进程由于信号量为赋值而阻塞直到子进程运行结束后才继续运行
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_PATH "../apue"//路径必须存在
#define PROJ_ID 10//8bit

//定义一个信号量集联合
union semun
{
	int val;
	struct semid_us *buf;
	unsigned short *arry;
};

/*
 功能:信号量初始化
 参数:无
 返回:
	成功返回信号量值
	失败返回-1
 */
int semaphore_init(void)
{
	key_t					key = -1;
	int						sem_val = -1;
	union semun				sem_union;
	int						rv = -1;

	key = ftok(FTOK_PATH,PROJ_ID);//获取IPC关键字
	if(key < 0)
	{
		printf("ftok error:%s\n",strerror(errno));
		return -1;
	}

	sem_val = semget(key, 1, IPC_CREAT|0644);//获取信号量,当信号量不存在时,创建一个心的
	if(sem_val < 0)
	{
		printf("semget error:%s\n",strerror(errno));
		return -1;
	}

	rv = semctl(sem_val, 0, SETVAL, sem_union);//初始化信号量集,设置信号量集中一个单独信号量的值。
	if(rv < 0)
	{
		printf("semctl error:%s\n",strerror(errno));
		return -1;
	}


	return sem_val;
}


/*
 功能:从系统中删除信号量集合
 参数:
	sem_val:信号量值
 返回:
	成功返回0;
	失败返回-1;
 */

int semaphore_delete(int sem_val)
{
	union semun				sem_union;

	if(semctl(sem_val, 0, IPC_RMID, sem_union) < 0)
	{
		printf("semctl delete error:%s\n",strerror(errno));
		return -1;
	}

	return 0;
}

/*
 功能:P操作
 参数:
	sem_val:信号量值
 返回:
	成功返回0
	失败返回-1
 */

int semaphore_p(int sem_val)
{
	struct sembuf		_sembuf;//信号量操作结构体

	//初始化结构体
	_sembuf.sem_num		= 0;//信号集中第一个信号的编号
	_sembuf.sem_op		= -1;//P操作
	_sembuf.sem_flg		= SEM_UNDO;//程序结束时,信号值恢复

	if(semop(sem_val, &_sembuf, 1) < 0)
	{
		printf("semop p error:%s\n",strerror(errno));
		return -1;
	}

	return 0;
}


/*
 功能:V操作
 参数:
	sem_val:信号量值
 返回:
	成功返回0
	失败返回-1
 */

int semaphore_v(int sem_val)
{
	struct sembuf		_sembuf;//信号量操作结构体

	//初始化结构体
	_sembuf.sem_num		= 0;//信号集中第一个信号的编号
	_sembuf.sem_op		= 1;//V操作
	_sembuf.sem_flg		= SEM_UNDO;//程序结束时,信号值恢复

	if(semop(sem_val, &_sembuf, 1) < 0)
	{
		printf("semop p error:%s\n",strerror(errno));
		return -1;
	}

	return 0;
}



int main(int argc, char **argv)
{
	int						pid = -1;
	int						sem_val = -1;
	
	sem_val = semaphore_init();
	if(sem_val < 0)
	{
		exit(0);
	}

	pid = fork();
	if(pid < 0)
	{
		printf("fork error:%s\n",strerror(errno));
		exit(0);
	}
	else if(pid == 0)
	{
		printf("child is working...\n");
		sleep(5);
		printf("child working over!\n");
		semaphore_v(sem_val);
		exit(0);//子进程退出
	}

	printf("father is blocking...\n");

	semaphore_p(sem_val);//若父进程线运行,p操作使信号量小于0,父进程阻塞直到子进程V操作
	
	

	printf("father woking over...\n");

	semaphore_delete(sem_val);//销毁信号集

	printf("program exit!\n");

	return 0;

}

在这里插入图片描述

6、共享内存

在这里插入图片描述

  • 共享内存就是多个进程可以访问通一块内存空间,共享内存映射到各个进程的用户空间,所以进程间的数据交换不涉及内核,是IPC中最快的一种方式。
  • 任一进程对共享内存中数据变量的修改,另一进程可以看到。
  • 用到的函数简述

key_t ftok(const char *pathname, int proj_id):生成键值,相当于身份证

int shmget(key_t key, size_t size, int shmflg):该函数用来创建共享内存

void *shmat(int shmid, const void *shmaddr, int shmflg):用来启动对该共享内存的访问,并映射到进程地址空间

int shmdt(const void *shmaddr):于将共享内存从当前进程中分离

int shmctl(int shmid, int cmd, struct shmid_ds *buf):于控制共享内存

struct shmid_ds
{
uid_t shm_perm.uid;//共享内存用户ID
uid_t shm_perm.gid;//共享内存组ID
mode_t shm_perm.mode;//共享内存模式

  • 一个例子:一个进程创建共享内存,另一个进程访问共享内存。

shared_mem_create.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/time.h>

#define FTOK_PATH "../apue"
#define PROJ_ID 10

//定义共享结构体
typedef struct
{
	int			money;
	char		ID[6];
}bank_card_t;

void seconds_delay(unsigned seconds)
{
	struct timeval tv;
	tv.tv_sec=seconds;
	tv.tv_usec=0;
	int err;
	do{
		err=select(0,NULL,NULL,NULL,&tv);
	}while(err<0 && errno==EINTR);
}

int main(int argc, char **argv)
{
	key_t			key = -1;
	bank_card_t		*b_card;//银行卡
	int				shmid = -1;//内存ID
	int				i ;

	key = ftok(FTOK_PATH, PROJ_ID);//获取身份证
	if(key < 0)
	{
		printf("ftok error:%s\n",strerror(errno));
		exit(0);
	}

	shmid = shmget(key, sizeof(bank_card_t), IPC_CREAT|0666);//分配共享内存
	if(shmid < 0)
	{
		printf("shmget error:%s\n",strerror(errno));
		exit(0);
	}

	b_card = shmat(shmid, NULL, 0);//启动共享内存并由系统选择共享内存地址,且连接到当前进程的地址空间
	if(b_card == (void *)-1)
	{
		printf("shmat error:%s\n",strerror(errno));
		exit(0);
	}

	//填充共享内存
	strcpy(b_card->ID,"a1234");
	b_card->money = 1000;

	for(i = 0; i < 4; i++)
	{
		b_card->money += 500;
		printf("账号:%s			余额:%d\n",b_card->ID,b_card->money);
		seconds_delay(2);//延时是因为给等待另外一个进程读内容
	}

	if(shmdt(b_card) < 0)//从当前进程分离共享内存
	{
		printf("shmdt error:%s\n",strerror(errno));
		exit(0);
	}

/*	if(shmctl(shmid, IPC_RMID, NULL))//删除共享内存
	{
		printf("shmdt error:%s\n",strerror(errno));
		exit(0);
	}
*/
	return 0;


}

shared_mem_visit.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/time.h>

#define FTOK_PATH "../apue"
#define PROJ_ID 10

//定义共享结构体
typedef struct
{
	int			money;
	char		ID[6];
}bank_card_t;

void seconds_delay(unsigned seconds)
{
	struct timeval tv;
	tv.tv_sec=seconds;
	tv.tv_usec=0;
	int err;
	do{
		err=select(0,NULL,NULL,NULL,&tv);
	}while(err<0 && errno==EINTR);
}



int main(int argc, char **argv)
{
	key_t			key = -1;
	bank_card_t		*b_card;//银行卡
	int				shmid = -1;//内存ID
	int				i ;

	key = ftok(FTOK_PATH, PROJ_ID);//获取身份证
	if(key < 0)
	{
		printf("ftok error:%s\n",strerror(errno));
		exit(0);
	}

	shmid = shmget(key, sizeof(bank_card_t), IPC_CREAT|0666);//分配共享内存
	if(shmid < 0)
	{
		printf("shmget error:%s\n",strerror(errno));
		exit(0);
	}

	b_card = shmat(shmid, NULL, 0);//启动共享内存并由系统选择共享内存地址,且连接到当前进程的地址空间
	if(b_card == (void *)-1)
	{
		printf("shmat error:%s\n",strerror(errno));
		exit(0);
	}


	for(i = 0; i < 4; i++)
	{
		printf("账号:%s			余额:%d\n",b_card->ID,b_card->money);
		seconds_delay(2);
	}

	if(shmdt(b_card) < 0)//从当前进程分离共享内存
	{
		printf("shmdt error:%s\n",strerror(errno));
		exit(0);
	}

	if(shmctl(shmid, IPC_RMID, NULL))//删除共享内存
	{
		printf("shmdt error:%s\n",strerror(errno));
		exit(0);
	}

	return 0;


}

运行程序时,必须在不同的终端运行,如果当create运行结束后运行visit,余额只会3000。
在这里插入图片描述
在这里插入图片描述

7、消息队列

消息队列简称MQ,可以用下面形象的图描述消息队列:放入消息、取消息。
在这里插入图片描述

  • linux内核通过链表实现消息队列,提供了进程之间交换数据的方法,进程之间发送和接收的是数据块,也就是一个结构体,结构体可以包含各种类型的数值。
  • 在内核中,可以创建和维护的消息队列的数量有限(MSGMNI),每个消息队列的长度有限(MSGMAX)以及每个消息队列总的字节数也有限(MSGMNB)。
  • linux下通过消息队列实现两进程间的通信,一般步骤如下:
    (1)、创建消息
    (2)、发送消息
    (3)、接收消息
    (4)、释放消息队列
  • 消息队列API简述:

key_t ftok(const char *pathname, int proj_id):获取键值

int msgget(key_t key, int msgflg):创建消息队列

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg):发送消息体
第二个参数必须它必须小于系统规定的上限值,ipcs -ql : 显示消息队列的限制信息,其次必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型,其参考类型定义形式如下:
typedef struct s_msgbuf
{
long mtype;
char mtext[512];
} t_msgbuf;

在这里插入图片描述

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg):接收消息体

int msgctl(int msqid, int cmd, struct msqid_ds *buf):操作消息体,比如删除消息体

  • 一个简单的例子:消息体
    msg_send.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define FTOK_PATH "../apue"
#define PROJ_ID 10

//定义消息体
typedef struct
{
	long	msg_type;//必须以long开始,接收函数根据它确定消息类型
	char    msg_val[512];
}msg_body_t;

int main(int argc, char **argv)
{

	key_t				key = -1;
	int					msg_id = -1;
	msg_body_t			msg;

	key = ftok(FTOK_PATH, PROJ_ID);//生成身份证
	if(key < 0)
	{
		printf("ftok error:%s\n",strerror(errno));
		exit(0);
	}

	msg_id = msgget(key, IPC_CREAT|0666);//创建消息队列
	if(msg_id < 0)
	{
		printf("msgget error:%s\n",strerror(errno));
		exit(0);
	}

	//初始化消息体
	msg.msg_type = (int)key;
	strcpy(msg.msg_val ,"hi,boy,good afternoon!");

	//发送消息
	
	if(msgsnd(msg_id, &msg, sizeof(msg.msg_val), IPC_NOWAIT) < 0)
	{
		printf("msgsnd error:%s\n",strerror(errno));
		exit(0);
	}

//	msgctl(msg_id, IPC_RMID, NULL);//销毁消息队列,发送端在接收端没接收之前不能销毁队列,否则基金接收不到消息
	return 0;

}

msg_receive.c

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

#define FTOK_PATH "../apue"
#define PROJ_ID 10

//定义消息体
typedef struct
{
	long	msg_type;//必须以long开始,接收函数根据它确定消息类型
	char    msg_val[512];
}msg_body_t;

int main(int argc, char **argv)
{

	key_t				key = -1;
	int					msg_id = -1;
	msg_body_t			msg;
	int					msgtype;

	key = ftok(FTOK_PATH, PROJ_ID);//生成身份证
	if(key < 0)
	{
		printf("ftok error:%s\n",strerror(errno));
		exit(0);
	}

	msg_id = msgget(key, IPC_CREAT|0666);//创建消息队列
	if(msg_id < 0)
	{
		printf("msgget error:%s\n",strerror(errno));
		exit(0);
	}

	msgtype = (int)key;

	//发送消息
	
	if(msgrcv(msg_id, &msg, sizeof(msg.msg_val), msgtype, IPC_NOWAIT) < 0)
	{
		printf("msgsnd error:%s\n",strerror(errno));
		exit(0);
	}
	printf("Receive Message: %s\n", msg.msg_val);

	msgctl(msg_id, IPC_RMID, NULL);//销毁消息队列

	return 0;


}

先运行发送端:
在这里插入图片描述
在运行接收端:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值