Linux进程间通信(上)

目录

一、无名管道PIPE

示例

二、有名管道FIFO

示例

fifowr.c

fiford.c

三、内存映射mmap

示例

mmapwr.c

mmaprd.c 


进程间通信IPC(Inter Process Communicating)主要包括:

        无名管道pipe、有名管道fifo、内存映射mmap、共享内存shm,消息队列msg、信号signal、套接字socket(多机之间,多用于网络编程)

一、无名管道PIPE

        管道,单工通信,通常指无名管道(无名在于它是伪文件),存在(运行)内存,只能用于具有亲缘关系的父子进程或兄弟进程(管道可共享于2个进程以上),因为管道数据是通过队列来维护的,一端固定只能读或写,管道建立时会创建两个文件描述符分别用于读写管道,所以只能用文件IO来操作管道中的数据只能读一次,再读也没内容 ​​​​​​

int pipe(int fd[2]);

@param: 大小为2的int型数组,用来保存创建的两个文件描述符,fd[0]用来读、fd[1]用来写
    
@return:成功返回0,失败返回-1    

读写特性:

1.读管道:

(1)管道中有数据,read从头读数据,返回实际读到的字节数

(2)管道中无数据且写端没有关闭时,read阻塞等待数据写入;写端关闭时,read返回0

2.写管道:

(1)读端全部关闭,“管道会发生爆裂”,进程异常终止,可捕捉SIGPIPE来让进程不终止

(2)读端没有全部关且管道未满,正常追加写入,write返回实际写入字节数;管道满了(大小64K),write阻塞

示例

父进程将键盘输入的信息写进管道,子进程读管道并打印

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

int main()
{
	int fd[2];
	pid_t pid;	
	char writebuf[128] = {'\0'};
	char readbuf[128];
	int readnum;

	//creat pipe
	if((pipe(fd)) == -1)  
	{
		perror("pipe");
		return 0;
	}
	//fork
	if((pid = fork()) == -1) 
	{
		perror("fork");
		return 0;
	}
	if(pid == 0)  //child process
	{
		close(fd[1]);//ready to read pipe
		
		while(1)
		{
			bzero(readbuf,128);
			do
			{
				readnum = read(fd[0],readbuf,128);//阻塞读管道
			} while (readnum < 0 && readnum == EINTR);
			if(readnum < 0)
			{
				perror("read");
			}
			if(readnum == 0)
			{
				printf("child quit!\n");
				break;
			}
			printf("child receive:%s\n",readbuf);
			
		}
		close(fd[0]);

	}else //father process
	{
		close(fd[0]);//ready to write pipe

		while(1)
		{
			bzero(writebuf,128);
			if(fgets(writebuf,127,stdin) == NULL)//键盘获取输入信息
			{
				continue;
			}
			writebuf[strlen(writebuf)-1] = '\0';//注意fgets自动添加换行符 所以手动去除
			write(fd[1],writebuf,128);//写进管道
			if(strncasecmp(writebuf,"quit",strlen("quit")) == 0)
			{
				printf("father quit!\n");
				break;
			}
		}

		close(fd[1]);
	}

	return 0;
}

二、有名管道FIFO

        一种文件类型,特殊文件有路径名与之关联,但数据存在运行内存中(ls -la命令查看文件大小始终为0),可在任意无关进程之间交换数据一端只读或只写半双工通信,不支持lseek操作,遵循先进先出,同样,FIFO中的数据读一次就清空了,读FIFO从头读,写FIFO追加写


功能:创建或打开fifo
int mkfifo(char *pathname, mode_t mode)

@param:	pathname 创建时给FIFO取个名 
    			 打开时指定FIFO
    
    	mode	 创建时,mode不用加O_CREAT,直接赋值权限即可
    			 打开时,mode设置O_WRONLY/O_RDONLY/O_RDWR 三选一
    
@return:成功返回0,失败返回-1(若存在同名fifo,创建失败,errno置EEXIST)

注意事项:

        open fifo时,某进程只读或只写打开(mode设置O_WRONLY/O_RDONLY),会默认阻塞。若mode中或上O_NONBLOCK,则为非阻塞状态。

        1)读写进程都默认阻塞时:先运行写或者读进程(被阻塞),再运行读或者写进程,不再阻塞,可以正常读写。

        2)读进程非阻塞时

                        先运行写进程(被阻塞),再运行读进程,可以正常读写;

                        先运行读进程直接崩溃,因为没数据的情况下还不阻塞读!!!

        3)写进程非阻塞时只能先运行读进程,再运行写进程

        关于数据完整性,多个进程写同一个管道时,如果写入数据长度<=4K,要么全部写入,要么一个字节都不写入(遵循先进先出);如果写入数据长度过长会导致数据交错

示例

实现不同进程间通过fifo通信

fifowr.c

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


