下载

2下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 1492408

1492408.doc

1492408

mingche_god
2018-09-09 0人阅读 举报 0 0 暂无简介

简介:本文档为《1492408doc》,可适用于IT/计算机领域

║嵌入式应用程序开发详解第章进程间通信║第章进程间通信本章目标  在上一章中读者已经学会了如何创建进程以及如何对进程进行基本的控制而这些都只是停留在父子进程之间的控制本章将要学习不同的进程间进行通信的方法通过本章的学习读者将会掌握如下内容。掌握Linux中管道的基本概念掌握Linux中管道的创建掌握Linux中管道的读写掌握Linux中有名管道的创建读写方法掌握Linux中消息队列的处理掌握Linux共享内存的处理Linux下进程间通信概述在上一章中读者已经知道了进程是一个程序的一次执行的过程。这里所说的进程一般是指运行在用户态的进程而由于处于用户态的不同进程之间是彼此隔离的就像处于不同城市的人们它们必须通过某种方式来提供通信例如人们现在广泛使用的手机等方式。本章就是讲述如何建立这些不同的通话方式就像人们有多种通信方式一样。Linux下的进程通信手段基本上是从UNIX平台上的进程通信手段继承而来的。而对UNIX发展做出重大贡献的两大主力ATT的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间的通信方面的侧重点有所不同。前者是对UNIX早期的进程间通信手段进行了系统的改进和扩充形成了“systemVIPC”其通信进程主要局限在单个计算机内后者则跳过了该限制形成了基于套接口(socket)的进程间通信机制。而Linux则把两者的优势都继承了下来如图所示。(UNIX进程间通信(IPC)方式包括管道、FIFO、信号。SHAPE*MERGEFORMAT图进程间通信发展历程(SystemV进程间通信(IPC)包括SystemV消息队列、SystemV信号灯、SystemV共享内存区。(Posix进程间通信(IPC)包括Posix消息队列、Posix信号灯、Posix共享内存区。现在在Linux中使用较多的进程间通信方式主要有以下几种。()管道(Pipe)及有名管道(namedpipe):管道可用于具有亲缘关系进程间的通信有名管道除具有管道所具有的功能外它还允许无亲缘关系进程间的通信。()信号(Signal):信号是在软件层次上对中断机制的一种模拟它是比较复杂的通信方式用于通知接受进程有某事件发生一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。()消息队列:消息队列是消息的链接表包括Posix消息队列systemV消息队列。它克服了前两种通cherrootSusbstoragerootSscsiehrootSpopenrootSshcpsefrootRps–efFIFO.有名管道说明前面介绍的管道是无名管道它只能用于具有亲缘关系的进程之间这就大大地限制了管道的使用。有名管道的出现突破了这种限制它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出并且在文件系统中是可见的。在建立了管道之后两个进程就可以把它当作普通文件一样进行读写操作使用非常方便。不过值得注意的是FIFO是严格地遵循先进先出规则的对管道及FIFO的读总是从开始处返回数据对它们的写则把数据添加到末尾它们不支持如lseek()等文件定位操作。有名管道的创建可以使用函数mkfifo()该函数类似文件中的open()操作可以指定管道的路径和打开的模式。小知识用户还可以在命令行使用“mknod管道名p”来创建有名管道。在创建管道成功之后就可以使用open、read、write这些函数了。与普通文件的开发设置一样对于为读而打开的管道可在open中设置ORDONLY对于为写而打开的管道可在open中设置OWRONLY在这里与普通文件不同的是阻塞问题。由于普通文件的读写时不会出现阻塞问题而在管道的读写中却有阻塞的可能这里的非阻塞标志可以在open函数中设定为ONONBLOCK。下面分别对阻塞打开和非阻塞打开的读写进行一定的讨论。对于读进程(若该管道是阻塞打开且当前FIFO内没有数据则对读进程而言将一直阻塞直到有数据写入。(若该管道是非阻塞打开则不论FIFO内是否有数据读进程都会立即执行读操作。对于写进程(若该管道是阻塞打开则写进程而言将一直阻塞直到有读进程读出数据。(若该管道是非阻塞打开则当前FIFO内没有读操作写进程都会立即执行读操作。.mkfifo函数格式表列出了mkfifo函数的语法要点。表mkfifo函数语法要点所需头文件#include<systypesh>#include<sysstateh>函数原型intmkfifo(constchar*filename,modetmode)函数传入值filename:要创建的管道续表函数传入值mode:ORDONLY:读管道OWRONLY:写管道ORDWR:读写管道ONONBLOCK:非阻塞OCREAT:如果该文件不存在那么就创建一个新的文件并用第三的参数为其设置权限OEXCL:如果使用OCREAT时文件存在那么可返回错误消息。这一参数可测试文件是否存在函数返回值成功:出错:(表再对FIFO相关的出错信息做一归纳以方便用户差错。表FIFO相关的出错信息EACCESS参数filename所指定的目录路径无可执行的权限EEXIST参数filename所指定的文件已存在ENAMETOOLONG参数filename的路径名称太长ENOENT参数filename包含的目录不存在ENOSPC文件系统的剩余空间不足ENOTDIR参数filename路径中的目录存在但却非真正的目录EROFS参数filename指定的文件存在于只读文件系统内.使用实例下面的实例包含了两个程序一个用于读管道另一个用于写管道。其中在写管道的程序里创建管道并且作为main函数里的参数由用户输入要写入的内容。读管道读出了用户写入管道的内容这两个函数用的是非阻塞读写管道。*fifowritec*#include<systypesh>#include<sysstath>#include<errnoh>#include<fcntlh>#include<stdioh>#include<stdlibh>#include<stringh>#defineFIFO"tmpmyfifo"main(intargc,char**argv)*参数为即将写入的字节数*{intfdcharwbufintnwriteif(fd==()if(errno==ENXIO)printf("openerrornoreadingprocessn")*打开FIFO管道并设置非阻塞标志*fd=open(FIFOSERVER,OWRONLY|ONONBLOCK,)if(argc==)printf("Pleasesendsomethingn")strcpy(wbuf,argv)*向管道中写入字符串*if((nwrite=write(fd,wbuf,))==(){if(errno==EAGAIN)printf("TheFIFOhasnotbeenreadyetPleasetrylatern")}elseprintf("writestotheFIFOn",wbuf)}*fiflreadc*#include<systypesh>#include<sysstath>#include<errnoh>#include<fcntlh>#include<stdioh>#include<stdlibh>#include<stringh>#defineFIFO"tmpmyfifo"main(intargc,char**argv){charbufrintfdintnread*创建有名管道并设置相应的权限*if((mkfifo(FIFO,OCREAT|OEXCL)<)(errno!=EEXIST))printf("cannotcreatefifoservern")printf("Preparingforreadingbytesn")memset(bufr,,sizeof(bufr))*打开有名管道并设置非阻塞标志*fd=open(FIFO,ORDONLY|ONONBLOCK,)if(fd==(){perror("open")exit()}while(){memset(bufr,,sizeof(bufr))if((nread=read(fd,bufr,))==(){if(errno==EAGAIN)printf("nodatayetn")}printf("readsfromFIFOn",bufr)sleep()}pause()unlink(FIFO)}为了能够较好地观察运行结果需要把这两个程序分别在两个终端里运行在这里首先启动读管道程序。由于这是非阻塞管道因此在建立管道之后程序就开始循环从管道里读出内容。在启动了写管道程序后读进程能够从管道里读出用户的输入内容程序运行结果如下所示。终端一:rootlocalhostFIFO#readPreparingforreadingbytes…readfromFIFOreadfromFIFOreadfromFIFOreadfromFIFOreadfromFIFOreadhellofromFIFOreadfromFIFOreadfromFIFOreadFIFOfromFIFOreadfromFIFOreadfromFIFO…终端二:rootlocalhost#writehellowritehellototheFIFOrootlocalhost#readFIFOwriteFIFOtotheFIFO信号通信信号概述信号是UNIX中所使用的进程通信的一种最古老的方法。它是在软件层次上对中断机制的一种模拟是一种异步通信方式。信号可以直接进行用户空间进程和内核进程之间的交互内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程而无需知道该进程的状态。如果该进程当前并未处于执行态则该信号就由内核保存起来直到该进程恢复执行再传递给它为止如果一个信号被进程设置为阻塞则该信号的传递被延迟直到其阻塞被取消时才被传递给进程。细心的读者是否还记得在第章kill命令中曾讲解到“−l”选项这个选项可以列出该系统所支持的所有信号列表。在笔者的系统中信号值在之前的则有不同的名称而信号值在以后的都是用“SIGRTMIN”或“SIGRTMAX”开头的这就是两类典型的信号。前者是从UNIX系统中继承下来的信号为不可靠信号(也称为非实时信号)后者是为了解决前面“不可靠信号”的问题而进行了更改和扩充的信号称为“可靠信号”(也称为实时信号)。那么为什么之前的信号不可靠呢?这里首先要介绍一下信号的生命周期。一个完整的信号生命周期可以分为个重要阶段这个阶段由个重要事件来刻画的:信号产生、信号在进程中注册、信号在进程中注销、执行信号处理函数如图所示。相邻两个事件的时间间隔构成信号生命周期的一个阶段。要注意这里的信号处理有多种方式一般是由内核完成的当然也可以由用户进程来完成故在此没有明确画出。一个不可靠信号的处理过程是这样的:如果发现该信号已经在进程中注册那么就忽略该信号。因此若前一个信号还未注销又产生了相同的信号就会产生信号丢失。而当可靠信号发送给一个进程时不管该信号是否已经在进程中注册都会被再注册一次因此信号就不会丢失。所有可靠信号都支持排队而不可靠信号则都不支持排队。图信号生命周期注意这里信号的产生、注册、注销等是指信号的内部实现机制而不是信号的函数实现。因此信号注册与否与本节后面讲到的发送信号函数(如kill()等)以及信号安装函数(如signal()等)无关只与信号值有关。用户进程对信号的响应可以有种方式。(忽略信号即对信号不做任何处理但是有两个信号不能忽略即SIGKILL及SIGSTOP。(捕捉信号定义信号处理函数当信号发生时执行相应的处理函数。(执行缺省操作Linux对每种信号都规定了默认操作。Linux中的大多数信号是提供给内核的表列出了Linux中最为常见信号的含义及其默认操作。表常见信号的含义及其默认操作信号名含义默认操作SIGHUP该信号在用户终端连接(正常或非正常)结束时发出通常是在终端的控制进程结束时通知同一会话内的各个作业与控制终端不再关联终止SIGINT该信号在用户键入INTR字符(通常是CtrlC)时发出终端驱动程序发送此信号并送到前台进程中的每一个进程终止SIGQUIT该信号和SIGINT类似但由QUIT字符(通常是Ctrl)来控制终止SIGILL该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误或者试图执行数据段、堆栈溢出时)发出终止SIGFPE该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误还包括溢出及除数为等其他所有的算术的错误终止SIGKILL该信号用来立即结束程序的运行并且不能被阻塞、处理和忽略终止SIGALRM该信号当一个定时器到时的时候发出终止SIGSTOP该信号用于暂停一个进程且不能被阻塞、处理或忽略暂停进程续表信号名含义默认操作SIGTSTP该信号用于交互停止进程用户可键入SUSP字符时(通常是CtrlZ)发出这个信号停止进程SIGCHLD子进程改变状态时父进程会收到这个信号忽略SIGABORT信号发送与捕捉发送信号的函数主要有kill()、raise()、alarm()以及pause()下面就依次对其进行介绍。.kill()和raise()()函数说明kill函数同读者熟知的kill系统命令一样可以发送信号给进程或进程组(实际上kill系统命令只是kill函数的一个用户接口)。这里要注意的是它不仅可以中止进程(实际上发出SIGKILL信号)也可以向进程发送其他信号。与kill函数所不同的是raise函数允许进程向自身发送信号。()函数格式表列出了kill函数的语法要点。表kill函数语法要点所需头文件#include<signalh>#include<systypesh>函数原型intkill(pidtpid,intsig)函数传入值pid:正数:要发送信号的进程号:信号被发送到所有和pid进程在同一个进程组的进程(:信号发给所有的进程表中的进程(除了进程号最大的进程外)sig:信号函数返回值成功:出错:(表列出了raise函数的语法要点。表raise函数语法要点所需头文件#include<signalh>#include<systypesh>函数原型intraise(intsig)函数传入值sig:信号函数返回值成功:出错:(()函数实例下面这个示例首先使用fork创建了一个子进程接着为了保证子进程不在父进程调用kill之前退出在子进程中使用raise函数向子进程发送SIGSTOP信号使子进程暂停。接下来再在父进程中调用kill向子进程发送信号在该示例中使用的是SIGKILL读者可以使用其他信号进行练习。*killc*#include<stdioh>#include<stdlibh>#include<signalh>#include<systypesh>#include<syswaith>intmain(){pidtpidintret*创建一子进程*if((pid=fork())<){perror("fork")exit()}if(pid==){*在子进程中使用raise函数发出SIGSTOP信号*raise(SIGSTOP)exit()}else{*在父进程中收集子进程发出的信号并调用kill函数进行相应的操作*printf("pid=dn",pid)if((waitpid(pid,,WNOHANG))==){if((ret=kill(pid,SIGKILL))==)printf("killdn",pid)else{perror("kill")}}}}该程序运行结果如下所示:root(none)tmp#killpid=kill.alarm()和pause()()函数说明alarm也称为闹钟函数它可以在进程中设置一个定时器当定时器指定的时间到时它就向进程发送SIGALARM信号。要注意的是一个进程只能有一个闹钟时间如果在调用alarm之前已设置过闹钟时间则任何以前的闹钟时间都被新值所代替。pause函数是用于将调用进程挂起直至捕捉到信号为止。这个函数很常用通常可以用于判断信号是否已到。()函数格式表列出了alarm函数的语法要点。表alarm函数语法要点所需头文件#include<unistdh>函数原型unsignedintalarm(unsignedintseconds)函数传入值seconds:指定秒数函数返回值成功:如果调用此alarm()前进程中已经设置了闹钟时间则返回上一个闹钟时间的剩余时间否则返回出错:(表列出了pause函数的语法要点。表pause函数语法要点所需头文件#include<unistdh>函数原型intpause(void)函数返回值(并且把error值设为EINTR()函数实例该实例实际上已完成了一个简单的sleep函数的功能由于SIGALARM默认的系统动作为终止该进程因此在程序调用pause之后程序就终止了。如下所示:*alarmc*#include<unistdh>#include<stdioh>#include<stdlibh>intmain(){intret*调用alarm定时器函数*ret=alarm()pause()printf("Ihavebeenwakenupn",ret)}root(none)tmp#alarmAlarmclock想一想用这种形式实现的sleep功能有什么问题?信号的处理在了解了信号的产生与捕获之后接下来就要对信号进行具体的操作了。从前面的信号概述中读者也可以看到特定的信号是与一定的进程相联系的。也就是说一个进程可以决定在该进程中需要对哪些信号进行什么样的处理。例如一个进程可以选择忽略某些信号而只处理其他一些信号另外一个进程还可以选择如何处理信号。总之这些都是与特定的进程相联系的。因此首先就要建立其信号与进程之间的对应关系这就是信号的处理。注意请读者区分信号的注册与信号的处理之间的差别前者信号是主动方而后者进程是主动方。信号的注册是在进程选择了特定信号处理之后特定信号的主动行为。信号处理的主要方法有两种一种是使用简单的signal函数另一种是使用信号集函数组。下面分别介绍这两种处理方式。.signal()()函数说明使用signal函数处理时只需把要处理的信号和处理函数列出即可。它主要是用于前种非实时信号的处理不支持信号传递信息但是由于使用简单、易于理解因此也受到很多程序员的欢迎。()函数格式Signal函数的语法要点如表所示。表signal函数语法要点所需头文件#include<signalh>函数原型void(*signal(intsignum,void(*handler)(int)))(int)函数传入值signum:指定信号handler:SIGIGN:忽略该信号SIGDFL:采用系统默认方式处理信号自定义的信号处理函数指针续表函数返回值成功:以前的信号处理配置出错:(这里需要对这个函数原型进行说明。这个函数原型非常复杂。可先用如下的typedef进行替换说明:typedefvoidsign(int)sign*signal(int,handler*)可见首先该函数原型整体指向一个无返回值带一个整型参数的函数指针也就是信号的原始配置函数。接着该原型又带有两个参数其中的第二个参数可以是用户自定义的信号处理函数的函数指针。()使用实例该示例表明了如何使用signal函数捕捉相应信号并做出给定的处理。这里myfunc就是信号处理的函数指针。读者还可以将其改为SIGIGN或SIGDFL查看运行结果。*mysignalc*#include<signalh>#include<stdioh>#include<stdlibh>*自定义信号处理函数*voidmyfunc(intsignno){if(signno==SIGINT)printf("IhavegetSIGINTn")elseif(signno==SIGQUIT)printf("IhavegetSIGQUITn")}intmain(){printf("WaitingforsignalSIGINTorSIGQUITn")*发出相应的信号并跳转到信号处理函数处*signal(SIGINT,myfunc)signal(SIGQUIT,myfunc)pause()exit()}rootwwwyul#mysignalWaitingforsignalSIGINTorSIGQUITIhavegetSIGINTrootwwwyul#mysignalWaitingforsignalSIGINTorSIGQUITIhavegetSIGQUIT.信号集函数组()函数说明使用信号集函数组处理信号时涉及一系列的函数这些函数按照调用的先后次序可分为以下几大功能模块:创建信号集合、登记信号处理器以及检测信号。其中创建信号集合主要用于创建用户感兴趣的信号其函数包括以下几个。(sigemptyset:初始化信号集合为空。(sigfillset:初始化信号集合为所有信号的集合。(sigaddset:将指定信号加入到信号集合中去。(sigdelset:将指定信号从信号集中删去。(sigismember:查询指定信号是否在信号集合之中。登记信号处理器主要用于决定进程如何处理信号。这里要注意的是信号集里的信号并不是真正可以处理的信号只有当信号的状态处于非阻塞状态时才真正起作用。因此首先就要判断出当前阻塞能不能传递给该信号的信号集。这里首先使用sigprocmask函数判断检测或更改信号屏蔽字然后使用sigaction函数用于改变进程接收到特定信号之后的行为。检测信号是信号处理的后续步骤但不是必须的。由于内核可以在任何时刻向某一进程发出信号因此若该进程必须保持非中断状态且希望将某些信号阻塞这些信号就处于“未决”状态(也就是进程不清楚它的存在)。所以在希望保持非中断进程完成相应的任务之后就应该将这些信号解除阻塞。Sigpending函数就允许进程检测“未决”信号并进一步决定对它们作何处理。()函数格式首先介绍创建信号集合的函数格式表列举了这一组函数的语法要点。表创建信号集合函数语法要点所需头文件#include<signalh>函数原型intsigemptyset(sigsett*set)intsigfillset(sigsett*set)intsigaddset(sigsett*set,intsignum)intsigdelset(sigsett*set,intsignum)intsigismember(sigsett*set,intsignum)函数传入值set:信号集signum:指定信号值函数返回值成功:(sigismember成功返回失败返回)出错:(表列举了sigprocmask的语法要点。表sigprocmask函数语法要点所需头文件#include<signalh>函数原型intsigprocmask(inthow,constsigsett*set,sigsett*oset)函数传入值how:决定函数的操作方式SIGBLOCK:增加一个信号集合到当前进程的阻塞集合之中SIGUNBLOCK:从当前的阻塞集合之中删除一个信号集合SIGSETMASK:将当前的信号集合设置为信号阻塞集合set:指定信号集oset:信号屏蔽字函数返回值成功:(sigismember成功返回失败返回)出错:(此处若set是一个非空指针则参数how表示函数的操作方式若how为空则表示忽略此操作。表列举了sigaction的语法要点。表sigaction函数语法要点所需头文件#include<signalh>函数原型intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact)函数传入值signum:信号的值可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号act:指向结构sigaction的一个实例的指针指定对特定信号的处理oldact:保存原来对相应信号的处理函数返回值成功:出错:(这里要说明的是sigaction函数中第个和第个参数用到的sigaction结构。这是一个看似非常复杂的结构希望读者能够慢慢阅读此段内容。首先给出了sigaction的定义如下所示:structsigaction{void(*sahandler)(intsigno)sigsettsamaskintsaflagsvoid(*sarestore)(void)}sahandler是一个函数指针指定信号关联函数这里除可以是用户自定义的处理函数外还可以为SIGDFL(采用缺省的处理方式)或SIGIGN(忽略信号)。它的处理函数只有一个参数即信号值。samask是一个信号集它可以指定在信号处理程序执行过程中哪些信号应当被阻塞在调用信号捕获函数之前该信号集要加入到信号的信号屏蔽字中。saflags中包含了许多标志位是对信号进行处理的各个选择项。它的常见可选值如下表所示。表常见信号的含义及其默认操作选项含义SANODEFERSANOMASK当捕捉到此信号时在执行其信号捕捉函数时系统不会自动阻塞此信号SANOCLDSTOP进程忽略子进程产生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTTOU信号SARESTART可让重启的系统调用重新起作用SAONESHOTSARESETHAND自定义信号只执行一次在执行完毕后恢复信号的系统默认动作最后表列举了sigpending函数的语法要点。表sigpending函数语法要点所需头文件#include<signalh>函数原型intsigpending(sigsett*set)函数传入值set:要检测的信号集函数返回值成功:出错:(总之在处理信号时一般遵循如图所示的操作流程。图信号操作一般处理流程()使用实例该实例首先把SIGQUIT、SIGINT两个信号加入信号集然后将该信号集设为阻塞状态并在该状态下使程序暂停秒。接下来再将信号集设置为非阻塞状态再对这两个信号分别操作其中SIGQUIT执行默认操作而SIGINT执行用户自定义函数的操作。源代码如下所示:*sigactionc*#include<systypesh>#include<unistdh>#include<signalh>#include<stdioh>#include<stdlibh>*自定义的信号处理函数*voidmyfunc(intsignum){printf("Ifyouwanttoquit,pleasetrySIGQUITn")}intmain(){sigsettset,pendsetstructsigactionaction,action*初始化信号集为空*if(sigemptyset(set)<)perror("sigemptyset")*将相应的信号加入信号集*if(sigaddset(set,SIGQUIT)<)perror("sigaddset")if(sigaddset(set,SIGINT)<)perror("sigaddset")*设置信号集屏蔽字*if(sigprocmask(SIGBLOCK,set,)<)perror("sigprocmask")else{printf("blockedn")sleep()}if(sigprocmask(SIGUNBLOCK,set,)<)perror("sigprocmask")elseprintf("unblockn")*对相应的信号进行循环处理*while(){if(sigismember(set,SIGINT)){sigemptyset(actionsamask)actionsahandler=myfuncsigaction(SIGINT,action,)}elseif(sigismember(set,SIGQUIT)){sigemptyset(actionsamask)actionsahandler=SIGDFLsigaction(SIGTERM,action,)}}}该程序的运行结果如下所示可以看见在信号处于阻塞状态时所发出的信号对进程不起作用。读者需等待秒在信号接触阻塞状态之后用户发出的信号才能正常运行。这里SIGINT已按照用户自定义的函数运行。root(none)tmp#sigactionblockedunblockIfyouwanttoquit,pleasetrySIGQUITQuit共享内存共享内存概述可以说共享内存是一种最为高效的进程间通信方式。因为进程可以直接读写内存不需要任何数据的拷贝。为了在多个进程间交换信息内核专门留出了一块内存区。这段内存区可以由需要访问的进程将其映射到自己的私有地址空间。因此进程就可以直接读写这一内存区而不需要进行数据的拷贝从而大大提高了效率。当然由于多个进程共享一段内存因此也需要依靠某种同步机制如互斥锁和信号量等。其原理示意图如图所示。图共享内存原理示意图共享内存实现.函数说明共享内存的实现分为两个步骤第一步是创建共享内存这里用到的函数是shmget也就是从内存中获得一段共享内存区域。第二步映射共享内存也就是把这段创建的共享内存映射到具体的进程空间去这里使用的函数是shmat。到这里就可以使用这段共享内存了也就是可以使用不带缓冲的IO读写命令对其进行操作。除此之外当然还有撤销映射的操作其函数为shmdt。这里就主要介绍这个函数。.函数格式表列举了shmget函数的语法要点。表shmget函数语法要点所需头文件#include<systypesh>#include<sysipch>#include<sysshmh>函数原型intshmget(keytkey,intsize,intshmflg)函数传入值Key:IPCPRIVATESize:共享内存区大小Shmflg:同open函数的权限位也可以用八进制表示法函数返回值成功:共享内存段标识符出错:(表列举了shmat函数的语法要点。表shmat函数语法要点所需头文件#include<systypesh>#include<sysipch>#include<sysshmh>函数原型char*shmat(intshmid,constvoid*shmaddr,intshmflg)函数传入值shmid:要映射的共享内存区标识符shmaddr:将共享内存映射到指定位置(若为则表示把该段共享内存映射到调用进程的地址空间)ShmflgSHMRDONLY:共享内存只读默认:共享内存可读写函数返回值成功:被映射的段地址出错:(表列举了shmdt函数的语法要点。表shmdt函数语法要点所需头文件#include<systypesh>#include<sysipch>#include<sysshmh>函数原型intshmdt(constvoid*shmaddr)函数传入值Shmaddr:被映射的共享内存段地址函数返回值成功:出错:(.使用实例该实例说明了如何使用基本的共享内存函数首先是创建一个共享内存区之后将其映射到本进程中最后再解除这种映射关系。这里要介绍的一个命令是ipcs这是用于报告进程间通信机制状态的命令。它可以查看共享内存、消息队列等各种进程间通信机制的情况这里使用了system函数用于调用shell命令“ipcs”。程序源代码如下所示:*shmaddc*#include<systypesh>#include<sysipch>#include<sysshmh>#include<stdioh>#include<stdlibh>#defineBUFSZintmain(){intshmidchar*shmadd*创建共享内存*if((shmid=shmget(IPCPRIVATE,BUFSZ,))<){perror("shmget")exit()}elseprintf("createdsharedmemory:dn",shmid)system("ipcsm")*映射共享内存*if((shmadd=shmat(shmid,,))<(char*)){perror("shmat")exit()}elseprintf("attachedsharedmemoryn")*显示系统内存情况*system("ipcsm")*删除共享内存*if((shmdt(shmadd))<){perror("shmdt")exit()}elseprintf("deletedsharedmemoryn")system("ipcsm")exit()}下面是运行结果。从该结果可以看出nattch的值随着共享内存状态的变化而变化共享内存的值根据不同的系统会有所不同。createdsharedmemory:SharedMemorySegmentskeyshmidownerpermsbytesnattchstatusxrootattachedsharedmemorySharedMemorySegmentskeyshmidownerpermsbytesnattchstatusxrootdeletedsharedmemorySharedMemorySegmentskeyshmidownerpermsbytesnattchstatusxroot消息队列消息队列概述顾名思义消息队列就是一个消息的列表。用户可以从消息队列种添加消息、读取消息等。从这点上看消息队列具有一定的FIFO的特性但是它可以实现消息的随机查询比FIFO具有更大的优势。同时这些消息又是存在于内核中的由“队列ID”来标识。消息队列实现.函数说明消息队列的实现包括创建或打开消息队列、添加消息、读取消息和控制消息队列这四种操作。其中创建或打开消息队列使用的函数是msgget这里创建的消息队列的数量会受到系统消息队列数量的限制添加消息使用的函数是msgsnd函数它把消息添加到已打开的消息队列末尾读取消息使用的函数是msgrcv它把消息从消息队列中取走与FIFO不同的是这里可以指定取走某一条消息最后控制消息队列使用的函数是msgctl它可以完成多项功能。.函数格式表列举了msgget函数的语法要点。表msgget函数语法要点所需头文件#include<systypesh>#include<sysipch>#include<sysshmh>函数原型intmsgget(keytkey,intflag)函数传入值Key:返回新的或已有队列的队列ID,IPCPRIVATEFlag:函数返回值成功:消息队列ID出错:(表列举了msgsnd函数的语法要点。表msgsnd函数语法要点所需头文件#include<systypesh>#include<sysipch>#include<sysshmh>函数原型intmsgsnd(intmsqid,constvoid*prt,sizetsize,intflag)函数传入值msqid:消息队列的队列IDprt:指向消息结构的指针。该消息结构msgbuf为:structmsgbuf{longmtype消息类型charmtext消息正文}size:消息的字节数不要以结尾flag:IPCNOWAIT若消息并没有立即发送而调用进程会立即返回:msgsnd调用阻塞直到条件满足为止函数返回值成功:出错:(表列举了msgrcv函数的语法要点。表msgrcv函数语法要点所需头文件#include<systypesh>#include<sysipch>#include<sysshmh>函数原型intmsgrcv(intmsgid,structmsgbuf*msgp,intsize,longmsgtype,intflag)函数传入值msqid:消息队列的队列IDmsgp:消息缓冲区size:消息的字节数不要以结尾Msgtype::接收消息队列中第一个消息大于:接收消息队列中第一个类型为msgtyp的消息小于:接收消息队列中第一个类型值不小于msgtyp绝对值且类型值又最小的消息flag:MSGNOERROR:若返回的消息比size字节多则消息就会截短到size字节且不通知消息发送进程IPCNOWAIT若消息并没有立即发送而调用进程会立即返回:msgsnd调用阻塞直到条件满足为止函数返回值成功:出错:(表列举了msgrcv函数的语法要点。表msgrcv函数语法要点所需头文件#include<systypesh>#include<sysipch>#include<sysshmh>函数原型intmsgrcv(intmsgid,structmsgbuf*msgp,intsize,longmsgtype,intflag)函数传入值msqid:消息队列的队列IDmsgp:消息缓冲区size:消息的字节数不要以结尾Msgtype::接收消息队列中第一个消息大于:接收消息队列中第一个类型为msgtyp的消息小于:接收消息队列中第一个类型值不小于msgtyp绝对值且类型值又最小的消息flag:MSGNOERROR:若返回的消息比size字节多则消息就会截短到size字节且不通知消息发送进程IPCNOWAIT若消息并没有立即发送而调用进程会立即返回:msgsnd调用阻塞直到条件满足为止函数返回值成功:出错:(表列举了msgctl函数的语法要点。表msgctl函数语法要点所需头文件#include<systypesh>#include<sysipch>#include<sysshmh>函数原型intmsgctl(intmsgqid,intcmd,structmsqidds*buf)函数传入值msqid:消息队列的队列IDcmd:IPCSTAT:读取消息队列的数据结构msqidds并将其存储在buf指定的地址中IPCSET:设置消息队列的数据结构msqidds中的ipcperm元素的值。这个值取自buf参数IPCRMID:从系统内核中移走消息队列Buf:消息队列缓冲区函数返回值成功:出错:(.使用实例这个实例体现了如何使用消息队列进行进程间通信包括消息队列的创建、消息发送与读取、消息队列的撤销等多种操作。注意这里使用了函数fotk它可以根据不同的路径和关键表示产生标准的key。程序源代码如下所示:*msgc*#include<systypesh>#include<sysipch>#include<sysmsgh>#include<stdioh>#include<stdlibh>#include<unistdh>#include<stringh>#defineBUFSZstructmessage{longmsgtypecharmsgtextBUFSZ}intmain(){intqidkeytkeyintlenstructmessagemsg*根据不同的路径和关键表示产生标准的key*if((key=ftok("",'a'))==(){perror("ftok")exit()}*创建消息队列*if((qid=msgget(key,IPCCREAT|))==(){perror("msgget")exit()}printf("openedqueuedn",qid)puts("Pleaseenterthemessagetoqueue:")if((fgets((msg)>msgtext,BUFSZ,stdin))==){puts("nomessage")exit()}msgmsgtype=getpid()len=strlen(msgmsgtext)*添加消息到消息队列*if((msgsnd(qid,msg,len,))<){perror("messageposted")exit()}*读取消息队列*if(msgrcv(qid,msg,BUFSZ,,)<){perror("msgrcv")exit()}printf("messageis:sn",(msg)>msgtext)*从系统内核中移走消息队列。*if((msgctl(qid,IPCRMID,))<){perror("msgctl")exit()}exit()}以下是程序的运行结果。root(none)tmp#msgopenedqueuePleaseenterthemessagetoqueue:hellomessageis:hello实验内容管道通信实验.实验目的通过编写有名管道多路通信实验读者可进一步熟练掌握管道的创建、读写等操作同时也复习使用select函数实现管道的通信。.实验内容在该实验中要求创建两个管道首先读出管道一中的数据再把从管道一中读入的数据写入到管道二中去。这里的select函数采用阻塞形式也就是首先在程序中实现将数据写入管道一并通过select函数实现将管道一的数据读出并写入管道二接着该程序一直等待用户输入管道一的数据并将其即时读出。.实验步骤()画出流程图。该实验流程图如图所示。图实验流程图()编写代码。该实验源代码如下所示。*execc*#include<sysstath>#include<systypesh>#include<errnoh>#include<fcntlh>#include<stdioh>#include<unistdh>#include<stdlibh>#include<timeh>intmain(void){intfdscharbufinti,rc,maxfdfdsetinset,insetstructtimevaltv*创建两个有名管道*if((mkfifo("fifo",OCREAT|OEXCL)<)(errno!=EEXIST))printf("cannotcreatefifoservern")if((mkfifo("fifo",OCREAT|OEXCL)<)(errno!=EEXIST))printf("cannotcreatefifoservern")*打开有名管道*if((fds=open("fifo",ORDWR|ONONBLOCK,))<)perror("openfifo")if((fds=open("fifo",ORDWR|ONONBLOCK,))<)perror("openfifo")if((rc=write(fds,"Hello!n",)))printf("rc=dn",rc)lseek(fds,,SEEKSET)maxfd=fds>fdsfds:fds*初始化描述集并将文件描述符加入到相应的描述集*FDZERO(inset)FDSET(fds,inset)FDZERO(inset)FDSET(fds,inset)*循环测试该文件描述符是否准备就绪并调用select函数*while(FDISSET(fds,inset)||FDISSET(fds,inset)){if(select(maxfd,inset,inset,,)<)perror("select")else{*对相关文件描述符做对应操作*if(FDISSET(fds,inset)){rc=read(fds,buf,)if(rc>){bufrc=''printf("read:sn",buf)}elseperror("read")}if(FDISSET(fds,inset)){rc=write(fds,buf,)if(rc>){bufrc=''printf("rc=d,write:sn",rc,buf)}elseperror("write")}}}exit()}()编译运行该程序。()另开一终端键入“cat>fifo”接着在该管道中键入相关内容并观察实验结果。.实验结果在一终端中键入“hello”、“why”、“sunq”:root(none)tmp#cat>fifohellowhysunq在另一终端中显示如下结果:root(none)tmp#exprc=read:Hello!rc=,write:Hello!read:helloread:whyread:sunq共享内存实验.实验目的通过编写共享内存实验读者就可以进一步了解共享内存的具体步骤同时也进一步加深了对共享内存的理解。由于共享内存涉及同步机制关于这方面的知识本书现在还没有涉及因此现在只在一个进程中对共享内存进行操作。.实验内容该实现要求利用共享内存实现文件的打开、读写操作。.实验步骤()画出流程图该实验流程图如图所示。图实验流程图()编写代码*execc*#include<systypesh>#include<sysipch>#include<sysshmh>#include<stdioh>#include<stdlibh>#include<fcntlh>#defineBUFSZintmain(){intshmid,i,fd,nwrite,nreadchar*shmaddcharbuf*创建共享内存*if((shmid=shmget(IPCPRIVATE,BUFSZ,))<){perror("shmget")exit()}elseprintf("createdsharedmemory:dn",shmid)*映射共享内存*if((shmadd=shmat(shmid,,))<(char*)){perror("shmat")exit()}elseprintf("attachedsharedmemoryn")shmadd="Hello"if((fd=open("share",OCREAT|ORDWR,))<){perror("open")exit()}elseprintf("opensuccess!n")if((nwrite=write(fd,shmadd,))<){perror("write")exit()}elseprintf("writesuccess!n")lseek(fd,,SEEKSET)if((nread=read(fd,buf,))<){perror("read")exit()}elseprintf("readdformfile:sn",nread,buf)*删除共享内存*if((shmdt(shmadd))<){perror("shmdt")exit()}elseprintf("deletedsharedmemoryn")exit()}.实验结果root(none)tmp#shmcreatedsharedmemory:attachedsharedmemoryopenfd=!writesuccess!readformfile:Hellodeletedsharedmemory本章小结本章详细讲解了Linux中进程间通信的几种机制包括管道通信、信号通信、消息队列、共享内存机制等并且讲解了进程间通信的演进。接下来详细对管道通信、信号通信、消息队列和共享内存机制进行了详细的讲解。其中管道通信又分为有名管道和无名管道。信号通信中要着重掌握如何对信号进行适当的处理如采用信号集等方式。消息队列和共享内存也是很好的进程间通信的手段其中共享内存具有很高的效率。本章的实验安排了管道通信实验和共享内存实现具体的实验数据根据系统的不同可能会有所区别希望读者认真完成。思考与练习.通过自定义信号完成进程间的通信。.编写一个简单的管道程序实现文件传输。docfdfd用户进程管道内核读写doc管道内核fdfd子进程fdfd父进程docLinux进程间通信POSIX进程间通信基于SystemV进程间通信基于Socket进程间通信最初UNIX的进程间通信docLinux进程间通信POSIX进程间通信基于SystemV进程间通信基于Socket进程间通信最初Unix的进程间通信doc管道内核fdfd子进程fdfd父进程doc信号注销信号注册用户进程信号处理信号产生内核进程docSigpending…测试信号samasksahandlersigaction…定义信号处理函数sigprocmask…设置信号屏蔽位SigemptysetSigaddset…定义信号集doc进程一共享内存进程二vsd��开始获取共享内存�映射共享内存�打开共享内存�读写文件接触共享内存映射关系结束doc进程grepntp进程psef管道内核vsd��开始创建fifo和fifo�打开fifo和fifo并在fifo中写入“Hello”�分别对fds、fds调用FDZERO、FDSET初始化调用FDISSET测试inset和inset是否有变化调用select读fifo写fifo循环等待结束

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/39

1492408

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利