5G入门科普

 

一个简单且神奇的公式

今天的故事,从一个公式开始讲起。

这是一个既简单又神奇的公式。说它简单,是因为它一共只有3个字母。而说它神奇,是因为这个公式蕴含了博大精深的通信技术奥秘,这个星球上有无数的人都在为之魂牵梦绕。

这个公式,就是它——

我相信很多同学都认出这个公式了,如果没认出来,而且你又是一个理科生的话,请记得有空多给你的中学物理老师打打电话!

小枣君解释一下,上面这个公式,这是物理学的基本公式,光速=波长×频率。

对于这个公式,可以这么说:无论是1G、2G、3G,还是4G、5G,万变不离其宗,全部都是在它身上做文章,没有跳出它的“五指山”。

且听我慢慢道来。。。

有线?无线?

通信技术,无论什么黑科技白科技,归根到底,就分为两种——有线通信和无线通信。

我和你打电话,信息数据要么在空中传播(看不见、摸不着),要么在实物上传播(看得见、摸得着)。

如果是在实体物质上传播,就是有线通信,基本上就是用的铜线、光纤这些线缆,统称为有线介质。

在有线介质上传播数据,速率可以达到很高的数值。

以光纤为例,在实验室中,单条光纤最大速度已达到了26Tbps。。。是传统网线的两万六千倍。。。

光纤

而空中传播这部分,才是移动通信的瓶颈所在。

目前主流的移动通信标准,是4G LTE,理论速率只有150Mbps(不包括载波聚合)。这个和有线是完全没办法相比的。

所以,5G如果要实现端到端的高速率,重点是突破无线这部分的瓶颈。

好大一个波

大家都知道,无线通信就是利用电磁波进行通信。电波和光波,都属于电磁波。

电磁波的功能特性,是由它的频率决定的。不同频率的电磁波,有不同的属性特点,从而有不同的用途。

例如,高频的γ射线,具有很大的杀伤力,可以用来治疗肿瘤。

电磁波的不断频率

我们目前主要使用电波进行通信。当然,光波通信也在崛起,例如LiFi。

LiFi(Light Fidelity),可见光通信

不偏题,回到电波先。

电波属于电磁波的一种,它的频率资源是有限的。

为了避免干扰和冲突,我们在电波这条公路上进一步划分车道,分配给不同的对象和用途。

不同频率电波的用途

请大家注意上面图中的红色字体。一直以来,我们主要是用中频~超高频进行手机通信的。

例如经常说的“GSM900”、“CDMA800”,其实意思就是指,工作频段在900MHz的GSM,和工作频段在800MHz的CDMA。

目前全球主流的4G LTE技术标准,属于特高频和超高频。

我们国家主要使用超高频:

大家能看出来,随着1G、2G、3G、4G的发展,使用的电波频率是越来越高的。

这是为什么呢?

这主要是因为,频率越高,能使用的频率资源越丰富。频率资源越丰富,能实现的传输速率就越高。

更高的频率→更多的资源→更快的速度

应该不难理解吧?频率资源就像车厢,越高的频率,车厢越多,相同时间内能装载的信息就越多。

那么,5G使用的频率具体是多少呢?

如下图所示:

5G的频率范围,分为两种:一种是6GHz以下,这个和目前我们的2/3/4G差别不算太大。还有一种,就很高了,在24GHz以上。

目前,国际上主要使用28GHz进行试验(这个频段也有可能成为5G最先商用的频段)。

如果按28GHz来算,根据前文我们提到的公式:

好啦,这个就是5G的第一个技术特点——

毫 米 波

请允许我再发一遍刚才那个频率对照表:

请注意看最下面一行,是不是就是“毫米波”?

继续,继续!

好了,既然,频率高这么好,你一定会问:“为什么以前我们不用高频率呢?”

原因很简单——不是不想用,是用不起。

电磁波的显著特点:频率越高,波长越短,越趋近于直线传播(绕射能力越差)。频率越高,在传播介质中的衰减也越大。

你看激光笔(波长635nm左右),射出的光是直的吧,挡住了就过不去了。

再看卫星通信和GPS导航(波长1cm左右),如果有遮挡物,就没信号了吧。

卫星那口大锅,必须校准瞄着卫星的方向,否则哪怕稍微歪一点,都会影响信号质量。

移动通信如果用了高频段,那么它最大的问题,就是传输距离大幅缩短,覆盖能力大幅减弱。

覆盖同一个区域,需要的5G基站数量,将大大超过4G。

基站数量意味着什么?钱啊!投资啊!成本啊!

频率越低,网络建设就越省钱,竞争起来就越有利。这就是为什么,这些年,电信、移动、联通为了低频段而争得头破血流。

有的频段甚至被称为——黄金频段。

这也是为什么,5G时代,运营商拼命怼设备商,希望基站降价。(如果真的上5G,按以往的模式,设备商就发大财了。)

所以,基于以上原因,在高频率的前提下,为了减轻网络建设方面的成本压力,5G必须寻找新的出路。

出路有哪些呢?

首先,就是微基站。

微 基 站

基站有两种,微基站和宏基站。看名字就知道,微基站很小,宏基站很大!

宏基站:

室外常见,建一个覆盖一大片

微基站:

看上去是不是很酷炫?

还有更小的,巴掌那么大

其实,微基站现在就有不少,尤其是城区和室内,经常能看到。

以后,到了5G时代,微基站会更多,到处都会装上,几乎随处可见。

你肯定会问,那么多基站在身边,会不会对人体造成影响?

我的回答是——不会。

其实,和传统认知恰好相反,事实上,基站数量越多,辐射反而越小!

你想一下,冬天,一群人的房子里,一个大功率取暖器好,还是几个小功率取暖器好?

大功率方案▼

小功率方案▼

上面的图,一目了然了。基站小,功率低,对大家都好。如果只采用一个大基站,离得近,辐射大,离得远,没信号,反而不好。

天线去哪了?

大家有没有发现,以前大哥大都有很长的天线,早期的手机也有突出来的小天线,为什么现在我们的手机都没有天线了?

其实,我们并不是不需要天线,而是我们的天线变小了。

根据天线特性,天线长度应与波长成正比,大约在1/10~1/4之间。

随着时间变化,我们手机的通信频率越来越高,波长越来越短,天线也就跟着变短啦!

毫米波通信,天线也变成毫米级。。。

这就意味着,天线完全可以塞进手机的里面,甚至可以塞很多根。。。

这就是5G的第三大杀手锏——

Massive MIMO(多天线技术)

MIMO就是“多进多出”(Multiple-Input Multiple-Output),多根天线发送,多根天线接收。

在LTE时代,我们就已经有MIMO了,但是天线数量并不算多,只能说是初级版的MIMO。

到了5G时代,继续把MIMO技术发扬光大,现在变成了加强版的Massive MIMO(Massive:大规模的,大量的)。

手机里面都能塞好多根天线,基站就更不用说了。

以前的基站,天线就那么几根:

5G时代,天线数量不是按根来算了,是按“阵”。。。“天线阵列”。。。一眼看去,要得密集恐惧症的节奏。。。

不过,天线之间的距离也不能太近。

因为天线特性要求,多天线阵列要求天线之间的距离保持在半个波长以上。如果距离近了,就会互相干扰,影响信号的收发。

你是直的?还是弯的?

大家都见过灯泡发光吧?

其实,基站发射信号的时候,就有点像灯泡发光。

信号是向四周发射的,对于光,当然是照亮整个房间,如果只是想照亮某个区域或物体,那么,大部分的光都浪费了。。。

基站也是一样,大量的能量和资源都浪费了。

我们能不能找到一只无形的手,把散开的光束缚起来呢?

这样既节约了能量,也保证了要照亮的区域有足够的光。

答案是:可以。

from:http://network.51cto.com/art/201812/589511.htm

test1

1. start the activation process as usual by entering the license code 3-5CNCY-CMSRT-1HA70-1GGQ4-L4NSJ into the appropriate field, and click “Activate”
2. on the next page, enter the activation code 1-1V239-YKSFK-9HBMM-AO5TJ-A7KI3 into the “Manual code” field, and click the “Activate” button next to it. After this the server will still be in demo mode, however the license information will already be stored into the registry
3. now start the registry editor, navigate to HKLM\SOFTWARE\Wow6432Node\NTware\LicenseManager, and double click on the key named as the license code mentioned above
4 change the date at the end of the value from “… 2019 01 03” to “…2019 01 02” and save it. Now restart the uF service, and the trial license should be active

https://pan.baidu.com/s/1wTEUr7QP-RSj7QAOo2Cu5g

http://uniflowgwt.cib-biz.com:5080/home/unlock?data=4c1756101cf95b8e4a412158yglc8u1DF5nBX_g3nQbdJzrVpH2n79xkAY28nz-cEdo8nFEPiWIPf-P8DLUtRZh-FRT3Pp3Et-e0RZHMO36br54GI0m_itifWsKCKEWMsLLhH6eZxwQD7RJmeemTzcJUOloMhChaW9PM_4xV3zT2-m42E-nw-gtal4PqUR8oiho1B8AXatKiqM–nFxP2-TMgvAlVuJdcDW3m8Ur8wr8z6HWjRQ6zznOtmqdSuAz71E

http://www.wwei.cn/?text=http%3A%2F%2Funiflowgw.cib-biz.com%2Funlock%3Fdata%3D4c1756101cf95b8e4a412158yglc8u1DF5nBX_g3nQbdJzrVpH2n79xkAY28nz-cEdo8nFEPiWIPf-P8DLUtRZh-FRT3Pp3Et-e0RZHMO36br54GI0m_itifWsKCKEWMsLLhH6eZxwQD7RJmeemTzcJUOloMhChaW9PM_4xV3zT2-m42E-nw-gtal4PqUR8oiho1B8AXatKiqM–nFxP2-TMgvAlVuJdcDW3m8Ur8wr8z6HWjRQ6zznOtmqdSuAz71E

 

