不过写这篇文章并不是为了帮大家准备面试,而是想借这道题来介绍计算机和互联网的基础知识,让读者了解它们之间是如何关联起来的。
为了便于理解,我将整个过程分为了六个问题来展开。
第一个问题:从输入 URL 到浏览器接收的过程中发生了什么事情?
从触屏到 CPU
首先是「输入 URL」,大部分人的第一反应会是键盘,不过为了与时俱进,这里将介绍触摸屏设备的交互。
触 摸屏一种传感器,目前大多是基于电容(Capacitive)来实现的,以前都是直接覆盖在显示屏上的,不过最近出现了 3种嵌入到显示屏中的技术,第一种是 iPhone 5 的 In-cell,它能减小了 0.5 毫米的厚度,第二种是三星使用的On-cell 技术,第三种是国内厂商喜欢用的 OGS 全贴合技术,具体细节可以阅读这篇文章。
当手指在这个传感器上触摸时,有些电子会传递到手上,从而导致该区域的电压变化,触摸屏控制器芯片根据这个变化就能计算出所触摸的位置,然后通过总线接口将信号传到CPU 的引脚上。
以 Nexus 5 为例,它所使用的触屏控制器是 SynapticsS3350B,总线接口为 I²C,以下是Synaptics 触摸屏和处理器连接的示例:
左边是处理器,右边是触摸屏控制器,中间的 SDA 和 SCL 连线就是 I²C 总线接口。
CPU 内部的处理
移动设备中的 CPU 并不是一个单独的芯片,而是和 GPU 等芯片集成在一起,被称为 SoC(片上系统)。
前面提到了触屏和 CPU的连接,这个连接和大部分计算机内部的连接一样,都是通过电气信号来进行通信的,也就是电压高低的变化,如下面的时序图:
在时钟的控制下,这些电流会经过 MOSFET晶体管,晶体管中包含 N 型半导体和 P 型半导体,通过电压就能控制线路开闭,然后这些 MOSFET 构成了 CMOS,接着再由CMOS实现「与」「或」「非」等逻辑电路门,最后由逻辑电路门上就能实现加法、位移等计算,整体如下图所示(来自《计算机体系结构》):
除了计算,在 CPU中还需要存储单元来加载和存储数据,这个存储单元一般通过触发器(Flip-flop)来实现,称为寄存器。
以上这些概念都比较抽象,推荐阅读「How to Buildan 8-Bit Computer」这篇文章,作者基于晶体管、二极管、电容等原件制作了一个 8位的计算机,支持简单汇编指令和结果输出,虽然现代 CPU 的实现要比这个复杂得多,但基本原理还是一样的。
另外其实我也是刚开始学习 CPU 芯片的实现,所以就不在这误人子弟了,感兴趣的读者请阅读本节后面推荐的书籍。
从 CPU 到操作系统内核
前面说到触屏控制器将电气信号发送到 CPU 对应的引脚上,接着就会触发 CPU 的中断机制,以 Linux为例,每个外部设备都有一标识符,称为中断请求(IRQ)号,可以通过/proc/interrupts
文件来查看系统中所有设备的中断请求号,以下是 Nexus 7 (2013) 的部分结果:
shell@flo:/ $ cat /proc/interrupts
CPU0
294:
314:
因为 Nexus 7 使用了 ELAN 的触屏控制器,所以结果中的 elan-ktf3k 就是触屏的中断请求信息,其中 294是中断号,1973609 是触发的次数(手指单击时会产生两次中断,但滑动时会产生上百次中断)。
为 了简化这里不考虑优先级问题,以 ARMv7 架构的处理器为例,当中断发生时,CPU会停下当前运行的程序,保存当前执行状态(如 PC 值),进入 IRQ状态),然后跳转到对应的中断处理程序执行,这个程序一般由第三方内核驱动来实现,比如前面提到的 Nexus 7 的驱动源码在这里touchscreen/ektf3k.c。
这个驱动程序将读取 I²C 总线中传来的位置数据,然后通过内核的 input_report_abs等方法记录触屏按下坐标等信息,最后由内核中的input子模块将这些信息都写进/dev/input/event0
这个设备文件中,比如下面展示了一次触摸事件所产生的信息:
130|shell@flo:/ $ getevent -lt /dev/input/event0
[
[
[
[
[
[
[
[
从操作系统 GUI 到浏览器
前面提到 Linux 内核已经完成了对硬件的抽象,其它程序只需要通过监听 /dev/input/event0
文件的变化就能知道用户进行了哪些触摸操作,不过如果每个程序都这么做实在太麻烦了,所以在图像操作系统中都会包含 GUI框架来方便应用程序开发,比如 Linux 下著名的X。
但 Android 并没有使用 X,而是自己实现了一套 GUI 框架,其中有个 EventHub的服务会通过 epoll 方式监听/dev/input/
目录下的文件,然后将这些信息传递到 Android 的窗口管理服务(WindowManagerService)中,它会根据位置信息来查找相应的app,然后调用其中的监听函数(如 onTouch 等)。
就这样,我们解答了第一个问题,不过由于时间有限,这里省略了很多细节,想进一步学习的读者推荐阅读以下书籍。
第二个问题:浏览器如何向网卡发送数据?
从浏览器到浏览器内核
前 面提到操作系统 GUI 将输入事件传递到了浏览器中,在这过程中,浏览器可能会做一些预处理,比如 Chrome会根据历史统计来预估所输入字符对应的网站,比如输入了「ba」,根据之前的历史发现 90% 的概率会访问「www.baidu.com」,因此就会在输入回车前就马上开始建立 TCP 链接甚至渲染了,这里面还有很多其它策略,感兴趣的读者推荐阅读HighPerformance Networking in Chrome。
接着是输入 URL 后的「回车」,这时浏览器会对 URL 进行检查,首先判断协议,如果是 http 就按照 Web来处理,另外还会对这个 URL 进行安全检查,然后直接调用浏览器内核中的对应方法,比如WebView 中的loadUrl 方法。
在浏览器内核中会先查看缓存,然后设置 UA 等 HTTP 信息,接着调用不同平台下网络请求的方法。