深刻理解Linux进程间通信IPC.docx

上传人:b****2 文档编号:1319013 上传时间:2023-04-30 格式:DOCX 页数:113 大小:173.88KB
下载 相关 举报
深刻理解Linux进程间通信IPC.docx_第1页
第1页 / 共113页
深刻理解Linux进程间通信IPC.docx_第2页
第2页 / 共113页
深刻理解Linux进程间通信IPC.docx_第3页
第3页 / 共113页
深刻理解Linux进程间通信IPC.docx_第4页
第4页 / 共113页
深刻理解Linux进程间通信IPC.docx_第5页
第5页 / 共113页
深刻理解Linux进程间通信IPC.docx_第6页
第6页 / 共113页
深刻理解Linux进程间通信IPC.docx_第7页
第7页 / 共113页
深刻理解Linux进程间通信IPC.docx_第8页
第8页 / 共113页
深刻理解Linux进程间通信IPC.docx_第9页
第9页 / 共113页
深刻理解Linux进程间通信IPC.docx_第10页
第10页 / 共113页
深刻理解Linux进程间通信IPC.docx_第11页
第11页 / 共113页
深刻理解Linux进程间通信IPC.docx_第12页
第12页 / 共113页
深刻理解Linux进程间通信IPC.docx_第13页
第13页 / 共113页
深刻理解Linux进程间通信IPC.docx_第14页
第14页 / 共113页
深刻理解Linux进程间通信IPC.docx_第15页
第15页 / 共113页
深刻理解Linux进程间通信IPC.docx_第16页
第16页 / 共113页
深刻理解Linux进程间通信IPC.docx_第17页
第17页 / 共113页
深刻理解Linux进程间通信IPC.docx_第18页
第18页 / 共113页
深刻理解Linux进程间通信IPC.docx_第19页
第19页 / 共113页
深刻理解Linux进程间通信IPC.docx_第20页
第20页 / 共113页
亲,该文档总共113页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

深刻理解Linux进程间通信IPC.docx

《深刻理解Linux进程间通信IPC.docx》由会员分享,可在线阅读,更多相关《深刻理解Linux进程间通信IPC.docx(113页珍藏版)》请在冰点文库上搜索。

深刻理解Linux进程间通信IPC.docx

深刻理解Linux进程间通信IPC

深刻理解Linux进程间通信(IPC)

0.序

1.管道

1.1.管道概述及相关API应用

1.2.有名管道概述及相关API应用

1.3.小结

1.4.参考资料

2.信号(上)

2.1.信号及信号来源

2.2.信号的种类

2.3.进程对信号的响应

2.4.信号的发送

2.5.信号的安装(设置信号关联动作)

2.6.信号集及信号集操作函数

2.7.信号阻塞与信号未决

2.8.参考资料

3.信号(下)

3.1.信号生命周期

3.2.信号编程注意事项

3.3.深入浅出:

信号应用实例

3.4.结束语

3.5.参考资料

4.消息队列

4.1.消息队列基本概念

4.2.操作消息队列

4.3.消息队列的限制

4.4.消息队列应用实例

4.5.小结

4.6.参考资料

5.信号灯

5.1.信号灯概述

5.2.Linux信号灯

5.3.信号灯与内核

5.4.操作信号灯

5.5.信号灯的限制

5.6.竞争问题

5.7.信号灯应用实例

5.8.参考资料

6.共享内存(上)

6.1.内核怎样保证各个进程寻址到同一个共享内存区域的内存页面

6.2.mmap()及其相关系统调用

6.3.mmap()范例

6.4.对mmap()返回地址的访问

6.5.参考资料

7.共享内存(下)

7.1.系统V共享内存原理

7.2.系统V共享内存API

7.3.系统V共享内存限制

7.4.系统V共享内存范例

7.5.结论

7.6.参考资料

8.套接口

8.1.背景知识

8.2.重要数据结构

8.3.套接口编程的几个重要步骤

8.4.典型调用代码

8.5.网络编程中的其他重要概念

8.6.参考资料

  本文系整理自网络

  作者郑彦兴,国防科大计算机学院

