一、引言:
1、解释性语言:PHP,swoole,JAVA(有争议:先被编译为.class字节码后被JVM解释执行,都是具两种特性。因为虽然java也需要编译,编译成.class文件,但是并不是机器可以识别的语言,而是字节码,最终还是需要 jvm的解释,所以一般被归为解释性语言),JS,python,shell
2、编译型语言:C,C++, Golang,汇编
汇编语言:
机器语言和汇编语言都是低级语言,它们都是面向机器的。汇编语言直接操作硬件,对CPU内的寄存器、运算器进行控制。
汇编语言由机器语言发展而来。开始人们是直接用机器语言编程的,后来有人编出了汇编程序,就可以直接用比较易用的汇编语言写程序。汇编程序负责把汇编语言写的程序转换成机器语言。有些编译器会现将高级语言的源代码编译成目标机器的汇编码,然后再经过汇编步骤才生成机器码。
汇编语言(汇编代码)-》汇编程序/汇编器(汇编语言转码即可,什么程序实现都行)-》汇编码-》二进制机器码。
我们编写的源代码是人类语言,我们自己能够轻松理解;但是对于计算机硬件(CPU),源代码就是天书,根本无法执行,计算机只能识别某些特定的二进制指令,在程序真正运行之前必须将源代码转换成二进制指令。
所谓的二进制指令,也就是机器码,是 CPU 能够识别的硬件层面的“代码”,简陋的硬件(比如古老的单片机)只能使用几十个指令,强大的硬件(PC 和智能手机)能使用成百上千个指令。
然而,究竟在什么时候将源代码转换成二进制指令呢?不同的编程语言有不同的规定:
有的编程语言要求必须提前将所有源代码一次性转换成二进制指令,也就是生成一个可执行程序(Windows 下的 .exe),比如C语言、C++、Golang、Pascal(Delphi)、汇编等,这种编程语言称为编译型语言,使用的转换工具称为编译器。
有的编程语言可以一边执行一边转换,需要哪些源代码就转换哪些源代码,不会生成可执行程序,比如 Python、JavaScript、PHP、Shell、MATLAB 等,这种编程语言称为解释型语言,使用的转换工具称为解释器。
Java 和 C# 是一种比较奇葩的存在,它们是半编译半解释型的语言,源代码需要先转换成一种中间文件(字节码文件),然后再将中间文件拿到虚拟机中执行。Java 引领了这种风潮,它的初衷是在跨平台的同时兼顾执行效率;C# 是后来的跟随者,但是 C# 一直止步于 Windows 平台,在其它平台鲜有作为。
是不是高级语言一定会先转变成汇编语言?
不是主要看编译器是如何设计的,有的编译器直接编译成机器语言,有的编译器直接编译成汇编语言再汇编成机器语言。
大多解释语言就不会转成汇编语言,例如Java就生成*.class字节码,然后解释执行。
二、C语言的编译:
程序员编写的C语言代码,首先要经过C语言编译器,生成汇编代码,这个过程称为编译阶断,当C语言编译器生成汇编代码后,再调用汇编器来将汇编代码编译成汇编指令。(如何写编译器:直接用指令码写出第一个汇编语言编译器,然后就可以用汇编语言写新的编译器,其实很多语言都可以写汇编编译器。比如第一个C语言编译器可能是用汇编写的,但是以后的C编译器都可以用C语言来写--也就是编译到最终还是执行的汇编代码)
这是一种站在巨人肩人的作法,最早的C++编程语言也是这样的实现方法,只不过那时候叫Cfront程序,Cfront程序的作用是将C++代码转换成C语言代码,类似于一个文本处理器,然后再调用C语言编译器,将C源码编译成汇编代码,然后再调用汇编器将汇编代码编译成机器码。
这个过程,在Windows平台上不容易操作,但是在Linux平台上很容易看到。以gcc这款c语言编译器为例,它实际上是四个小程序。
cp: c语言预处理程序,有它负责进行预处理操作。
cc: C语言编译器,它负责将C源码编译成汇编代码。
as: 汇编器,它负责将汇编代码编译成机器码,一般使用gcc test.c这样的命令编译C语言时,会生成一个a.out的程序,它实际上指的就是as ouput,即汇编器输出文件。
link: 链接器,它负责将汇编器输入的机器码和库打包成一个操作系统可以运行的可执行文件,在Linux上的可执行文件格式是ELF格式,这个格式的实现是有链接器来完成的。
高级语言都会先转译成C再编译吗?
历史上是有一些语言会选择转译到c,再交给c编译器完成到二进制的编译(事实上现在也有),但这一直不是也不可能是主流做法。稍了解过编译相关知识就可以明白编译器(或者转译器)跑完AST生成后,是直接生成二进制或是转译成其他语言的工作量不是在一个数量级的,除非特殊原因没必要多此一举。
三、PHP的编译:
就PHP语言来说,它也是一组符合一定规则的约定的指令。 php的解释器是用c写的,解释器相当于弱编译器,但是php本身并不基于某种底层语言。 在编程人员将自己的想法以PHP语言实现后,通过PHP的虚拟机(确切的来说应该是引擎Zend)将这些PHP指令转变成C语言指令(编译过程)opcode(类似JAVA的.class是字节码,由虚拟机JVM执行),而C语言又会转变成汇编语言, 最后汇编语言将根据处理器的规则转变成机器码执行。PHP跨平台不是解释器解释而是zend虚拟机屏蔽了操作系统的区别,和java的JVM虚拟机有点像。但和Java不同的是.class是保留住的可以打包运行,而PHP编译完opcode程序执行完就丢弃。
我们分别来脑补下PHP请求运行的整个流程:
nginx -》 httpd-》fastCGI-》PHP-FPM(前两统称SAPI)-》PHP (PHPAPI + EXT)-》Zend引擎-》调用C函数编译成opcode -》并由Zend虚拟机执行这些指令
apache -> httpd -> php5_module(sapi) -> PHP->...
从一种语言到另一种语言的转化称之为编译,这两种语言分别可以称之为源语言和目标语言。 这种编译过程通过发生在目标语言比源语言更低级(或者说更底层)。 语言转化的编译过程是由编译器来完成, 编码器通常被分为一系列的过程:词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成等。 前面几个阶段(词法分析、语法分析和语义分析)的作用是分析源程序,我们可以称之为编译器的前端。 后面的几个阶段(中间代码生成、代码优化和目标代码生成)的作用是构造目标程序,我们可以称之为编译器的后端。
一种语言被称为编译类语言,一般是由于在程序执行之前有一个翻译的过程, 其中关键点是有一个形式上完全不同的等价程序生成(如C的.exe和Java的.class文件,但JAVA不属于编译型语言)。 而PHP之所以被称为解释类语言,就是因为并没有这样的一个程序生成, 它生成的是中间代码Opcode,这只是PHP的一种内部数据结构。其实严格地说PHP是一种编译类语言,因为综上所述PHP是有编译过程的,先编译成中间代码opcode后执行,只不过每次执行完程序编译完的Opcode被销毁。但现在已经可以借助一些第三方工具加速器来缓存住opcode,类似JAVA的.class文件。
四、PHP底层源码分析:
这里推荐PHP底层原理的参考文:
PHP底层源码分析的视频(强推):PHP5.3底层运行原理(底层源码分析C)
推荐书籍:PHP 7底层设计与源码实现
视频分析了PHP的底层原理C的实现,小马把大概的提纲列一下当脑图用,有助于复盘。总结一下就是一切皆结构体,哈希表作为花名册发挥着重大作用。
以下把一些精华笔记也放上供回忆复盘用。
五、如何写一个PHP拓展:
1、如何写一个PHP拓展:
写拓展要先熟悉底层源码才好上手。参考如下:
假设安装extend_test拓展:
1、生成扩展:
在extend_test拓展文件夹下,运行/home/php/bin/phpize(实际为phpize所在路径)
然后运行./configure --with-php-config=/home/php/bin/php-config(实际为php-config所在路径)
2、编译安装
make
make install
3、配置PHP文件,将拓展配置进去即可。
swoole可以理解为在C的上面套了一层PHP语法的壳,使PHP可以使用上协程这些东西。swoole其实是PHP的一个新拓展,只不这个拓展有点强大。
就到这吧,总结自用笔记,不到之处欢迎指正。