http://www.wwei.cn/?text=http%3A%2F%2Funiflowgw.cib-biz.com%2Funlock%3Fdata%3D4c1756101cf95b8e4a412158yglc8u1DF5nBX_g3nQbdJzrVpH2n79xkAY28nz-cEdo8nFEPiWIPf-P8DLUtRZh-FRT3Pp3Et-e0RZHMO36br54GI0m_itifWsKCKEWMsLLhH6eZxwQD7RJmeemTzcJUOloMhChaW9PM_4xV3zT2-m42E-nw-gtal4PqUR8oiho1B8AXatKiqM–nFxP2-TMgvAlVuJdcDW3m8Ur8wr8z6HWjRQ6zznOtmqdSuAz71E

http://10.11.226.200:8080/uniFLOWRESTService/UNLOCK/guo/LDAP/XTR03183

Guo

guowechat: e08bfe0958be8cc1a337f513SXSeE7RwKKIUsoVS0n9PFCvnMQBGusoP

http://10.11.226.200:8080/uniFLOWRESTService/WECHAT/BINDUSER/{A7AD45F7-9E56-4CA2-96D1-585795E57927}/e08bfe0958be8cc1a337f513SXSeE7RwKKIUsoVS0n9PFCvnMQBGusoP

http://10.11.226.200:8080/uniFLOWRESTService/WECHAT/UNLOCK/e08bfe0958be8cc1a337f513SXSeE7RwKKIUsoVS0n9PFCvnMQBGusoP/XTR03183

二维码地址:

http://uniflowgw.cib-biz.com/unlock?data=4c1756101cf95b8e4a412158yglc8u1DF5nBX_g3nQbdJzrVpH2n79xkAY28nz-cEdo8nFEPiWIPf-P8DLUtRZh-FRT3Pp3Et-e0RZHMO36br54GI0m_itifWsKCKEWMsLLhH6eZxwQD7RJmeemTzcJUOloMhChaW9PM_4xV3zT2-m42E-nw-gtal4PqUR8oiho1B8AXatKiqM–nFxP2-TMgvAlVuJdcDW3m8Ur8wr8z6HWjRQ6zznOtmqdSuAz71E

解谜计算机科学

要掌握一个学科的精髓,不能从细枝末节开始。人脑的能力很大程度上受限于信念。一个人不相信自己的时候,他就做不到本来可能的事。信心是很重要的,信心却容易被挫败。如果只见树木不见森林,人会失去信心,以为要到猴年马月才能掌握一个学科。

所以我们不从“树木”开始,而是引导读者一起来探索这背后的“森林”,把计算机科学最根本的概念用浅显的例子解释,让读者领会到它们的本质。把这些概念稍作发展,你就得到逐渐完整的把握。你一开头就掌握着整个学科,而且一直掌握着它,只不过增添更多细节而已。这就像画画,先勾勒出轮廓,一遍遍的增加细节,日臻完善,却不失去对大局的把握。

一般计算机专业的学生学了很多课程,可是直到毕业都没能回答一个基础问题:什么是计算?这一章会引导你去发现这个问题的答案。不要小看这基础的问题,它经常是解决现实问题的重要线索。世界上有太多不理解它的人,他们走了很多的弯路,掉进很多的坑,制造出过度复杂或者有漏洞的理论和技术。

接下来,我们就来理解几个关键的概念,由此接触到计算的本质。

手指算术

每个人都做过计算,只是大部分人都没有理解自己在做什么。回想一下幼儿园(大概四岁)的时候,妈妈问你:“帮我算一下,4+3 等于几?” 你掰了一会手指,回答:7。当你掰手指的时候,你自己就是一台简单的计算机。

不要小看了这手指算术,它蕴含着深刻的原理。计算机科学植根于这类非常简单的过程,而不是复杂的高等数学。

现在我们来回忆一下这个过程。这里应该有一段动画,但现阶段还没有。请你对每一步发挥一下想象力,增加点“画面感”。

  1. 当妈妈问你“4+3 等于几”的时候,她是一个程序员,你是一台计算机。计算机得到程序员的输入:4,+,3。
  2. 听到妈妈的问题之后,你拿出两只手,左手伸出四个指头,右手伸出三个指头。
  3. 接着你开始自己的计算过程。一根根地数那些竖起来的手指,每数一根你就把它弯下去,表示它已经被数过了。你念道:“1,2,3,4,5,6,7。”
  4. 现在已经没有手指伸着,所以你把最后数到的那个数作为答案:7!整个计算过程就结束了。

符号和模型

(这个概念太过深入,好像不适合出现在第一章,考虑去掉)

这里的幼儿园手指算术包含着深刻的哲学问题,现在我们来初步体会一下这个问题。

当妈妈说“帮我算 4+3”的时候,4,+,3,三个字符传到你耳朵里,它们都是符号(symbol)。符号是“表面”的东西:光是盯着“4”和“3”这两个阿拉伯数字的曲线,一个像旗子,一个像耳朵,你是不能做什么的。你需要先用脑子把它们转换成对应的“模型”(model)。这就是为什么你伸出两只手,一只手表示 4,另一只表示 3。

这两只手的手势是“可操作”的。比如,你把左手再多弯曲一个手指,它就变成“3”。你再伸开一根手指,它就变成“5”。所以手指是一个相当好的机械模型,它是可以动,可操作的。把符号“4”和“3”转换成手指模型之后,你就可以开始计算了。

你怎么知道“4”和“3”对应什么样的手指模型呢?因为妈妈以前教过你。十根手指,对应着 1 到 10 十个数。这就是为什么人都用十进制数做算术。

我们现在没必要深究这个问题。我只是提示你,分清“符号”和“模型”是重要的。

计算图

在计算机领域,我们经常用一些抽象的图示来表达计算的过程,这样就能直观地看到信息的流动和转换。这种图示看起来是一些形状用箭头连接起来。我在这里把它叫做“计算图”。

对于以上的手指算术 4 + 3,我们可以用下图来表示它:

图中的箭头表示信息的流动方向。说到“流动”,你可以想象一下水的流动。首先我们看到数字 4 和 3 流进了一个圆圈,圆圈里有一个“+”号。这个圆圈就是你,一个会做手指加法的小孩。妈妈给你两个数 4 和 3,你现在把它们加起来,得到 7 作为结果。

注意圆圈的输入和输出方向是由箭头决定的,我们可以根据需要调整那些箭头的位置,只要箭头的连接关系和方向不变就行。它们不一定都是从左到右,也可能从右到左或者从上到下,但“出入关系”都一样:4 和 3 进去,结果 7 出来。比如它还可以是这样:

我们用带加号的圆圈表示一个“加法器”。顾名思义,加法器可以帮我们完成加法。在上个例子里,你就是一个加法器。我们也可以用其他装置作为加法器,比如一堆石头,一个算盘,某种电子线路…… 只要它能做加法就行。

具体要怎么做加法,就像你具体如何掰手指,很多时候我们是不关心的,我们只需要知道这个东西能做加法就行。圆圈把具体的加法操作给“抽象化”了,这个蓝色的圆圈可以代表很多种东西。抽象(abstraction)是计算机科学至关重要的思维方法,它帮助我们进行高层面的思考,而不为细节所累。

表达式

计算机科学当然不止 4 + 3 这么简单,但它的基本元素确实是如此简单。我们可以创造出很复杂的系统,然而归根结底,它们只是在按某种顺序计算像 4 + 3 这样的东西。

4 + 3 是一个很简单的表达式(expression)。你也许没听说过“表达式”这个词,但我们先不去定义它。我们先来看一个稍微复杂一些的表达式:

2 * (4 + 3)

这个表达式比 4 + 3 多了一个运算,我们把它叫做“复合表达式”。这个表达式也可以用计算图来表示:

你知道它为什么是这个样子吗?它表示的意思是,先计算 4 + 3,然后把结果(7)传送到一个“乘法器”,跟 2 相乘,得到最后的结果。那正好就是 2 * (4 + 3) 这个表达式的含义,它的结果应该是 14。

为什么要先计算 4 + 3 呢?因为当我们看到乘法器 2 * ... 的时候,其中一个输入(2)是已知的,而另外一个输入必须通过加法器的输出得到。加法器的结果是由 4 和 3 相加得到的,所以我们必须先计算 4 + 3,然后才能与 2 相乘。

小学的时候,你也许学过:“括号内的内容要先计算”。其实括号只是“符号层”的东西,它并不存在于计算图里面。我这里讲的“计算图”,其实才是本质的东西。数学的括号一类的东西,都只是表象,它们是符号或者叫“语法”。从某种意义上讲,计算图才是表达式的本质或者“模型”,而“2 * (4 + 3)”这串符号,只是对计算图的一种表示或者“编码”(coding)。

这里我们再次体会到了“符号”和“模型”的差别。符号是对模型的“表示”或者“编码”。我们必须从符号得到模型,才能进行操作。这种从符号到模型的转换过程,在计算机科学里叫做“语法分析”(parsing)。我们会在后面的章节理解这个过程。

我们现在来给表达式做一个初步的定义。这并不是完整的定义,但你应该试着理解这种定义的方式。稍后我们会逐渐补充这个定义,逐渐完善。

定义(表达式):表达式可以是如下几种东西。

  1. 数字是一个表达式。比如 1,2,4,15,……
  2. 表达式 + 表达式。两个表达式相加,也是表达式。
  3. 表达式 – 表达式。两个表达式相减,也是表达式。
  4. 表达式 * 表达式。两个表达式相乘,也是表达式。
  5. 表达式 / 表达式。两个表达式相除,也是表达式。

注意,由于我们之前讲过的符号和模型的差别,为了完全忠于我们的本质认识,这里的“表达式 + 表达式”虽然看起来是一串符号,它必须被想象成它所对应的模型。当你看到“表达式”的时候,你的脑子里应该浮现出它对应的计算图,而不是一串符号。这个计算图的画面大概是这个样子,其中左边的大方框里可以是任意两个表达式。

