Linux下进程间通信

Linux下进程间通信

Linux下的进程通信机制基本是从unix平台继承而来的。传统的Unix进程间通信方式包括无名管道、有名管道以及信号。这些通信方式同样适用于Linux,在Linux中又增加了另外一些通信方式,如消息队列、信号量以及共享内存这些进程通信方式称为IPC,现在Linux中使用较多的进程间通信方式主要有以下几种。
(1)无名管道(pipe)以及有名管道(fifo)。无名管道可用于具有亲缘关系进程间通信(通过fork函数建立的父子进程通信);有名管道除此之外还可用于无亲缘关系直接进程的通信使用。
(2)信号。信号是在软件层次上对中断机制的一种模拟,是一种比较复杂的通信方式,用于通知进程某一事件发生。
(3)消息队列。消息队列是消息的链接表,他克服了前两种通信方式之间信息量有限的缺点,当进程拥有写权限时可向消息队列中添加新消息,当具有读权限时可向消息队列中读取信息。
(4)共享内存。这种通信方式是进程中最有效的一种通信方式。这种通信方式使得多个进程可以访问一块内存空间,不同进程可以及时看到对方进程对共享内存中数据的修改更新,但这种通信机制需要依靠某种同步机制,如互斥锁和信号量等。
(5)信号量。主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。‘
(6)套接字。套接字是一种使用更为广泛的进程间通信机制,可用于网络中不同主机之间的进程通信,应用非常广泛。

1、无名管道

这种通信方式使Linux中管道通信的一种原始方式。
父进程与子进程共同访问内核实现通信。他具有以下几个特点:
(1)只能用于具有亲缘关系的进程间通信(就是写在同一个代码中的两个进程)
(2)是一个单工的通信模式,具有固定的读端和写端。
(3)管道也可以看做一个特殊的文件。对于他的读写也可以使用read()、write()等函数来进行操作。但是他并不存在与任何文件系统中,只存在于内存中。

(1)无名管道的创建

无名管道的创建是基于文件描述符的通信方式。当一个管道建立时,他会创建两个文件描述符:fd[0]和fd[1]。其中fd[0]固定用于读管道,fd[1]用于写管道,这样通过fd[0]和fd[1]就构建起了一个单向的数据通道。
管道创建通过下面这个函数进行

   #include <unistd.h>

   int pipe(int pipefd[2]);	
   参数:
   		pipefd:存放无名管道读端和写端的数组首地址
   		
   		pipefd[0] -- 读端
   		pipefd[1] -- 写端
   
   返回值:
   		成功返回0,失败返回-1

该函数创建的管道两端处于一个进程中,由于管道主要是用于两个不同进程之间的通信,通常是先创建管道再调用fork()函数创建一个子进程,该子进程会继承父进程的管道。这时子进程中就具有了总共4个文件描述符,由于无名管道是单工的工作方式,即要么只能读要么只能写。因此,我们在后续使用管道是应关闭其中一个,要么父进程写子进程读,要么子进程写父进程读。

注意

读特性:当写端存在时:
管道有数据:返回读到的字节数
管道无数据:阻塞
当写端不存在时:
管道有数据:返回读到的字节数
管道无数据:返回0
写特性:当读端存在时:
管道有空间:返回写入的字节数
管道无空间:阻塞,直到有空间为止
当读端不存在时:
无论管道是否有空间,管道破裂
下面是创建有名管道的代码实现:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
int main()
{
	pid_t pid;
	int pipe_fd[2];
	int ret = pipe(pipe_fd);
	if(ret <0)
	{
		perror("pipe");
		exit(-1);
	}
	if((pid=fork()) == 0) //创建子进程
	{
		char buf[32]={0};
		close(pipe_fd[0]);//关闭子进程的读端
		while(1){
			fgets(buf,32,stdin);
			buf[strlen(buf)-1]='\0';
			int num = write(pipe_fd[1],buf,strlen(buf)); 
		}
	}
	close(pipe_fd[1]);//关闭父进程的写端
	char ch[32]={0};
	while(1)
	{
		memset(ch,0,sizeof(ch));
		read(pipe_fd[0],ch,32);
		printf("%s\n",ch);
		wait(NULL);
	}
}

