Linux内核工程导论——内核为何使用C语言

C与C++的对比无数人说过,都说C效率高,但很多人做过实验如果C++不使用RTTI,C++的效率也不会低太多(25%左右)。还有人说C++强大的STL,但是对效率讲究点的话那个真的不能用,具体我后面说。一般大部分人的心态是,学C++出身的,就经常吐槽linux的C代码乱的一塌糊涂,各种敏捷,面向对象原则,代码不如C++精简,连个STL或者boost都用不上,等软件工程相关问题都是被他们吐槽的重灾区,这些人肯定没有给内核贡献过代码。做嵌入式一直用C的,接触内核后的反应是:啊,内核真大,啊,内核真难懂,是一种敬畏,与吐槽可不一样。

面向对象的实现方法不止C++一种,C也可以做到,只是不如C++那样浑然天成。重要的是,C做的面向对象,由于封装性差,你看一个结构体就会发现里面咋啥都有,乱七八糟。而用C++封装的结构体,可以设计的很潇洒。vector<People>可比list* people更能传递更多的信息。利用C++的封装特性带来的好处是视觉上的显而易见,基本上不需要考虑内存布局,但C的结构体到处都是内存上的考虑,例如通过结构体成员找到结构体本身的container_of,在结构体最后添加一个0长度的数组,在结构体的开头添加一个list*节点。诈一看,C++也能做到,这不是废话吗?C的所有关键字C++都是完全支持的。问题是,这并不是面向对象的思路,有一种编程思路叫做ODP,面向数据编程,说C比C++高效,更多的并不一定是说C++本身效率低,而是其所代表的面向对象思路效率低。我举个例子:

struct A{

int a;

int b;

}

vector<A>  aList;

我们队这个aList的a变量进行遍历,比起下面这个写法:

struct A{

vector<int> a;

vector<int> b;

}aList;

一定是下面这个效率高。为什么呢?原因在缓存。所有的计算机系统都有缓存行,第二种写法被调入缓存的全部是a,而第一种,还调入了b,遍历起来相当于第一种比第二种缓存减小了一半。自断一臂。这就是ODP比OOP高效的地方。而当你不选用OOP时,C++比C的最大优势就荡然无存。

有的人说C++的STL和boost库。这个就不讲究了。这种库都是通用的,一般大型的企业用到后期对效率开始纠结的时候,通用的东西永远,是永远不可能在所有应用情况是最优的选择。包括内存算法,包括进程调度,包括网络应用,简单说要针对不同应用调整参数,例如TCP在samba时要关闭nagle才能提高效率,但有的应用nagle则是提高效率的利器。内核这种底层的东西,使用这种程度上通用的东西确实不讲究了。最通用的数据结构应该是list了,在数据组织上也是非常的轻量,甚至在组织不同的应用时还有hlist和llist等变体。这种变体可不是C++的再封装所能满足的。

C++最骄傲的:封装、不怎么内存关心、虚函数、继承。虚函数说真的确实是机制上的效率问题,一用它就得多一个虚函数表,这还不是小事,函数调用跳转带来的缓存的刷新可真不是小事。只要你用了虚函数,说不定哪个地方就给你刷一下缓存,这可要命了。在实现一个算法的时候都是小心翼翼的关心每个函数调用是不是坑。至于继承,这个说真的,任何一个写了很久C++代码的人都有一个体会:除非你是做应用软件,否则真没那么多变体。在内核开发来说,基本就是鸡肋,可能会有几个地方可以用到这个特性,但这完全不构成因此而选择这门更复杂语言的理由。不关心内存的潇洒的写代码,如果你是做大型商用平台式应用软件的恐怕都不会不考虑内核布局,甚至是有的代码还要刻意的挑出来用C或者汇编重写。这种优化技能存在已久。

我们在使用C++的时候有很多设计模式,有很多编程技巧。他们带来的效果大部分是架构清晰,代码量变少,易于修改。然而在调优一份嵌入式代码的时候,或者一个系统的使用的时候,那点代码量的差距在使用上根本不是个事。最多多出1M的代码?即使不压缩都不会有这么多,压缩后就基本看不出来了。所以,你只能嫌弃他写的代码难看。但是我推荐你用source insight跟一下代码,或者是你有别的工具。C的代码确实多一些,但是跟代码的时候的痛快是C++你要到处去找到底是哪个虚函数还是子类在起作用。毕竟人家是运行时才确定的,而你看代码的时候是静态的。这有一个本质的原因:C可以看静态代码得出全部意图,但C++不能或者很难。而,内核的开发者(其实用过的都知道,大部分工作都是跟代码的人)真正要动手写的代码基本不存在。所以,对内核来说,即使是深度使用内核的人大部分也是使用者,而并非开发者,如此情况,追求敏捷的意义又有多大呢?

我也曾诟病内核的很多C代码写的那简直叫没有人性。比如电梯算法的框架代码,你得看到头发都白了才能大概体会作者的意图。然而,那又怎样?即使我很容易看懂了,我会去修改他吗?他提供了很多的参数,我所需要的,参数调整而已。修改和维护自有作者自己在维护。可能大家说我不知道上进,那就是没有实际做过内核开发的人了,当你告诉上司你修改了很多电梯的框架时,你问问公司的领导敢用吗?做到这个程度的产品基本是嵌入式产品,嵌入式产品的软件一发布基本就不容易更新软件了。这种冒险如果修改参数可以完成,怎么会有决策者采用呢?技术在产品面前,是要服务于产品的。