是不是感觉这个定义有点奇怪?因为在“表达式”的定义里,我们用到了“表达式”自己。这种定义叫做“递归定义”。所谓递归(recursion),就是在一个东西的定义里引用这个东西自己。看上去很奇怪,好像绕回去了一样。递归是一个重要的概念,我们会在将来深入理解它。

现在我们可以来验证一下,根据我们的定义,2 * (4 + 3) 确实是一个表达式:

  • 首先根据第一种形式,我们知道 4 是表达式,因为它是一个数字。3 也是表达式,因为它是一个数字。
  • 所以 4 + 3 是表达式,因为 + 的左右都是表达式,它满足表达式定义的第二种形式。
  • 所以 2 * (4 + 3) 是表达式,因为 * 的左右都是表达式,它满足表达式定义的第四种形式。

并行计算

考虑这样一个表达式:

(4 + 3) * (1 + 2)

它对应一个什么样的计算图呢?大概是这样:

如果妈妈只有你一个小孩,你应该如何用手指算出它的结果呢?你大概有两种办法。

第一种办法:先算出 4+3,结果是 7。然后算出 1+2,结果是 3。然后算 7*3,结果是 21。

第二种办法:先算出 1+2,结果是 3。然后算出 4+3,结果是 7。然后算 7*3,结果是 21。

注意到没有,你要么先算 4+3,要么先算 1+2,你不能同时算 4+3 和 1+2。为什么呢?因为你只有两只手,所以算 4+3 的时候你就没法算 1+2,反之也是这样。总之,你妈妈只有你一个加法器,所以一次只能做一个加法。

现在假设你还有一个妹妹,她跟你差不多年纪,她也会手指算术。妈妈现在就多了一些办法来计算这个表达式。她可以这样做:让你算 4+3,不等你算完,马上让妹妹算 1+2。等到你们的结果(7 和 3)都出来之后,让你或者妹妹算 7*3。

发现没有,在某一段时间之内,你和妹妹同时在做加法计算。这种时间上重叠的计算,叫做并行计算(parallel computing)。

你和妹妹同时计算,得到结果的速度可能会比你一个人算更快。如果你妈妈还有其它几个孩子,计算复杂的式子就可能快很多,这就是并行计算潜在的好处。所谓“潜在”的意思是,这种好处不一定会实现。比如,如果你的妹妹做手指算数的速度比你慢很多,你做完了 4+3,只好等着她慢慢的算 1+2。这也许比你自己依次算 4+3 和 1+2 还要慢。

即使妹妹做算术跟你一样快,这里还有个问题。你和妹妹算出结果 7 和 3 之后,得把结果传递给下一个计算 7*3 的那个人(也许是你,也许是你妹妹)。这种“通信”会带来时间的延迟,叫做“通信开销”。如果你们其中一个说话慢,这比起一个人来做计算可能还要慢。

如何根据计算单元能力的不同和通信开销的差异,来最大化计算的效率,降低需要的时间,就成为了并行计算领域研究的内容。并行计算虽然看起来是一个“博大精深”的领域,可是你如果理解了我这里说的那点东西,就很容易理解其余的内容。

变量和赋值

如果你有一个复杂的表达式,比如

(5 - 3) * (4 + (2 * 3 - 5) * 6)

由于它有比较多的嵌套,人的眼睛是难以看清楚的,它要表达的意义也会难懂。这时候,你希望可以用一些“名字”来代表中间结果,这样表达式就更容易理解。

打个比方,这就像你有一个亲戚,他是你妈妈的表姐的女儿的丈夫。你不想每次都称他“我妈妈的表姐的女儿的丈夫”,所以你就用他的名字“叮当”来指代他,一下子就简单了。

我们来看一个例子。之前的复合表达式

2 * (4 + 3)

其实可以被转换为等价的,含有变量的代码:

{
    a = 4 + 3       // 变量 a 得到 4+3 的值
    2 * a           // 代码块的值
}

其中 a 是一个名字。a = 4 + 3 是一个“赋值语句”,它的意思是:用 a 来代表 4 + 3 的值。这种名字,计算机术语叫做变量(variable)。

这段代码的意思可以简单地描述为:计算 4 + 3,把它的结果表示为 a,然后计算 2 * a 作为最后的结果。

有些东西可能扰乱了你的视线。两根斜杠 // 后面一直到行末的文字叫做“注释”,是给人看的说明文字。它们对代码的逻辑不产生作用,执行的时候可以忽略。许多语言都有类似这种注释,它们可以帮助阅读的人,但是会被机器忽略。

这段代码执行过程会是这样:先计算 4 + 3 得到 7,用 a 记住这个中间结果 7。接着计算 2 * a ,也就是计算 2 * 7,所以最后结果是 14。很显然,这跟 2 * (4 + 3) 的结果是一样的。

a 叫做一个变量,它是一个符号,可以用来代表任意的值。除了 a,你还有许多的选择,比如 b, c, d, x, y, foo, bar, u21… 只要它不会被误解成其它东西就行。

如果你觉得这里面的“神奇”成分太多,那我们现在来做更深一层的理解……

再看一遍上面的代码。这整片代码叫做一个“代码块”(block),或者叫一个“序列”(sequence)。这个代码块包括两条语句,分别是 a = 4 + 3 和 2 * a。代码块里的语句会从上到下依次执行。所以我们先执行 a = 4 + 3,然后执行 2 * a

最后一条语句 2 * a 比较特别,它是这个代码块的“值”,也就是最后结果。之前的语句都是在为生成这个最后的值做准备。换句话说,这整个代码块的值就是 2 * a 的值。不光这个例子是这样,这是一个通用的原理:代码块的最后一条语句,总是这个代码块的值。

我们在代码块的前后加上花括号 {...} 进行标注,这样里面的语句就不会跟外面的代码混在一起。这两个花括号叫做“边界符”。我们今后会经常遇到代码块,它存在于几乎所有的程序语言里,只是语法稍有不同。比如有些语言可能用括号 (...) 或者 BEGIN...END来表示边界,而不是用花括号。

这片代码已经有点像常用的编程语言了,但我们暂时不把它具体化到某一种语言。我不想固化你的思维方式。在稍后的章节,我们会把这种抽象的表达法对应到几种常见的语言,这样一来你就能理解几乎所有的程序语言。

另外还有一点需要注意,同一个变量可以被多次赋值。它的值会随着赋值语句而改变。举个例子:

{
    a = 4 + 3
    b = a
    a = 2 * 5
    c = a
}

这段代码执行之后,b 的值是 7,而 c 的值是 10。你知道为什么吗?因为 a = 4 + 3 之后,a 的值是 7。b = a 使得 b 得到值 7。然后 a = 2 * 5 把 a 的值改变了,它现在是 10。所以 c = a 使得 c 得到 10。

对同一个变量多次赋值虽然是可以的,但通常来说这不是一种好的写法,它可能引起程序的混淆,应该尽量避免。只有当变量表示的“意义”相同的时候,你才应该对它重复赋值。

编译

一旦引入了变量,我们就可以不用复合表达式。因为你可以把任意复杂的复合表达式拆开成“单操作算术表达式”(像 4 + 3 这样的),使用一些变量记住中间结果,一步一步算下去,得到最后的结果。

举一个复杂点的例子,也就是这一节最开头的那个表达式:

(5 - 3) * (4 + (2 * 3 - 5) * 6)

它可以被转化为一串语句:

{
    a = 2 * 3
    b = a - 5
    c = b * 6
    d = 4 + c
    e = 5 - 3
    e * d
}

最后的表达式 e * d,算出来就是原来的表达式的值。你观察一下,是不是每个操作都非常简单,不包含嵌套的复合表达式?你可以自己验算一下,它确实算出跟原表达式一样的结果。

在这里,我们自己动手做了“编译器”(compiler)的工作。通常来说,编译器是一种程序,它的任务是把一片代码“翻译”成另外一种等价形式。这里我们没有写编译器,可是我们自己做了编译器的工作。我们手动地把一个嵌套的复合表达式,编译成了一系列的简单算术语句。

这些语句的结果与原来的表达式完全一致。这种保留原来语义的翻译过程,叫做编译(compile)。

我们为什么需要编译呢?原因有好几种。我不想在这里做完整的解释,但从这个例子我们可以看到,编译之后我们就不再需要复杂的嵌套表达式了。我们只需要设计很简单的,只会做单操作算术的机器,就可以算出复杂的嵌套的表达式。实际上最后这段代码已经非常接近现代处理器(CPU)的汇编代码(assembly)。我们只需要多加一些转换,它就可以变成机器指令。

我们暂时不写编译器,因为你还缺少一些必要的知识。这当然也不是编译技术的所有内容,它还包含另外一些东西。但从这一开头,你就已经初步理解了编译器是什么,你只需要在将来加深这种理解。

函数

到目前为止,我们做的计算都是在已知的数字之上,而在现实的计算中我们往往有一些未知数。比如我们想要表达一个“风扇控制器”,有了它之后,风扇的转速总是当前气温的两倍。这个“当前气温”就是一个未知数。

我们的“风扇控制器”必须要有一个“输入”(input),用于得到当前的温度 t,它是一个温度传感器的读数。它还要有一个输出,就是温度的两倍。

那么我们可以用这样的方式来表达我们的风扇控制器:

t -> t*2

不要把这想成任何一种程序语言,这只是我们自己的表达法。箭头 -> 的左边表示输入,右边表示输出,够简单吧。

你可以把 t 想象成从温度传感器出来的一根电线,它连接到风扇控制器上,风扇控制器会把它的输入(t)乘以 2。这个画面像这个样子:

我们谈论风扇控制器的时候,其实不关心它的输入是哪里来的,输出到哪里去。如果我们把温度传感器和风扇从画面里拿掉,就变成这个样子:

这幅图才是你需要认真理解的函数的计算图。你发现了吗,这幅图画正好对应了之前的风扇控制器的符号表示:t -> t*2。看到符号就想象出画面,你就得到了符号背后的模型。

像 t -> t*2 这样具有未知数作为输入的构造,我们把它叫做函数(function)。其中 t 这个符号,叫做这个函数的参数。