有名管道

有名管道是对无名管道的一种改进,它具有一下特点。
(1)它可以使两个互不相关的进程实现通信。
(2)该管道可以通过路径名来指出,并且在系统文件中是可见的。在建立好管道后,两个进程可以把他当作普通文件来进行读写操作,使用非常方便。
(3)FIFO遵守着先进先出规则,对管道及FIFO的读写总是从开始处返回数据,写操作是在末尾操作,没有固定的读写端口。但不支持文件定位操作(lseek()函数)。

(1)有名管道的创建

有名管道的创建可以通过使用mkfifo()函数来进程创建,在创建好后可以使用open()、read()、write()等函数进行操作。
mkfifo()函数:

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

   int mkfifo(const char *pathname, mode_t mode);	
   参数:
   		pathname:创建管道文件的文件名
   		mode:创建管道文件的权限
   
   返回值:
   		成功返回0,失败返回-1

对于读进程:
如果当前FIFO内没有数据,读进程将一直阻塞到有数据写入或是FIFO写端都被关闭。
对于写进程:
只要FIFO有空间,数据就可以被写入。若空间不足,写进程将会被阻塞,直到数据都被阻塞为止。
下面实例为写入操作:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
	int ret = mkfifo("fifo",0665); //创建管道文件,文件名为fifo,权限为0665
	if(ret <0)
	{
		perror("mkfifo");
		exit(-1);
	}
	int fd = open("fifo",O_WRONLY); //以只写的方式打开文件
	if(fd < 0)
	{
		perror("open");
		exit(-1);
	}
	char buf[32]={0};
	while(1){
		fgets(buf,32,stdin);
		buf[strlen(buf)-1]='\0'; //向文件写入数据,fgets自带换行,去除换行
		write(fd,buf,strlen(buf));
	}
	close(fd);
	
	return 0;
}

下面为读操作

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
	int fd = open("fifo",O_RDONLY); //以只读方式打开文件
	if(fd < 0)
	{
		perror("open");
		exit(-1);
	}
	char buf[32]={0};
	while(1){
		read(fd,buf,32);
		printf("%s\n",buf);//将读取到的内容打印在终端
		memset(buf,0,32); //清空buf缓冲区数据
	}
	close(fd);
	
	return 0;
}

信号通信

信号通信相对比前两个复杂一点,这里先介绍一下什么是信号。
信号是在软件层次上上对一个中断机制的一种模拟。在原理上,一个进程收到一个信号与处理去收到一个中断请求是一样的。一个进程不必通过任何操作来等待信号的到来,事实上进程也不知道信号什么时候到达。这里可以这样理解,当你在打游戏的时候你妈妈跟你说去买瓶酱油,然后你停下游戏去买酱油,这里你妈妈说的“去买瓶酱油”就是一个中断请求这里你,也不用一直等着你妈妈叫你去买酱油,你也不知道你妈妈什么时候会叫你去买酱油。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了那些系统事件。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程处于不可执行态,则该信号就又内核保存下来,知道该进程恢复可执行态。

信号是进程间通信机制中唯一异步通信机制。这里后续会介绍到什么是异步。

