• 上百种电路图,接线不求人,记得收藏……

    上百种电路图,接线不求人,记得收藏……

    03-07 206浏览
  • 射频芯片面积减少45%!

    就在当前成熟制程有产能过剩疑虑,使得各家以成熟制程为主的晶圆代工厂,开始发展利基型产品,以突围出自己的一片天空之际,晶圆代工大厂联电宣布,推出业界首项RFSOI制程技术的3D IC解决方案,此55纳米 RFSOI 制程平台上所使用的硅堆叠技术,在不损耗射频(RF)效能下,可将芯片尺寸缩小45% 以上,使客户能够有效率地集成更多射带组件,以满足5G更大的带宽需求。 联电指出,RFSOI是用于低噪声放大器、开关和天线调谐器等射频芯片的晶圆制程。随着新一代智能手机对频段数量需求的不断成长,联电的 RFSOI 3D IC 解决方案,利用晶圆对晶圆的键合技术,并解决了芯片堆叠时常见的射频干扰问题,将装置中传输和接收数据的关键组件,通过垂直堆栈芯片来减少面积,以解决在设备中为整合更多射频前端模块带来的挑战。该制程已获得多项国际专利,准备投入量产。 联电技术开发处执行处长马瑞吉(Raj Verma)表示,联电领先业界以创新射频前端模组的3D IC技术,为客户打造最先进的解决方案。这项突破性技术不仅解决了 5G/6G 智能手机频段需求增加所带来的挑战,更有助于在移动、物联网和虚拟现实的设备中,通过同时容纳更多频段来实现更快的数据传输。未来我们将持续开发如 5G 毫米波芯片堆叠技术的解决方案,以满足客户对射频芯片的需求。 联电强调,拥有业界最完整的射频前端模组芯片解决方案,提供包括移动设备、Wi-Fi、汽车、物联网和卫星通讯等广泛应用的需求。RFSOI解决方案系列从130到40纳米的制程技术,以8吋和12寸晶圆生产,目前已完成超500个产品设计定案,出货量更高达380多亿颗。除了RFSOI技术外,联电的6寸晶圆厂联颖光电还提供化合物半导体砷化镓(GaAs)和氮化镓(GaN),以及射频滤波器(RF filters)技术,可充分满足市场对射频前端模组应用的各种需求。

    03-03 68浏览
  • 芯片制造:MOSFET的一个工艺流程

    芯片制造工艺流程包括光刻、刻蚀、扩散、薄膜、离子注入、化学机械研磨、清洗等等,在前面的文章我们简要的介绍了各个工艺流程的细节,这篇文章大致讲解这些工艺流程是如何按顺序整合在一起并且制造出一个MOSFET的。 1.我们首先拥有一个硅纯度高达99.9999999%的衬底。 2.在硅晶衬底上生长一层氧化薄膜。 3.均匀的旋涂上光刻胶。 3.通过光掩膜进行光刻,把光掩膜板上的图案转移到光刻胶上 4.感光区域的光刻胶显影之后被清洗掉。 5.通过刻蚀把没有被覆盖光刻胶的氧化薄膜刻蚀掉,这样把光刻图案转移到晶圆上了。 6.清洗去掉多余的光刻胶。 7.再长一层较薄的氧化膜。之后再通过上面的光刻和刻蚀,只保留栅极区域的氧化膜。 8.在上面生长一层多晶硅 9.和第7步一样通过光刻和刻蚀,只保留栅氧化层上面的多晶硅。 10.在进行光刻清洗覆盖住氧化层和栅极,这样就对整片晶圆进行离子注入,就有了源极和漏极。 11,在晶圆上面生长一层绝缘薄膜。 12.通过光刻和刻蚀把源极、栅极和漏极的接触孔刻蚀出来。 13.再在刻蚀的地方进行金属的沉积,这样就有了源极、栅极和漏极的导电金属线了。 最后通过各种工艺的组合就制造出来一个完整的MOSFET。 ------------------------------------------------------------------------ 其实芯片的底层就是大量的晶体管组成的。 MOSFET制造图源极、栅极、漏极各种晶体管组成逻辑门逻辑门组成运算器 最后组成只有一个指甲大小的芯片 金刚石/GaN 异质外延与键合技术研究进展 吴海平 安康 许光宇 张亚琛 李利军 张永康 李鸿 张旭芳 刘峰斌 李成明 (北方工业大学 机械与材料工程学院 北京科技大学 新材料技术研究院) 摘要: 氮化镓(GaN)功率器件具有功率高、小型化的优势,但散热问题已经成为限制其高功率输出的新问题。金刚石具有块体材料最高的热导率,是GaN 功率器件的理想散热材料,将金刚石与GaN 功率器件集成,可以降低器件运行温度,提高功率密度,推进器件小型化发展。但是由于金刚石与GaN 存在大的热膨胀失配和晶格失配,以及金刚石的高硬度和稳定的化学性质,其与GaN 集成存在很多问题,无法发挥金刚石的超高热导率优势。针对金刚石与GaN 的集成已经进行了研究与探索,主要包括GaN 功率器件的器件层散热和衬底层散热。器件层散热主要有金刚石钝化散热技术,其在GaN 器件层中异质外延金刚石散热层;衬底层散热主要有键合技术、异质外延技术,其中键合技术通常需要在金刚石和GaN 表面沉积键合层或形成封端,包括表面活化键合技术、亲水键合技术、原子扩散键合技术和水解辅助固化键合技术等;异质外延技术通常需要在外延表面沉积缓冲层,包括金刚石异质外延GaN 技术和GaN 底面异质外延金刚石技术。详细介绍了GaN 材料的优势和应用领域及面临的挑战,对上述集成技术的研究现状和优缺点进行了归纳,展望了金刚石与GaN 功率器件集成技术的未来发展方向。 1.引言 GaN 作为第三代半导体材料的典型代表,与Si、Ge、GaAs、InP 等第一、二代半导体材料相比,在禁带宽度、击穿场强、电子迁移率、热导率、最高工作温度等关键性能上更具优势[1]。使用GaN 材料制造的GaN 功率器件拥有高转换效率、低导通损耗、高工作频率、大带宽以及高功率密度[2],广泛应用于通信、雷达、卫星、电力电子等领域[3-4]。 随着系统小型化的发展,需要进一步提高GaN 功率器件的功率密度,但是GaN 功率器件工作时,本身会产生一定的功率耗散,而这部分功率耗散将会在器件内部,尤其是在导电沟道处产生大量热量使得器件结温有明显升高,晶格振动散射大大加强使得漂移区内的电子迁移率降低,器件导通电阻出现明显上升,这种现象被称作“自热效应”。姜守高等[5]发现GaN 基HEMT 器件在200℃存储300 小时后,其饱和电流降低9.05%,最大跨导降低5.3%,因此散热问题越来越重要[6-7]。如何实现有效的散热以减弱自热效应的影响,成为高功率密度GaN 功率器件保持高可靠性和长寿命的挑战[8-13]。 目前GaN 材料通常使用外延法制备[14],衬底使用Si、SiC、Al2O3、AlN、GaN 等材料[15-17],其热导率如表1 所示,无法发挥GaN 器件高频率、高功率密度的优势[18-23]。金刚石是块体材料中热导率最高的材料,单晶金刚石在室温下的导热系数高达2400 W·m-1·K-1[24],多晶金刚石热导率也达到了2000 W·m-1·K-1 [25],远高于其他常用GaN 衬底材料的热导率,是理想的散热材料[26]。将高热导率的金刚石与GaN 功率器件集成,凭借金刚石的超高热导率,热源产生的热量会迅速地横向扩散在基板内,提升了热源与外界的有效换热面积,从而可以极大地提升系统的换热能力,大幅度提高GaN 功率器件的散热效果,降低器件沟道温度,从而提高GaN 功率器件的工作功率密度[27]。本文对近年来金刚石与GaN 集成技术的发展进行了介绍,详细阐述了各种技术的设计方案、工艺流程、优缺点及应用范围,并对未来金刚石与GaN 功率器件集成技术的发展方向进行了分析和展望。 2.金刚石/GaN 集成技术研究现状 目前金刚石与GaN 功率器件的集成通常从两方面进行,一是GaN 顶部的器件层散热,主要应用金刚石钝化散热技术,金刚石钝化散热是直接在器件顶部沉积金刚石,提高热点顶部的热扩散,同时起到增大换热面积的作用;二是GaN 底部金刚石衬底散热,主要有GaN 底部异质外延金刚石、金刚石表面异质外延GaN 和键合技术[28-30]。 2018 年,美国Akash Systems 公司[31]开发出金刚石基GaN,用于卫星功率放大器,相比SiC 基GaN,其工作温度从232℃降低到152℃,功率密度提升3.6 倍以上。2019年,搭载该金刚石基GaN 功率器件的卫星已成功发射,得益于金刚石的高导热率,GaN 的高功率高频率优势得以发挥,卫星的数据传输速率达到了14 Gbps。2019 年,日本富士通公司[32]报道了一种金刚石-GaN-金刚石的双层金刚石散热结构,如图1 所示,结合了金刚石钝化散热技术与金刚石衬底散热技术,经测试,该结构具有优异的散热性能,热点温度较无金刚石的结构下降了77%。 2.1 金刚石/GaN 键合技术 金刚石/GaN 键合技术的技术路线通常是将GaN 外延层的原始衬底通过机械研磨,化学蚀刻等方法去除,然后在GaN 暴露的底面通过磁控溅射、金属有机化学气相沉积(MOCVD)等方法沉积中间层,之后与金刚石结合。该技术存在的难点是对金刚石的表面粗糙度、弯曲度要求极高,还存在键合强度低,键合层热阻高等问题。目前金刚石表面加工方法主要有机械抛光、化学抛光、等离子体辅助抛光、激光抛光等[33-34],例如Yamamura等[35]使用等离子体辅助抛光获得了粗糙度Sq 0.13 nm 的单晶金刚石片,杨志亮等[36]使用机械研磨抛光,得到了粗糙度0.27 nm、弯曲度13.84 μm 的3 英寸多晶金刚石片,但是成本都比较高。根据键合层处理方法与材料的不同,目前常用的键合技术有表面活化键合、亲水键合、原子扩散键合、水解辅助固化键合等。 2.1.1 金刚石/GaN 表面活化键合技术 表面活化键合通常使用Si、SiC 等能与金刚石形成稳定化学键的非金属材料作为键合层,基本流程如图2 所示,首先通过粘片工艺将GaN 固定到载片上,之后去除原始衬底,在待键合面沉积键合层或使用离子束活化待键合表面,最后将键合表面贴合并加压完成键合。由于需要保持待键合表面活性,避免氧化或污染,沉积键合层或离子束活化表面步骤与键合步骤需要在高真空度环境(~5×10-6 Pa)中进行,对设备条件要求很高。 由于键合层通常是非晶态材料,热导率较低,Cheng 等[37]为了探究键合层厚度对界面热导的影响,制备了不同键合层厚度的金刚石/GaN 结构,分别为13 nm 和4.2 nm,并使用时域热反射技术测量界面热导,分别为53 MW·m-2·K-1 和92 MW·m-2·K-1,可以看出,键合层厚度与热导率有强相关性,减小键合层厚度对整体热阻的控制十分重要。 为了减小键合层厚度或使键合层转变成晶态材料,有研究人员使用高温退火的方式,使键合层发生扩散、化学反应等。Kagawa 等[38-39] 使用Si作为中间层, 制备了AlGaN/GaN/3C-SiC/金刚石结构,在800℃退火后,形成欧姆接触,键合结构稳定,得到完整GaN HEMT 器件。如图3 所示,在1100℃退火后,键合层中Si和C 原子发生再结晶生成SiC,厚度从退火前的15.5 nm 减小到7.4 nm,且在高温退火过程中键合层拉应力也有所减小。在VGS=2 V,VDS=20 V 时,相比于Si 和SiC 衬底,使用金刚石衬底的器件最高温度分别降低57%和43%,器件电性能也有所提高。Liang等[40]报道了仅通过Ar 离子束照射金刚石和GaN 表面,不使用其它材料作为键合层的方法制备了金刚石/GaN 结构,其中有5.3 nm 厚的非晶碳中间层。在700℃、1000℃退火后非晶碳中间层厚度分别减小到2.3 nm和1.5 nm,在其中观察到晶格条纹,部分转化为金刚石。这些研究都表明通过优化退火工艺可以减小甚至完全去除键合层,且键合结构稳定。 为了进一步减小键合层厚度,Matsumae 等[41]优化工艺,使用Ar 离子束照射Si靶,在金刚石上沉积1 nm 厚的Si 层,GaN 表面使用Ar 离子束轰击活化,在4.4 MPa 的压力下与GaN 成功键合,键合层厚度仅1.5 nm,剪切强度4.5 MPa,如图4 所示。推测界面热阻小于10 m2·K·GW-1。相比Liang 等[40]的工艺,这个工艺没有高温退火的过程,减小了对GaN层的损伤。 金刚石表面粗糙度控制对键合成功率也十分重要,但是由于金刚石的高硬度、高化学 惰性,将其加工到0.5 nm 以下成本很高,有研究人员发现在沉积键合层的过程中,可以降低金刚石键合面粗糙度。Kobayashi 等[42]使用射频磁控溅射在金刚石键合面沉积15 nm SiC层,使金刚石键合面粗糙度由0.768 nm 降低到0.365 nm,而后在表面活化键合设备中室温键合,成功制备了金刚石/GaN 结构,键合面积85%,键合层厚度11 nm,其中3.7 nm 为缺陷金刚石层,7 nm 为非晶SiC 层。在1000℃退火后,由于硅与碳原子反应生成SiC,SiC层厚度略有增加,非晶SiC 层在退火工艺之后变成多晶。结果表明,SiC 层的沉积可以降低金刚石表面的粗糙度,并有利于多晶金刚石与异种材料的室温结合,通过退火工艺,还可以将低热导率的非晶SiC 转变成高热导率的多晶SiC。 目前的研究大多处于小尺寸探索阶段,针对大面积键合的工艺优化和实验验证,廖龙忠等[43]使用纳米级氧化硅作为键合层,使用优化的键合工艺,成功实现了4 英寸金刚石与GaN 的键合,如图5 所示。所制备的金刚石/GaN 器件热阻较转移前降低了39.5%,在6.5 W总耗散功率下其结温降低了33.77℃,电气性能经测试也有提高。实现了大尺寸金刚石与GaN 的键合,验证了大尺寸键合的可行性,为产业化提供了依据。 2.1.2 金刚石/GaN 亲水键合技术 亲水键合是在金刚石和GaN 表面生成OH端,通过OH 端之间的反应实现金刚石与GaN 的键合,目前已广泛应用于金刚石与Si、SiO2、InP,InGaP,Ga2O3 等材料的键合[44-47]。Matsumae 等[48]使用NH4OH/H2O2混合溶液在70℃下处理金刚石,使用HCl 溶液在70℃下处理GaN,在金刚石和GaN 表面成功生成了OH 端,后将金刚石与GaN 在1 MPa 的压力下200℃处理2 小时,成功得到金刚石/GaN 结构,如图6 所示。剪切强度8.19 MPa,有3nm 由sp2-C、Ga 和O 组成的键合层。亲水键合的方法相对于表面活化键合,步骤简单,对设备要求低,但是OH 端反应产物包括大量高温水分子,在大尺寸键合过程中可能无法及时逸出,影响键合效果,且据Sumiya 等[49]报道,高温水蒸气会对GaN 产生不利影响。 2.1.3 金刚石/GaN 原子扩散键合技术 原子扩散键合技术使用Au、Mo、Ag、Cu 等金属材料作为键合层,利用金属原子在温度和压力下的扩散实现键合,已经应用于金刚石与Si、GaN 与Si 的键合[50-51]。Wang 等[52]在单晶金刚石、多晶金刚石和GaN 上沉积5 nm Mo 层和11 nm Au 层,在室温下加载2000N 的载荷完成键合,制作了单晶金刚石/GaN 和多晶金刚石/GaN 两个样品。单晶金刚石与GaN 完全键合,在680 N 的拉力下,样品从Au-Au 键合层断裂。多晶金刚石键合面积98.5%,经过45℃到125℃的1000 次热循环后,键合面积降低到73%,如图7 所示。使用原子扩散键合成功制备了金刚石/GaN 结构,但是由于金属材料与金刚石和GaN 之间热膨胀系数失配很大,导致其热稳定性较差,且并未报道金属键合层厚度、热导率以及金属层对GaN器件电气性能的影响。 2.1.4 金刚石/GaN 水解辅助固化键合技术 水解辅助固化键合是一种利用中间层发生化学反应来进行键合的方法。Gerrer 等[53]在Si 基AlN/AlGaN/GaN上黏贴一块650 μm 厚的蓝宝石,而后放入HNO3/HF 混合溶液中去除Si 层,后在去离子水中将AlN/AlGaN/GaN 的AlN 面与金刚石接触,旋转去除接触面多余的水,放入真空炉中在200℃完成键合,去除蓝宝石片后得到金刚石/AlGaN/GaN 结构。在键合过程中,键合层是通过AlN 与水在40℃以上温度反应形成的,生成物为Al(OH)3和AlO(OH),键合层厚度30 nm,如图8 所示。整个工艺流程都是在液体中进行的,工艺简单,对金刚石粗糙度要求较低,但是键合层材料导热率极低,且厚度难以控制,导致其热阻高,据Fatimah 等[54]报道Al 的氢氧化合物可以在高温下分解成热导率更高的Al2O3,后续可以开发其它高热导率的键合层材料或通过高温退火等方法提高键合层热导率。 2.2 金刚石异质外延GaN 技术 另一种金刚石衬底散热技术是异质外延技术,由于金刚石是立方晶体结构,不同于GaN 的纤锌矿晶体结构,GaN 和金刚石之间存在很大的晶格失配和热膨胀系数失配,如表1 所示,给异质外延技术带来了许多难题,如何有效控制应力以及生长的晶体质量都是其中的关键问题。按照外延层的区别可以分为GaN 异质外延金刚石技术和金刚石异质外延GaN 技术。GaN 异质外延金刚石技术,是在去除GaN 原衬底及部分缓冲层后,先在GaN背面沉积一层介电层用于保护GaN,而后再沉积金刚石层;金刚石异质外延GaN 技术,是在金刚石上直接使用分子束外延(MBE)、MOCVD 等方法沉积GaN 层。目前研究使用较多的异质外延技术是GaN 异质外延金刚石技术。 GaN 异质外延金刚石技术相比键合技术,界面结合强度高,成本较低,但是也存在一些问题,如异质外延金刚石的形核层质量差、热导率低,金刚石的生长环境通常都是高温、高氢等离子体密度,GaN 在高温冷却到室温的过程中会发生翘曲甚至破裂,在氢等离子体环境中会发生严重的刻蚀、分解[55-59]。因此需要在GaN 表面先制备一层保护层,缓解热应力,同时保护GaN 不受刻蚀。技术基本流程如图9 所示。 Field 等[60]研究了保护层对金刚石/GaN 结构的影响,比较了具有SiC 保护层和没有保护层的两种样品的形貌特征和热导率。从结果来看,没有保护层的样品结合面粗糙,GaN被刻蚀,结合强度低,界面热阻高(107±44 m2·K·GW-1),具有SiC 保护层的样品结合面平整光滑,有较低的界面热阻(30±5 m2·K·GW-1),认为是SiC 保护层与金刚石有更强的结合力,相比于直接在AlGaN 上沉积金刚石,SiC 与金刚石之间形成了更多的碳化物键,有助于界面的声子传热,改善了热传输。 由于保护层的材料和厚度都对热阻有很大影响,很多研究人员对此展开了研究。Yates等[61]研究了没有保护层、具有5 nm 厚SiN 保护层、具有5 nm 厚AlN 保护层的金刚石/GaN结构的界面热阻,在没有保护层和具有5 nm 厚AlN 保护层的样品中,发现GaN 被刻蚀,导致界面粗糙,使样品的界面热阻增加,界面热阻分别为41.4 +14.0/-12.3 m2·K·GW-1、18.2+1.5/-3.6 m2·K·GW-1,而且在没有保护层的样品中,金刚石与GaN 之间发生了分层。在具有5 nm 厚SiN 保护层的样品中,金刚石与GaN 结合界面清晰,平整光滑,GaN 层完整,没有受到刻蚀,界面热阻最低,为9.5 +3.8/-1.7 m2·K·GW-1。 金刚石异质外延工艺对所制备金刚石层的质量有很大影响,金刚石形核层由于晶粒较小,晶界多,导致其热导率低,如何提高金刚石形核层热导率也是一个问题。Malakoutian等[62]使用快速形核的方法,减小形核层厚度,降低界面热阻。他们使用MOCVD 法在GaN表面沉积5 nm Si3N4 作保护层,而后使用聚合物辅助浸晶技术,使氧终端金刚石颗粒呈-50mv zeta 电位,Si3N4 层表面呈+50 mv zeta 电位,由于金刚石颗粒与Si3N4 层表面巨大的电位差,可以实现金刚石颗粒的高密度播种,最终播种密度大于1012 cm-2。而后放入微波等离子体化学气相沉积(MPCVD)装置中,金刚石在低温度(~700℃)、低功率(600 W)、低腔压(20 Torr)、高甲烷浓度(5%)中10分钟快速形核,形核层厚度仅25-30 nm。形完成后迅速提高功率(1800 W)和腔压(70 Torr),降低甲烷浓度,金刚石生长完成后在氢等离子体环境中缓慢冷却至室温。他们使用这种方法成功制备了金刚石/GaN 结构,如图10 所示。其中保护层的厚度由最初的5 nm 降至仅1 nm,其中1 nm 的Si3N4 层被氢等离子体刻蚀,还有3 nm 转化为热导率更高的SiC,极大地降低了界面热阻(3.1±0.7 m2·K·GW-1)。 对于异质外延金刚石的形核层质量差、热导率低的问题,Smith 等[63]也提出了一种混合金刚石晶粒播种的方法,他们使用MOCVD 分别制备了Si 基GaN 和Si 基AlN 两个样品,在两个样品上使用静电喷雾法分两步播种微米金刚石(2±1 μm)和纳米金刚石(3.3±0.6nm),然后使用微波等离子体增强化学气相沉积法(MWCVD)生长了厚金刚石层。发现在Si 基GaN 上金刚石和GaN 大部分在冷却时分层,未分层的样品在结合面也有大的间隙或裂纹,而在Si 基AlN 上金刚石和AlN 结合良好,热面热阻低(1.47±0.35 m2·K·GW-1),远小于仅使用纳米金刚石引晶生长的界面热阻(67±58 m2·K·GW-1)。这种方法制备的金刚石形核层大部分为微米金刚石,纳米金刚石填充了微米金刚石之间的空隙,大幅减小了金刚石形核层热阻,但是没有使用该方法制备GaN/AlN/金刚石结构样品。 金刚石在GaN 上异质外延的过程中,通常是在700~1000℃的高温环境中[64],由于金刚石与GaN 材料之间存在大的热膨胀系数失配,在金刚石生长完成冷却下来后,在金刚石与GaN 之间会存在1 GPa 左右的应力。Jia 等[65]针对热应力的问题,提出了一种GaN 两侧生长金刚石的方法,首先在GaN 上层沉积2 μm 厚Si 层,然后在Si 层上低温、高甲烷浓度沉积低质量牺牲层金刚石,GaN 下层先去除原衬底、沉积SiN 保护层,再沉积高质量金刚石作为散热层。如图11 所示,制备了金刚石-GaN-金刚石结构,牺牲层金刚石和散热层金刚石共同分担了GaN 收缩带来的应力,在经过退火、去除Si 层和牺牲层金刚石后,GaN/金刚石结构的GaN 层有0.5 GPa 的张应力,GaN转移过程产生的应力得到有效缓解。 2.3 GaN 异质外延金刚石技术 2.3.1 金刚石钝化层散热技术 金刚石钝化散热技术,是一种利用金刚石薄膜替换原有源区的传统钝化层SiNx 的技术,使用金刚石包覆器件层,可以显著提高器件的性能。相比其他技术,这项技术的优势在于金刚石层与热源接近,散热效率更高,但是存在金刚石层生长工艺与GaN 器件层工艺的兼容性问题。 金刚石钝化层通常使用异质外延的方法制备,如MPCVD、HFCVD 等,这些方法都需要高温、富氢等离子体的条件,GaN 会发生严重的刻蚀、分解,为了解决这个问题,通常在GaN 器件层上沉积保护层用于保护GaN,且由于金刚石和GaN 之间大的热膨胀失配,需要在低温环境中沉积金刚石钝化层,减小应力。例如Yaita 等[66]在沉积金刚石钝化层之前,先沉积40 nm 厚SiNx 层,然后在700℃的温度下沉积了2.5 μm 厚金刚石钝化层,由于沉积温度较低,金刚石层热导率仅为200 W·m-1·K-1,但是GaN 器件的热阻也从12.7mm·K·W-1 降低到7.4 mm·K·W-1,降幅达到了41.7%。国内南京电子器件研究所Guo 等[67]进一步优化工艺,采用栅前金刚石的方法,使用三步金刚石刻蚀技术和20 nm SiN 保护层,成功在GaN HEMTs 器件的顶端制备了500 nm 厚的金刚石钝化散热层,其结构示意图如图12 所示。经测试,金刚石/GaN HEMTs 的热阻比传统SiN/GaN HEMTs 低21.4%,截止频率为34.6 GHz,比SiN/GaN HEMTs 提高了1.8%,尤其是电流电压(VGS=1 V)和小信号增益(10 GHz)分别提高了27.9%和36.7%。金刚石钝化散热技术具有很好的应用前景,但是制备过程需要高精度的加工工艺,同时,由于金刚石膜沉积温度低,导致其晶体质量不高,无法发挥金刚石导热率高的优势,在实际应用中还需要进一步研究和改进。 2.3.2 金刚石衬底外延技术 随着金刚石制备技术不断发展和完善,金刚石衬底异质外延GaN 技术也被用于改善散热需求,和金刚石衬底异质外延GaN 技术一样,也存在热膨胀系数失配和晶格失配的问题,会使GaN 外延层发生剥离或破裂,需要使用额外的缓冲层缓解失配。同时金刚石衬底质量对GaN 外延层质量也有很大影响,现在常用(111)单晶金刚石作为衬底,但是成本昂贵[68-71]。Pantle 等[72]研究了单晶金刚石取向和缓冲层对GaN 质量的影响,在(111)单晶金刚石、(001)单晶金刚石和具有AlN 缓冲层的(001)单晶金刚石上使用MBE 工艺选择性沉积了GaN 纳米线,GaN 纳米线结构如图13所示,在(111)金刚石上生长的GaN 纳米线具有一致的形貌,在(001)金刚石上生长的GaN 纳米线有多重形核和聚结。在具有AlN 缓冲层的(001)金刚石上生长的GaN纳米线有最一致的形貌,表面光滑,生长偏转角度小,且缺陷最少。Xu 等[73]在多晶金刚石上使用MOCVD 工艺生长了GaN 薄膜,发现在具有2.5 nm h-BN 插入层和1000℃低温AlN 层时GaN 层晶体质量最好,相比没有h-BN 插入层的GaN 层,其表面光滑,(002)摇摆曲线的半峰全宽从4.67°降低到1.98°。 Ahmed 等[74-75]结合了GaN 异质外延金刚石技术和金刚石异质外延GaN 技术,工艺步骤如图14 所示,在GaN 表面使用PECVD沉积一层SiNx 保护层,而后使用纳米金刚石颗粒和光刻工艺相结合的方法,经过纳米金刚石加光刻胶旋涂、UV 曝光、显影、干燥和刻蚀过程,在SiNx 保护层上选择性的沉积多晶金刚石层,刻蚀掉未被金刚石覆盖的SiNx 保护层部分,暴露出GaN 层,之后在暴露的GaN 和金刚石上层使用优化的MOCVD 工艺横向外延过生长GaN 层,再生长GaN 层质量比原始GaN有更好的结晶度和更低的缺陷密度,但是由于金刚石与GaN 热膨胀系数失配较大,以及多晶金刚石的粗糙表面(RMS>30 nm),在金刚石与GaN 之间出现了图15 所示的孔隙和空洞,进一步降低金刚石表面粗糙度也许可以获得完全结合的界面。 3.总结与展望 本文对近年来金刚石和GaN 集成技术的研究现状进行了详细的综述。GaN 功率器件的高功率密度优势受制于其衬底材料的低导热率而无法完全发挥,使用高导热率的金刚石作为器件的钝化层和衬底层,可以大幅度降低器件热点温度,提高器件功率密度。对于各种金刚石与GaN 的集成技术,优缺点以及可能的改进方法可以总结为以下几点: (1)键合技术的优势在于键合温度普遍较低,键合后金刚石与GaN 之间热应力小,对GaN 层的热损伤小,缺点在于键合层质量、厚度难以控制导致键合层热阻高,且大尺寸、低粗糙度、低弯曲度的金刚石获取成本高,可以从键合后处理工艺以及键合设备开发入手,还需要优化金刚石精密抛光工艺,探索新型金刚石表面处理技术; (2)GaN 异质外延金刚石技术优势在于金刚石与GaN 结合强度高,结合均匀性好,但是由于金刚石与GaN 之间的热膨胀失配和晶格失配,冷却后金刚石与GaN 之间热应力大,可能导致分层或外延层开裂,还存在保护层、金刚石形核层热阻高的问题,可以从金刚石低温沉积以及提高形核层晶粒尺寸、减小形核层厚度入手,同时探索新型保护层材料和保护层制备工艺; (3)金刚石异质外延GaN 技术优势在于可以使用大尺寸高导热率金刚石作为散热层衬底,但是也存在热膨胀失配和晶格失配的问题,同时缓冲层热阻高,可以从开发新型缓冲层或多层缓冲层入手,以减小热应力和缓解晶格失配。 总的来说,目前的金刚石与GaN 集成技术已经大大提升了GaN 的实际应用功率密度,但是仍有许多问题亟需解决。目前的研究大多是对器件层或衬底层单独的研究,将其高效的结合起来,金刚石用于GaN 功率器件的三维散热,将大幅度降低器件温度,有望在未来完全发挥GaN 的高功率密度高频率优势。

    03-03 79浏览
  • 三相异步电动机的结构

    三相异步电动机的结构三相交流异步电动机的主要部件,见下图所示:它主要由定子和转子两大部分组成。定子是指电动机的静止部分。它主要由机座、定子铁心、端盖、定子三相对称绕组等组成。机座通常由铸铁或...

    02-20 32浏览
  • 深入理解Redis网络模型结构及其流程

    1.0 用户空间与内核空间概述 1)用户空间: 用户空间是指运行用户应用程序的内存区域。在这一空间中,应用程序可以执行其代码并处理数据,但不允许直接访问内核空间中的资源或数据结构。 每个用户程序在其独立的地址空间中运行,彼此之间是隔离的。这意味着一个程序不能直接干扰另一个程序的内存或资源。 2)内核空间: 内核空间是操作系统内核所占用的内存区域。内核负责管理硬件资源、进程调度、内存管理、文件系统以及网络协议等核心功能。 内核空间拥有对所有硬件和系统资源的权限,应用程序无法直接访问这一空间。 2.0 Redis 网络模型 Linux 系统为了提高 IO 效率,会在用户空间和内核空间都加入缓冲区: 以上是读数据的过程: 当用户要从网络中读取数据时,首先在用户空间中执行命令来调用内核空间中的命令,因为用户空间的命令不能直接来调用或者使用硬件资源。此时,需要等待内核空间调用命令来从网卡中获取数据,接着,将从网卡中获取到的数据先是拷贝到内核空间中的缓冲区,最后再从内核空间中的缓冲区数据拷贝到用户空间缓冲区中。 简单来说: 1)写数据时:要把用户缓冲数据拷贝到内核缓冲区,然后写入设备。 2)读数据时:要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区。 这一整个过程可以分为两个小过程: 1)等待数据就绪。 2)从内核拷贝数据到用户空间。 因此,通过对以上两个过程不同的处理就演变出不同的方式:阻塞 IO、非阻塞 IO、IO 多路复用、信号驱动 IO、异步 IO 。 2.1 Redis 网络模型 - 阻塞 IO 顾名思义,阻塞 IO 就是两个阶段都必须阻塞等待。 当用户来读取数据时,此时内核还没有准备好数据,那么进程就直接 "硬等",直到内核准备好数据,所以,第一个过程是当前线程阻塞为阻塞状态。由于第一个过程还没获取到数据,还在等待数据,自然而然的,第二个过程也就没有数据拷贝到用户缓冲区中,也就是说,第二个过程同样是阻塞状态。 2.2 Redis 网络模型 - 非阻塞 IO 顾名思义,非阻塞 IO 的 recvfrom 操作会立即返回结果而不是阻塞用户进程。 可以看到,非阻塞 IO 模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制导致 CPU 空转,CPU 使用率暴增。 2.3 Redis 网络模型 - IO 多路复用 无论是阻塞 IO 还是非阻塞 IO,用户应用在一阶段都需要调用 recvfrom 来获取数据,差别在于无数据时的处理方案: 1)如果调用 recvfrom 时,恰好没有数据,阻塞 IO 会使进程阻塞,非阻塞 IO 使 CPU 空转,都不能发挥 CPU 的作用。 2)如果调用 recvfrom 时,恰好有数据,则用户进程可以直接进入第二阶段,读取并处理数据。 比如服务端处理客户端 Socket 请求时,在单线程情况下,只能依次处理每一个 Socket,如果正在处理的 Socket 恰好未就绪,线程就会被阻塞,所有其他客户端 Socket 都必须等待,性能自然会很差。 因此,可以采用 IO 多路复用的方式来解决。 IO 多路复用方式简单来说,就是通过一个用户进程来监视内核中的多个数据,并在某个数据准备好则进行读写处理。 那么用户进程如何知道内核中数据是否就绪呢? 可以通过文件描述符表(File Descriptor):简称 FD,是一个从 0 开始递增的无符号整数,用来关联 Linux 中的一个文件。在 Linux 中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字(Socket)。 因此 IO 多路复用:是利用单个线程来同时监听多个 FD,并在某个 FD 可读、可写时得到通知,从而避免无效的等待,充分利用 CPU 资源。 在 IO 多路复用中,获取到就绪的 FD,然后根据 FD 的信息再来调用 recvfrom 命令,此时,内核中的缓冲区一定会有相对应的数据,直接从内核中拷贝回用户缓冲区即可。 需要注意的是,如果等待数据过程中,没有监听到已就绪的 FD,仍旧是阻塞等待,所以第二个阶段也是处于阻塞状态。不过,概率很小,因为 FD 很多,总有很大可能在短时间内获取到已就绪的 FD。 在 IO 多路复用中,对于如何监听 FD 的方式、通知的方式又有多种实现,常见的有:select、poll、epoll 三种常见的方式。 select、poll、epoll 的主要差异: 1)select 和 poll 只会通知用户进程有 FD 就绪,但不确定具体是哪个 FD,需要用户进程逐个遍历 FD 来确认。 2)epoll 则会在通知用户进程 FD 就绪的同时,把已就绪的 FD 写入用户空间。 2.3.1 IO 多路复用 - select select 是 Linux 中最早的 IO 多路复用实现方案: select 的相关源码: 在源码中,可以看到有 int select() 函数,里面的主要字段: 1)int nfds:要监视的 fd_set 的最大 FD + 1,也就是集合中有多个 FD,按照顺序从 1 到 1024 存放在集合中,nfds 表示监视的范围从 0 到最大的 FD + 1 之内。 2)fd_set *readfds:需要监视读事件的 FD 集合。 3)struct timeval *timeout:监听的超时时间,null 表表示永不超时、0 表示不阻塞等待、大于 0 表示等待的时间。 fd_set:表示一种类型,实际就是一个整型数组,数组大小是固定为 1024 个比特位。每一个 bit 表示一个 FD,0 表示未就绪,1 表示已就绪。 select 具体监听 FD 的过程: 在用户进程中创建一个 fd_set 集合,也就是一个 1024  比特大小的整型数组,再收集需要监听的 FD 并且存放在该数组中,接着调用 select() 方法,开始监听:首先将收集好 FD 数组拷贝到内存缓冲区中,接着在内核空间对该数组进行遍历查看是否有相对应的数据,如果一个都没有找到就绪的 FD,则休眠,直到等到的数据已就绪或者超时就会被唤醒,假设 FD = 1 数据就绪了,接着将对应的 FD 设置为 1,其他设置为 0,再拷贝回用户缓冲区中,最后再由用户空间对数组进行遍历,找到就绪的 FD,调用 recvfrom 命令获取数据。 select 模式存在的问题: 1)需要将整个 fd_set 从用户空间拷贝到内核空间,select 结束还要再次拷贝回用户空间。 2)select 无法得知具体哪个 fd 就绪,需要遍历整个 fd_set 。 3)fd_set 监听的 fd 数量不能超过 1024 。 2.3.2 IO 多路复用 - poll poll 模式对 select 模式做了简单改进,但性能提升不明显,部分关键代码如下: poll() 函数的主要字段: 1)struct pollfd *fds:是一个 pollfd 类型的数组,pollfd 的内部结构封装了要监听的 FD、events 要监听的事件类型、revents 实际发生的事件类型。该数组可以自定义大小,这解决了 select 模式中能最大监听 1024 个 FD 的问题。 2)nfds_t nfds:数组元素个数。 3)int timeout:超时时间。 poll 模式监听 FD 的具体流程: 1)创建 pollfd 数组,向其中添加关注的 fd 信息,数组大小自定义。 2)调用 poll 函数,将 pollfd 数组拷贝到内核空间,转链表存储,无上限。 3)内核遍历 fd,判断是否就绪。 4)数据就绪或超时后,拷贝 pollfd 数组到用户空间,返回就绪 fd 数量 n 。 5)用户进程判断 n 是否大于 0 。 6)大于 0 则遍历 pollfd 数组,找到就绪的 fd 。 与 select 对比: 1)select 模式的 fd_set 大小固定为 1024,而 pollfd 在内核中采用链表,理论上无上限。 2)监听 FD 越多,每次遍历消耗时间也越久,性能反而会下降。 需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享 2.3.3 IO 多路复用 - epoll epoll 模式是对 select 和 poll 的改进,提供了三个函数。 相关源码如下: 1)int epoll_create():会直接在内核创建 eventpoll 结构体,一颗红黑树,用来记录要监听的 FD,另一个链表,用来记录已就绪的 FD。返回对应的句柄 epfd,用来标记。 2)int epoll_ctl():该方法主要是将要监听的 FD 添加到内核中的红黑树中。 主要参数是 epfd,记录 epoll 实例的句柄;op,要执行的类型,包含:ADD、MOD、DEL;fd,要监听的 FD; 3)int epoll_wait():该方法主要是用来等待接收已就绪的 FD。 主要参数是 epfd,eventpoll 实例的句柄;*events,用来接收已就绪的 FD;maxevents,数组的最大长度;timeout,超时时间; epoll 模式监听 FD 的具体流程: 首先在内核中创建一颗红黑树,用来接收需要监听的 FD 和一个接收已就绪的链表。接着用户进程会将需要监听的 FD 直接添加到红黑树中,并且设置  ep_poll_callback,当 callback 自动触发时,就把对应的已就绪的 FD 从红黑树加入到链表中。此时,list_head 就会通知用户进程来接收链表中已就绪的 FD,将其拷贝到 events 数组中,此时的数组中为已就绪的 FD,可以直接知道已就绪的 FD,不需要遍历。最后就可以根据已就绪的 FD 来调用 recvfrom 命令来获取数据了。 小结: 1)select 模式存在的三个问题: 每监听的 FD 最大不超过 1024 。 每次 select 都需要把所有要监听的 FD 拷贝到内核空间。 每次都要遍历所有 FD 来判断就绪状态。 2)poll 模式的问题: poll 利用链表解决了 select 中监听 FD 上限的问题,但依然要遍历所有 FD,如果监听较多,性能会下降。 3)epoll 模式中如何解决这些问题: 基于 epoll 实例中的红黑树保存要监听的 FD,理论上无上限,而且增删改查效率都非常高,性能不会随监听的 FD 数量增多而下降。 每个 FD 只需要执行一次 epoll_ctl 添加到红黑树,以后每次 epol_wait 无需传递任何参数,无需重复拷贝 FD 到内核空间。 内核会将就绪的 FD 直接拷贝到用户空间的指定位置,用户进程无需遍历所有 FD 就能知道就绪的 FD 是谁。 2.3.4 epoll 的 ET 和 LT 模型 当 FD 有数据可读时,调用 epoll_wait 就可以得到通知。但是事件通知的模式有两种: 1)LevelTriggered:简称 LT 。当 FD 有数据可读时,会重复通知多次,直到数据处理完成。是 epoll 的默认模式。 具体流程: 当内核中的链表中存在已就绪的 FD,那么就会通知多次给用户进程来获取数据到 events 集合中,可能数据量比较大,一次性拷贝不了全部数据,那么就会分多次进行拷贝,在这个阶段,LT 模式会重复多次发送通知给用户来拷贝数据,这个过程中,链表中的数据是不会删除,依旧会在链表中存储,直到数据处理完成。 2)EdgeTriggered:简称 ET。当 FD 有数据可读时,只会被通知一次,不管数据是否处理完成。 具体流程: 在内核中链表已存储就绪的 FD 时,就会给用户进程发送一次通知,当用户进程来拷贝数据的时候,链表中的 FD 就会自动删除,不管数据是否处理完毕。 举个例子: 假设一个客户端 socket 对应的 FD 已经注册到了 epoll 实例中,客户端 socket 发送了 2kb 的数据,服务端调用 epoll_wait,得到的通知说 FD 就绪。服务端从 FD 读取了 1kb 数据,接着再次调用 epoll_wait,形成循环。 如果采用 ET 模式,每一次调用 epoll_wait 函数服务端都会得到通知来读取剩下的数据。 如果采用 LT 模式,只有第一次调用 epoll_wait 函数的时候服务端才会得到通知,循环再次调用 epoll_wait 函数时,不再会发送通知给服务端,那么剩下的数据就会丢失。 解决方案: 1)在第一次拷贝数据完之后,再继续调用添加 FD 的函数,则在红黑树中的已就绪的 FD 又会再次拷贝到链表中,继续下一次通知用户进程。 2)在第一次拷贝的时候,使用循环,将其数据全部拷贝完毕。 小结: ET 模式避免了 LT 模式可能出现的惊群现象。 ET 模式最好结合非阻塞 IO 读取 FD 数据,相比 LT 会复杂一些。 2.3.5 基于 epoll 的服务端流程 基于 epoll 模式的 web 服务的基本流程: 首先调用 epoll_create 函数创建实例,在内核中创建一颗红黑树,接收需要监听的 FD 和一个链表,接收已就绪的 FD。 接着创建服务端 serverSocket 并将得到的 FD 添加到红黑树中进行监听,调用 epoll_ctl 函数进行添加 FD 到红黑树中,还要给注册 ep_poll_callback,当 FD 就绪时,将就绪的 FD 记录到链表中。 再接着就是调用 epoll_wait 函数进行等到链表中已就绪的 FD,此时会进行休眠,当超时或者被通知的时候,线程就会被唤醒,如果时超时导致的唤醒,证明目前链表中还没得到已就绪的 FD,那么就需要继续调用 epoll_wait 函数继续等待;如果被通知链表中存在就绪的 FD 被唤醒的时候,就会接着判断事件类型。 此时,当服务端的 FD 已就绪时,则 serverSocket 接收到 socket 客户端,将对应的 scoket 的 FD 添加到红黑树中,并且注册 ep_poll_callback,对客户端进行监听。 对客户端进行监听,当监听到了客户端的 FD 状态已就绪了,则说明有请求发送到服务端中,接着就会通知给用户进程,得到对应的 FD,再接着调用 recvfrom 命令来读取内核中的请求数据。 这就是基于 epoll 模式的 web 服务的基本流程。 2.4 Redis 网络模型 - 信号驱动 IO 信号驱动 IO 是与内核建立 SIGIO 的信号关联并设置回调,当内核有 FD 就绪时,会发生 SIGIO 信号通知用户,期间用户应用可以执行其他业务,无需阻塞等待。直到数据就绪,递交 SIGIO 信号,也就是得到通知,告诉用户进程,数据准备好了,可以从内核缓冲区获取了。 在第一阶段,也就是等待数据是不阻塞的,进程可以执行其他业务,而第二个阶段,拷贝数据是阻塞的。 信号驱动 IO 存在的问题: 当有大量 IO 操作时,信号较多,SIGIO 处理函数不能及时处理可能导致信号队列溢出而且内核空间与用户空间的频繁信号交互性能也较低。 2.5 Redis 网络模型 - 异步 IO 异步 IO 的整个过程都是非阻塞的,用户进程调用完异步 API 后就可以去做其他事情,内核等待数据就绪并拷贝到用户空间后才会递交信号,通知用户进程。 可以看到,异步 IO 模型中,用户进程在两个阶段都是非阻塞状态。 3.0 Redis 单线程及多线程网络模型 3.1 经典面试题:Redis 是单线程还是多线程? 1)如果对于 Redis 的核心业务部分,也就是命令处理的部分,Redis 就是单线程。 2)如果对于 Redis 整体来说,那么 Redis 就是多线程。 在 Redis 版本迭代过程中,在两个重要的时间节点引入了多线程的支持: 1)Redis v4.0:引入多线程异步处理一些耗时较长的任务,例如异步删除命令 unlink。 2)Redis v6.0:在核心网络模型中引入多线程,进一步提高对于多核 CPU 的利用率。 3.2 经典面试题:为什么 Redis 要选择单线程? 1)抛开持久化不谈,Redis 是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,因此多线程并不会带来巨大的性能提升。 2)多线程会导致过多的上下文切换,带来不必要的开销。 3)引入多线程会面临线程安全问题,必然要引入线程锁这样的安全手段,实现复杂度增高,而且性能也会大打折扣。 3.3 Redis 网络模型的结构及具体流程 1)Redis 网络模型流程:第一个阶段 创建 serverSocket 服务端,调用 aeEventLoop 在内核中创建红黑树和链表,接着将服务端的 FD 添加到内核的红黑树中进行监听,再接调用 aeApiPoll 函数进行等待数据。一旦监听到服务端的 FD 就绪,就会调用 tcpAccepthandler 连接处理器,简单来说,该处理器就是将连接到服务端的客户端的 FD 添加到内核红黑树中进行监听处理。 如果是已经添加到红黑树的客户端的 FD 已就绪了,就会将 FD 添加到链表中,且通知用户进程来获取 FD,再接着使用 recvfrom 命令来获取该客户端的请求数据。因此,readQueryFromClient 命令请求处理器的作用是:读取请求数据。 2)Redis 网络模型流程:第二个阶段 对于 readQueryFromClient 命令请求处理器的作用是请求数据,那么具体是如何读取的呢? 相关的源代码: 可以看到里面有三个函数,readQueryFromClient() 调用了 processCommand(),而 processCommand() 调用了 addReply() 。 该函数具体的任务: 1)readQueryFromClient():获取当前客户端,客户端中有缓冲区用来读和写。读取请求数据到缓冲区和解析缓冲区字符串,转为 Redis 命令参数存入到数组中。 2)processCommand():根据命令名称,寻找对应的 command,执行命令,得到结果。 3)addReply():尝试把结果写到客户端缓存区,如果 buf 写不下,则写到 reply,这是一个链表,容量无上限。再接着将客户端添加到队列中,等待被写出。 具体结构如下: 在读取客户端发送过来的请求过程中,是 IO 操作。 3)Redis 网络模型流程:第三个阶段 现在结果已经存在到队列中了,等待被读取,也就是输出到对应的客户端,该过程进行 IO 操作。 相关源码如下: 在等待数据之前,会调用 beforeSleep 函数,监听 socket 的 FD 读事件,并且绑定写处理器,可以把响应写到客户端 socket 。一旦客户端写操作 FD 就绪了,则将队列中的结果写入到客户端中。 Redis 网络模型单线程的最终形态: Redis 6.0 版本引入了多线程,目的是为了提高 IO 读写效率。因此解析客户端命令、写响应结果时采用多线程。而核心的命令执行 IO 多路复用模块依然是由主线程执行。 Redis 网络模型多线程的最终形态: 在单线程的基础上,在 IO 读写步骤中添加多线程进行操作:在读取请求数据、解析数据的步骤、将队列中的结果写入到对应的客户端中,这些步骤都是可以使用多线程进行操作。

    02-18 136浏览
  • 一文带你全面了解光耦及光耦设计全攻略

    光半导体的类型 光半导体的类型如下: (1)发光器件・・・可见光LED、红外LED、紫外LED、激光二极管 (2)受光器件・・・光传感器、太阳能电池、CMOS传感器 (3)复合器件(发光元件与受光元件的组合)・・・光耦、光纤耦合器 LED的发光原理 发光二极管(LED)的发光原理是向化合物半导体的pn结施加正向电流。 当正向电流通过发光二极管时,载流子(电子和空穴)移动。p型区的空穴向n型区移动,n型区的电子向p型区移动。注入的载流子重组,重组前后的能量差将以光的形式释放出来。发射光取决于化合物半导体的能隙(Eg)。 (备注:传统的硅二极管不发光,因为重组能量变成了热能。) LED的波长范围 LED发射不同波长的紫外光乃至红外光。发射波长将通过下面采用化合物半导体材料能隙(Eg)的等式进行表示。 λ(nm)=1240/Eg(eV) 具有较大能隙的材料发射较短的波长,具有较小能隙的材料发射较长的波长。 对于应用于电视遥控器等的红外LED,使用GaAs(砷化镓)材料;对于红色/绿色指示器LED,使用GaP(磷化镓)或InGaAlP(磷化铝镓铟);对于蓝色LED,使用InGaN(氮化铟镓)或GaN(氮化镓)。 不同材料LED的发光颜色(按材料) 什么是光耦? 光耦是将发光二极管(LED)和光电探测器集成于一个封装中的器件。与其它光学器件不同,光不会发射至封装外。其外观类似于非隔离器/固态继电器。虽然光耦是一种光学器件,但它不处理光,而是处理电信号。 光耦的操作示例: (1)LED接通(0⇒1)。 (2)LED光进入光电晶体管。 (3)光电晶体管接通。 (4)输出电压改变0⇒1。 (1)LED关断(1⇒0)。 (2)LED光停止进入光电晶体管。 (3)光电晶体管关断。 (4)输出电压改变1⇒0。 为什么需要光耦? 在光耦中,原边(LED侧)和副边(受光器件侧)是电绝缘的。因此,即使原边和副边的电位(甚至GND电位)不同,也可以将原边电信号传输到副边。 如右图所示的逆变器应用中,控制单元(如微控制器)通常在低直流电压下工作。另一方面,IPM和IGBT将驱动高电压负载(比如需要200V交流电)。高压系统部件可通过耦合器直接由微控制器控制。 光耦的类型 LED用于光耦的输入。另一方面,有各种器件可用于输出。 晶体管输出 光电晶体管是一种探测器。也可使用达林顿类型。 IC输出 我们有光电二极管作为受光器件的产品、逻辑等输出产品、用于IGBT和MOSFET栅极驱动的大电流输出产品、以及隔离放大器等高功能产品。 双向可控硅/晶闸管输出 光电晶闸管或光电可控硅用于输出。它们主要用于交流线路的控制。 光继电器(MOSFET输出) 光伏阵列(光电二极管阵列)驱动MOSFET的栅极来打开/关闭输出。通过这种操作,它可以用作MOSFET输出的继电器开关。 光耦的类型(封装) 光耦必须具有符合安全标准的封装形状和介电强度。根据安全标准进行设计时,需要检查以下各项。 绝缘爬电距离 两个导体之间沿绝缘体表面的最短距离(原边和副边)。 间隙 通过空气测量的两个导体之间的最短距离。 绝缘厚度 两个导体之间绝缘体的最小距离。 隔离电压 两个导体之间的隔离电压 * 根据UL规定,即使施加1分钟也不会破坏绝缘的交流电压。 光耦的类型(内部结构) 由于要求的绝缘性能、封装尺寸和内部芯片尺寸等限制,光耦具有不同类型的内部封装结构。 单模透射型: 框架式LED和框架式光电探测器采用面对面的模压封装。LED与光电探测器之间的透光部件采用硅树脂材料。 带膜的单模透射型: 为了提高隔离电压,可在LED和光电探测器之间插入聚酰亚胺薄膜。 双模透射型: 在这种透射结构中,内模为白色,外模为黑色。红外线透光率高的树脂用于透光部件的白色模具。 反射型: 框架式LED和框架式光电探测器位于同一平面。LED光在硅树脂中反射并到达光电探测器。因此,它被称为反射型。 光耦的安全标准 将光耦安装在电气设备中以保护人体免于触电时,光耦可能需要遵守不同安全标准方面的规定。 现行有各种确保安全的法规和标准。 从设计和制造的角度来看,安全标准可分为“设定标准”和“零件标准”。 设定标准是设计和制造电视机、录像机和电源装置等设备的基础。“整机标准”根据设备类型、隔离方法及其等级、驱动电压等不同而异。 此外,绝缘部分必须保持的项目(介电强度(绝缘电压)、爬电距离、间隙等)被指定为“零件标准”。 主要安全标准 零件标准 UL1577(cUL) 隔离电压标准(1分钟) 批准组织:UL(美国保险商实验室公司) EN60747-5-5 最大工作隔离电压和最大过压标准 批准组织:VDE(德国电气工程师协会) 批准组织:TUV(技术监督协会) 整机标准 (批准组织:BSI(UK)SEMKO(瑞典)等) EN60950 电信网络设备(工作站、PC机、打印机、传真电阻器、调制解调器等)标准 EN60065 家用电器设备标准(电视、收音机、录像机等) 光耦的特性(电流传输比:CTR) 晶体管耦合器的电流传输比:它是用输出电流相对于输入电流的放大率来表示的,比如晶体管hFE。 电流传输比=CTR=IC/IE=输出(集电极)电流/输入电流×100(%) 例如: 当输出是IF=5mA时,得到IC=10mA。 CTR=IC /IF=10mA/5mA×100(%)=200% 光耦的主要特性(触发LED电流) 触发LED电流”是指“触发状态发生变化的LED电流”。 IFT, IFH, IFLH, IFLH等用作符号。 规格书中显示的触发LED电流表示了产品保证的电流值。为了稳定运行,设计人员在设计时必须保证至少有触发LED电流(最大值)流动。 输入LED电流IF从0mA逐渐增大,如果输出在1mA时切换到导通状态,则IFT=1mA。 在下面的规格书中,将输出切换到导通状态所需的IF 最大值为3mA。 触发LED电流是电路设计和使用寿命设计的重要项目。 光耦的老化变化数据 光耦的老化变化数据 发光元件(LED)的光输出会随时间的推移而减弱。在光耦中,LED光输出的老化变化比受光器件的老化变化更为明显。因此,设计人员需要利用所采用的光耦的老化变化数据来估计发光等级的降低趋势。设计人员将根据使用设备的使用环境和LED的总工作时间来计算LED的光输出变化。必须将该值反映在LED正向电流(IF)的初始值中。 *例如,当占空比(发光持续时间)为50%,工作时间为1000小时,则计算总运行时间为500小时。 左图显示了LED光输出老化变化数据。 右图显示了LED光输出低于某一标准时的运行时间。 例如,左图中的A点和右图中的B点显示了相同条件下的老化变化。(IF=50mA,Ta=40℃,8000小时) 如何使用光耦 这些问题将在下一页通过以下步骤进行解释。 第1步:设计LED输入电流IF及输入侧电阻RIN 第2步:根据IF和CTR计算输出电流 第3步:设计输出侧电阻RL 第4步:检查每个设计常数 如何使用光耦“输入电流” 第1步:设计LED输入电流和输入电阻RIN。 光耦的输入电流(IF)是多少? 它将由(1)输入电源电压(5V),(2)限流电阻(RIN)和(3)LED正向电压(VF)决定。 根据规格示例,确定限流电阻和输入电流(IF)。 RIN=(VCC-VF)/IF=(5V-1.3V)/10mA=370Ω 如何使用光耦“输出电流” 第2步:根据IF和CTR计算光电晶体管的输出电流。 光耦的输出电流(IC)值是多少? 根据电流传输比(IC/IF)计算输入电流(IF)=10mA时输出电流(IC)的变化。从IC-IF曲线可以看出IC=10mA(@IF=5mA,VCE=5V,Ta=25℃)。所以这个样本的CTR是200%, 这与当BL最小时的值相同(200%到600%)。还可以看到IC=20 mA(@IF=10 mA、VCE=5V、Ta=25℃)。 接下来,我们用这里得到的IC值来推导RL。在这项计算中,设计RL的值,使得VCE即使在IC值最小时也成为饱和电压。 如何使用光耦“输出侧电阻器” 第3步:设计输出侧电阻RL 根据输出晶体管的IC-VCE特性确定RL。为了用于信号传输,必须完全满足连接到负载侧的器件的“L”电平。 这里,我们设置VCE=0.3V作为目标值。 当RL=1kΩ时,IF=10mA,VCE=0.9V,这无法满足目标值。当RL=2kΩ时,VCE=0.2V左右,这可以满足目标值。因此,选择RL=2kΩ。在实际设计中,还必须考虑负载侧的阻抗。 如何使用光耦检查 第4步:检查每个设计常数 考虑工作温度、速度、使用寿命设计、电阻公差等是否具有足够的裕度。 温度范围 ⇒ VF,CTR,允许电流等。 负载电阻 ⇒ 开关速度,暗电流的影响等。 确认器件使用寿命 光耦的输入LED光输出会随着时间的推移而减弱。 必须确认特性满足要求,同时必须考虑器件在使用寿命目标期间的退化趋势。 光耦的老化变化可以根据输入电流(IF)和环境温度来计算。 文章转载来源网络,仅供学习使用,侵删。

    02-14 344浏览
  • 用FPGA实现Aurora 8B/10B接口(1)—— 什么是Aurora 8B/10B协议?

    1、概述\x26amp;nbsp; \x26amp;nbsp; \x26amp;nbsp;Aurora 8B/10B协议是一个用于在点对点串行链路间

    02-07 240浏览
  • PPT详解:纯电动汽车整车控制策略

    本文来源:汽车电子学堂

    02-07 211浏览
  • 热拔插烧芯片怎么办?

    “热拔插”其实就是带电拔插,对于日常使用的电子产品来说,很多时候我们都是经常性的带电拔插,比如路由器,手机,平板,笔记本电脑等等,你很难做的到,说先把充电线接上设备,然后再去插220V的排插,反正我日常使用不会去刻意纠结这个顺序,咋方便咋来。那“热拔插”由于是带电拔插,所以就会在接触的那一瞬间,会产生尖峰电压,这对板子上的器件是致命的。那该如何改善这一情况呢?(1)加RC缓冲电路,如下图所示:具体的选值,需要先测出电路中尖峰的频率(本质上和LC振荡区别不大),再根据公式:C是电路中的杂散电容。L是电路中的等效电感。详细的计算过程我们后面在反激电源RC吸收电路中再展开讲。 (2)加TVS或者ESD,这个方式是比较简单,具体的选型要求可以翻看下面这两篇文章:TVS管几点选型经验!从SD卡电路中学习如何使用ESD管防静电 (3)复合防护电路,主要由TVS管和压敏,安规电容组成,效果较好,但成本稍高!好了,就先写到这吧!

    01-09 186浏览
  • 详解linux中mmap的底层原理及应用

    一、mmap在linux系统哪里? 二、什么是mmap? 通过上图我们可以知道mmap是操作硬件设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED)、LCD控制器、磁盘控制器,实际上就是往设备的物理地址上读写数据。 但是,由于应用程序不能直接操作设备硬件地址,所以操作系统提供了这样一种机制——内存映射,把设备地址映射到进程虚拟地址,mmap就是实现内存映射的接口。 操作设备还有很多方法,如ioctl、ioremap等。 mmap的好处就是,mmap把设备内存映射到虚拟内存,则用户操作虚拟内存相当于在直接操作设备,省去了用户空间到内核空间的复制过程,相对IO操作来说,增加了数据的吞吐量。 三、什么是内存映射? 既然mmap是实现内存映射的接口,那么内存映射是什么呢?如下图所示: 每个进程都有独立的进程地址空间,通过页表和MMU,可以将虚拟地址转换为物理地址,每个进程都有独立的页表数据,这就可以解释为什么两个不同进程相同的虚拟地址却对应着不同的物理地址。 四、mmap在文件内存映射上的应用 在unix/linux平台上读写文件,一般有两种方式。分别为open系统调用函数和mmap内存映射。 1.open系统调用 首先open文件,接着使用read系统调用函数来读取文件。于是内核将文件的内容从磁盘上读取到内核页高速缓存中(主存RAM中),再从内核高速缓存读取到用户进程的地址空间。这么做需要在内核和用户空间之间做多次数据拷贝。而且当多个进程同时读取一个文件时,那么每个进程在自己的页表中都有这个文件的副本并且都对应到物理内存中各自的副本,这样便造成了物理内存的浪费。如下图所示: 2.mmap内存映射 mmap是一种内存映射的方法,就是将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。如下图所示: 五、linux内存描述符 了解了内存映射的概念后,那么内核是怎么管理这些地址空间的呢?任何复杂的理论最终也是通过各种数据结构体现出来的,而这里使用的数据结构就是进程描述符。从内核角度看,进程是分配系统资源(CPU、内存)的载体,为了管理进程,内核必须对每个进程所做的事情进行清楚的描述,这就是进程描述符。内核用task_struct结构体来表示进程,并且维护一个该结构体链表来管理所有进程。该结构体包含一些进程状态、调度信息等上千个成员,我们这里主要关注进程描述符里面的内存描述符(struct mm_struct mm)。内存管理如下图所示: 现在已经知道了内存映射是把设备地址映射到进程空间地址(注意:并不是所有内存映射都是映射到进程地址空间的,ioremap是映射到内核虚拟空间的,mmap是映射到进程虚拟地址上的),实质上是分配了一个vm_area_struct结构体加入到进程的地址空间,也就是说,把设备地址映射到这个结构体,具体到映射的过程就是驱动程序要做的事情了。vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用到的信息,都可以从vm_area_struct中获得。并且如上图所示vm_area_struct结构通常是用链表的形式保存以方便进程快速的访问。 六、mmap内存映射原理 1.内存映射的步骤 用open系统调用打开文件,并返回文件描述符fd; 用mmap建立内存映射,并返回映射首地址指针start; 对映射文件进行各种操作,可以用指针偏移的方式进行读写; 用munmap关闭内存映射; 用close系统调用关闭文件描述符fd。 2.mmap内存映射的实现过程,总的来说可以分为三个阶段 (一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域 进程在用户空间调用库函数mmap,原型为:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址; 为此虚拟区域分配一个vm_area_struct结构体,接着对这个结构体的各个字段进行初始化; 将新建的虚拟区域结构体(vm_area_struct)插入到进程的虚拟地址区域链表或树中。 (二)调用内核空间的mmap函数(不同于用户空间的mmap函数),实现文件物理地址和进程虚拟地址的一一映射关系 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关的各项信息。 通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),非用户空间的mmap函数; 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址; 通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。(本质上,用户进程的虚拟地址映射到磁盘文件中间还是需要经过物理内存的,也就是说进程操作虚拟地址会对应到物理地址上,物理内存再与磁盘交互数据) (三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存的拷贝 注:前两个阶段仅在于创建虚拟区域并完成地址映射,但是并没有将任何文件数据拷贝至主存,真正的文件读取是当进程发起读或写操作时。 进程的读或写操作访问虚拟地址空间这一段地址映射,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。 之后进程即可对这片主存进行读或写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应的磁盘地址,也即完成了写入文件的过程。 修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步,这样所写的内容就能立即保存到文件里了。 3.mmap和常规文件读写的区别 简单介绍一下常规文件系统操作(调用read/fread等函数),函数的调用过程是: 进程发起读写文件请求; 内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode; inode在address_space上查找要请求的文件页是否已经缓存在内核页缓存中。如果存在,则直接返回这片文件页的内容; 如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到内核页缓存。之后再次发起读页面的过程,进而将内核页缓存中的数据发给用户进程。 总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了内核页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到内核页缓存中,由于内核页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将内核页缓存中的数据页再次拷贝到用户进程对应的物理内存中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间是不能直接访问的,必须要先拷贝至内核空间对应的主存,再写回磁盘中(系统回延迟写脏页面),也是需要两次数据拷贝。 而使用mmap操作文件时,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入用户进程的内存中,供进程使用。 总而言之,常规文件操作需要从磁盘到内核页缓存再到用户进程物理内存的两次数据拷贝。而mmap操作文件,只需要从磁盘到用户进程物理内存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间到内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程,因此mmap效率更高。 七、mmap函数说明 头文件 #include 创建内存映射mmap void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 参数说明: addr: 入参,如果这个地址为null那么内核将自己为你指定一个地址,如果不为null,将使用这个地址作为映射区的起始地址 length:映射区的大小(<=文件的大小) prot: 访问属性,一般用PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE flags:这个参数是确定映射的更新是否对映射相同区域的其他进程可见,以及是否对基础文件进行更新 MAP_SHARED:共享此映射,映射的更新对映射相同区域的其他进程可见 MAP_PRIVATE: 创建写时专用拷贝映射,映射的更新对映射的其他进程不可见,相同的文件,并且不会传递到基 础文件。 我们一般用MAP_SHARED,这两个权限是限制内存的,而不限制文件 fd:被映射的文件句柄 offset:默认为0,表示映射文件全部。偏移未知,需要时4K的整数倍。 返回值:成功:被映射的首地址 失败:MAP_FAILED (void *)-1 释放内存映射 int munmap(void *addr, size_t length); 参数说明: addr: 被映射的首地址 length: 映射的长度 返回值:0:成功 -1:失败 八、mmap的基础使用 #include #include #include #include #include #include int main(int argc, const char *argv[]){ char *p = NULL; int fd = -1; // 打开文件 fd = open("temp", O_RDWR|O_CREAT|O_TRUNC, 0644); if (-1 == fd) { printf("文件打开失败...\n"); return -1; } // 因为我们文件不能是一个0大小的文件,所以我们需要修改文件的大小 // 有两种方式:leek,write,或者ftruncate都可以 /* // 改变一个文件的读写指针 lseek(fd, 1023, SEEK_END); // 写入一个结束符\0 write(fd, "\0", 1); */ // 我们还是用这种,比较方便,直接就修改了,和上面效果一样 ftruncate(fd, 1024); // 创建一个内存映射,让内和指定一个映射地址,大小为1024,可读可写,共享,映射到这个fd上 p = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { printf("mmap failed\n"); close(fd); return -1; } // 拿到地址之后我们就可以像操作普通地址一样写数据,读数据了,例如memcpy,strcpy等等 memcpy(p, "hello world", sizeof("hello world")); // 读数据 printf("p = %s\n",p); // 最后释放这个映射 if (munmap(p, 1024) == -1) { printf("munmap failed\n"); close(fd); return -1; } close(fd); return 0;} gcc mmap.c 进行编译得到可执行文件a.out./a.out 可以得到执行结果p = hello world 然后看当前文件夹下会出现一个temp的文件 我们直接用cat命令进行输出: 我们会发现其实是和程序输出的一样的,到这里,基本使用就结束了。 九、mmap的使用注意事项 1.能使用创建出来的新文件进行映射吗? 答案:能,但是需要修改文件的大小,如果不修改则会出现总线错误,程序如下: #include #include #include #include #include #include int main(int argc, const char *argv[]){ char *p = NULL; int fd = -1; // 打开文件 fd = open("temp", O_RDWR|O_CREAT|O_TRUNC, 0644); if (-1 == fd) { printf("文件打开失败...\n"); return -1; } // 因为我们文件不能是一个0大小的文件,所以我们需要修改文件的大小 // 有两种方式:leek,write,或者ftruncate都可以 /* // 改变一个文件的读写指针 lseek(fd, 1023, SEEK_END); // 写入一个结束符\0 write(fd, "\0", 1); */ // 我们还是用这种,比较方便,直接就修改了,和上面效果一样 // TODO ftruncate(fd, 1024); // 主要修改了这行,我们不进行文件大小调整,那么文件大小就是0 // 创建一个内存映射,让内和指定一个映射地址,大小为1024,可读可写,共享,映射到这个fd上 p = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { printf("mmap failed\n"); close(fd); return -1; } // 拿到地址之后我们就可以像操作普通地址一样写数据,读数据了,例如memcpy,strcpy等等 memcpy(p, "hello world", sizeof("hello world")); // 读数据 printf("p = %s\n",p); // 最后释放这个映射 if (munmap(p, 1024) == -1) { printf("munmap failed\n"); close(fd); return -1; } close(fd); return 0;} 和基础使用例子一样,只是注释了修改文件大小的逻辑ftruncate(fd, 1024),这样新创建的文件大小就是0, 我们编译运行,如下图:Bus error 所以结论就是:创建映射区的文件大小为0,而指定的大小非零的时候会出现总线错误 2.创建映射区的文件大小为0,实际指定映射区的大小为0 得到的结果:无效的参数 3.如果打开文件时flag为O_RDONLY,mmap时PROT参数为PROT_READ|PROT_WRITE会怎样? 得到的结果:无效的参数 4.如果打开文件时flag为O_RDONLY(新文件不行,需要一个有文件大小的文件),mmap时PROT参数为PROT_READ会怎样? 得到的结果:在写数据的时候段错误 5.如果打开文件时flag为O_WRONLY(新文件不行,需要一个有文件大小的文件),mmap时PROT参数为PROT_WRITE会怎样? 得到的结果:没有权限,mmap在创建的时候需要读权限,mmap的读写权限应该小于等于文件的打开权限,文件至少必须要有读权限。(前提是MAP_SHARED 模式下) 6.文件描述符fd,在mmap创建映射区完成即可关闭,后续访问文件,用地址访问。 7.如果offset是1000会怎么样? 得到的结果:无效的参数,必须是4K的整数倍(这个跟MMU有关,MMU映射的最小单位就是4K) 8.对mmap越界操作会怎样? 得到的结果:段错误,mmap映射以页为单位,就是说得到的空间的大小是4096的倍数,举个例子就是你申请了10个字节,但系统会给你申请4096,因为不够一页(4k),如果你申请4097,那么会给你申请两个页,所以才会发现你申请10个空间却能写如20个或者4096以下的字节数也不会崩溃的原因。 9.对mmap++是否还能munmap成功 得到的结果:不能,无效的参数,首地址变了,munmap必须释放申请的地址 十、mmap父子进程间通信 #include #include #include #include #include #include #include #include #include // 全局变量 var int var = 100;int main(int argc, const char *argv[]){ int *p; pid_t pid; int ret = 0; int fd; // 打开一个文件 fd = open("temp", O_RDWR|O_TRUNC, 0644); if (fd < 0) { perror("open error"); exit(1); } // truncate文件大小 ftruncate(fd, 4); // 创建映射区 p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("mmap error"); exit(1); } // 关闭fd,mmap创建成功后就可以关闭了,因为直接使用地址了,不需要fd了 close(fd); // fork一个进程 pid = fork(); if (pid == 0) // 子进程 { *p = 2000; var = 1000; printf("child *p = %d, var = %d\n", *p, var); }else{ // 父进程 sleep(1); // 休眠一秒,让子进程先执行 printf("parent *p = %d, var = %d\n", *p, var); wait(NULL); // 回收子进程 // 释放共享内存 if (munmap(p, 4) == -1) { perror("munmap error"); exit(1); } } return 0;} 结果: 结果发现p指向的地址的内容改掉了,而var没有被改掉(对于父子进程共享的东西是读共享,写复制) 十一、mmap无血缘关系的进程间通信 写进程,循环写这个结构体大小的数据到共享内存 #include #include #include #include #include #include #include #include #include struct student{ int id; char name[256]; int age; }; int main(int argc, const char *argv[]){ int fd; struct student stu = {0, "zhangsan", 18}; struct student *p; fd = open("temp", O_RDWR|O_CREAT|O_TRUNC, 0644); if (fd < 0) { perror("open error"); exit(1); } ftruncate(fd, sizeof(stu)); p = mmap(NULL, sizeof(stu), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("mmap error"); exit(1); } close(fd); while (1) { // 循环写 memcpy(p, &stu, sizeof(stu)); stu.id++; sleep(3); } if (-1 == munmap(p, sizeof(stu))) { perror("munmap error"); exit(1); } return 0; } 读进程,循环从共享内存中读 #include #include #include #include #include #include #include #include #include struct student{ int id; char name[256]; int age; }; int main(int argc, const char *argv[]){ int fd; struct student stu = {0, "zhangsan", 18}; struct student *p; fd = open("temp", O_RDONLY, 0644); if (fd < 0) { perror("open error"); exit(1); } p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("mmap error"); exit(1); } close(fd); while (1) { // 循环读 printf("stu.id = %d, stu.name = %s, stu.age = %d\n", p->id, p->name, p->age); sleep(3); } if (-1 == munmap(p, sizeof(stu))) { perror("munmap error"); exit(1); } return 0; } 一个读端一个写端执行结果如下: 一个写端多个读端执行结果如下: 多个写端一个读端: 十二、匿名映射 前面我们每次使用共享内存时,都会创建一个文件,这样会造成垃圾文件,接下来我们使用unlink把创建的文件删除掉,创建完就删除这个文件:unlink(文件名) #include #include #include #include #include #include #include #include #include // 全局变量 varint var = 100;int main(int argc, const char *argv[]){ int *p; pid_t pid; int ret = 0; int fd; // 打开一个文件 fd = open("temp", O_RDWR|O_TRUNC, 0644); if (fd < 0) { perror("open error"); exit(1); } // TODO 添加了这句删除文件 ret = unlink("temp"); if (ret == -1) { perror("unlink error"); exit(1); } // truncate文件大小 ftruncate(fd, 4); // 创建映射区 p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("mmap error"); exit(1); } // 关闭fd,mmap创建成功后就可以关闭了,因为直接使用地址了,不需要fd了 close(fd); // fork一个进程 pid = fork(); if (pid == 0) // 子进程 { *p = 2000; var = 1000; printf("child *p = %d, var = %d\n", *p, var); }else{ // 父进程 sleep(1); // 休眠一秒,让子进程先执行 printf("parent *p = %d, var = %d\n", *p, var); wait(NULL); // 回收子进程 // 释放共享内存 if (munmap(p, 4) == -1) { perror("munmap error"); exit(1); } } return 0;} 这样执行完成之后,那个临时文件就没了 又要open,又要unlink的好麻烦,有没有更方便的方法。答案是有的。可以直接使用匿名映射来代替,其实linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区,同样需要借助标志位flags来指定。 使用MAP_ANONYMOUS(或MAP_ANON),如: int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); 需要注意的是,MAP_ANONYMOUS和MAP_ANON这两个宏是linux操作系统中特有的,类UNIX系统中无该宏定义,可以使用如下两步来完成匿名映射区的建立 fd = open("/dev/zero", O_RDWR);p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, fd, 0); linux匿名映射的例子如下:只能用于有血缘关系的进程间通信 #include #include #include #include #include #include #include #include #include // 全局变量 var int var = 100; int main(int argc, const char *argv[]){ int *p; pid_t pid; int ret = 0; // 创建映射区-----TODO 匿名映射,大小随便指定,权限随便指定,fd用-1 p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) { perror("mmap error"); exit(1); } // fork一个进程 pid = fork(); if (pid == 0) // 子进程 { *p = 2000; var = 1000; printf("child *p = %d, var = %d\n", *p, var); }else{ // 父进程 sleep(1); // 休眠一秒,让子进程先执行 printf("parent *p = %d, var = %d\n", *p, var); wait(NULL); // 回收子进程 // 释放共享内存 if (munmap(p, 4) == -1) { perror("munmap error"); exit(1); } } return 0; } 类unix的例子 #include #include #include #include #include #include #include #include #include // 全局变量 varint var = 100;int main(int argc, const char *argv[]){ int *p; pid_t pid; int ret = 0; int fd; // 打开一个文件 TODO /dev/zero fd = open("/dev/zero", O_RDWR|O_TRUNC, 0644); if (fd < 0) { perror("open error"); exit(1); } if (ret == -1) { perror("unlink error"); exit(1); } // 创建映射区 flags 加 MAP_ANONYMOUS p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, fd, 0); if (p == MAP_FAILED) { perror("mmap error"); exit(1); } // 关闭fd,mmap创建成功后就可以关闭了,因为直接使用地址了,不需要fd了 close(fd); // fork一个进程 pid = fork(); if (pid == 0) // 子进程 { *p = 2000; var = 1000; printf("child *p = %d, var = %d\n", *p, var); }else{ // 父进程 sleep(1); // 休眠一秒,让子进程先执行 printf("parent *p = %d, var = %d\n", *p, var); wait(NULL); // 回收子进程 // 释放共享内存 if (munmap(p, 4) == -1) { perror("munmap error"); exit(1); } } return 0;}

    01-09 227浏览
正在努力加载更多...
广告