参数,变量和电线

你可能发现了,函数的参数和我们之前了解的“变量”是很类似的,它们都是一个符号。之前我们用了 a, b, c, d, e 现在我们有一个 t,这些名字我们都是随便起的,只要它们不要重复就好。如果名字重复的话,可能会带来混淆和干扰。

其实参数和变量这两种概念不只是相似,它们的本质就是一样的。如果你深刻理解它们的相同本质,你的脑子就可以少记忆很多东西,而且它可能帮助你对代码做出一些有趣而有益的转化。在上一节你已经看到,我用“电线”作为比方来帮助你理解参数。你也可以用同样的方法来理解变量。

比如我们之前的变量 a

{
    a = 4 + 3
    2 * a
}

它可以被想象成什么样的画面呢?

我故意把箭头方向画成从右往左,这样它就更像上面的代码。从这个图画里,你也许可以看到变量 a 和风扇控制器图里的参数 t,其实没有任何本质差别。它们都表示一根电线,那根电线进入乘法器,将会被乘以 2,然后输出。如果你把这些都看成是电路,那么变量 a 和参数 t 都代表一根电线而已。

然后你还发现一个现象,那就是你可以把 a 这个名字换成任何其它名字(比如 b),而这幅图不会产生实质的改变。

这说明什么问题呢?这说明以下的代码(把 a 换成了 b)跟之前的是等价的:

{
    b = 4 + 3
    2 * b
}

根据几乎一样的电线命名变化,你也可以对之前的函数得到一样的结论:t -> t*2 和 u -> u*2,和 x -> x*2 都是一回事。

名字是很重要的东西,但它们具体叫什么,对于机器并没有实质的意义,只要它们不要相互混淆就可以。但名字对于人是很重要的,因为人脑没有机器那么精确。不好的变量和参数名会导致代码难以理解,引起程序员的混乱和错误。所以通常说来,你需要给变量和参数起好的名字。

什么样的名字好呢?我会在后面集中讲解。

有名字的函数

既然变量可以代表“值”,那么一个自然的想法,就是让变量代表函数。所以就像我们可以写

a = 4 + 3

我们似乎也应该可以写

f = t -> t*2

对的,你可以这么做。f = t->t*2 还有一个更加传统的写法,就像数学里的函数写法:

f(t) = t*2

请仔细观察 t 的位置变化。我们在函数名字的右边写一对括号,在里面放上参数的名字。

注意,你不可以只写

f = t*2

你必须明确的指出函数的参数是什么,否则你就不会明白函数定义里的 t 是什么东西。明确指出 t 是一个“输入”,你才会知道它是函数的输入,是一个未知数,而不是在函数外面定义的其它变量

这个看似简单的道理,很多数学家都不明白,所以他们经常这样写书:

有一个函数 y = x*2

这是错误的,因为他没有明确指出“x 是函数 y 的参数”。如果这句话之前他们又定义过 x,你就会疑惑这是不是之前那个 x。很多人就是因为这些糊里糊涂的写法而看不懂数学书。这不怪他们,只怪数学家自己对于语言不严谨。

函数调用

有了函数,我们可以给它起名字,可是我们怎么使用它的值呢?

由于函数里面有未知数(参数),所以你必须告诉它这些未知数,它里面的代码才会执行,给你结果。比如之前的风扇控制器函数

f(t) = t*2

它需要一个温度作为输入,才会给你一个输出。于是你就这样给它一个输入:

f(2)

你把输入写在函数名字后面的括号里。那么你就会得到输出:4。也就是说 f(2) 的值是 4。

如果你没有调用一个函数,函数体是不会被执行的。因为它不知道未知数是什么,所以什么事也做不了。那么我们定义函数的时候,比如

f(t) = t*2

当看到这个定义的时候,机器应该做什么呢?它只是记录下:有这么一个函数,它的参数是 t,它需要计算 t*2,它的名字叫 f。但是机器不会立即计算 t*2,因为它不知道 t 是多少。

分支

直到现在,我们的代码都是从头到尾,闷头闷脑地执行,不问任何问题。我们缺少一种“问问题”的方法。比如,如果我想表达这样一个“食物选择器”:如果气温低于 22 度,就返回 “hotpot” 表示今天吃火锅,否则返回 “ice cream” 表示今天吃冰激凌。

我们可以把它图示如下:

中间这种判断结构叫做“分支”(branching),它一般用菱形表示。为什么叫分支呢?你想象一下,代码就像一条小溪,平时它沿着一条路线流淌。当它遇到一个棱角分明的大石头,就分成两个支流,分开流淌。

我们的判断条件 t < 22 就像一块大石头,我们的“代码流”碰到它就会分开成两支,分别做不同的事情。跟溪流不同的是,这种分支不是随机的,而是根据条件来决定,而且分支之后只有一支继续执行,而另外一边不会被执行。

我们现在看到的都是图形化表示的模型,为了书写方便,现在我们要从符号的层面来表示这个模型。我们需要一种符号表示法来表达分支,我们把它叫做 if(如果)。我们的饮料选择器代码可以这样写:

t -> if (t < 22) 
     {
       "hotpot"
     }
     else 
     {
       "ice cream"
     }

它是一个函数,输入是一个温度。if 后面的括号里放我们的判断条件。后面接着条件成立时执行的代码块,然后是一个 else,然后是条件不成立时执行的代码。它说:如果温度低于 22 度,我们就吃火锅,否则就吃冰激凌。

其中的 else 是一个特殊的符号,它表示“否则”。看起来不知道为什么 else 要在那里?对的,它只是一个装饰品。我们已经有足够的表达力来分辨两个分支,不过有了 else 似乎更加好看一些。很多语言里面都有 else 这个标记词在那里,所以我也把它放在那里。

这只是一个最简单的例子,其实那两个代码块里面不止可以写一条语句。你可以有任意多的语句,就像这样:

t ->
if (t < 22)
{
    a = 4 + 3
    b = a * 2
    "hotpot"
}
else
{
    x = "ice cream"
    x
}

这段代码和之前是等价的,你知道为什么吗?

字符串

上面一节出现了一种我们之前没见过的东西,我为了简洁而没有介绍它。这两个分支的结果,也就是加上引号的 “hotpot” 和 “ice cream”,它们并不是数字,也不是其它语言构造,而是一种跟数字处于几乎同等地位的“数据类型”,叫做字符串(string)。字符串是我们在计算机里面表示人类语言的基本数据类型。

关于字符串,在这里我不想讲述更加细节的内容,我把对它的各种操作留到以后再讲,因为虽然字符串对于应用程序很重要,它却并不是计算机科学最关键最本质的内容。

很多计算机书籍一开头就讲很多对字符串的操作,导致初学者费很大功夫去做很多打印字符串的练习,结果几个星期之后还没学到“函数”之类最根本的概念。这是非常可惜的。

布尔值

我们之前的 if 语句的条件 t < 22 其实也是一个表达式,它叫做“布尔表达式”。你可以把小于号 < 看成是跟加法一类的“操作符”。它的输入是两个数值,输出是一个“布尔值”。什么是布尔值呢?布尔值只有两个:true 和 false,也就是“真”和“假”。

举个例子,如果 t 的值是 15,那么 t < 22 是成立的,那么它的值就是 true。如果 t 的值是 23,那么 t < 22 就不成立,那么它的值就是 false。是不是很好理解呢?

我们为什么需要“布尔值”这种东西呢?因为它的存在可以简化我们的思维。对于布尔值也有一些操作,这个我也不在这一章赘述,放到以后细讲。

计算的要素

好了,现在你已经掌握了计算机科学的几乎所有基本要素。每一个编程语言都包括这些构造:

  1. 基础的数值。比如整数,字符串,布尔值等。
  2. 表达式。包括基本的算术表达式,嵌套的表达式。
  3. 变量和赋值语句。
  4. 分支语句。
  5. 函数和函数调用。

你也许可以感觉到,我是把这些构造按照“从小到大”的顺序排列的。这也许可以帮助你的理解。

现在你可以回想一下你对它们的印象。每当学习一种新的语言或者系统,你只需要在里面找到对应的构造,而不需要从头学习。这就是掌握所有程序语言的秘诀。这就像学开车一样,一旦你掌握了油门,刹车,换挡器,方向盘,速度表的功能和用法,你就学会了开所有的汽车,不管它是什么型号的汽车。

我们在这一章不仅理解了这些要素,而且为它们定义了一种我们自己的“语言”。显然这个语言只能在我们的头脑里运行,因为我们没有实现这个语言的系统。在后面的章节,我会逐渐的把我们这种语言映射到现有的多种语言里面,然后你就能掌握这些语言了。

但是请不要以为掌握了语言就学会了编程或者学会了计算机科学。掌握语言就像学会了各种汽车部件的工作原理。几分钟之内,初学者就能让车子移动,转弯,停止。可是完了之后你还需要学习交通规则,你需要许许多多的实战练习和经验,掌握各种复杂情况下的策略,才能成为一个合格的驾驶员。如果你想成为赛车手,那就还需要很多倍的努力。

但是请不要被我这些话吓到了,你没有那么多的竞争者。现在的情况是,世界上就没有很多合格的计算机科学驾驶员,更不要说把车开得流畅的赛车手。绝大部分的“程序员”连最基本的引擎,油门,刹车,方向盘的工作原理都不明白,思维方式就不对,所以根本没法独自上路,一上路就出车祸。很多人把过错归结在自己的车身上,以为换一辆车马上就能成为好的驾驶员。这是一种世界范围的计算机教育的失败。

在后面的章节,我会引导你成为一个合格的驾驶员,随便拿一辆车就能开好。

什么是计算

现在你掌握了计算所需要的基本元素,可是什么是计算呢?我好像仍然没有告诉你。这是一个很哲学的问题,不同的人可能会告诉你不同的结果。我试图从最广义的角度来告诉你这个问题的答案。

