接收灵敏度,这应该是最基本的概念之一,表征的是接收机能够在不超过一定误码率的情况下识别的最低信号强度。
讲灵敏度的时候我们常常联系到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而言,如果功率检测(反馈)环路设计无误,那么我们对发射机闭环功控能做的事情并不多(绝大多数工作都是由物理层协议算法完成的),最主要的就是发射机带内平坦度。
因为发射机校准事实上只会在有限的几个频点上进行,尤其在生产测试中,做的频点越少越好。但是实际工作场景中,发射机是完全可能在频段内任一载波工作的。在典型的生产校准中,我们会对发射机的高中低频点进行校准,意味着高中低频点的发射功率是准确的,所以闭环功控在进行过校准的频点上也是无误的。然而,如果发射机发射功率在整个频段内不平坦,某些频点的发射功率与校准频点偏差较大,因此以校准频点为参考的闭环功控在这些频点上也会发生较大误差乃至出错。
sem_wait() 减小(锁定)由sem指定的信号量的值.如果信号量的值比0大,那么进行减一的操作,函数立即返回.如果信号量当前为0值,那么调用就会一直阻塞直到或者是信号量变得可以进行减一的操作(例如,信号量的值比0大),或者是信号处理程序中断调用
sem_trywait() 和 sem_wait()是一样的,除了如果不能够对信号量立即进行减一,那么sem_trywait()就会返回一个错误(错误号是AGAIN)而不是锁定.sem_timedwait() 和 sem_wait()是一样的,除了如果减一操作不能立即执行的话,abs_timeout 指定了调用应该被阻塞的时间限制.abs_timeout 参数指向了一个结构体指定了由秒和纳秒组成的绝对的超时值:从1970-01-01 00:00:00 +0000纪元开始的UTC,结构体的定义如下:struct timespec {time_t tv_sec/* Seconds */long tv_nsec/* Nanoseconds [0 .. 999999999] */}如果超时值已经超过了调用规定的值,那么信号量不能被立即锁定,之后sem_timedwait() 为超时失败(error设置为ETIMEDOUT).
如果操作立即生效,那么sem_timedwait() 永远不会返回超时的错误,不管abs_timeout的值.更进一步的是,在这种情况下abs_timeout值的有效性都不会检查. EINTR The call was interrupted by a signal handlersee signal(7).//调用被信号处理中断
EINVAL sem is not a valid semaphore.//sem不是有效的信号量
The following additional error can occur for sem_trywait()://下面的错误是sem_trywait()可能发生的:
EAGAIN The operation could not be performed without blocking (i.e., thesemaphore currently has the value zero).//除了锁定无法进行别的操作(如信号量当前是0值).
The following additional errors can occur for sem_timedwait()://下面的错误是sem_timedwait()可能发生的:
EINVAL The value of abs_timeout.tv_nsecs is less than 0, or greater than orequal to 1000 million.//abs_timeout.tv_nsecs 的值比0小或者大于等于1000毫秒(译者注:纳秒的值不能比0小,不能比1秒大)
ETIMEDOUTThe call timed out before the semaphore could be locked.//在信号量锁定之前就超时了 对这些函数,信号处理程序总是会中断阻塞,不管是否使用了sigaction(2)的SA_RESTART标志位.
信号量强调的是线程(或进程)间的同步:“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都 在sem_wait的时候,就阻塞在那里)。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
有名信号量:可以用于不同进程间或多线程间的互斥与同步
创建打开有名信号量
sem_t *sem_open(const char *name, int oflag)
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value)
成功返回信号量指针;失败返回SEM_FAILED,设置errnoname是文件路径名,但不能写成/tmp/a.sem这样的形式,因为在linux下,sem都是在/dev/shm目录下,可写成"/mysem"或"mysem",创建出来的文件都 是"/dev/shm/sem.mysem",mode设置为0666,value设置为信号量的初始值.所需信号灯等已存在条件下指定O_CREAT|O_EXCL却是个错误。
关闭信号量,进程终止时,会自动调用它
int sem_close(sem_t *sem)
成功返回0;失败返回-1,设置errno
删除信号量,立即删除信号量名字,当其他进程都关闭它时,销毁它
int sem_unlink(const char *name)
等待信号量,测试信号量的值,如果其值小于或等于0,那么就等待(阻塞);一旦其值变为大于0就将它减1,并返回
int sem_wait(sem_t *sem)
int sem_trywait(sem_t *sem)
成功返回0;失败返回-1,设置errno
当信号量的值为0时,sem_trywait立即返回,设置errno为EAGAIN。如果被某个信号中断,sem_wait会过早地返回,设置errno为EINTR
发出信号量,给它的值加1,然后唤醒正在等待该信号量的进程或线程
int sem_post(sem_t *sem)
成功返回0;失败返回-1,不会改变它的值,设置errno,该函数是异步信号安全的,可以在信号处理程序里调用它无名信号量,用于进程体内各线程间的互斥和同步,使用如下API(无名信号量,基于内存的信号量)
(1)、sem_init
功能:用于创建一个信号量,并初始化信号量的值。
头文件:
函数原型: int sem_init (sem_t* sem, int pshared, unsigned int value)
函数传入值: sem:信号量。pshared:决定信号量能否在几个进程间共享。由于目前LINUX还没有实现进程间共享信息量,所以这个值只能取0。
(2)其他函数。
int sem_wait (sem_t* sem)
int sem_trywait (sem_t* sem)
int sem_post (sem_t* sem)
int sem_getvalue (sem_t* sem)
int sem_destroy (sem_t* sem)
功能:sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量的值小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。sem_post相当于V操作,它将信号量的值加一,同时发出唤醒的信号给等待的进程(或线程)。
sem_getvalue 得到信号量的值。
sem_destroy 摧毁信号量。
如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。
互斥锁(又名互斥量)强调的是资源的访问互斥:互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”
也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
在linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:
对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.
对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr)
int pthread_mutex_destroy(pthread_mutex_t *mutex)
头文件:
返回值: 成功则返回0, 出错则返回错误编号.
说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解.
首先说一下加锁函数:
头文件:
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
返回值: 成功则返回0, 出错则返回错误编号.
说 明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限如果互斥量 被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态.
再说一下解所函数:
头文件:
原型: int pthread_mutex_unlock(pthread_mutex_t *mutex)
返回值: 成功则返回0, 出错则返回错误编号.
条件变量常与互斥锁同时使用,达到线程同步的目的:条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。在发 送信号时,如果没有线程 等待在该条件变量上,那么信号将丢失;而信号量有计数值,每次信号量post操作都会被记录
互斥锁必须是谁上锁就由谁来解锁,而信号量的wait和post操作不必由同一个线程执行。
2. 互斥锁要么被锁住,要么被解开,和二值信号量类似
3. sem_post是各种同步技巧中,唯一一个能在信号处理程序中安全调用的函数
4. 互斥锁是为上锁而优化的;条件变量是为等待而优化的; 信号量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性
5. 互斥锁,条件变量都只用于同一个进程的各线程间,而信号量(有名信号量)可用于不同进程间的同步。当信号量用于进程间同步时,要求信号量建立在共享内存区。
6. 信号量有计数值,每次信号量post操作都会被记录,而条件变量在发送信号时,如果没有线程在等待该条件变量,那么信号将丢失。
读写锁
读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。
读写锁可以由三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写
锁。
在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。
读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥 有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。
读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。
初始化和销毁:
#include
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
成功则返回0, 出错则返回错误编号.
同互斥量以上, 在释放读写锁占用的内存之前, 需要先通过thread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.
读和写:
#include
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
成功则返回0, 出错则返回错误编号.
这3个函数分别实现获取读锁, 获取写锁和释放锁的操作. 获取锁的两个函数是阻塞操作, 同样, 非阻塞的函数为:
#include
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
成功则返回0, 出错则返回错误编号.
非阻塞的获取锁操作, 如果可以获取则返回0, 否则返回错误的EBUSY.
虽然读写锁提高了并行性,但是就速度而言并不比互斥量快.
可能这也是即使有读写锁存在还会使用互斥量的原因,因为他在速度方面略胜一筹。这就需要我们在写程序的时候综合考虑速度和并行性并找到一个折中。
比如: 假设使用互斥量需要0.5秒,使用读写锁需要0.8秒。在类似学生管理系统这类中,可能百分之九十的时间都是查询操作,那么假如现在突然来个个20个请求,如果使用的是互斥量,那么最后的那个查询请求被满足需要10后。这样,估计没人能受得了。而使用读写锁,应为 读锁能够多次获得。所以所有的20个请求,每个请求都能在1秒左右得到满足。
也就是说,在一些写操作比较多或是本身需要同步的地方并不多的程序中我们应该使用互斥量,而在读操作远大于写操作的一些程序中我们应该使用读写锁来进行同步
条件变量(condition)
条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会察觉到这种改变,因此必须锁定互斥量以后才能计算条件。
条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件
变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
1. 初始化:
条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:
静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.
动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.
#include
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr)
int pthread_cond_destroy(pthread_cond_t *cond)
成功则返回0, 出错则返回错误编号.
注意:条件变量占用的空间并未被释放。
当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量非默认情况以后讨论.
2. 等待条件:
#include
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex)
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout)
成功则返回0, 出错则返回错误编号.
这两个函数分别是阻塞等待和超时等待.
等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数. 函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样 便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.
当pthread_cond_wait返回时, 互斥量再次被锁住.
pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条 件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:
pthread_mutex_lock()
while (condition_is_false)
pthread_cond_wait()
pthread_mutex_unlock()
阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条 件变量相关的互斥锁仍将处在锁定状态。
pthread_cond_timedwait函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
注意:pthread_cond_timedwait函数也是退出点。
超时时间参数是指一天中的某个时刻。使用举例:
pthread_timestruc_t to
to.tv_sec = time(NULL) + TIMEOUT
to.tv_nsec = 0
超时返回的错误码是ETIMEDOUT。
3. 通知条件:
#include
int pthread_cond_signal(pthread_cond_t *cond)
int pthread_cond_broadcast(pthread_cond_t *cond)
成功则返回0, 出错则返回错误编号.
这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号.
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)