0.序

linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。

而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。

前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“systemVIPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。

Linux则把两者继承了下来,如图示:

其中,最初UnixIPC包括:

管道、FIFO、信号;SystemVIPC包括:

SystemV消息队列、SystemV信号灯、SystemV共享内存区;PosixIPC包括:

Posix消息队列、Posix信号灯、Posix共享内存区。

有两点需要简单说明一下:

1)由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSIUnix标准被称为计算机环境的可移植性操作系统界面(PSOIX)。

现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵循POSIX标准;2)BSD并不是没有涉足单机内的进程间通信(socket本身就可以用于单机内的进程间通信)。

事实上,很多Unix版本的单机IPC留有BSD的痕迹,如4.4BSD支持的匿名内存映射、4.3+BSD对可靠信号语义的实现等等。

图一给出了linux所支持的各种IPC手段,在本文接下来的讨论中,为了避免概念上的混淆,在尽可能少提及Unix的各个版本的情况下,所有问题的讨论最终都会归结到Linux环境下的进程间通信上来。

并且,对于Linux所支持通信手段的不同实现版本(如对于共享内存来说,有Posix共享内存区以及SystemV共享内存区两个实现版本),将主要介绍PosixAPI。

linux下进程间通信的几种主要手段简介:

1管道(Pipe)及有名管道(namedpipe):

管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

2信号(Signal):

信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);

3报文(Message)队列(消息队列):

消息队列是消息的链接表,包括Posix消息队列systemV消息队列。

有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。

消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

4共享内存:

使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。

是针对其他通信机制运行效率较低而设计的。

往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

5信号量(semaphore):

主要作为进程间以及同一进程不同线程之间的同步手段。

6套接口(Socket):

更为一般的进程间通信机制,可用于不同机器之间的进程间通信。

起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:

Linux和SystemV的变种都支持套接字。

下面将对上述通信机制做具体阐述。

附1:

参考文献[2]中对linux环境下的进程进行了概括说明:

一般来说,linux下的进程包含以下几个关键要素:

∙有一段可执行程序;

∙有专用的系统堆栈空间;

∙内核中有它的控制块(进程控制块),描述进程所占用的资源,这样,进程才能接受内核的调度;

∙具有独立的存储空间

进程和线程有时候并不完全区分,而往往根据上下文理解其含义。

参考资料

∙UNIX环境高级编程,作者:

W.RichardStevens,译者:

尤晋元等,机械工业出版社。

具有丰富的编程实例,以及关键函数伴随Unix的发展历程。

∙linux内核源代码情景分析(上、下),毛德操、胡希明著,浙江大学出版社,提供了对linux内核非常好的分析,同时,对一些关键概念的背景进行了详细的说明。

∙UNIX网络编程第二卷:

进程间通信,作者:

W.RichardStevens,译者:

杨继张,清华大学出版社。

一本比较全面阐述Unix环境下进程间通信的书(没有信号和套接口,套接口在第一卷中)。

1.管道

在本系列序中作者概述了linux进程间通信的几种主要手段。

其中管道和有名管道是最早的进程间通信机制之一,管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

认清管道和有名管道的读写规则是在程序中应用它们的关键,本文在详细讨论了管道和有名管道的通信机制的基础上,用实例对其读写规则进行了程序验证,这样做有利于增强读者对读写规则的感性认识,同时也提供了应用范例。

1.1.管道概述及相关API应用

1.1.1管道相关的关键概念

管道是Linux支持的最初UnixIPC形式之一,具有以下特点:

∙管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

∙只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);

∙单独构成一种独立的文件系统:

管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

∙数据的读出和写入:

一个进程向管道中写的内容被管道另一端的进程读出。

写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

1.1.2管道的创建

#include

intpipe(intfd[2])

该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义,因此,一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。

1.1.3管道的读写规则

管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。

即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。

如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。

一般文件的I/O函数都可以用于管道,如close、read、write等等。

从管道中读取数据:

∙如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;

∙当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。

注:

(PIPE_BUF在include/linux/limits.h中定义,不同的内核版本可能会有所不同。

Posix.1要求PIPE_BUF至少为512字节,redhat7.2中为4096)。