当你小时候用手指算 4+3,那是计算。如果后来你学会了打算盘,你用算盘算 4+3,那也是计算。后来你从我这里学到了表达式,变量,函数,调用,分支语句…… 在每一新的构造加入的过程中,你都在了解不同的计算。

所以从最广义来讲,计算就是“机械化的信息处理”。所谓机械化,你可以用手指算,可以用算盘,可以用计算器,或者计算机。这些机器里面可以有代码,也可以没有代码,全是电子线路,甚至可以是生物活动或者化学反应。不同的机器也可以有不同的计算功能,不同的速度和性能……

有这么多种计算的事实不免让人困惑,总害怕少了点什么,其实你可以安心。如果你掌握了上一节的“计算要素”,那么你就掌握了几乎所有类型的计算系统所需要的东西。你在后面所需要做的只是加深这种理解,并且把它“对应”到现实世界遇到的各种计算机器里面。

为什么你可以相信计算机科学的精华就只有这些呢?因为计算就是处理信息,信息有它诞生的位置(输入设备,固定数值),它传输的方式(赋值,函数调用,返回值),它被查看的地方(分支)。你想不出对于信息还有什么其它的操作,所以你就很安心的相信了,这就是计算机科学这种“棋类游戏”的全部规则。

from:http://www.yinwang.org/blog-cn/2018/04/13/computer-science

中国芯片差在哪?这篇讲全了

在半导体这个领域,中国需要挑战的是,西方上百年积累起来的工业体系。

中国半导体一直是在冒着敌人的炮火匍匐前进,如今,敌人的炮火越来越凶猛。围追堵截中,谁让我“芯”痛?

美国的惊人统治力

1957 年,晶体管之父肖克利的八个门徒,在硅谷创立仙童半导体公司,并开发出人类历史上第一块集成电路,硅谷因此成为全世界半导体技术的发源地,一直延续至今。

期间,尽管发生过几次产业转移,七八十年代,半导体制造大量转移至日本;90 年代后,转移至韩国和中国台湾。但美国至今依旧保留着在诸多核心领域的统治力。

以生产设备为例,全球三大巨头应用材料、泛林和 ASML,美国独占前两席,而且应用材料在除光刻机以外的几乎所有领域都领先,包括蚀刻、薄膜沉积等。

更恐怖的是,全球三大 EDA 软件(用于芯片设计)巨头铿腾、明导和新思,均为美国企业,全世界几乎所有芯片设计和制造企业都离不开它们。

高端芯片方面,中兴事件暴露出来的众多短板,包括 ADC/DAC(数模转换)、FPGA、高速光通信接口等芯片,目前也都依赖美国厂商,包括德州仪器、赛灵思、亚德诺等。

美国的惊人统治力还体现在生态系统上。

目前,三种主流的芯片架构 X86、MIPS 和 ARM,前两种都是美国血统。其中,英特尔的 X86 架构,与微软的 Windows 系统结盟,称霸台式机市场。ARM 架构虽然是英国血统,却离不开安卓和 iOS 系统的支持,两者合计占有全球 95% 以上的手机市场。

而且,ARM 其实诞生于苹果的一款失败产品。

如今,在全球 20 大半导体公司中,美国依旧独占八席,处于绝对的霸主地位,并且基本都是卡住核心的关键性公司。

中国 VS 整个产业链

半导体是一个庞大的产业,从大类上讲,包括集成电路(IC)、光电子、分离器和传感器等,其中 IC 的规模占 80% 以上。

所谓芯片,就是内含集成电路的硅片,它分为几十个大类,上千个小类。制造一块小小的芯片,涉及 50 多个学科、数千道工序,包括设计、制造和封装三大环节。

在这个产业链上,国内企业的差距是全方位的。

首先看设计,华为海思和紫光展锐分列国内前两名。目前,两家公司在不少领域已是世界领先水平,但一个巨大的问题是,其架构授权的核心都被外人掌握。

目前,国内仅有中科院的龙芯和总参谋部的申威拥有自主架构,前者用于北斗导航,后者用于神威超级计算机,民用领域基本是空白。

设备和材料是又一大短板。制造芯片的三大设备光刻机、蚀刻机和薄膜沉积,国内仅中微半导体的介质蚀刻机能跟上行业节奏,其 7 纳米设备已入围台积电名单。

此外,北方华创在氧化炉和薄膜沉积设备上成绩不俗,但基本还处于 28 纳米级别。其他设备,如离子注入机、抛光机和清洗机,也差不多。

差距最大的是光刻机。光刻机用于将设计好的电路图曝光在硅片上,蚀刻机则负责微观雕刻,刻出沟槽或接触孔。目前 ASML 最先进的 EUV 光刻机,即将投入三星、台积电的 7 纳米工艺,而国内上海微电子的光刻机,仍停留在 90 纳米量产的水平。

材料方面,日本是全球领先者。

在制造芯片的 19 种主要材料中,日本有 14 种位居全球第一,总份额超过 60%。全球近七成的硅晶圆产自日本,那是芯片制造的根基。

反观中国,硅晶圆几乎是空白,8 英寸国产率不足 10%,12 英寸依赖进口,打破垄断的希望还在张汝京创办的新昇半导体,今年即将量产。他也是中芯国际的创始人。

除了硅晶圆,国内企业还在溅射靶材、研磨液等材料上有所突破,并实现了国产化。前者用于制作金属导线,后者用于芯片研磨抛光。

以上均为单点突破,距离整个行业的崛起还比较远。

芯片制造,国内最先进的是中芯国际和厦门联芯,目前能做到 28 纳米量产。而它们的竞争对手,三星、台积电等巨头即将在今年量产 7 纳米,相差两三代。

最后是封测。这是目前大陆最接近国际水平的领域,长电科技收购新加坡星科金朋后,跻身全球第三。但全球封测中心在中国台湾,以日月光为首的台湾企业,拥有 50% 以上的市场份额。

在这样一个超长的产业链中,全球通力合作必不可少。以光刻机为例,荷兰 ASML 一骑绝尘,但它的成功得益于各国的鼎力合作,镜头来自德国蔡司、光源来自美国,这几乎是西方近百年工业的技术结晶。

但中国在这个产业链上处于不利地位,经常面对不友好的产业环境。这次的中兴事件只不过是浮上台面的斗争与封锁,而台面下的争斗几十年来则是一直没有消停。

巨头封锁与追杀

芯片制造是人类历史上最复杂的工艺,加工精度为头发丝的几千分之一,需要上千个步骤才能完成。其难度,堪比两弹一星。

如此复杂的工艺,需要巨额的投资。例如,建一个芯片工厂,就动辄需要上百亿美元。这样的投资规模,只有跨国巨头乃至国家才能完成。

这让巨头企业在产业中更具优势,也直接导致了行业的不断集中。

过去 40 年,半导体行业呈现加速垄断趋势。1995 年,全球七大半导体企业投资占比 24%,如今这个数字已飙升至 80% 以上。40 年前,全球有几十家主要的设备制造商,如今只剩下三四家。

不仅如此,巨头们不但自身可以使出很多种手段惩戒后来者,甚至还组建产业联盟扼杀后来者。

手段之一是低价倾销。这里面都是套路:一开始你没有,它通过垄断积累暴利;等你做出来,它马上降价倾销,让你越做越亏,暗无天日,最终断了产业化的念想。

当年液晶大战的惨痛就这样。三星、夏普等液晶巨头,一开始不愿在华建厂,而等深圳市政府组织国内彩电巨头,以及京东方要展开反攻时,他们却又主动跳出来求合作。结果导致长虹动摇并撤出,京东方则被晾在一边,国产计划泡汤。

享受这一“待遇”的,还有 MOCVD 设备商。MOCVD 是制造 LED 芯片的设备,在国产化之前,美、德两家巨头凭借垄断,每台设备卖 2000 万。而等国内厂商开始介入时,售价立刻暴跌至 600 万。结果,数十个国内玩家,如今只剩下中微、中晟光电等少数几家。

去年,高通在华推出重磅计划,与大唐电信旗下的联芯科技成立领盛科技,联手进军中低端芯片市场。关心国内芯片产业的很多人士都一致认为,这是高通要“借刀杀人”,要以领盛科技用价格战的方式绞杀正在向中高端芯片市场进军的紫光展讯。而 5 月 4 日的最新消息显示,这家合资企业已正式获批。

手段之二是发动专利战,拖住对手,打击下游客户信心。
手段之二是发动专利战,拖住对手,打击下游客户信心。

2000 年,张汝京出走台湾,在上海创建中芯国际。之后,他从台积电四处挖人,使得中芯短时间内迅速崛起。但厄运随之而来,老冤家张忠谋很快就发起了专利战。这场持续近七年的战争,最终的结果是中芯割地赔款,张汝京黯然出局。

台积电通过此举,成功拖住了中芯。而当初引进张汝京的江上舟,也在两年后辞世,死前一直惦记着中芯的未来。

同样的一幕发生在中微身上。2007 年,尹志尧领衔的中微刚推出自己的蚀刻机,就被老东家美国应用材料告上法庭。紧接着,另一巨头泛林火上浇油。好在中微从一开始就小心规避专利陷阱,最终赢得了诉讼。

但结果却不甚理想,中微不但赔上巨额的诉讼费,还赔上了下游客户的信心。再加上适逢经济危机,不得不暂时砍掉 MOCVD 业务。

很不友好的环境

事实上,中国很早就重视了半导体的大战。最早可追溯至上世纪 60 年代。但在后来的发展中,由于自身路径,国内产研环境,以及不友好的产业环境等多种原因,逐渐掉队。

中国半导体一直是在冒着敌人的炮火匍匐地前进。而今,敌人的炮火更是越来越凶猛,越来越密集。

西方一直有一个针对出口管制的制度安排,最早是 1949 年成立的巴黎统筹委员会,之后在 1996 年演变为瓦森纳协定。该协定包含军用、民用两份控制清单,目的是限制向相关国家出口敏感产品和技术,中国就属于被限制的对象。

