数字中国·星火文集 | Linux 内核深度剖析——以Hello, world 为例
- 发布时间:2022-06-01
- 来源:
- 大 中 小
- 打印
Linux 内核深度剖析
——以Hello, world 为例
6165cc金沙总站 工程院
刘清华
武侠的世界和程序员的世界是相通的,楚留香在对阵无花和尚时,无花和尚用了少林神拳,弹指神通等好几种威名赫赫的武功,而楚留香所用的招式却是江湖中最普通,最平凡的,江湖中也不知有几千几万人能施展这种招式。乔峰在聚贤庄只身战群雄,也是以太祖长拳这种武林中最普通的功夫对敌。可见功夫只有深浅,却无高下。
由武侠的江湖转身回到二进制世界。在程序员的圈子,若说最常见、最流行、所有人都会的程序,莫过于“Hello, world”了。
这个程序最初的版本出现在C Bible(The C Programming Language)中
程序很短,内容如下:
图一:Hello, world
通常所有的程序员都是从Hello, world 开始学习编程的,区别在于计算机语言不同,
打印函数名称不同。该程序的功能就是调用打印函数在屏幕打印一行话“Hello, world”。
如果再深入一点,printf是怎么实现的?为什么在terminal运行这个程序,就会在屏幕上打出Hello, world?我们今天就聊聊如果我们自己写,该怎么做。
首先,我们分析printf函数,尝试着抽丝剥茧。
1. printf先整理数据格式,最终形成一个包含打印数据的数组,并把指针传递到控制台程序。字符串我们依然可以认为是一个数组。这件事主要是通过调用vsprintf函数来实现。例如参数传入的数字是不是要计算,计算后要转换成对应该实现的字符串。譬如如果是16进制表示,那么16本来用十进制显示的是2个字符“16”,换成16进制,就要显示4个字符“0x10”,因为今天主要考虑是Hello, world。所以这个部分我们先跳过。
2. 在整理完应该输出的字符串之后,printf就要调用console_print函数了,这个是控制台函数,主要就是在屏幕上输出字符串,包含换行、光标移动和定位等。这里console_print嵌入了一段涉及显示的汇编:
这段汇编主要功能就是把一个字符在指定的屏幕位置显示。这个指定的位置涉及到当前光标所在位置。而console_print使用while循环,将整个Hello, world显示出来。
3. 这段汇编为什么可以在屏幕上显示呢?这里就需要了解显示的驱动原理,简单的说,显示一个字符,需要显卡对于屏幕进行驱动,就是显卡告诉屏幕该显示啥。但是显卡无从得知该在第几行第几列显示什么字符。或者说显卡真实的工作原理是控制在第几行第几列的那个像素点该不该点亮,如果是黑白屏,那么就是0,1的区别,如第6行,第6列这个像素点要点亮,0是熄灭,1是点亮。那么显卡就需要三个参数(行数,列数,灭还是亮(0,1))。如果是彩色屏,再加上RGB的参数。
4. PC的编程,即使是linux系统的boot程序,也已经是相对进行了驱动简化,我们先从简化版开始说起,以上的汇编,其实是告知了显示参数,具体要显示,还要调用BIOS中断0x10,功能号ah = 0x13,显示字符串;al = 放置光标的方式及属性。当把参数都配置好后,BIOS被调用
int 0x10
OK, BIOS内嵌的程序把我们传递给它的参数:“Hello, world”在屏幕上显示了。
5. 这是PC的程序,BIOS就是内嵌在主板的通用驱动。假设我们自己设计了主板,使用的CPU是ARM,也没有BIOS程序,需要我们自己写显卡驱动,怎么办?刚才我们已经简单介绍了LCD屏幕的显卡驱动原理,事实上,LCD的数据线就包含了行、列地址线,控制线,数据线等。我们通过CPU的I/O口,输出地址给LCD模组,告诉模组行,列分别是多少,输出数据,告诉模组显示的是0 or 1(代表熄灭还是点亮)。这个时候,我们就可以成功控制LCD 的 1个像素点。控制1个像素点,输出不了Hello,world啊。这里我们还需要字符的点阵库。下面是一个示意图:
图二:字符“A”点阵图
通过这个图,我们可以看到第三行0x10(0b00010000),其实就一一对应着右侧第三行的LCD显示,一共8个点,每个点用1个bit表示(0是熄灭,1是点亮)。这里就是A的最高的那个点。左侧的这组数据,就表示A的16*8格式下的点阵字符。
6. 那么显示Hello,world的第一个字符H,就是先告诉LCD模组,我要从第几行第几列开始显示,即显示起始点,就是图二右侧的LCD的左上角那个点的位置。显示共16行8列,把这一块的LCD的哪些点给点亮,就能得到H了。以此类推,显示全部字符串。
7. 怎么告诉LCD模组呢?PC用的是BIOS,自己写驱动,就先写一个输出字符串的函数printk,调用一个自己写的LCD函数LCD_print,给出字符串“Hello,world”。LCD_print函数就从系统调取现在的光标位置,得到显示字符的起始点,然后根据字符串,转换成ASIC II的编码,然后根据码表数值,从点阵字库中获取到该字符所对应的点阵数组,有了位置,有了显示内容,逐行驱动,最终就在LCD上获得了“Hello, world”。
以上,概要阐述了Hello, world程序的运行过程。涉及到的知识点如下:操作系统 kernel,ASM汇编,C语言, BIOS,CPU架构及中断,LCD驱动,基础硬件设计。往深了说,还有进程切换实现方式,linux的多任务和保护模式。内核态和用户态的切换,等等。
如果继续引申,譬如从一个手机发送“hello,world”到另外一台设备的屏幕进行显示。这就涉及到通信协议,如果数据是经过运营商网络的互联网数据,那么就会涉及到TCP/IP协议的解析,TCP数据包的内容里还有私有协议的解析。通信链路还包含基站注册,心跳监测,长链接的保障,域名解析、IP地址及端口号。如果涉及到后台云端部署,又涉及到云平台以及云存储。如果是内外网的隔离,那么还涉及到防火墙的建立,以及如何防止IP嗅探和DDOS攻击。如果是超过10万台手机同时发送,那就涉及到大数据及高并发处理,考虑如何做负载均衡。如果超过1000万台手机,就涉及到CDN架构。如果手机发送的是一张手写的“Hello, world”的jpg图片,那么又涉及到OCR图像识别的人工智能技术。如果手机的图片需要存储,又涉及到分布式文件系统。如果显示终端不是直连运营商大网,而是和边缘网关短距无线或者有线直连,那么又涉及到边缘计算。
我们假设这么一个场景:手机APP发送一张图片到云端,云平台利用AI模块识别出图片内容为“Hello,world”,然后在云端分别存储原始图片数据到HDFS,存储字符串到MysqL。并且做好1主2备的Hadoop备份,同时考虑服务器节点的主备切换。做完存储后云平台通过运营商网络,将字符串加密后发送到指定边缘网关,边缘网关解析完数据内容后,再经过防火墙,通过有线RS485的私有协议,发送到指定的终端,最后,终端屏幕闪烁着“Hello,world”。这就是标准的云管边端四层技术架构的物联网了。
所以,我们可以看到Hello, world是一个很值得深入理解的程序,从月薪5000的应届毕业生,到年薪百万的架构师,我们都可以很愉快的聊一聊这个程序。如果有人对以上内容都很了解并熟练应用,欢迎来到工程院物联网研发中心,我们这里一堆技术极客期待你的加入。