关于管道的读规则验证:

/**************

*readtest.c*

**************/

#include

#include

#include

main()

{

intpipe_fd[2];

pid_tpid;

charr_buf[100];

charw_buf[4];

char*p_wbuf;

intr_num;

intcmd;

memset(r_buf,0,sizeof(r_buf));

memset(w_buf,0,sizeof(r_buf));

p_wbuf=w_buf;

if(pipe(pipe_fd)<0)

{

printf("pipecreateerror\n");

return-1;

}

if((pid=fork())==0)

{

printf("\n");

close(pipe_fd[1]);

sleep(3);//确保父进程关闭写端

r_num=read(pipe_fd[0],r_buf,100);

printf("readnumis%dthedatareadfromthepipeis%d\n",r_num,atoi(r_buf));

close(pipe_fd[0]);

exit();

}

elseif(pid>0)

{

close(pipe_fd[0]);//read

strcpy(w_buf,"111");

if(write(pipe_fd[1],w_buf,4)!

=-1)

printf("parentwriteover\n");

close(pipe_fd[1]);//write

printf("parentclosefd[1]over\n");

sleep(10);

}

}

/**************************************************

*程序输出结果:

*parentwriteover

*parentclosefd[1]over

*readnumis4thedatareadfromthepipeis111

*附加结论:

*管道写端关闭后,写入的数据将一直存在,直到读出为止.

****************************************************/

向管道中写入数据:

∙向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。

如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。

注:

只有在管道的读端存在时,向管道中写入数据才有意义。

否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)。

对管道的写规则的验证1:

写端对读端存在的依赖性

#include

#include

main()

{

intpipe_fd[2];

pid_tpid;

charr_buf[4];

char*w_buf;

intwritenum;

intcmd;

memset(r_buf,0,sizeof(r_buf));

if(pipe(pipe_fd)<0)

{

printf("pipecreateerror\n");

return-1;

}

if((pid=fork())==0)

{

close(pipe_fd[0]);

close(pipe_fd[1]);

sleep(10);

exit();

}

elseif(pid>0)

{

sleep

(1);//等待子进程完成关闭读端的操作

close(pipe_fd[0]);//write

w_buf="111";

if((writenum=write(pipe_fd[1],w_buf,4))==-1)

printf("writetopipeerror\n");

else

printf("thebyteswritetopipeis%d\n",writenum);

close(pipe_fd[1]);

}

}

则输出结果为:

Brokenpipe,原因就是该管道以及它的所有fork()产物的读端都已经被关闭。

如果在父进程中保留读端,即在写完pipe后,再关闭父进程的读端,也会正常写入pipe,读者可自己验证一下该结论。

因此,在向管道写入数据时,至少应该存在某一个进程,其中管道读端没有被关闭,否则就会出现上述错误(管道断裂,进程收到了SIGPIPE信号,默认动作是进程终止)

对管道的写规则的验证2:

linux不保证写管道的原子性验证

#include

#include

#include

main(intargc,char**argv)

{

intpipe_fd[2];

pid_tpid;

charr_buf[4096];

charw_buf[4096*2];

intwritenum;

intrnum;

memset(r_buf,0,sizeof(r_buf));

if(pipe(pipe_fd)<0)

{

printf("pipecreateerror\n");

return-1;

}

if((pid=fork())==0)

{

close(pipe_fd[1]);

while

(1)

{

sleep

(1);

rnum=read(pipe_fd[0],r_buf,1000);

printf("child:

readnumis%d\n",rnum);

}

close(pipe_fd[0]);

exit();

}

elseif(pid>0)

{

close(pipe_fd[0]);//write

memset(r_buf,0,sizeof(r_buf));

if((writenum=write(pipe_fd[1],w_buf,1024))==-1)

printf("writetopipeerror\n");

else

printf("thebyteswritetopipeis%d\n",writenum);

writenum=write(pipe_fd[1],w_buf,4096);

close(pipe_fd[1]);

}

}

输出结果:

thebyteswritetopipe1000

thebyteswritetopipe1000//注意,此行输出说明了写入的非原子性

thebyteswritetopipe1000

thebyteswritetopipe1000

thebyteswritetopipe1000

thebyteswritetopipe120//注意,此行输出说明了写入的非原子性

thebyteswritetopipe0

thebyteswritetopipe0

......

结论:

写入数目小于4096时写入是非原子的!

如果把父进程中的两次写入字节数都改为5000,则很容易得出下面结论:

写入管道的数据量大于4096字节时,缓冲区的空闲空间将被写入数据(补齐),直到写完所有数据为止,如果没有进程读数据,则一直阻塞。

1.1.4管道应用实例

实例一:

用于shell

管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。

比如,当在某个shell程序(Bourneshell或Cshell等)键入who│wc-l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道。

考虑下面的命令行:

$kill-l运行结果见附一。

$kill-l|grepSIGRTMIN运行结果如下:

30)SIGPWR31)SIGSYS32)SIGRTMIN33)SIGRTMIN+1