过去几十年,国家一直在努力突破这种封锁。90 年代,先后批复 908/909 工程,时任领导人表态:砸锅卖铁也要把半导体搞上去。国务院则用财政赤字拨款。

然而,作为两项工程的产物,华晶、华虹等企业到国际上采购设备却遭到抵制,最终发展受限,一直未有大的突破。

2006 年以后,国家又搞了 01 和 02 专项。前者剑指核心电子器件、高端通用芯片和基础软件,俗称核高基;后者剑指 IC 制造和成套工艺。

近年来,两个专项相继开花结果,例如中微的蚀刻机已看齐世界水平,中芯国际的工艺已挺进到 28 纳米,但受限于不友好的产业环境,水平还是差强人意。

以光刻机为例,ASML 的 EUV 光刻机即将投入 7 纳米工艺,而国内最先进的量产水平是 90 纳米。之所以差距惊人,原因之一是买不到高水平的镜头和光源,这是光刻机的核心部件,而国内缺乏相关的技术。

坊间一直传说,瓦森纳协定禁止向中国出口高端光刻机。这种说法后来遭到 ASML 公司的否认,该公司声称,最快将于 2019 年在中国晶圆厂见到 EUV 光刻机。

但业内人士透露,在瓦森纳协定中,确实是有光刻机限售条款的,只不过每隔几年,条款就会作相应的调整。之所以调的原因也是,国产水平在不断提高,所谓的调整,顶多也就是“敌人”不断地根据我方的进展调整炮火的轻重与射程。

一个有意思的细节是,国内研究机构在相关技术上取得突破不久,ASML 公司就否认了禁售的传闻。

产业环境并不是唯一的障碍,来自国家层面的干预更加要命。

美国政府曾多次否决中国企业针对美企的收购行为,包括著名的紫光并购美光计划。最近更是制裁了中兴通讯,断供其芯片。

2016 年,国内某基金收购德国爱思强时,连 FBI 都跳出来施压,最终迫使德方放弃了交易。而据中兴员工透露,在中兴断芯事件前,FBI 就入驻了公司内部。

为钱一把辛酸泪

半导体是一个烧钱的行业。上世纪 90 年代,中央在财政非常拮据的情况下,特批了 40 亿元搞半导体。但这点钱只是杯水车薪。

向民间要投资,更遭到冷遇。

这个行业不但烧钱,而且周期长,技术更新快,你刚研发出来,别人已经开始打价格战。这意味着,前期要不断砸钱,还见不到水花。对民营资本而言,这是无法承受之痛。

中微董事长尹志尧 2004 年满腔热血回国创业,就遇到了这个难题。

为造蚀刻机,中微在短时间内,烧光了地方政府和自筹资金,只好四处筹钱续命。当时民间资本对这个行业缺乏了解,也缺乏意愿,尹的一腔热血只能碰一鼻子灰。

万般无奈之下,只好赴硅谷融资。两周内,十几家风投踏破门槛,愿意提供 5000 万美元。此情此景,令报国心切的尹志尧百感交集:难道只有美国造得出蚀刻机?

苦心积虑,只为国产化,绝不能大权旁落。在拿回几笔续命钱后,尹志尧继续寻找国内投资人。后来,在江上舟的引荐下,终于有国开行为中微背书。

跟中微有类似遭遇的,还有京东方。

做液晶面板 20 年,京东方一直伴随各种非议。尤其是它越亏越投的做法,更是被人质疑为绑架政府,因为其大部分资金来自银行和地方政府。

在资本市场上,由于不断增发,京东方背上了圈钱的骂名。但这些钱,大部分来自国资背景。其间,京东方曾多次拉私募基金入股,均被拒绝,理由是投资大,短期难见利。

幸运的是,苦熬多年后,京东方靓丽崛起,勇夺五个全球第一,让当初的坚持者赚得盆满钵满。

近年来,国家加大了对半导体行业的投入。大基金一期投入 1300 亿,已收尾;二期预计超过 2000 亿。乍一看,钱不少,但需要投资的项目也很多,涵盖芯片设计、制造、封测、设备等诸多领域。以一期为例,累计投资 62 个项目,涉及 23 家上市公司。

这样平均下来,每家获得的投资额并不多,跟撒胡椒面一样。

而纯靠市场手段去募集资金的难度同样非常大。以紫光为例,真正的大规模投资还没开始,市场就已不乏圈钱的质疑声,跟京东方当年恶战面板产业时所遭遇的挑战几乎没有两样。

显然,这样的投资强度是不够的。再看一组数据,就更能看出差距。

全球芯片三巨头,三星、英特尔、台积电,每年的投资都在百亿美元级别,而中芯国际不到对方的十分之一。

设备三巨头,应用材料、泛林、东京电子,每年在研发上投入5—10 亿美元不等,而中微半导体直到去年,收入才破 10 亿,还是人民币。

人才的切肤之痛

搞好半导体,主要靠三件事:一个是钱,一个是人,外加一个政策。钱的事好说,毕竟这些年国家不差钱;人的事很难搞,因为非一朝一夕之功。

数据显示,我国未来需要 70 万半导体人才,目前只有不到 30 万,缺口 40 万。

我们尤其缺行业的领军人物。这些年,01/02 专项取得的重大突破,很多都是海归创造的,他们长期任职于欧美半导体公司,拥有丰富的行业经验。

张汝京,德州仪器工作 20 年,在全球盖过 20 座芯片工厂。回国后,创办了中芯国际,以及国内第一家 12 寸硅晶圆厂,被誉为中国半导体之父。

尹志尧,闯荡硅谷 20 年,先后任职于英特尔、泛林和应用材料。回国后,创办中微半导体,几乎以一己之力,将国内介质蚀刻机带到了世界水平。

此外,曾任职于霍尼韦尔的姚力军,回国后做出了高纯度溅射靶材;美国留学归来的王淑敏,研发出国内第一款研磨液,打破了国外垄断。

日本、韩国和中国台湾地区,也是半导体人才的重要来源。大陆两大代工厂,中芯国际和厦门联芯,都有台湾背景,很多技术人员也来自台湾。

这几年,国内存储器的跨越式发展,也离不开日本、韩国、中国台湾技术人员的贡献。日本厂商尔必达破产后,大批日本人赴中国寻找机会,包括前社长坂本幸雄。

以紫光为例,外界看到其董事长赵伟国在资本和产业上动作频频,而事实上,他用心同样多的也是找人。其总投资预计 1000 亿美元左右的长江储存的执行董事长高启全,便是他费尽心思从台湾争抢过来的世界级半导体产业猛人。

目前,长江存储的 3D NAND 闪存已经获得第一笔订单,总计 10776 颗芯片,将用于 8GB USD 存储卡产品。今年 10 月,我国首批拥有完全自主知识产权的 32 层三维 NAND 闪存芯片将在这里实现量产,这也是中国集成电路闪存芯片产业规模化发展“零”的突破。

然而,引进人才毕竟不是长久之计。国内半导体行业要想大发展,必须立足于培养本土人才。

一方面,外来人才和本土人才,在利益、观念等方面是有冲突的。中芯国际在江上舟离世后,就陷入外来人员和本土人员的派系之争,一度影响到公司的发展。

另一方面,半导体是微加工行业,工艺很关键。很多外国技术人员之所以牛,是他们一辈子只干一件事积累起来的。

2002 年,上海微电子总经理赴德国考察,有工程师告诉他:“给你们全套图纸,也做不出来。”开始他不服,后来明白了。那里的抛光工人,祖孙三代干着同样一件事,“同样一个镜片,不同工人去磨,光洁度相差十倍。”

这恰是中国半导体行业的一个切肤之痛。

我们不缺设计人员,但缺工艺工程师,而这类人才很难靠引进来满足。

中芯国际之所以在制程上落后2-3 代,除了光刻机等设备受限外,工艺上的经验欠缺才是更重要的原因。

遗憾的是,目前国内不少高校的人才培养,与现实脱节。大多数学生跑去做软件,做应用,却不愿搞更基础的计算机系统和底层结构。

不容乐观的生态链

在自然界,动植物要生存,必须融入生物链。

做企业也一样。只不过,在企业这个生态链中,先行者有成本优势,再加上稳定可靠的供应链,使得他们能够持续盈利,进而支撑着技术的不断进步。

这对后来者而言,如同一道不可逾越的壁垒。

这些年,中国半导体产业面临的一大难题,就是如何融入这个生态链。

龙芯是一个很好的例子。这款中科院计算所自主研发的芯片,尽管性能不俗,但一直游离在民用市场外。原因很简单,市场上有更成熟、性价比更高的处理器。

龙芯的遭遇并非个案。大部分芯片制造厂,在采购装备时,一定考虑的是进口设备,因为国产设备刚起步,质量不稳定、一致性差。

当年,LED 芯片刚在国内兴起时,各大芯片厂均只认美、德设备,而地方政府的补贴也只给进口设备。

内忧外患,将国内 MOCVD 设备商逼到了绝境,“客户不太愿意用……因为不信任,需要重新验证,这又要花钱。”中晟光电负责人陈爱华说。

直到后来,工信部为每台国产设备提供 2000 万补贴,形势才开始好转。

新产品研制出来,没有人用,就不可能盈利;没有盈利,就没钱搞研发。结果只能是恶性循环,胎死腹中。

这个时候,需要来自生态链的支持。国内半导体行业近年来的进步,尤其是设备和材料领域,很大程度上得益于中芯国际、厦门联芯等晶圆制造厂的带动。

但这种机会,不是国外厂商所能提供的。那种一切交给市场的想法,不能说幼稚,至少也是罔顾事实的。

几年前,韩国 SK 海力士曾采购过中微半导体的蚀刻机,后来放弃了。表面上是因为性能不及预期,实际是担心泄露核心工艺的秘密。

一个有意思的细节是,尽管中国民用芯片九成依赖进口,但军用芯片却基本能自给自足,甚至还有出口。比如,龙芯就稳定运行在北斗导航系统上。

另一款自主芯片,来自总参 56 所的申威,则撑起了我们的太湖之光超级计算机。