信号事件的产生有硬件来源和软件来源,硬件来源比如当你按下键盘或者其他硬件故障都会产生信号,软件来源包括一些非法运算符及一些相关函数如kill()、raise()、alarm()等,有兴趣的可以去看一下这几个函数。
进程可以通过三种方式来响应一个信号。
(1)忽略信号。及不对信号做任何处理,其中,有两个信号不能忽略:SIGKILL以及SIGSTOP。
(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数。
(3)执行默认操作。以下是一个信号表在这里插入图片描述
在这里插入图片描述
信号相关函数包括信号的发送和设置:
(1)发送信号函数:kill()、raise()
(2)设置信号函数:signal()、sigaction()
(3)其他函数:alarm()、pause()

信号发送与设置

信号发送:kill()和raise()
kill()函数与kill系统命令是一样的,可以发送信号给进程或进程组(实际上kill系统命令就是用kill()函数实现的)。

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

   int kill(pid_t pid, int sig);
   参数:
   		pid:指定进程号
   		sig:指定信号
   返回值:
   		成功返回0,失败返回-1--------------------------------------------------------------------------- 		
   		
    #include <signal.h>

    int raise(int sig);
    参数:
    	sig:指定信号
   	返回值:
   		成功返回0值,失败返回非0

信号设置:signal()和sigaction()
这里只介绍signal()函数
使用signal()函数时,只需要制定信号类型和信号处理函数即可。它主要是用于前32种非实时信号的处理,不支持信号传递信息。这也是程序员常用的一种信号处理函数。

   #include <signal.h>

   typedef void (*sighandler_t)(int);

   sighandler_t signal(int signum, sighandler_t handler);
   
   参数:
   		signum:指定信号
   		handler:信号处理函数
   			SIG_IGN:选择以忽略方式处理指定信号
   			SIG_DFL:选择以默认方式处理指定信号

消息队列

顾名思义,消息队列就是一些消息的列表。用户可在消息队列中添加消息和读取消息等,可以看出消息队列具有一定的FIFO特性。
消息队列的实现包括创建或打开消息队列、添加信息、读取信息和控制消息队列这4个操作。其中每个操作都有对应的函数,创建或打开消息队列使用msgget()函数,其创建的消息队列数量受系统消息队列数量限制;添加消息使用函数msgsend(),该函数添加的消息位置位于打开的消息队列末尾;读取消息使用函数msgrcv(),能够从消息队列中读取(拿走)消息,可以取走制定消息;控制消息队列的函数为msgctl(),可使用该函数完成多想功能。

有关这几个函数的具体参数与用法可查看下面这篇文章

下面是对消息队列的具体使用示例:
发送端:

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

typedef struct message
{
	long msg_type;//用于标识发送端的消息类型,这里设置为本身进程号
	char msg_buf[1024];//发送消息的内容
}Message;

int main()
{
	int pid;
	key_t key;
	Message msg;

	if((key = ftok(".",'a'))==-1)//由路径和关键字产生标准key
	{
		perror("ftok");
		exit(-1);
	}
	/*创建消息队列*/
	if((pid=msgget(key,IPC_CREAT|0666))== -1)
	{
		perror("msgget");
		exit(-1);
	}
	
	while(1)
	{
		printf("请输入消息:");
		if((fgets(msg.msg_buf,1024,stdin))== NULL )
		{
			puts("没有消息");
			exit(-1);
		}

		msg.msg_type = getpid();//保存消息类型,使用进程号标识
		if((msgsnd(pid,&msg,strlen(msg.msg_buf),0))<0)
		{
			perror("message");
			exit(-1);
		}
		if(strncmp(msg.msg_buf,"quit",4)==0)
		{
			break;
		}
	}
	return 0;
}

接收端:

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

typedef struct message
{
	long msg_type;//用于标识发送端的消息类型,这里设置为本身进程号
	char msg_buf[1024];//发送消息的内容
}Message;

int main()
{
	int pid;
	key_t key;
	Message msg;

	if((key = ftok(".",'a'))==-1)//由路径和关键字产生标准key
	{
		perror("ftok");
		exit(-1);
	}
	/*创建消息队列*/
	if((pid=msgget(key,IPC_CREAT|0666))== -1)
	{
		perror("msgget");
		exit(-1);
	}
	
	while(1)
	{
		memset(msg.msg_buf,0,1024);
		if(msgrcv(pid,(void *)&msg,1024,0,0)<0)
		{
			perror("msgrcv");
			exit(-1);
		}
		printf("读取到的消息为:%s",msg.msg_buf);		
	}
	return 0;
}

运行结果如下:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值