int main()
{
        int fd;
        char wrbuf[128];

        if(mkfifo("fifo1",0600) == -1 && errno != EEXIST) //如果创建失败并且是因为该fifo已存在
        {
                perror("mkfifo");
                return 0;
        }
        fd = open("fifo1",O_WRONLY);
        if(fd < 0)
        {
                perror("open fifo");
                return 0;
        }
        while(1)
        {
                fgets(wrbuf,128,stdin); //从键盘获取信息到wrbuf
                int nwrite = write(fd,wrbuf,strlen(wrbuf));//写进fifo
                printf("write %d bytes:%s\n",nwrite,wrbuf);
        }
        return 0;
}

fiford.c

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


int main()
{
	int fd;
	char rdbuf[128];

	if(mkfifo("fifo1",0600) == -1 && errno != EEXIST) //如果创建失败并且是因为该fifo已存在
	{
		perror("mkfifo");
		return 0;
	} 
	fd = open("fifo1",O_RDONLY);
	if(fd < 0)
	{
		perror("open fifo");
		return 0;
	}
	while(1)
	{
		memset(rdbuf,'\0',128);
		int nread = read(fd,rdbuf,128); //读出fifo
		rdbuf[strlen(rdbuf)-1] = '\0';
		if(nread > 0)
		{
			printf("read %d bytes:%s\n",nread,rdbuf);
		}else{break;};
	}

	return 0;
}

三、内存映射mmap

        内存映射可以通过mmap()映射普通文件将一个磁盘文件映射到进程自身的地址空间中,进程访问它通过返回的指针即可,不必调用read/write,全双工通信,因为访问内存的时间是纳秒级的,而访问磁盘是毫秒级的,大大提升了通信效率,适合于需要频繁读写大文件的场景

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
功能:申请创建 文件的内存映射区
    
@param:	
		addr 	映射区目的地址,一般设置为NULL,操作系统自动分配,可通过返回值获取
    	length 	必须>0 映射区的字节数,从文件开头offset个字节开始算起
        prot 	指定共享内存的访问权限 PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE
        flags 	常用的有:MAP_SHARED共享的、MAP_PRIVATE私有的,频繁访问磁盘操作时、MAP_ANOYMOUS匿名映射(用于无fd文件,比如父子进程间通信)
        fd 		文件描述符,匿名映射时为-1
        offset 	偏移量,指从文件头偏移offset个字节开始映射,一般都给0表示完整映射到映射区
@return:    
		成功返回映射区的地址,后续的读写操作通过这个地址
        失败返回MAP_FAILED
int munmap(void *addr, size_t length)
功能:释放内存映射,即断开连接

@param: addr 映射区首地址  length 映射区大小    

注意事项:

        创建映射区出错概率很高,应养成返回值查错习惯!

        flags为MAP_SHARED时,要求指定的共享内存访问权限<=open的文件权限;为MAP_PRIVATE时,只需要文件有可读权限即可,操作仅对内存有效,不会写入到磁盘,且不能在进程间共享

        要映射的文件大小必须>0,否则会报错“总线错误”,系统所分配的映射区大小是以4K(一页内存大小)为单位的,文件大小<4K,系统仍分配4K,超过文件大小的范围可以访问,但仅仅是访问,无法真正写入文件

        共享内存映射成功后,可以立即关闭文件,对后续共享内存的操作无影响

        offset的偏移量必须为0/4K的整数倍

示例

mmapwr.c

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

int main()
{
	void *addr;
	int fd;
	char wrbuf[128];
	
	fd = open("test_mmap",O_RDWR);
	if(fd < 0)
	{
		perror("open");
		return 0;	
	
	}
	addr = mmap(NULL,128,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(addr == MAP_FAILED)
	{
		perror("mmap");
		return 0;
	}
	close(fd);

	while(1)  //mmap opened up at least 4K space 
	{
		bzero(wrbuf,128);
		fgets(wrbuf,127,stdin);
		wrbuf[strlen(wrbuf)-1] = '\0';//手动去除换行符
		memset(addr,0,128);
		memcpy(addr,wrbuf,strlen(wrbuf));
        if( strncasecmp(wrbuf,"quit",strlen("quit")) == 0) //输入了quit
        {
            break;
        }
	}
	munmap(addr,128);
	close(fd);	
	return 0;
}

mmaprd.c 

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

int main()
{
	void *addr;
	int fd;

	fd = open("test_mmap",O_RDWR);
	if(fd < 0)
	{
		perror("open");
		return 0;	
	
	}
	addr = mmap(NULL,128,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(addr == MAP_FAILED)
	{
		perror("mmap");
		return 0;
	}
	close(fd);
	while(1)
	{
		printf("read contents:%s\n",(char *)addr);
		if( strncasecmp(addr,"quit",strlen("quit")) == 0) //输入了quit
        {
            break;
        }
		sleep(1);
	}

	munmap(addr,128);
	close(fd);
	return 0;
}

目前这个实验还存在一个bug,具体如下:写进程停止写后,读进程读着读着少了一个字符

后续试试信号量的pv操作来解决~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值