军用很出彩,民用却卖不出去?问题就在生态链上。

军用市场是一个封闭的小圈子,产品追求稳定性和抗干扰,对性能并不敏感。龙芯和申威在这里能找到自己的位置。

反观民用市场,性能为王,技术迭代快,龙芯和申威很难融入这样的生态链。

结束语

通过以上梳理,我们看到,国内造不好高端芯片,有外部因素,也有自身原因。

形势看似悲观,前景却很光明。

一方面,半导体行业向中国转移的大趋势不会改变。另一方面,摩尔定律在工艺上逐渐趋近极限,客观上给了国内企业追赶的机会,而国家也正进一步加大支持和投入。

最近,我国国家领导人在武汉考察时,就特别到长江存储作了考察与指示。受此鼓舞的赵伟国则表示,公司将尽快在全球集成电路产业占据重要地位,用 5 到 10 年时间成为全球三维闪存主要供应商之一。

在国家的支持和企业的自身努力下,国内半导体产业链正在出现由点到面的突破,而在三大历史性机遇的支持下,我们也必须迎头赶上。

否则,后面的仗将会越来越难打,因为半导体产业不光是现代高科技产业的基础,更是支撑和保障国家安全的战略性、基础性和先导性产业,而且重要性会越来越大。

from:https://news.cnblogs.com/n/613348/

从 0 到 1 再到 100, 搭建、编写、构建一个前端项目

1. 选择现成的项目模板还是自己搭建项目骨架

搭建一个前端项目的方式有两种:选择现成的项目模板、自己搭建项目骨架。

选择一个现成项目模板是搭建一个项目最快的方式,模板已经把基本的骨架都搭建好了,你只需要向里面填充具体的业务代码,就可以通过内置的工具与命令构建代码、部署到服务器等。

一般来说,一个现成的项目模板会预定义一定的目录结构、书写方式,在编写项目代码时需要遵循相应的规范;也会内置必要的工具,比如 .editorconfigeslintstylelintprettierhuskylint-staged 等;也会内置必要的命令(package.json | scripts),比如 本地开发:npm run dev本地预览:npm run start构建:npm run build部署:npm run deploy等。

社区比较好的项目模板:

这些模板的使用又分为两种:使用 git 直接克隆到本地、使用命令行创建。

(使用现有模板构建的项目,可以跳过第 2 ~ 7 步)

1.1 使用 git 直接克隆到本地

这是一种真正意义上的模板,可以直接到模板项目的 github 主页,就能看到整个骨架,比如 react-boilerplateant-design-provue-element-adminreact-starter-kit

以 react-boilerplate 为例:

克隆到本地:

git clone --depth=1 https://github.com/react-boilerplate/react-boilerplate.git <你的项目名字>

切换到目录下:

cd <你的项目名字>

一般来说,接下来运行 npm run install 安装项目的依赖后,就可以运行;有些模板可能有内置的初始化命令,比如 react-boilerplate

npm run setup

启动应用:

npm start

这时,就可以在浏览器中预览应用了。

1.2 使用命令行创建

这种方式需要安装相应的命令,然后由命令来创建项目。

以 create-react-app 为例:

安装命令:

npm install -g create-react-app

创建项目:

create-react-app my-app

运行应用:

cd my-app
npm start

1.3 自己搭建项目骨架

如果你需要定制化,可以选择自己搭建项目的骨架,但这需要开发者对构建工具如 webpacknpmnode 及其生态等有相当的了解与应用,才能完美的把控整个项目。

下面将会一步一步的说明如何搭建一个定制化的项目骨架。

2. 选择合适的规范来写代码

js 模块化的发展大致有这样一个过程 iife => commonjs/amd => es6,而在这几个规范中:

  • iifejs 原生支持,但一般不会直接使用这种规范写代码
  • amdrequirejs 定义的加载规范,但随着构建工具的出现,便一般不会用这种规范写代码
  • commonjsnode 的模块加载规范,一般会用这种规范写 node 程序
  • es6ECMAScript2015 定义的模块加载规范,需要转码后浏览器才能运行

这里推荐使用 es6 的模块化规范来写代码,然后用工具转换成 es5 的代码,并且 es6 的代码可以使用 Tree shaking 功能。

参考:

3. 选择合适的构建工具

对于前端项目来说,构建工具一般都选用 webpackwebpack 提供了强大的功能和配置化运行。如果你不喜欢复杂的配置,可以尝试 parcel

参考:

4. 确定是单页面应用(SPA)还是多页面应用

因为单页面应用与多页面应用在构建的方式上有很大的不同,所以需要从项目一开始就确定,使用哪种模式来构建项目。

4.1 多页面应用

传统多页面是由后端控制一个 url 对应一个 html 文件,页面之间的跳转需要根据后端给出的 url 跳转到新的 html 上。比如:

http://www.example.com/page1 -> path/to/page1.html
http://www.example.com/page2 -> path/to/page2.html
http://www.example.com/page3 -> path/to/page3.html

这种方式的应用,项目里会有多个入口文件,搭建项目的时候就需要对这种多入口模式进行封装。另外,也可以选择一些封装的多入口构建工具,如 lila

4.2 单页面应用

单页面应用(single page application),就是只有一个页面的应用,页面的刷新和内部子页面的跳转完全由 js 来控制。

一般单页面应用都有以下几个特点:

  • 本地路由,由 js 定义路由、根据路由渲染页面、控制页面的跳转
  • 所有文件只会加载一次,最大限度重用文件,并且极大提升加载速度
  • 按需加载,只有真正使用到页面的时候,才加载相应的文件

这种方式的应用,项目里只有一个入口文件,便无需封装。

参考:

5. 选择合适的前端框架与 UI 库

一般在搭建项目的时候就需要定下前端框架与 UI 库,因为如果后期想更换前端框架和 UI 库,代价是很大的。

比较现代化的前端框架:

一些不错的组合:

参考:

6. 定好目录结构

一个好的目录结构对一个好的项目而言是非常必要的。

一个好的目录结构应当具有以下的一些特点:

  1. 解耦:代码尽量去耦合,这样代码逻辑清晰,也容易扩展
  2. 分块:按照功能对代码进行分块、分组,并能快捷的添加分块、分组
  3. 编辑器友好:需要更新功能时,可以很快的定位到相关文件,并且这些文件应该是很靠近的,而不至于到处找文件

比较推荐的目录结构:

多页面应用

|-- src/ 源代码目录

    |-- page1/ page1 页面的工作空间(与这个页面相关的文件都放在这个目录下)
        |-- index.html html 入口文件
        |-- index.js js 入口文件
        |-- index.(css|less|scss) 样式入口文件
        |-- html/ html 片段目录
        |-- (css|less|scss)/ 样式文件目录
        |-- mock/ 本地 json 数据模拟
        |-- images/ 图片文件目录
        |-- components/ 组件目录(如果基于 react, vue 等组件化框架)
        |-- ...
        
    |-- sub-dir/ 子目录
        |-- page2/ page2 页面的工作空间(内部结构参考 page1)
            |-- ...
        
    |-- ...
    
|-- html/ 公共 html 片段
|-- less/ 公共 less 目录
|-- components/ 公共组件目录
|-- images/ 公共图片目录
|-- mock/ 公共 api-mock 文件目录
|-- ...

单页面应用

|-- src/ 源代码目录
    |-- page1/ page1 页面的工作空间
        |-- index.js 入口文件
        |-- services/ service 目录
        |-- models/ model 目录
        |-- mock/ 本地 json 数据模拟
        |-- images/ 图片文件目录
        |-- components/ 组件目录(如果基于 react, vue 等组件化框架)
        |-- ...
        
    |-- module1/ 子目录
        |-- page2/ page2 页面的工作空间(内部结构参考 page1)
        
    |-- ...
    
|-- images/ 公共图片目录
|-- mock/ 公共 api-mock 文件目录
|-- components/ 公共组件目录   
|-- ... 

参考:

7. 搭建一个好的脚手架

搭建一个好的脚手架,能够更好的编写代码、构建项目等。

可以查看 搭建自己的前端脚手架 了解一些基本的脚手架文件与工具。

比如:

|-- /                              项目根目录
    |-- src/                       源代码目录
    |-- package.json               npm 项目文件
    |-- README.md                  项目说明文件
    |-- CHANGELOG.md               版本更新记录
    |-- .gitignore                 git 忽略配置文件
    |-- .editorconfig              编辑器配置文件
    |-- .npmrc                     npm 配置文件
    |-- .npmignore                 npm 忽略配置文件
    |-- .eslintrc                  eslint 配置文件
    |-- .eslintignore              eslint 忽略配置文件
    |-- .stylelintrc               stylelint 配置文件
    |-- .stylelintignore           stylelint 忽略配置文件
    |-- .prettierrc                prettier 配置文件
    |-- .prettierignore            prettier 忽略配置文件
    
    |-- .babelrc                   babel 配置文件
    |-- webpack.config.js          webpack 配置文件
    |-- rollup.config.js           rollup 配置文件
    |-- gulpfile.js                gulp 配置文件
    
    |-- test/                      测试目录
    |-- docs/                      文档目录
    |-- jest.config.js             jest 配置文件
    |-- .gitattributes             git 属性配置
  • .editorconfig: 用这个文件来统一不同编辑器的一些配置,比如 tab 转 2 个空格、自动插入空尾行、去掉行尾的空格等,http://editorconfig.org
  • eslintstylelintprettier: 规范化代码风格、优化代码格式等
  • huskylint-staged: 在 git 提交之前对代码进行审查,否则不予提交
  • .gitlab-ci.ymlgitlab ci 持续集成服务

参考:

=================================================

到这里为止,一个基本的项目骨架就算搭建好了。

8. 使用版本控制系统管理源代码(git)

项目搭建好后,需要一个版本控制系统来管理源代码。

比较常用的版本管理工具有 gitsvn,现在一般都用 git

一般开源的项目可以托管到 http://github.com,私人的项目可以托管到 https://gitee.comhttps://coding.net/,而企业的项目则需要自建版本控制系统了。

