06Linux进程间通信.docx
- 文档编号:26418867
- 上传时间:2023-06-19
- 格式:DOCX
- 页数:49
- 大小:110.64KB
06Linux进程间通信.docx
《06Linux进程间通信.docx》由会员分享,可在线阅读,更多相关《06Linux进程间通信.docx(49页珍藏版)》请在冰豆网上搜索。
06Linux进程间通信
1.进程间通信
1.1.(InterprocessCommunication,IPC)简介
Linux下的进程通信手段基本上是从UNIX平台上的进程通信手段继承而来的。
而对UNIX发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间的通信方面的侧重点有所不同。
前者是对UNIX早期的进程间通信手段进行了系统的改进和扩充,形成了“systemVIPC”,其通信进程主要局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。
而Linux则把两者的优势都继承了下来
UNIX进程间通信(IPC)方式包括管道、FIFO以及信号。
SystemV进程间通信(IPC)包括SystemV消息队列、SystemV信号量以及SystemV共享内存区。
Posix进程间通信(IPC)包括Posix消息队列、Posix信号量以及Posix共享内存区
linux进程之间的通讯主要有下面几种
1管道pipe和命名管道:
管道有亲缘关系进程间的通信,命名管道还允许无亲缘关系进程间通信
2信号signal:
在软件层模拟中断机制,通知进程某事发生
3消息队列:
消息的链表包括posix消息队列和SystemV消息队列
4共享内存:
多个进程访问一块内存主要以同步
5信号量:
进程间同步
6套接字socket:
不同机器间进程通信
下面是对它们的详解:
·管道(Pipe)及有名管道(namedpipe):
管道可用于具有亲缘关系进程间的通信,有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
·信号(Signal):
信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。
·消息队列(MessgeQueue):
消息队列是消息的链接表,包括Posix消息队列和SystemV消息队列。
它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
·共享内存(Sharedmemory):
可以说是最有用的进程间通信方式,是最快的可用IPC形式。
是针对其他通信机制运行效率较低而设计。
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。
这种通信方式需要依靠某种同步机制,如互斥锁和信号量。
·信号量(Semaphore):
主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。
·套接字(Socket):
这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
1.2.信号通信signal
信号是unix进程通信最古老的方法。
它模拟中断机制,是一种异步通信方式。
信号可以直接进行用户空间进程和内核进程之间的交互,内核进程利用它来通知用户空间进程发生了哪些系统事件。
它可以在任何时候发给某一进程,而无需知道该进程的状态。
如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程
信号分为:
不可靠信号和可靠信号。
不可靠信号的处理过程:
如果发现该信号已经注册,就忽略该信号。
因此,若前一个信号还未注销又产生了相同的信号就会产生信号丢失。
而当可靠信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此信号就不会丢失。
所有可靠信号都支持排队,而所有不可靠信号都不支持排队。
对于可靠信号和不可靠信号示例如下:
signal.c
#include
#include
#include
intiCount=0;
voidhandler(intiSigNo)
{
iCount++;
printf("%d:
doing%d\n",iCount,iSigNo);
sleep(3);
printf("%d:
done%d\n",iCount,iSigNo);
}
intmain(intargc,char*argv[])
{
if(argc<2)
{
printf("Usage:
EXESIGNO\n");
exit(-1);
}
signal(atoi(argv[1]),handler);
while
(1);
return0;
}
对不可靠信号的测试:
./signal2另外的一个终端通过ps–aux查看进程,然后快速执行几次kill-211902会发现没有排队,而是只执行了2次。
对可靠信号的测试:
./signal41另外的一个终端通过ps–aux查看进程,然后快速执行几次kill-4111902会发现进行了排队,全部都执行了。
一个完整的信号生命周期可以分为3个重要阶段,这3个阶段由4个重要事件来刻画的:
信号产生、信号在进程中注册、信号在进程中注销、执行信号处理函数
信号的生命周期:
信号处理有3种方式:
忽略:
(SIG_IGN)但不能忽略SIGKILL,SIGSTOP
捕获:
定义处理函数,信号发生时调用
默认:
SIG_DFL
常见信号:
用kill–l查看信号
1.3.标准流管道
像文件操作有标准io流一样,管道也支持文件流模式。
用来创建连接到另一进程的管道,是通过函数popen和pclose。
函数原型:
#include
FILE*popen(constchar*command,constchar*open_mode);
intpclose(FILE*fp);
函数popen():
允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。
command字符串是要运行的程序名。
open_mode必须是“r”或“w”。
如果open_mode是“r”,被调用程序的输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数(如fread)来读取被调用程序的输出;如果open_mode是“w”,调用程序就可以用fwrite向被调用程序发送数据,而被调用程序可以在自己的标准输入上读取这些数据。
函数pclose():
用popen启动的进程结束时,我们可以用pclose函数关闭与之关联的文件流。
Example1:
从标准管道流中读取打印/etc/profile的内容
#include
intmain()
{
FILE*fp=popen("cat/etc/profile","r");
charbuf[512]={0};
while(fgets(buf,sizeof(buf),fp))
{
puts(buf);
}
pclose(fp);
return0;
}
Example2:
写到标准管道流统计buf单词数
#include
intmain()
{
charbuf[]={"aaabbbcccdddeeefffggghhh"};
FILE*fp=popen("wc-w","w");
fwrite(buf,sizeof(buf),1,fp);
pclose(fp);
return0;
}
1.4.无名管道(PIPE)
管道是linux进程间通信的一种方式,如命令ps-ef|grepntp
无名管道的特点:
1只能在亲缘关系进程间通信(父子或兄弟)
2半双工(固定的读端和固定的写端)
3他是特殊的文件,可以用read、write等,只能在内存中
管道函数原型:
#include
intpipe(intfds[2]);
管道在程序中用一对文件描述符表示,其中一个文件描述符有可读属性,一个有可写的属性。
fds[0]是读,fds[1]是写。
函数pipe用于创建一个无名管道,如果成功,fds[0]存放可读的文件描述符,fds[1]存放可写文件描述符,并且函数返回0,否则返回-1。
通过调用pipe获取这对打开的文件描述符后,一个进程就可以从fds[0]中读数据,而另一个进程就可以往fds[1]中写数据。
当然两进程间必须有继承关系,才能继承这对打开的文件描述符。
管道不象真正的物理文件,不是持久的,即两进程终止后,管道也自动消失了。
示例:
创建父子进程,创建无名管道,父写子读
#include
#include
#include
intmain()
{
intfds[2]={0};
pipe(fds);
charszBuf[32]={'\0'};
if(fork()==0){//表示子进程
close(fds[1]);//子进程关闭写
sleep
(2);//确保父进程有时间关闭读,并且往管道中写内容
if(read(fds[0],szBuf,sizeof(szBuf))>0)
puts(buf);
close(fds[0]);//子关闭读
exit(0);
}else{//表示父进程
close(fds[0]);//父关闭读
write(fds[1],"hello",6);
waitpid(-1,NULL,0);//等子关闭读
//write(fds[1],"world",6);//此时将会出现“断开的管道”因为子的读已经关闭了
close(fds[1]);//父关闭写
exit(0);
}
return0;
}
管道两端的关闭是有先后顺序的,如果先关闭写端则从另一端读数据时,read函数将返回0,表示管道已经关闭;但是如果先关闭读端,则从另一端写数据时,将会使写数据的进程接收到SIGPIPE信号,如果写进程不对该信号进行处理,将导致写进程终止,如果写进程处理了该信号,则写数据的write函数返回一个负值,表示管道已经关闭。
示例:
#include
#include
#include
#include
intmain()
{
intfds[2];
pipe(fds);
//注释掉这部分将导致写进程被信号SIGPIPE终止
sigset_tsetSig;
sigemptyset(&setSig);
sigaddset(&setSig,SIGPIPE);
sigprocmask(SIG_BLOCK,&setSig,NULL);
charszBuf[10]={0};
if(fork()==0){
close(fds[1]);//子关闭写
sleep
(2);//确保父关闭读
if(read(fds[0],szBuf,sizeof(szBuf))>0)
puts(szBuf);
close(fds[0]);//子关闭读
}else{
close(fds[0]);//父关闭读
write(fds[1],"hello",6);
wait(NULL);
write(fds[1],"world",6);//子的读关闭,父还在写
close(fds[1]);//父关闭写
}
return0;
}
1.5.命名管道(FIFO)
无名管道只能在亲缘关系的进程间通信大大限制了管道的使用,有名管道突破了这个限制,通过指定路径名的范式实现不相关进程间的通信
1.5.1.创建、删除FIFO文件
创建FIFO文件与创建普通文件很类似,只是创建后的文件用于FIFO。
1.用函数创建和删除FIFO文件
·创建FIFO文件的函数原型:
#include
#include
intmkfifo(constchar*pathname,mode_tmode);
参数pathname为要创建的FIFO文件的全路径名;
参数mode为文件访问权限
如果创建成功,则返回0,否则-1。
示例:
#include
#include
#include
#include
intmain(intargc,char*argv[])//演示通过命令行传递参数
{
if(argc!
=2){
puts("Usage:
MkFifo.exe{filename}");
return-1;
}
if(mkfifo(argv[1],0666)==-1){
perror("mkfifofail");
return-2;
}
return0;
}
·删除FIFO文件的函数原型为:
#include
intunlink(constchar*pathname);
示例:
#include
main()
{
unlink("pp");
}
2.用命令创建和删除FIFO文件
·用命令mkfifo创建不能重复创建
·用命令unlink删除
创建完毕之后,就可以访问FIFO文件了:
一个终端:
cat 另一个终端: echo“hello”>myfifo 1.5.2.打开、关闭FIFO文件 对FIFO类型的文件的打开/关闭跟普通文件一样,都是使用open和close函数。 如果打开时使用O_WRONLY选项,则打开FIFO的写入端,如果使用O_RDONLY选项,则打开FIFO的读取端,写入端和读取端都可以被几个进程同时打开。 如果以读取方式打开FIFO,并且还没有其它进程以写入方式打开FIFO,open函数将被阻塞;同样,如果以写入方式打开FIFO,并且还没其它进程以读取方式FIFO,open函数也将被阻塞。 与PIPE相同,关闭FIFO时,如果先关读取端,将导致继续往FIFO中写数据的进程接收SIGPIPE的信号。 1.5.3.读写FIFO 可以采用与普通文件相同的读写方式读写FIFO, Example: 先执行#mkfifoMyFifo.pip命令 然后viwrite.c如下: #include #include #include #include intmain() { intfdFifo=open("MyFifo.pip",O_WRONLY);//1.打开(判断是否成功打开略) write(fdFifo,"hello",6);//2.写 close(fdFifo);//3.关闭 return0; } 然后viread.c如下: #include #include #include #include intmain() { charszBuf[128]; intfdFifo=open("MyFifo.pip",O_RDONLY);//1.打开 if(read(fdFifo,szBuf,sizeof(szBuf))>0)//2.读 puts(szBuf); close(fdFifo);//3.关闭 return0; } 然后gcc–owritewrite.c gcc–oreadread.c ./write//发现阻塞,要等待执行./read ./read 在屏幕上输出hello 1.6.共享内存 共享内存也是进程间(进程间不需要有继承关系)通信的一种常用手段。 一般OS通过内存映射与页交换技术,使进程的内存空间映射到不同的物理内存,这样能保证每个进程运行的独立性,不至于受其它进程的影响。 但可以通过共享内存的方式,使不同进程的虚拟内存映射到同一块物理内存,一个进程往这块物理内存的更新数据,另外的进程可以立即看到这块物理内存的修改。 内存映射和共享内存的区别: 内存映射: 跟普通文件的读写相比,加快对文件/设备的访问速度。 共享内存: 多进程间进行通信。 ·内存映射函数: mmapmunmapmsync(加快文件访问速度) 函数原型: #include #include void*mmap(void*start,size_tlength,intprot,intflags,intfd,off_toffsize); intmsync(constvoid*start,size_tlength,intflags); intmunmap(void*start,size_tlength); ·mmap(): 用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。 返回值: 成功,返回共享内存的起始地址。 参数start指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回。 参数length代表将文件中多大的部分对应到内存。 参数prot代表映射区域的保护方式有下列组合 PROT_EXEC映射区域可被执行 PROT_READ映射区域可被读取 PROT_WRITE映射区域可被写入 PROT_NONE映射区域不能存取 参数flags会影响映射区域的各种特性: MAP_FIXED如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。 通常不鼓励用此旗标。 MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。 MAP_PRIVATE对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copyonwrite)对此区域作的任何修改都不会写回原来的文件内容。 MAP_ANONYMOUS建立匿名映射。 此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。 MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。 MAP_LOCKED将映射区域锁定住,这表示该区域不会被置换(swap)。 在调用mmap()时必须要指定MAP_SHARED或MAP_PRIVATE。 参数fd为open()返回的文件描述词,代表欲映射到内存的文件。 参数offset为文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。 ·munmap(): 用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。 当进程结束,映射内存会自动解除,但关闭对应的文件描述词时不会解除映射。 返回值: 如果解除映射成功则返回0,否则返回-1 Example: 利用内存映射机制实现进程间通信 写端: (打开的文件不能为空,可以利用文件空洞技术) #include #include #include #include #include main() { intfd=open("mm.dat",O_RDWR|O_CREAT);//创建一个新文件并以读写方式打开 charbuf[20]={0}; write(fd,buf,sizeof(buf));//文件不能为空,写入20个0,保证文件不为空 lseek(fd,0,SEEK_SET);//重新定位到文件头 char*p=(char*)mmap(NULL,20*sizeof(char),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); memset(p,0,20);//最好先初始化为0 memcpy(p,"helloworld",12);//写hello到内存 sleep(10); munmap(p,20); close(fd); } 读端: #include #include #include #include #include main() { intfd=open("mm.dat",O_RDWR);//需要打开同一个文件 char*p=(char*)mmap(NULL,20*sizeof(char),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); puts(p); munmap(p,20); close(fd); } 此时你会发现文件mm.dat的内容为hellowrold。 因为MAP_SHARED会将修改的内容写回文件。 如果你将上面的MAP_SHARED换成MAP_PRIVATE,则没有任何输出效果。 因为MAP_PRIVATE是私人的“写入时复制”,不会写回文件,只是自己本身的进程有效,即使你用有亲缘关系的也不行,这是内存映射MAP_PRIVATE与SYSTEMV的共享内存的IPC_PRIVATE的区别,如图: 内存映射实质是为了加快文件读写的速度的。 因为普通文件的读写是对硬盘的读写,而内存映射将文件映射到内存,因为访问的是内存,所以速度会很快。 将a.txt映射到A进程的内存地址40017000,然后往该地址里面写入内容“helloworld”,此时用的MAP_SHARED可以写回到文件a.txt,然后将a.txt又映射到B进程的内存地址40017000,然后从该内存取数据,实质上是从a.txt里面取,则可以取出数据“helloworld”。 如果用MAP_PRIVATE,则A进程往内存里面写入的“helloworld”,不能写回到文件a.txt,然后将a.txt映射到B进程的内存地址40017000上,从该内存取数据,实质上是从文件a.txt取,之前A进程的内容没有写入到a.txt,所以此时B进程获取不到数据“helloworld”。 在这里a.txt充当了桥梁的作用,所以说内存映射的实质还是文件的操作,相当于A进程往a.txt写了“helloworld”,然后B进程从a.txt里面读“helloworld”,只是利用内存映射可以加快读写的速度而已。 注意这里进程A和B将a.txt映射到自己的内存空间都是40017000上,但是由于
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 06 Linux 进程 通信