我也是C++的拥护者,甚至是狂热者,日常的软件,大部分的公司产品,哪怕是互联网产品,我都会选择C++或者java,而绝对不会选择C。我怕团队离开一个人,我怕项目的增量能力不足。然而,在深入内核之后,我也会选择C,面向对象确实是编程的一大进步,那你怎么不去问问数据库大行其道的为啥不是实体联系模式,而是关系模式?我们可以采用新技术,我也承认新技术带来的生产力革命,但是我们不应该迷信新技术。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-27 17:11:50

Linux内核工程导论——内核为何使用C语言的相关文章

Linux内核工程导论——用户空间进程使用内核资源

本文大部分转载和组装,只是觉得这些知识应该放到一起比较好. 进程系统资源的使用原理 大部分进程通过glibc申请使用内存,但是glibc也是一个应用程序库,它最终也是要调用操作系统的内存管理接口来使用内存.大部分情况下,glibc对用户和操作系统是透明的,所以直接观察操作系统记录的进程对内存的使用情况有很大的帮助.但是glibc自己的实现也是有问题的,所以太特殊情况下追究进程的内存使用也要考虑glibc的因素.其他操作系统资源使用情况则可以直接通过proc文件系统查看. 进程所需要的系统资源种类

Linux内核工程导论——基础架构

基础功能元素 workqueue linux下的工作队列时一种将工作推后执行的方式,其可以被睡眠.调度,与内核线程表现基本一致,但又比内核线程使用简单,一般用来处理任务内容比较动态的任务链.workqueue有个特点是自动的根据CPU不同生成不同数目的队列.每个workqueue都可以添加多个work(使用queue_work函数). 模块支持 模块概述 可访问地址空间,可使用资源, 模块参数 用户空间通过"echo-n ${value} > /sys/module/${modulenam

Linux内核工程导论——进程

进程 进程调度 概要 linux是个多进程的环境,不但用户空间可以有多个进程,而且内核内部也可以有内核进程.linux内核中线程与进程没有区别,因此叫线程和进程都是一样的.调度器调度的是CPU资源,按照特定的规则分配给特定的进程.然后占有CPU资源的资源去申请或使用硬件或资源.因此这里面涉及到的几个问题: 对于调度器来说: l  调度程序在运行时,如何确定哪一个程序将被调度来使用CPU资源? n  如何不让任何一个进程饥饿? n  如何更快的定位和响应交互式进程? l  单个CPU只有一个流水线

Linux内核工程导论——用户空间设备管理

用户空间设备管理 用户空间所能见到的所有设备都放在/dev目录下(当然,只是一个目录,是可以变化的),文件系统所在的分区被当成一个单独的设备也放在该目录下.以前的2.4版本的曾经出现过devfs,这个思路非常好,在内核态实现对磁盘设备的动态管理.可以做到当用户访问一个设备的设备的时候,devfs驱动才会去加载该设备的驱动.甚至每个节点的设备号都是动态获得的.但是该机制的作者不再维护他的代码,linux成员经过讨论,使用用户态的udev代替内核态的devfs,所以现在的devfs已经废弃了.用户态

Linux内核工程导论——网络:Netfilter概览

简介 最早的内核包过滤机制是ipfwadm,后来是ipchains,再后来就是iptables/netfilter了.再往后,也就是现在是nftables.不过nftables与iptables还处于争雄阶段,谁能胜出目前还没有定论.但是他们都属于netfilter项目的子成员. 钩子 netfilter基于钩子,在内核网络协议栈的几个固定的位置由netfilter的钩子.我们知道数据包有两种流向,一种是给本机的:驱动接收-->路由表-->本机协议栈-->驱动发送.一种是要转发给别人的:

Linux内核工程导论——网络:Filter(LSF、BPF)

数据包过滤 LSF(Linux socket filter)起源于BPF(Berkeley Packet Filter),基础从架构一致,但使用更简单.其核心原理是对用户提供了一种SOCKET选项:SO_ATTACH_FILTER.允许用户在某个sokcet上添加一个自定义的filter,只有满足该filter指定条件的数据包才会上发到用户空间.因为sokket有很多种,你可以在各个维度的socket添加这种filter,如果添加在raw socket,就可以实现基于全部IP数据包的过滤(tcp

Linux内核工程导论——内存管理(一)

Linux内存管理 概要 物理地址管理 很多小型操作系统,例如eCos,vxworks等嵌入式系统,程序中所采用的地址就是实际的物理地址.这里所说的物理地址是CPU所能见到的地址,至于这个地址如何映射到CPU的物理空间的,映射到哪里的,这取决于CPU的种类(例如mips或arm),一般是由硬件完成的.对于软件来说,启动时CPU就能看到一片物理地址.但是一般比嵌入式大一点的系统,刚启动时看到的已经映射到CPU空间的地址并不是全部的可用地址,需要用软件去想办法映射可用的物理存储资源到CPU地址空间.

Linux内核工程导论——内存管理(三)

用户端内核内存参数调整 /proc/sys/vm/ (需要根据内核版本调整) 交换相关 swap_token_timeout Thisfile contains valid hold time of swap out protection token. The Linux VM hastoken based thrashing control mechanism and uses the token to preventunnecessary page faults in thrashing s

Linux内核工程导论——进程:ELF文件执行原理(2)

ELF 强符号与弱符号(本小节是转别人的) 我们经常在编程中碰到一种情况叫符号重复定义.多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误.比如我们在目标文件A和目标文件B都定义了一个全局整形变量global,并将它们都初始化,那么链接器将A和B进行链接时会报错: 1 b.o:(.data+0x0): multiple definition of `global'2 a.o:(.data+0x0): first defined here 这种符号的定义