自建版本控制系统主要有 gitlabgogsgiteagitlab 是由商业驱动的,比较稳定,社区版是免费的,一般建议选用这个;gogs, gitea 是开源的项目,还不太稳定,期待进一步的更新。

所以,git + gitlab 是不错的配合。

9. 编写代码

编写代码时,js 选用 es6 的模块化规范来写(如果喜欢用 TypeScript,需要加上 ts-loader),样式可以用 lessscsscss 来写。

写 js 模块文件时,注释可以使用 jsdoc 的规范来写,如果配置相应的工具,可以将这些注释导出接口文档。

因为脚手架里有 huskylint-staged 的配合,所以每次提交的代码都会进行代码审查与格式优化,如果不符合规范,则需要把不规范的代码进行修改,然后才能提交到代码仓库中。

比如 console.log(haha.hehe); 这段代码就会遇到错误,不予提交:

这个功能定义在 package.json 中:

{
  "devDependencies": {             工具依赖
    "babel-eslint": "^8.2.6",
    "eslint": "^4.19.1",
    "husky": "^0.14.3",
    "lint-staged": "^7.2.0",
    "prettier": "^1.14.0",
    "stylelint": "^9.3.0",
    "eslint-config-airbnb": "^17.0.0",
    "eslint-config-prettier": "^2.9.0",
    "eslint-plugin-babel": "^5.1.0",
    "eslint-plugin-import": "^2.13.0",
    "eslint-plugin-jsx-a11y": "^6.1.0",
    "eslint-plugin-prettier": "^2.6.2",
    "eslint-plugin-react": "^7.10.0",
    "stylelint-config-prettier": "^3.3.0",
    "stylelint-config-standard": "^18.2.0"
  },
  "scripts": {                     可以添加更多命令
    "precommit": "npm run lint-staged",
    "prettier": "prettier --write \"./**/*.{js,jsx,css,less,sass,scss,md,json}\"",
    "eslint": "eslint .",
    "eslint:fix": "eslint . --fix",
    "stylelint": "stylelint \"./**/*.{css,less,sass,scss}\"",
    "stylelint:fix": "stylelint \"./**/*.{css,less,sass,scss}\" --fix",
    "lint-staged": "lint-staged"
  },
  "lint-staged": {                 对提交的代码进行检查与矫正
    "**/*.{js,jsx}": [
      "eslint --fix",
      "prettier --write",
      "git add"
    ],
    "**/*.{css,less,sass,scss}": [
      "stylelint --fix",
      "prettier --write",
      "git add"
    ],
    "**/*.{md,json}": [
      "prettier --write",
      "git add"
    ]
  }
}
  • 如果你想禁用这个功能,可以把 scripts 中 "precommit" 改成 "//precommit"
  • 如果你想自定 eslint 检查代码的规范,可以修改 .eslintrc, .eslintrc.js 等配置文件
  • 如果你想自定 stylelint 检查代码的规范,可以修改 .stylelintrc, .stylelintrc.js 等配置文件
  • 如果你想忽略某些文件不进行代码检查,可以修改 .eslintignore, .stylelintignore 配置文件

参考:

10. 组件化

当项目拥有了一定量的代码之后,就会发现,有些代码是很多页面共用的,于是把这些代码提取出来,封装成一个组件,供各个地方使用。

当拥有多个项目的时候,有些组件需要跨项目使用,一种方式是复制代码到其他项目中,但这种方式会导致组件代码很难维护,所以,一般是用另一种方式:组件化。

组件化就是将组件独立成一个项目,然后在其他项目中安装这个组件,才能使用。

一般组件化会配合私有 npm 仓库一起用。

|-- project1/ 项目1
    |-- package.json
    
|-- project2/ 项目2
    |-- package.json    

|-- component1/ 组件1
    |-- package.json

|-- component2/ 组件2
    |-- package.json

在 project1 中安装 component1, component2 组件:

# package.json
{
  "dependencies": {
    "component1": "^0.0.1",
    "component2": "^0.0.1"
  }
}
import compoennt1 from 'compoennt1';
import compoennt2 from 'compoennt2';

如果想要了解怎样写好一个组件(npm package),可以参考 从 1 到完美,写一个 js 库、node 库、前端组件库

参考:

11. 测试

测试的目的在于能以最少的人力和时间发现潜在的各种错误和缺陷,这在项目更新、重构等的过程中尤其重要,因为每当更改一些代码后,你并不知道这些代码有没有问题、会不会影响其他的模块。如果有了测试,运行一遍测试用例,就知道更改的代码有没有问题、会不会产生影响。

一般前端测试分以下几种:

  1. 单元测试:模块单元、函数单元、组件单元等的单元块的测试
  2. 集成测试:接口依赖(ajax)、I/O 依赖、环境依赖(localStorage、IndexedDB)等的上下文的集成测试
  3. 样式测试:对样式的测试
  4. E2E 测试:端到端测试,也就是在实际生产环境测试整个应用

一般会用到下面的一些工具:

另外,可以参考 聊聊前端开发的测试

12. 构建

一般单页面应用的构建会有 npm run build 的命令来构建项目,然后会输出一个 html 文件,一些 js/css/images ... 文件,然后把这些文件部署到服务器就可以了。

多页面应用的构建要复杂一些,因为是多入口的,所以一般会封装构建工具,然后通过参数传入多个入口:

npm run build -- page1 page2 dir1/* dir2/all --env test/prod
  • page1, page2 确定构建哪些页面;dir1/*, dir2/all 某个目录下所有的页面;all, * 整个项目所有的页面
  • 有时候可能还会针对不同的服务器环境(比如测试机、正式机)做出不同的构建,可以在后面加参数
  • -- 用来分割 npm 本身的参数与脚本参数,参考 npm – run-script 了解详情

多页面应用会导出多个 html 文件,需要注意这些导出的 html 不要相冲突了。

当然,也可以用一些已经封装好的工具,如 lila

13. 部署

在构建好项目之后,就可以部署到服务器了。

传统的方式,可以用 ftp, sftp 等工具,手动传到服务器,但这种方式比较笨拙,不够自动化。

自动化的,可以用一些工具部署到服务器,如 gulpgulp-ssh,当然也可以用一些封装的工具,如 md-synclila 等

以 md-sync 为例:

npm install md-sync --save-dev

md-sync.config.js 配置文件:

module.exports = [
  {
    src: './build/**/*',
    remotePath: 'remotePath',
    server: {
      ignoreErrors: true,
      sshConfig: {
        host: 'host',
        username: 'username',
        password: 'password'
      }
    },
  },
  {
    src: './build/**/*.html',
    remotePath: 'remotePath2',
    server: {
      ignoreErrors: true,
      sshConfig: {
        host: 'host',
        username: 'username',
        password: 'password'
      }
    },
  },
];

在 package.json 的 scripts 配置好命令:

"scripts": {
  "deploy": "md-sync"
}
npm run deploy

另外,一般大型项目会使用持续集成 + shell 命令(如 rsync)部署。

14. 持续集成测试、构建、部署

一般大型工程的的构建与测试都会花很长的时间,在本地做这些事情的话就不太实际,这就需要做持续集成测试、构建、部署了。

持续集成工具用的比较多的:

jenkins 是通用型的工具,可以与 githubbitbucketgitlab 等代码托管服务配合使用,优点是功能强大、插件多、社区活跃,但缺点是配置复杂、使用难度较高。

gitlab ci 是 gitlab 内部自带的持续集成功能,优点是使用简单、配置简单,但缺点是不及 jenkins 功能强大、绑定 gitlab 才能使用。

以 gitlab 为例(任务定义在 .gitlab-ci.yml 中):

stages:
  - install
  - test
  - build
  - deploy

# 安装依赖
install:
  stage: install
  only:
    - dev
    - master
  script:
    - npm install

# 运行测试用例
test:
  stage: test
  only:
    - dev
    - master
  script:
    - npm run test

# 编译
build:
  stage: build
  only:
    - dev
    - master
  script:
    - npm run clean
    - npm run build

# 部署服务器
deploy:
  stage: deploy
  only:
    - dev
    - master
  script:
    - npm run deploy

以上配置表示只要在 dev 或 master 分支有代码推送,就会进行持续集成,依次运行:

  • npm install
  • npm run test
  • npm run clean
  • npm run build
  • npm run deploy

最终完成部署。如果中间某个命令失败了,将停止接下的命令的运行,并将错误报告给你。

这些操作都在远程机器上完成。

=================================================

到这里为止,基本上完成了一个项目的搭建、编写、构建。

15. 清理服务器上过期文件

现在前端的项目基本上都会用 webpack 打包代码,并且文件名(html 文件除外)都是 hash 化的,如果需要清除过期的文件而又不想把服务器上文件全部删掉然后重新构建、部署,可以使用 sclean 来清除过期文件。

16. 收集前端错误反馈

当用户在用线上的程序时,怎么知道有没有出 bug;如果出 bug 了,报的是什么错;如果是 js 报错,怎么知道是那一行运行出了错?

所以,在程序运行时捕捉 js 脚本错误,并上报到服务器,是非常有必要的。

这里就要用到 window.onerror 了:

window.onerror = (errorMessage, scriptURI, lineNumber, columnNumber, errorObj) => {
  const data = {
    title: document.getElementsByTagName('title')[0].innerText,
    errorMessage,
    scriptURI,
    lineNumber,
    columnNumber,
    detailMessage: (errorObj && errorObj.message) || '',
    stack: (errorObj && errorObj.stack) || '',
    userAgent: window.navigator.userAgent,
    locationHref: window.location.href,
    cookie: window.document.cookie,
  };

  post('url', data); // 上报到服务器
};

线上的 js 脚本都是压缩过的,需要用 sourcemap 文件与 source-map 查看原始的报错堆栈信息,可以参考 细说 js 压缩、sourcemap、通过 sourcemap 查找原始报错信息 了解详细信息。

参考:

后续

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

from:https://segmentfault.com/a/1190000017158444