34)SIGRTMIN+235)SIGRTMIN+336)SIGRTMIN+437)SIGRTMIN+5

38)SIGRTMIN+639)SIGRTMIN+740)SIGRTMIN+841)SIGRTMIN+9

42)SIGRTMIN+1043)SIGRTMIN+1144)SIGRTMIN+1245)SIGRTMIN+13

46)SIGRTMIN+1447)SIGRTMIN+1548)SIGRTMAX-1549)SIGRTMAX-14

实例二:

用于具有亲缘关系的进程间通信

下面例子给出了管道的具体应用,父进程通过管道发送一些命令给子进程,子进程解析命令,并根据命令作相应处理。

#include

#include

main()

{

intpipe_fd[2];

pid_tpid;

charr_buf[4];

char**w_buf[256];

intchildexit=0;

inti;

intcmd;

memset(r_buf,0,sizeof(r_buf));

if(pipe(pipe_fd)<0)

{

printf("pipecreateerror\n");

return-1;

}

if((pid=fork())==0)

//子进程:

解析从管道中获取的命令,并作相应的处理

{

printf("\n");

close(pipe_fd[1]);

sleep

(2);

while(!

childexit)

{

read(pipe_fd[0],r_buf,4);

cmd=atoi(r_buf);

if(cmd==0)

{

printf("child:

receivecommandfromparentover\nnowchildprocessexit\n");

childexit=1;

}

elseif(handle_cmd(cmd)!

=0)

return;

sleep

(1);

}

close(pipe_fd[0]);

exit();

}

elseif(pid>0)

//parent:

sendcommandstochild

{

close(pipe_fd[0]);

w_buf[0]="003";

w_buf[1]="005";

w_buf[2]="777";

w_buf[3]="000";

for(i=0;i<4;i++)

write(pipe_fd[1],w_buf[i],4);

close(pipe_fd[1]);

}

}

//下面是子进程的命令处理函数(特定于应用):

inthandle_cmd(intcmd)

{

if((cmd<0)||(cmd>256))

//supposechildonlysupport256commands

{

printf("child:

invalidcommand\n");

return-1;

}

printf("child:

thecmdfromparentis%d\n",cmd);

return0;

}

1.1.5管道的局限性

管道的主要局限性正体现在它的特点上:

∙只支持单向数据流;

∙只能用于具有亲缘关系的进程之间;

∙没有名字;

∙管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);

∙管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等;

1.2.有名管道概述及相关API应用

1.2.1有名管道相关的关键概念

管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(namedpipe或FIFO)提出后,该限制得到了克服。

FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。

这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。

值得注意的是,FIFO严格遵循先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。

它们不支持诸如lseek()等文件定位操作。

1.2.2有名管道的创建

#include

#include

intmkfifo(constchar*pathname,mode_tmode)

该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。

第二个参数与打开普通文件的open()函数中的mode参数相同。

如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。

一般文件的I/O函数都可以用于FIFO,如close、read、write等等。

1.2.3有名管道的打开规则

有名管道比管道多了一个打开操作:

open。

FIFO的打开规则:

如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2