接收灵敏度,这应该是最基本的概念之一,表征的是接收机能够在不超过一定误码率的情况下识别的最低信号强度。
讲灵敏度的时候我们常常联系到SNR(信噪比,我们一般是讲接收机的解调信噪比),我们把解调信噪比定义为不超过一定误码率的情况下解调器能够解调的信噪比门限(面试的时候经常会有人给你出题,给一串NF、Gain,再告诉你解调门限要你推灵敏度)。那么S和N分别何来?
S即信号Signal,或者称为有用信号;N即噪声Noise,泛指一切不带有有用信息的信号。有用信号一般是通信系统发射机发射出来,噪声的来源则是非常广泛的,最典型的就是那个著名的-174dBm/Hz——自然噪声底,要记住它是一个与通信系统类型无关的量,从某种意义上讲是从热力学推算出来的(所以它跟温度有关);另外要注意的是它实际上是个噪声功率密度(所以有dBm/Hz这个量纲),我们接收多大带宽的信号,就会接受多大带宽的噪声——所以最终的噪声功率是用噪声功率密度对带宽积分得来。
发射功率的重要性,在于发射机的信号需要经过空间的衰落之后才能到达接收机,那么越高的发射功率意味着越远的通信距离。
那么我们的发射信号要不要讲究SNR?譬如说,我们的发射信号SNR很差,那么到达接收机的信号SNR是不是也很差?
这个牵涉到刚才讲过的概念,自然噪声底。我们假设空间的衰落对信号和噪声都是效果相同的(实际上不是,信号能够通编码抵御衰落而噪声不行)而且是如同衰减器一般作用的,那么我们假设空间衰落-200dB,发射信号带宽1Hz,功率50dBm,信噪比50dB,接收机收到信号的SNR是多少?
接收机收到信号的功率是50-200=-150Bm(带宽1Hz),而发射机的噪声50-50=0dBm通过空间衰落,到达接收机的功率是0-200=-200dBm(带宽1Hz)?这时候这部分噪声早已被“淹没”在-174dBm/Hz的自然噪声底之下了,此时我们计算接收机入口的噪声,只需要考虑-174dBm/Hz的“基本成分”即可。
这在通信系统的绝大部分情况下是适用的。
我们把这些项目放在一起,是因为它们表征的实际上是“发射机噪声”的一部分,只是这些噪声不是在发射信道之内,而是发射机泄漏到临近信道中去的部分,可以统称为“邻道泄漏”。
其中ACLR和ACPR(其实是一个东西,不过一个是在终端测试中的叫法,一个是在基站测试中的叫法罢了),都是以“Adjacent Channel”命名,顾名思义,都是描述本机对其他设备的干扰。而且它们有个共同点,对干扰信号的功率计算也是以一个信道带宽为计。这种计量方法表明,这一指标的设计目的,是考量发射机泄漏的信号,对相同或相似制式的设备接收机的干扰——干扰信号以同频同带宽的模式落到接收机带内,形成对接收机接收信号的同频干扰。
在LTE中,ACLR的测试有两种设置,EUTRA和UTRA,前者是描述LTE系统对LTE系统的干扰,后者是考虑LTE系统对UMTS系统的干扰。所以我们可以看到EUTRA ACLR的测量带宽是LTE RB的占用带宽,UTRA ACLR的测量带宽是UMTS信号的占用带宽(FDD系统3.84MHz,TDD系统1.28MHz)。换句话说,ACLR/ACPR描述的是一种“对等的”干扰:发射信号的泄漏对同样或者类似的通信系统发生的干扰。
这一定义是有非常重要的实际意义的。实际网络中同小区邻小区还有附近小区经常会有信号泄漏过来,所以网规网优的过程实际上就是容量最大化和干扰最小化的过程,而系统本身的邻道泄漏对于邻近小区就是典型的干扰信号;从系统的另一个方向来看,拥挤人群中用户的手机也可能成为互相的干扰源。
同样的,在通信系统的演化中,从来是以“平滑过渡”为目标,即在现有网络上升级改造进入下一代网络。那么两代甚至三代系统共存就需要考虑不同系统之间的干扰,LTE引入UTRA即是考虑了LTE在与UMTS共存的情形下对前代系统的射频干扰。
讲SEM的时候,首先要注意它是一个“带内指标”,与spurious emission区分开来,后者在广义上是包含了SEM的,但是着重看的其实是发射机工作频段之外的频谱泄漏,其引入也更多的是从EMC(电磁兼容)的角度。
SEM是提供一个“频谱模版”,然后在测量发射机带内频谱泄漏的时候,看有没有超出模版限值的点。可以说它与ACLR有关系,但是又不相同:ACLR是考虑泄漏到邻近信道中的平均功率,所以它以信道带宽为测量带宽,它体现的是发射机在邻近信道内的“噪声底”;SEM反映的是以较小的测量带宽(往往100kHz到1MHz)捕捉在邻近频段内的超标点,体现的是“以噪声底为基础的杂散发射”。
如果用频谱仪扫描SEM,可以看到邻信道上的杂散点会普遍的高出ACLR均值,所以如果ACLR指标本身没有余量,SEM就很容易超标。反之SEM超标并不一定意味着ACLR不良,有一种常见的现象就是有LO的杂散或者某个时钟与LO调制分量(往往带宽很窄,类似点频)串入发射机链路,这时候即便ACLR很好,SEM也可能超标。
首先,EVM是一个矢量值,也就是说它有幅度和角度,它衡量的是“实际信号与理想信号的误差”,这个量度可以有效的表达发射信号的“质量”——实际信号的点距离理想信号越远,误差就越大,EVM的模值就越大。
很难定义EVM与ACPR/ACLR的定量关系,从放大器的非线性来看,EVM与ACPR/ACLR应该是正相关的:放大器的AM-AM、AM-PM失真会扩大EVM,同时也是ACPR/ACLR的主要来源。
但是EVM与ACPR/ACLR并不总是正相关,我们这里可以找到一个很典型的例子:数字中频中常用的Clipping,即削峰。Clipping是削减发射信号的峰均比(PAR),峰值功率降低有助于降低通过PA之后的ACPR/ACLR;但是Clipping同时会损害EVM,因为无论是限幅(加窗)还是用滤波器方法,都会对信号波形产生损伤,因而增大EVM。
PAR(信号峰均比)通常用CCDF这样一个统计函数来表示,其曲线表示的是信号的功率(幅度)值和其对应的出现概率。譬如某个信号的平均功率是10dBm,它出现超过15dBm功率的统计概率是0.01%,我们可以认为它的PAR是5dB。
所以对于正弦波,假设他的峰值是4,那么他的峰值功率就是4^2=16;而他的平均功率计算
t = [0:0.01:4*pi]
a = 4 * sin(t)
% b = fft(a, 1024)
% plot(abs(b))
result = sum(a.^2)/length(t)
计算得到的结果是8,也就是4^2/2=8;所以他的PAR是3dB。
PAR是现代通信系统中发射机频谱再生(诸如ACLP/ACPR/Modulation Spectrum)的重要影响因素。峰值功率会将放大器推入非线性区从而产生失真,往往峰值功率越高、非线性越强。
在GSM时代,因为GMSK调制的衡包络特性,所以PAR=0,我们在设计GSM功放的时候经常把它推到P1dB,以得到最大限度的效率。引入EDGE之后,8PSK调制不再是衡包络,因此我们往往将功放的平均输出功率推到P1dB以下3dB左右,因为8PSK信号的PAR是3.21dB。
UMTS时代,无论WCDMA还是CDMA,峰均比都比EDGE大得多。原因是码分多址系统中信号的相关性:当多个码道的信号在时域上叠加时,可能出现相位相同的情况,此时功率就会呈现峰值。
LTE的峰均比则是源自RB的突发性。OFDM调制是基于将多用户/多业务数据在时域上和频域上都分块的原理,这样就可能在某一“时间块”上出现大功率。LTE上行发射用SC-FDMA,先用DFT将时域信号扩展到频域上,等于“平滑”掉了时域上的突发性,从而降低了PAR。
这里的“干扰指标”,指的是出了接收机静态灵敏度之外,各种施加干扰下的灵敏度测试。实际上研究这些测试项的由来是很有意思的。
我们常见的干扰指标,包括Blocking,Desense,Channel Selectivity等。
Blocking实际上是一种非常古老的RF指标,早在雷达发明之初就有。其原理是以大信号灌入接收机(通常最遭殃的是第一级LNA),使得放大器进入非线性区甚至饱和。此时一方面放大器的增益骤然变小,另一方面产生极强非线性,因而对有用信号的放大功能就无法正常工作了。
另一种可能的Blocking其实是通过接收机的AGC来完成的:大信号进入接收机链路,接收机AGC因此产生动作降低增益以确保动态范围;但是同时进入接收机的有用信号电平很低,此时增益不足,进入到解调器的有用信号幅度不够。
Blocking指标分为带内和带外,主要是因为射频前端一般会有频带滤波器,对于带外blocking会有抑制作用。但是无论带内还是带外,Blocking信号一般都是点频,不带调制。事实上完全不带调制的点频信号在现实世界里并不多见,工程上只是把它简化成点频,用以(近似)替代各种窄带干扰信号。
对于解决Blocking,主要是RF出力,说白了就是把接收机IIP3提高,动态范围扩大。对于带外Blocking,滤波器的抑制度也是很重要的。
When the defined useful signal coexist with blocking signal, throughput loss less than 1%
useful signal = PREFSENS + 14dB, 20MHz, -79.5dBm
这里我们统称为“邻信道选择性”。在蜂窝系统中,我们组网除了要考虑同频小区,还要考虑邻频小区,其原因可以在我们之前讨论过的发射机指标ACLR/ACPR/Modulation Spectrum中可以找到:因为发射机的频谱再生会有很强的信号落到相邻频率中(一般来说频偏越远电平越低,所以邻信道一般是受影响最大的),而且这种频谱再生事实上是与发射信号有相关性的,即同制式的接收机很可能把这部分再生频谱误认为是有用信号而进行解调,所谓鹊巢鸠占。
举个例子:如果两个相邻小区A和B恰好是邻频小区(一般会避免这样的组网方式,这里只是讨论一个极限场景),当一台注册到A小区的终端游走到两个小区交界处,但是两个小区的信号强度还没有到切换门限,因此终端依然保持A小区连接;B小区基站发射机的ACPR较高,因此终端的接收频带内有较高的B小区ACPR分量,与A小区的有用信号在频率上重叠;因为此时终端距离A小区基站较远,因此接收到的A小区有用信号强度也很低,此时B小区ACPR分量进入到终端接收机时就可以对原有用信号造成同频干扰。
如果我们注意看邻道选择性的频偏定义,会发现有Adjacent和Alternative的区别,对应ACLR/ACPR的第一邻道、第二邻道,可见通信协议中“发射机频谱泄漏(再生)”与“接收机邻道选择性”实际上是成对定义的。
When the defined useful signal coexist with interference signal, throughput loss less than 1%
Blocking是“大信号干扰小信号”,RF尚有周旋余地;而以上的AM Suppression, Adjacent (Co/Alternative) Channel Suppression (Selectivity)这些指标,是“小信号干扰大信号”,纯RF的工作意义不大,还是靠物理层算法为主。
这种描述的是绝对的同频干扰,一般是指两个同频小区之间的干扰模式。
按照之前我们描述的组网原则,两个同频小区的距离应该尽量远,但是即便再远,也会有信号彼此泄漏,只是强度高低的区别。
对于终端而言,两个小区的信号都可以认为是“正确的有用信号”(当然协议层上有一组接入规范来防范这种误接入),衡量终端的接收机能否避免“西风压倒东风”,就看它的同频选择性。
动态范围,温度补偿和功率控制很多情况下是“看不到”的指标,只有在进行某些极限测试的时候才会表现出它们的影响,但是本身它们却体现着RF设计中最精巧的部分。
发射机动态范围表征的是发射机“不损害其他发射指标前提下”的最大发射功率和最小发射功率。
“不损害其他发射指标”显得很宽泛,如果看主要影响,可以理解为:最大发射功率下不损害发射机线性度,最小发射功率下保持输出信号信噪比。
最大发射功率下,发射机输出往往逼近各级有源器件(尤其末级放大器)的非线性区,由此经常发生的非线性表现有频谱泄漏和再生(ACLR/ACPR/SEM),调制误差(PhaseError/EVM)。此时最遭殃的基本上都是发射机线性度,这一部分应该比较好理解。
最小发射功率下,发射机输出的有用信号则是逼近发射机噪声底,甚至有被“淹没”在发射机噪声中的危险。此时需要保障的是输出信号的信噪比(SNR),换句话说就是在最小发射功率下的发射机噪声底越低越好。
在实验室曾经发生过一件事情:有工程师在测试ACLR的时候,发现功率降低ACLR反而更差(正常理解是ACLR应该随着输出功率降低而改善),当时第一反应是仪表出问题了,但是换一台仪表测试结果依然如此。我们给出的指导意见是测试低输出功率下的EVM,发现EVM性能很差;我们判断可能是RF链路入口处的噪声底就很高,对应的SNR显然很差,ACLR的主要成分已经不是放大器的频谱再生、而是通过放大器链路被放大的基带噪声。
接收机动态范围其实与之前我们讲过的两个指标有关,第一个是参考灵敏度,第二个是接收机IIP3(在讲干扰指标的时候多次提到)。
参考灵敏度实际上表征的就是接收机能够识别的最小信号强度,这里不再赘述。我们主要谈一下接收机的最大接收电平。
最大接收电平是指接收机在不发生失真情况下能够接收的最大信号。这种失真可能发生在接收机的任何一级,从前级LNA到接收机ADC。对于前级LNA,我们唯一可做的就是尽量提高IIP3,使其可以承受更高的输入功率;对于后面逐级器件,接收机则采用了AGC(自动增益控制)来确保有用信号落在器件的输入动态范围之内。简单的说就是有一个负反馈环路:检测接收信号强度(过低/过高)-调整放大器增益(调高/调低)-放大器输出信号确保落在下一级器件的输入动态范围之内。
这里我们讲一个例外:多数手机接收机的前级LNA本身就带有AGC功能,如果你仔细研究它们的datasheet,会发现前级LNA会提供几个可变增益段,每个增益段有其对应的噪声系数,一般来讲增益越高、噪声系数越低。这是一种简化的设计,其设计思想在于:接收机RF链路的目标是将输入到接收机ADC的有用信号保持在动态范围之内,且保持SNR高于解调门限(并不苛求SNR越高越好,而是“够用就行”,这是一种很聪明的做法)。因此当输入信号很大时,前级LNA降低增益、损失NF、同时提高IIP3;当输入信号小时,前级LNA提高增益、减小NF、同时降低IIP3。
一般来讲,我们只在发射机作温度补偿。
当然,接收机性能也是受到温度影响的:高温下接收机链路增益降低,NF增高;低温下接收机链路增益提高,NF降低。但是由于接收机的小信号特性,无论增益还是NF的影响都在系统冗余范围之内。
对于发射机温度补偿,也可以细分为两部分:一部分是对发射信号功率准确度的补偿,另一部分是对发射机增益随温度变化进行补偿。
现代通信系统发射机一般都进行闭环功控(除了略为“古老”的GSM系统和Bluetooth系统),因此经过生产程序校准的发射机,其功率准确度事实上取决于功控环路的准确度。一般来讲功控环路是小信号环路,且温度稳定性很高,所以对其进行温度补偿的需求并不高,除非功控环路上有温度敏感器件(譬如放大器)。
对发射机增益进行温度补偿则更加常见。这种温度补偿常见的有两种目的:一种是“看得见的”,通常对没有闭环功控的系统(如前述GSM和Bluetooth),这类系统通常对输出功率精确度要求不高,所以系统可以应用温度补偿曲线(函数)来使RF链路增益保持在一个区间之内,这样当基带IQ功率固定而温度发生变化时,系统输出的RF功率也能保持在一定范围之内;另一种是“看不见的”,通常是在有闭环功控的系统中,虽然天线口的RF输出功率是由闭环功控精确控制的,但是我们需要保持DAC输出信号在一定范围内(最常见的例子是基站发射系统数字预失真(DPD)的需要),那么我们就需要将整个RF链路的增益比较精确的控制在某个值左右——温补的目的就在于此。
发射机温补的手段一般有可变衰减器或者可变放大器:早期精度稍低以及低成本精度要求较低的情况下,温补衰减器比较常见;对精度要求更高的情形下,解决方案一般是:温度传感器+数控衰减器/放大器+生产校准。
讲完动态范围和温度补偿,我们来讲一个相关的、而且非常重要的概念:功率控制。
发射机功控是大多数通信系统中必需的功能,在3GPP中常见的诸如ILPC、OLPC、CLPC,在RF设计中都是必需被测试、经常出问题、原因很复杂的。我们首先来讲发射机功控的意义。
所有的发射机功控目的都包含两点:功耗控制和干扰抑制。
我们首先说功耗控制:在移动通信中,鉴于两端距离变化以及干扰电平高低不同,对发射机而言,只需要保持“足够让对方接收机准确解调”的信号强度即可;过低则通信质量受损,过高则空耗功率毫无意义。对于手机这样以电池供电的终端更是如此,每一毫安电流都需锱铢必量。
干扰抑制则是更加高级的需求。在CDMA类系统中,由于不同用户共享同一载频(而以正交用户码得以区分),因此在到达接收机的信号中,任何一个用户的信号对于其他用户而言,都是覆盖在同一频率上的干扰,若各个用户信号功率有高有高低,那么高功率用户就会淹没掉低功率用户的信号;因此CDMA系统采取功率控制的方式,对于到达接收机的不同用户的功率(我们称之为空中接口功率,简称空口功率),发出功控指令给每个终端,最终使得每个用户的空口功率一样。这种功控有两个特点:第一是功控精度非常高(干扰容限很低),第二是功控周期非常短(信道变化可能很快)。
在LTE系统中,上行功控也有干扰抑制的作用。因为LTE上行是SC-FDMA,多用户也是共享载频,彼此间也互为干扰,所以空口功率一致同样也是必需的。
GSM系统也是有功控的,GSM中我们用“功率等级”来表征功控步长,每个等级1dB,可见GSM功率控制是相对粗糙的。
这里提一个相关的概念:干扰受限系统。CDMA系统是一个典型的干扰受限系统。从理论上讲,如果每个用户码都完全正交、可以通过交织、解交织完全区分开来,那么实际上CDMA系统的容量可以是无限的,因为它完全可以在有限的频率资源上用一层层扩展的用户码区分无穷多的用户。但是实际上由于用户码不可能完全正交,因此在多用户信号解调时不可避免的引入噪声,用户越多噪声越高,直到噪声超过解调门限。
换而言之,CDMA系统的容量受限于干扰(噪声)。
GSM系统不是一个干扰受限系统,它是一个时域和频域受限的系统,它的容量受限于频率(200kHz一个载频)和时域资源(每个载频上可共享8个TDMA用户)。所以GSM系统的功控要求不高(步长较粗糙,周期较长)。
讲完发射机功控,我们进而讨论一下在RF设计中可能影响发射机功控的因素(相信很多同行都遇到过闭环功控测试不过的郁闷场景)。
对于RF而言,如果功率检测(反馈)环路设计无误,那么我们对发射机闭环功控能做的事情并不多(绝大多数工作都是由物理层协议算法完成的),最主要的就是发射机带内平坦度。
因为发射机校准事实上只会在有限的几个频点上进行,尤其在生产测试中,做的频点越少越好。但是实际工作场景中,发射机是完全可能在频段内任一载波工作的。在典型的生产校准中,我们会对发射机的高中低频点进行校准,意味着高中低频点的发射功率是准确的,所以闭环功控在进行过校准的频点上也是无误的。然而,如果发射机发射功率在整个频段内不平坦,某些频点的发射功率与校准频点偏差较大,因此以校准频点为参考的闭环功控在这些频点上也会发生较大误差乃至出错。
进程间的通信方式:1.管道(pipe)及有名管道(named pipe):
管道可用于具有亲缘关系进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
2.信号(signal):
信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致得。
3.消息队列(message queue):
消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。
消息缓冲通信技术是由Hansen首先提出的,其基本思想是:根据”生产者-消费者”原理,利用内存中公用消息缓冲区实现进程之间的信息交换.
内存中开辟了若干消息缓冲区,用以存放消息.每当一个进程向另一个进程发送消息时,便申请一个消息缓冲区,并把已准备好的消息送到缓冲区,然后把该消息缓冲区插入到接收进程的消息队列中,最后通知接收进程.接收进程收到发送里程发来的通知后,从本进程的消息队列中摘下一消息缓冲区,取出所需的信息,然后把消息缓冲区不定期给系统.系统负责管理公用消息缓冲区以及消息的传递.
一个进程可以给若干个进程发送消息,反之,一个进程可以接收不同进程发来的消息.显然,进程中关于消息队列的操作是临界区.当发送进程正往接收进程的消息队列中添加一条消息时,接收进程不能同时从该消息队列中到出消息:反之也一样.
消息缓冲区通信机制包含以下列内容:
(1) 消息缓冲区,这是一个由以下几项组成的数据结构:
1、 消息长度
2、 消息正文
3、 发送者
4、 消息队列指针
(2)消息队列首指针m-q,一般保存在PCB中。
(1) 互斥信号量m-mutex,初值为1,用于互斥访问消息队列,在PCB中设置。
(2) 同步信号量m-syn,初值为0,用于消息计数,在PCB中设置。
(3) 发送消息原语send
(4) 接收消息原语receive(a)
4.共享内存(shared memory):
可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
这种通信模式需要解决两个问题:第一个问题是怎样提供共享内存;第二个是公共内存的互斥关系则是程序开发人员的责任。
5.信号量(semaphore):
主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。
6.套接字(socket);
这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
http://blog.csdn.net/eroswang/archive/2007/09/04/1772350.aspx
linux下的进程间通信-详解
详细的讲述进程间通信在这里绝对是不可能的事情,而且笔者很难有信心说自己对这一部分内容的认识达到了什么样的地步,所以在这一节的开头首先向大家推荐著 名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX Environment》,它的中文译本《UNIX环境高级编程》已有机械工业出版社出版,原文精彩,译文同样地道,如果你的确对在Linux下编程有浓 厚的兴趣,那么赶紧将这本书摆到你的书桌上或计算机旁边来。说这么多实在是难抑心中的景仰之情,言归正传,在这一节里,我们将介绍进程间通信最最初步和最 最简单的一些知识和概念。
首先,进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法。但一般说来, 进程间通信(IPC:InterProcess Communication)不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系 统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信 方法:管道、消息队列、共享内存、信号量、套接口等等。下面我们将逐一介绍。
2.3.1 管道
管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。
无名管道由pipe()函数创建:
#include <unistd.h>
int pipe(int filedis[2]);
参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。
#define INPUT 0
#define OUTPUT 1
void main() {
int file_descriptors[2]
/*定义子进程号 */
pid_t pid
char buf[256]
int returned_count
/*创建无名管道*/
pipe(file_descriptors)
/*创建子进程*/
if((pid = fork()) == -1) {
printf("Error in fork\n")
exit(1)
}
/*执行子进程*/
if(pid == 0) {
printf("in the spawned (child) process...\n")
/*子进程向父进程写数据,关闭管道的读端*/
close(file_descriptors[INPUT])
write(file_descriptors[OUTPUT], "test data", strlen("test data"))
exit(0)
} else {
/*执行父进程*/
printf("in the spawning (parent) process...\n")
/*父进程从管道读取子进程写的数据,关闭管道的写端*/
close(file_descriptors[OUTPUT])
returned_count = read(file_descriptors[INPUT], buf, sizeof(buf))
printf("%d bytes of data received from spawned process: %s\n",
returned_count, buf)
}
}
在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:
方式一:mkfifo("myfifo","rw")
方式二:mknod myfifo p
生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面即是一个简单的例子,假设我们已经创建了一个名为myfifo的有名管道。
/* 进程一:读有名管道*/
#include <stdio.h>
#include <unistd.h>
void main() {
FILE * in_file
int count = 1
char buf[80]
in_file = fopen("mypipe", "r")
if (in_file == NULL) {
printf("Error in fdopen.\n")
exit(1)
}
while ((count = fread(buf, 1, 80, in_file)) >0)
printf("received from pipe: %s\n", buf)
fclose(in_file)
}
/* 进程二:写有名管道*/
#include <stdio.h>
#include <unistd.h>
void main() {
FILE * out_file
int count = 1
char buf[80]
out_file = fopen("mypipe", "w")
if (out_file == NULL) {
printf("Error opening pipe.")
exit(1)
}
sprintf(buf,"this is test data for the named pipe example\n")
fwrite(buf, 1, 80, out_file)
fclose(out_file)
}
2.3.2 消息队列
消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。
事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也不再解释,也建议读者忽略这种方式。
2.3.3 共享内存
共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行 读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是 实际的物理内存,在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。常用的方式是通过shmXXX函数族来实现利 用共享内存进行存储的。
首先要用的函数是shmget,它获得一个共享存储标识符。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flag)
这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数 的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的 key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。
当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。
void *shmat(int shmid, void *addr, int flag)
shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。
使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存 储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。
2.3.4 信号量
信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
(1) 测试控制该资源的信号量。
(2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。
(3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。
(4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include /linux /sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获 得一个信号量ID。
struct sem {
short sempid/* pid of last operaton */
ushort semval/* current value */
ushort semncnt/* num procs awaiting increase in semval */
ushort semzcnt/* num procs awaiting semval = 0 */
}
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag)
key是前面讲过的IPC结构的关键字,flag将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新 集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0。
semctl函数用来对信号量进行操作。
int semctl(int semid, int semnum, int cmd, union semun arg)
不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。
semop函数自动执行信号量集合上的操作数组。
int semop(int semid, struct sembuf semoparray[], size_t nops)
semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量。
下面,我们看一个具体的例子,它创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后我们清除信号量。在下面的代码中,函数ftok生成我们上文所说的唯一的IPC关键字。
#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
void main() {
key_t unique_key/* 定义一个IPC关键字*/
int id
struct sembuf lock_it
union semun options
int i
unique_key = ftok(".", 'a')/* 生成关键字,字符'a'是一个随机种子*/
/* 创建一个新的信号量集合*/
id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666)
printf("semaphore id=%d\n", id)
options.val = 1/*设置变量值*/
semctl(id, 0, SETVAL, options)/*设置索引0的信号量*/
/*打印出信号量的值*/
i = semctl(id, 0, GETVAL, 0)
printf("value of semaphore at index 0 is %d\n", i)
/*下面重新设置信号量*/
lock_it.sem_num = 0/*设置哪个信号量*/
lock_it.sem_op = -1/*定义操作*/
lock_it.sem_flg = IPC_NOWAIT/*操作方式*/
if (semop(id, &lock_it, 1) == -1) {
printf("can not lock semaphore.\n")
exit(1)
}
i = semctl(id, 0, GETVAL, 0)
printf("value of semaphore at index 0 is %d\n", i)
/*清除信号量*/
semctl(id, 0, IPC_RMID, 0)
}
semget()
可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:
系统调用:semget()
原型:intsemget(key_t key,int nsems,int semflg)
返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)
EEXIST(信号量集已经存在,无法创建)
EIDRM(信号量集已经删除)
ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
ENOMEM(没有足够的内存创建新的信号量集)
ENOSPC(超出限制)
系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是在linux/sem.h中定义的:
#defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
下面是一个打开和创建信号量集的程序:
intopen_semaphore_set(key_t keyval,int numsems)
{
intsid
if(!numsems)
return(-1)
if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
{
return(-1)
}
return(sid)
}
}
==============================================================
semop()
系统调用:semop()
调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops)
返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)
EACCESS(权限不够)
EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行)
EFAULT(sops指向的地址无效)
EIDRM(信号量集已经删除)
EINTR(当睡眠时接收到其他信号)
EINVAL(信号量集不存在,或者semid无效)
ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)
ERANGE(信号量值超出范围)
第一个参数是关键字值。第二个参数是指向将要操作的数组的指针。第三个参数是数组中的操作的个数。参数sops指向由sembuf组成的数组。此数组是在linux/sem.h中定义的:
/*semop systemcall takes an array of these*/
structsembuf{
ushortsem_num/*semaphore index in array*/
shortsem_op/*semaphore operation*/
shortsem_flg/*operation flags*/
sem_num将要处理的信号量的个数。
sem_op要执行的操作。
sem_flg操作标志。
如果sem_op是负数,那么信号量将减去它的值。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。如果sem_op是正数,则信号量加上它的值。这也就是进程释放信号量控制的资源。最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。
===============================================================
semctl()
系统调用:semctl()
原型:int semctl(int semid,int semnum,int cmd,union semunarg)
返回值:如果成功,则为一个正数。
如果失败,则为-1:errno=EACCESS(权限不够)
EFAULT(arg指向的地址无效)
EIDRM(信号量集已经删除)
EINVAL(信号量集不存在,或者semid无效)
EPERM(EUID没有cmd的权利)
ERANGE(信号量值超出范围)
系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。因为信号量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。这两个系统调用都使用了参数cmd,它用来指出要操作的具体命令。两个系统调用中的最后一个参数也不一样。在系统调用msgctl中,最后一个参数是指向内核中使用的数据结构的指针。我们使用此数据结构来取得有关消息队列的一些信息,以及设置或者改变队列的存取权限和使用者。但在信号量中支持额外的可选的命令,这样就要求有一个更为复杂的数据结构。
系统调用semctl()的第一个参数是关键字值。第二个参数是信号量数目。
参数cmd中可以使用的命令如下:
·IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
·IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)