LINUX是全人类的选择。
今天是这样的,
这种情况在未来几十年内也不会改变。
对于软件工程师来说,
不用考虑是否需要学习LINUX。
就想着怎么学好LINUX吧...
>>>>>>>
独自登上高楼
LINUX代表着未来。我们今天选择了它,未来几十年它也不会改变。对于今天的程序员来说,我们正在经历这种变化。在桌面领域,它仍然占有比较大的份额,但我相信,正在进行的革命最终会让Linux成为一个越来越普遍的系统平台。
对于各位从事软件开发的朋友来说,我们当然应该走在普通用户的前面,按照技术发展的方向做好技术储备。所以,在我看来,今天你不用考虑要不要学Linux,你只需要考虑怎么学好Linux。
十几年前就有人问我为什么还在分享,从中可以看出十几年前就有人预测Linux会逐渐成为主流,我也认为这个预测是正确的。
受到国学大师王国维的启发,我把 Linux 的学习概括为六种境界。王国维曾在《人间话》中把做人、学习分为三种境界。对于软件设计来说,境界也是一个很重要的概念,甚至可以说是一种方法论。后面我会把我概括的六种境界和王国维总结的三种境界做个对比,我概括的前三种境界其实不算,我会把后三种境界对应到王国维总结的三种境界。
如果你对人生的领悟和学术的研究没有达到一定的境界,就不会有这样的感悟。
“我独登高塔,望天涯路”是王国维眼中的第一个境界。所以我总结的前三个境界其实还是零十版,而不是第一个境界。
介绍的就这么多,现在我们进入第一个境界。
境界一
读
艺术
特点
说到学习,很多人想到的就是“我要买书”。如今,关于Linux的书籍数不胜数。阅读当然是有益的,所以我们将其归为第一层次。买书是好事,但能否从书中有所收获,则取决于阅读的方法和认真程度。
除了书籍,如今网络时代的文章也很多,推荐大家去读一下Linux内核的官方文档,如果把Linux内核的官方文档分成不同的层级,有一些写得非常好的文档,比如:《Linux内核指南》。
这篇文档的标题很谦虚,意思是:我给你写了一份有点不靠谱的攻克Linux内核的指南。这种看似不靠谱的说法越是值得一读。
本文作者是Rusty,我的一位澳大利亚同事,曾经在IBM的Linux技术中心工作过。在我看来,写作是一门需要很多方法的技能,不同的人写同样的内容有不同的方法。Rusty写过很多内核代码,他的文章也展现了一些很独特的观点,这些观点都非常简洁明了。
在这篇并不太长的文档里,他写了很多理解内核的关键点。比如上面这段其实只有几十个字的文字,就写到了阅读内核代码需要了解的一个关键特性,即:返回值的约定。Linux 内核是用 C 语言编写的,简洁,强调简单而不是复杂。因此,Linux 内核代码没有太多杂七杂八的误判和太多冗余(比如:保留很多参数以备将来使用)。
Linux 内核代码尽可能简洁,这也体现在返回值上。Rusty 对返回值提出了两点:
成功时返回零,失败时返回负数。
这也是我很喜欢的一种做法,但是对于一些教条式的编码规范来说,返回值是无符号的,如果要表示错误,需要在参数中单独设置一个指针作为错误代码,很别扭。
如果函数返回的是指针,则不需要单独返回错误代码,只需将错误代码编码到指针中即可。因此,内核中有几个著名的宏:
()
()
如果是返回指针的函数,在指针处失败,则返回值也会被编码进去。为了提高可读性,定义了这些宏。
在Rusty的文章里,像这样简短精炼的总结就有十几个,文章也很短,用通俗的话说,真的都是有用的信息。
总结:学习内核的第一步就是阅读内核的文本,书籍,好文章,官方文档。
领域 2
读
一代
代码
第二个层次是阅读代码。Linux内核是开源的,所以大家一定要抓住机会,利用它的开源特性,阅读它的源代码。
如果你还没有下载Linux内核源代码,请立即打开并下载一份源代码。我鼓励大家下载LTS(长期支持版本)。最新的LTS是6.1。在此之前,更常用的LTS是5.10或5.15。如果你目前在产品中使用5.10,则它被认为是相对较新的。上一个版本是4.19。下载LTS后,将其解压到你的电脑上,你可以随时打开它查看。
Linux内核的源代码是无价的,是全世界人类智慧的结晶。它是基于全世界程序员的共同努力才产生这样一套代码。因此,我们可以从这套代码中学到很多东西。
“广度比深度更重要”
——Linux目录树的定义原理
首先Linux内核目录树的定义原则是目录宜多不宜过深。现实中很多软件项目做不好就是因为目录定义得太深,不利于浏览。浏览目录的时候相当于进入、进入、进入、再退出、进入、进入、进入、退出。
所以建议源代码目录的组织方式要宽而不深,同一层级的目录多也没关系,因为一目了然,可以知道这一层级里有什么。尽量避免一层层往下铺,比如最上面只有两个目录,然后就分开了,每一层级都扩展到多个目录。这样的目录树浏览起来非常不方便,无法一下子看到全貌。
阅读源代码
——以 Panic 函数为例
在阅读Linux内核源代码的时候,可以找出一些自己感兴趣的函数去读,比如Panic函数,这个函数很有趣,因为它很形象,我们可以直接打开panic.c,读到Panic函数的逻辑。
它是Linux内核中一个很老的函数,早在0.1版本就有了。但是在0.1版本中Panic是一个死循环,在后续的发展中变得越来越复杂。和函数原型类似,Panic有固定的模板,后面可以跟不确定的参数。内核代码中很多地方都会调用它,调用时根据原型传递各种参数。如果分析Linux崩溃时,看一下Linux发生Panic时的源码,就可以一边研究源码一边了解Panic的原因。
结合工业界发展阅读内核代码
——以Arch子目录为例
内核源代码还可以结合业界发展来阅读,比如Arch目录,代表了CPU厂商的开发实力。
从4.4内核到5.13,再从5.13到6.16,Arch子目录的变化巨大。
关于 Arch 子目录,我在庐山桃花源工作坊讨论的时候,有位博主提了一个非常好的问题:
“为什么ARM 32位和64位分为两个目录,而x86在一个目录中?”
我觉得这个问题问的很好,代表着做同一件事的不同方式,或者说不同的人生态度。
ARM 分成两个品类,体现了它的年轻、无畏和勇于突破自我的精神。它认为老的 32 位系统存在很多问题,所以在发展到 64 位的时候,就创造了一个新的品类,把历史包袱都扔掉了。这就好比一个年轻人,如果不想干这份工作,干脆放弃,学一门新技术,从头再来。
x86 历史上有 IA-64 架构,Intel 希望 64 位就是 IA-64,但是 AMD 不同意,搞出了 AMD64。因为 AMD 的口号是 64 位和 32 位无缝兼容,所以没必要搞两个目录。x86 之所以重,是因为它背负了所有的历史包袱,所有的东西都混杂在一个目录里。
第三境界
读
“
信
”
接下来的第三境界是读“信”。信的英文是 ,还有消息()和电子邮件(Mail),广义上可以称为信件。我上大学的时候,很流行写信。下课后,大家都盼望着收到信。
为什么读一封信和读一本书的感觉不同?
因为书信篇幅较短,且有特定的场景,所以更有针对性和沉浸感。对于一本厚厚的书,我们往往坚持不下去,不知道作者在说什么,也不知道先读什么,后读什么。但对于一封信,你读的时候通常不会有这种感觉,因为你能够很快进入到信的场景中,立刻明白信中所描述的问题,所以很快就能看懂。所以,学习Linux内核的第三个境界,就是读懂内核写给我们的信。
内核是怎样写字母的?
内核中有一个著名的功能,这是Linux内核开发者Linus最先依赖的调试方法。
我统计了一下 Linux 3.12 内核的行数,有 6 万多行。到今天可能已经突破 10 万行了。也就是说,内核中有 10 万行主干代码。同时这些行中还有大量的变量,因为它们可以包含变量信息,所以在不同的场景下执行到这里都会打印一条消息。把这些消息积累在一起,就像内核的时钟在记录它走过的路。它此刻的心情就像朋友之间写信,我们读信的时候,能感受到对方的经历,以及写信时的心情。
阅读内核打印的这些消息,我们也能感受到:
为什么我们要关注内核消息?
内核消息是一个非常生动的内核数据。如果你在不同的系统上启动,你会发现很多不同。例如在 x86 上,你会看到一些 x86 的特性,而且固件和设备与 ARM 也不同。因此,每当我接触到一个新的 Linux 系统时,我都喜欢用 dmesg 查看内核消息,而且每次都有收获。
当然内核消息不仅仅在启动时显示,当你在进行某些操作的时候,内核也会打印出消息,这时候打印出来的消息也是非常有用的。
第四境界
写
一代
代码
第四境界是写代码。前三境界的第一个动词是“读”,但第四境界不再是“读”,而是“写”。我们不用动手,用眼睛就能读,但要想写代码,就需要动手,把代码打进去,编译执行,还要经历这样一个过程。
这里我想引用我非常敬佩的丹尼斯·里奇的一句话,他是C语言的发明者,也是UNIX操作系统最重要的两位先驱之一,他说:“学习一门编程语言的唯一方法就是用它编写程序。”
我很相信这句话,要学好编程,就得写代码,同样,要学好内核,也得写代码,所以我会用 的话来描述学习内核:“学习内核的捷径就是写代码,然后在内核里跑起来。”
内核中代码该如何写?
一种方法是修改内核编译,但是这种方法需要较多的技巧和时间。
还有一种方式就是写驱动程序,因为驱动程序也是运行在内核空间的。由于驱动程序是模块化的,我们可以先写一个小的驱动程序运行在内核空间,这样可以体会内核编程的不同,探索如何与内核交互,如何编写内核代码,体会做内核的不易——内核虽然很强大,但是也有不少的难处。
在环境中,如果想在内核空间运行自己的代码,已经有一些限制了,如果想在内核空间运行代码,就必须对代码进行签名。从Vista开始微软就要求内核空间的代码必须签名,这样就不是很方便了。如果要做测试签名,还需要重新开启禁止签名强制执行的功能。所以,我今天逐渐转向Linux。因为环境越来越不适合开发者了,规矩太多,太守规矩的系统缺乏生机和活力,所以,我鼓励大家像我一样转向Linux。在Linux环境中,你可以轻松地加载驱动程序而不需要签名。当然,如果要搭建一个构建驱动程序的环境,就有点困难了,需要做一些准备工作,比如:安装内核头文件。
这里我们具体以幽蓝为例,幽蓝是格物科技推出的一款专门为程序员打造的笔记本,拿到手就能构建内核驱动,各位拥有幽蓝的格物朋友可以马上试用,测试成功后再进行一些修改,修改完之后再编译加载,不断重复这个过程,对内核就会越来越熟悉。
演示:使用 构建内核驱动程序
说到这里,我们再来回顾一下王国维总结的三种境界,他总结的第一种境界是“昨夜西风凋绿树,独上高楼望天涯路。”
很多接触Linux的人根本没有这种感受。在我看来,只有自己写过一些内核代码之后才会有这种感受,体会到内核的难度,内核代码的独特性,知道内核代码面临的真正挑战——代码量巨大,各方面负担巨大,所以做好是非常困难的。
5 级
里面
核
调
尝试
第五层级是用调试器调试内核。当你真正写完内核代码之后,你会越来越了解内核,会发现还有很多东西需要学习,这就好比“独攀高楼,望天涯路”,处处辽阔,你想去探索,但时间有限,所以要提高效率。
如何提高效率?
调试器是提高效率的好方法。
回顾我这么多年在技术上的探索,其实对我帮助最大的就是调试器了。所以每次学习一个新技术,我们都会尝试使用调试器。这几年我转向ARM+Linux,开发了好几个调试器,比如硬件调试器Swing Code Gun,软件调试器Nano Code。出差的时候,我经常会带着这些东西。
上面这张图是我出差的时候拍的,码枪有两把,一把是GDK3(用来调试ARM的M核),一把是GDK8(用来调试ARM的A核,里面也包含Linux)。
这个截图是在WiFi发包函数里设置的断点,断点命中之后可以看到这是来自用户空间的调用,一步步往上层,进入网络协议栈,再进入电路层到设备驱动,设备驱动最后送到物理层进行物理传输。
很多人对网络感兴趣,不管是学安全的,还是做通信的。如何把网络学得活灵活现?调试器肯定是一个非常高效的方法。为什么我们把代码阅读仅仅看作是第二层级呢?因为在阅读代码的时候,你往往读不到真正需要的代码,读到的都是死代码。只有在调试的时候,你才能读到活的代码,因为这个代码是真实运行的,你设置一个断点之后,才能真正看到它。
通过内核调试,我们可以提高理解内核的效率,通过设置断点,我们可以立刻发现很多问题。
当然,我们也可以用这样的方式来看待调度。
Child-SP RetAddr Call Site
ffffff80`0f08bb00 ffffff80`090a060c lk!__switch_to+0x8 [arch/arm64/kernel/process.c @ 544]
ffffff80`0f08bb90 ffffff80`090a10d8 lk!__schedule+0x2f4 [kernel/sched/core.c @ 3437]
ffffff80`0f08bbb0 ffffff80`08082fa0 lk!preempt_schedule_irq+0x38 [./arch/arm64/include/asm/irqflags.h @ 62]
ffffff80`0f08bbb0 00000000`9200004f lk!el1_preempt+0x8 [arch/arm64/kernel/entry.S @ 665]
dt prev -y comm
Local var @ 0 Type task_struct*
+0x768 comm : [16]char[] "Chrome_HistoryT"
dt next -y comm
Local var @ 0 Type task_struct*
+0x768 comm : [16]char[] "migration/0"
这是一个抢占式调度的执行过程,实际上就是将CPU上下文从一个线程切换到另一个线程。
自愿放弃行政权力
Child-SP RetAddr Call Site
ffffff80`0a313d30 ffffff80`090a060c lk!cpu_switch_to [arch/arm64/kernel/entry.S @ 1067]
ffffff80`0a313de0 ffffff80`090a0c80 lk!__schedule+0x2f4 [kernel/sched/core.c @ 3437]
ffffff80`0a313e00 ffffff80`080e5f00 lk!schedule+0x38 [kernel/sched/core.c @ 4171]
ffffff80`0a313e60 ffffff80`080e147c lk!smpboot_thread_fn+0x258 [kernel/smpboot.c @ 160]
ffffff80`0a313ec0 ffffff80`08085db0 lk!kthread+0x12c [kernel/kthread.c @ 260]
ffffff80`0a313ec0 00000000`00000000 lk!ret_from_fork+0x10 [arch/arm64/kernel/entry.S @ 1104]
线程切换的场景有很多种,有时候主动让出执行权,有时候主动把执行权交给调度器,由调度器负责切换。
由于等待信号量而切换线程
Child-SP RetAddr Call Site
ffffff80`0d8abbb0 ffffff80`090a060c lk!cpu_switch_to [arch/arm64/kernel/entry.S @ 1067]
ffffff80`0d8abc60 ffffff80`090a0c80 lk!__schedule+0x2f4 [kernel/sched/core.c @ 3437]
ffffff80`0d8abc80 ffffff80`090a47b8 lk!schedule+0x38 [kernel/sched/core.c @ 4171]
ffffff80`0d8abd40 ffffff80`090a30e8 lk!schedule_timeout+0x1e0 [kernel/time/timer.c @ 1795]
ffffff80`0d8abdc0 ffffff80`0811a4cc lk!__down_interruptible+0xa0 [kernel/locking/semaphore.c @ 221]
ffffff80`0d8abdf0 ffffff80`011c1134 lk!down_interruptible+0x54 [kernel/locking/semaphore.c @ 85]
ffffff80`0d8abe60 ffffff80`080e147c bcmdhd!dhd_rxf_thread+0x9c [drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd/dhd_linux.c @ 6614]
ffffff80`0d8abec0 ffffff80`08085db0 lk!kthread+0x12c [kernel/kthread.c @ 260]
ffffff80`0d8abec0 00000000`00000000 lk!ret_from_fork+0x10 [arch/arm64/kernel/entry.S @ 1104]
有时候会进入等待,比如这里就是在等待一个信号量,当信号量调用down函数的时候就进入等待,触发线程切换。
从空闲线程中抛出工作
Child-SP RetAddr Call Site
ffffff80`0a323e70 ffffff80`090a060c lk!cpu_switch_to [arch/arm64/kernel/entry.S @ 1067]
ffffff80`0a323f20 ffffff80`090a104c lk!__schedule+0x2f4 [kernel/sched/core.c @ 3437]
ffffff80`0a323f40 ffffff80`080f4f50 lk!schedule_idle+0x24 [./include/asm-generic/bitops/non-atomic.h @ 106]
ffffff80`0a323fb0 ffffff80`080f5224 lk!do_idle+0x168 [kernel/sched/idle.c @ 292]
ffffff80`0a323fd0 ffffff80`08097c38 lk!cpu_startup_entry+0x24 [kernel/sched/idle.c @ 370]
ffffff80`0a324000 00000000`00000000 lk!secondary_start_kernel+0x150 [arch/arm64/kernel/smp.c @ 255]
另一种情况是从空闲线程切换到新线程。
如果你对内核调试感兴趣,我推荐你使用代码枪加GDK8,这是性价比非常高的内核调试方案。如果要自己搭建内核调试环境,无论是连接串口还是重新编译内核使用网络,都需要花费不少时间。代码枪加GDK8相当于拿到手后立刻连接,五分钟内断开内核,设置好符号后进入状态。
我把内核调试看作是学习Linux内核的第五个境界,这对应着王国维所说的第二个境界:“衣裳渐宽,我终不悔,为她憔悴。”当你达到这个境界的时候,你就会把学习内核看作是一种爱好,看作是一件理所当然的事情。
第六境界
相互
伴侣
最后一种状态很特殊,它有好几个名字,我现在写的这个名字比较优雅,叫陪伴。夫妻相伴到老。陪伴是一种很高的状态,我们和Linux内核之间的最高状态也是陪伴。
这种心态其实源自于NT内核。在NT内核开发的最后阶段,开发小组开始把这个正在开发的内核当成自己的工作机器。当然,最初也派了好几位专家去做这个任务,他们先把自己的机器换上正在开发的NT内核,然后自己用,马上就发现了大量Bug。作为一个软件工程师,你把你实际开发的系统当成你使用的系统,体会你写的代码是什么样的,感受你开发的产品是好是坏,有多差,有多好。如果你能达到这种心态,那是很了不起的。
目前对于整个Linux系统来说,它的可用性还是有些欠缺,所以很多人还是比较依赖桌面或者Mac。在我看来,如果你真的想学好Linux,决定投身Linux内核,为什么不干脆把工作机换成Linux呢?我今天就是干这个,所以最近一直背着两台电脑,虽然有点重,但其实没关系。上面那台是Linux的,很薄,因为无风扇,所以轻很多。
这个过程是需要付出一些努力的,但是这个努力的过程恰恰是我们学习的过程,在这个过程当中,我们才能更好的学习内核,因为在这个过程中,我们能够立刻感受到Linux环境当中存在的一些不足。
对于我来说,很多时间都是在Linux上开发一些代码,所以我在努力将自己的开发环境切换到Linux,也鼓励大家和我一起这样做。有人说:“我不要当实验鼠,不要这样,等大家都体验好了,我再切换到Linux。”这不是开发者的心态,而是普通用户的心态。作为一名程序员,你要时刻站在技术的最前沿,学习新技术,遇到各种问题,就去解决。当然,我这样说并不是为了让大家在没有问题的时候发现问题,而是要有一个真实的环境,这样你遇到的问题才很有代表性,有意义,有价值,代表着趋势,然后你去钻研这些问题,就是为了顺应这个趋势。
进入这种状态,我想只要坚持,就能达到王国维总结的第三种境界——“我千方百计于众人之中寻他,回头在灯火阑珊处寻他”。坚持下去,昼夜陪伴在Linux环境里,今天学一点,明天学一点,你输入的每一个命令,你进行的每一个操作,你点击的每一个鼠标按钮,你都在感受Linux、学习Linux、与内核对话。这时候你的进步是一点一滴的,无形中你在向前走。有了这样的日常陪伴,相信有一天你回头一看,会发现自己的水平不一样了,自然而然就会明白内核的各种机制了。
通过购买幽兰密码本,您可以成为幽兰社的一员,与众多技术高手一起成长。
您可以前往淘宝歌友店购买:
【圣歌塾】
真诚诚实,研究事物以获得知识
用人文情怀审视软件,用软件技术改变生活
格友公众号
圣歌居小程序
扫描上方二维码或在微信搜索“圣歌书”小程序
扫一扫在手机端查看
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。