Linux系统编程——进程间通信(一)

版权声明:版权归博主所有,转载请注明 https://blog.csdn.net/qq_17808707/article/details/88206751

Linux系统主要的进程间通信机制如下:

  1. 无名管道(Pipe)及命名管道(Named pipe):无名管道可用于具有父子关系进程间的通信;命名管道用于无父子关系的进程之间的通信。无父子关系的进程可将信息发送到某个命名管道中,通过管道名读取信息
  2. 信号(Signal):进程间的高级通信方式,用于通知其他进程有何种事件发生。此外,进程可以向自身发送信号,还可获得Linux内核发出的信号。
  3. 报文(Message)队列:又称为消息队列,是以POSIX和System V为标准的通信机制。报文队列克服了信号的数据结构过于简单的问题,同时也解决了管道数据流无格式和缓冲区长度受限等问题。报文队列规定了每个进程的权限,避免了仿冒信息的出现。
  4. 共享内存:让多个进程访问同一个内存空间,适合于数量极大和数据结构极为复杂的进程间通信。牺牲了系统的安全性,通常与其他进程间通信形式混合使用,并避免以根用户权限执行。
  5. 信号量(Semaphore):用于解决进程的同步和相关资源抢占而设计的
  6. 套接字(Socket):一种数据访问机制,不仅可以用于进程间通信,还可用于网络通信。
  7. D—Bus:一种高级的进程间通信机制,以套接字机制为基础实现

一 进程间使用管道通信

管道本身是一种数据结构,遵循先进先出的原则。先进入管道的数据,也能先从管道中读出。数据一旦读取后,就会在管道中自动删除。
管道通信以管道数据结构作为内部数据存储方式,以文件系统作为数据存储媒体。

1.pipe系统调用

pipe()函数别定义在头文件unistd.h中,它的一般形式是:

int pipe(int filedes[2]);

pipe系统调用需要打开两个文件,文件标识符通过参数传递给pipe()函数。文件描述符filedes[0]用来读取数据,filedes[1]用来写数据。调用成功时,返回值为0,错误时返回-1。
管道的工作方式可以总结为以下3个步骤

(1)将数据写入管道

使用的是write()函数,与写入普通文件的操作方法一样。不同的是管道长度受到限制,管道满时写入操作会被阻塞。执行写操作的进程进入睡眠状态,直到管道中的数据被读取。fcntl()函数可将管道设置为非阻塞模式,管道满时,write()函数的返回值为0。如果写入数据长度小于管道数据,则要求一次写入完成,若大于管道长度,写完管道长度的数据时,write()将被阻塞。

(2)从管道读取数据

读取数据使用read()函数实现,读取的顺序与写入顺序相同。当数据被读取后,这些数据将自动被管道清除。因此,使用管道通信的方式只能是一对一,不能由一个进程同时向多个进程传递同一数据。
**如果读取的管道为空,并且管道写入端口是打开的,read()函数将被阻塞。**读取操作的进程进入睡眠状态,直到有数据写入管道为止。fcntl()函数也可将管道读取模式设置为非阻塞。

(3)关闭管道

管道虽然有两个端口,但只有一个端口能被打开,避免了同时对管道进行读和写的操作。关闭端口使用的是close()函数,关闭读端口时,在管道上进行写操作的进程将收到SIGPIPE信号,关闭写端口时,进行读操作的read()函数将返回0。如下例所示:

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

int main()
{
	int fd[2],cld_pid,len;
	char buf[200];
	int status;
	if(pipe(fd)==-1)
	{
		perror("创建管道出错");
		exit(1);
	}
	if((cld_pid=fork())==0)
	{
		printf("\n进入子进程\n");
		close(fd[1]);
		len=read(fd[0],buf,sizeof(buf));
		buf[len]=0;
		printf("子进程从管道中读取的数据是:\n%s",buf);
		printf("\n退出子进程\n");
		exit(0);
	}
	else
	{
		printf("\n进入父进程\n");
		close(fd[0]);
		sprintf(buf,"父进程为子进程(PID=%d)创建该数据\n123\t456\t789",cld_pid);
		write(fd[1],buf,strlen(buf));
		printf("\n退出父进程\n");
		//exit(0);
		
	}
	wait(&status);
	return 0;
}

输出结果如下:
在这里插入图片描述
上述程序中首先创建了一个管道,并且将管道的文件标识符传递给fp[]数组。该数组有两个元素,fd[0]是读取管道的端口,fd[1]是写入管道的端口。然后,通过fork()系统调用创建子进程。父进程的操作是向管道写入数据,子进程的操作是读取管道内的数据,最后子进程将所读数据显示到终端上。
sprintf()函数是向缓冲区中写入内容。

2.dup系统调用

系统调用dup用来复制一个文件描述符,该操作是通过对u区中文件描述符复制实现的。因此,系统调用dup能让多个文件描述符指向同一文件,便于管道操作。与该调用相关的函数有两个,分别是dup()函数和dup2()函数,一般形式如下:

int dup(int oldfd);
int dup2(int oldfd,int newfd);
  • oldfd:原有的文件描述符
  • newfd:指定的新文件描述符

两个函数的区别为,dup()函数自动分配新文件描述符,并保证该文件描述符没有被使用。dup2()函数使用newfd参数指定新文件描述符,如果该文件描述符已存在,则覆盖对应的文件描述符。
新旧文件描述符可交换使用,并共享文件锁、文件指针和文件状态。
调用成功时,函数返回值为新文件描述符,否则返回-1。例如下例:

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

int main()
{
	int fd;
	if((fd=open("output",O_CREAT|O_RDWR,0644))==-1)
	{
		perror("打开或创建文件出错");
		return 1;
	}
	close(1);         //标准输出的文件描述符为1
	dup(fd);
	close(fd);
	puts("该行数据将输出到文件中");
	return 0;
}

程序执行结果如下:
在这里插入图片描述
上述代码中,标准输出(文件描述符为1)关闭,并将一个普通文件output的文件描述符复制到标准输出上。因为刚关闭了文件描述符,文件描述符表的第一个空表项是1,dup()函数将调用fd的文件描述符复制到该位置上。所以,程序以后的向标准输出写的内容都写到了文件output中。

猜你喜欢

转载自blog.csdn.net/qq_17808707/article/details/88206751
今日推荐