内核级程序开发的特点
2010年06月28日
In fact, a good percentage of the total code in a program consists of nothing but error checking. /* 人生就像一盒巧克力,你永远也不知道你将得到什么.你只有亲自去品味她的苦涩和缠绵.我们在平淡中体味彼此的真实.在明媚的月光下,在雨后的操场,或者, 在一首老歌的旋律当中…… */ #include int main(void){ int money = 1; int girl = 1; for( ; 1; money++, girl++ ) printf("I have money \n I have girl \n"); return 1; }
这篇文章太好了!太牛X了!受益匪浅!
http://www.lupaworld.com/26540/viewspace-46508.htm l
相 对于用户空间内应用程序的开发,内核开发有很大的不同。这种差异给开发内核带来了了挑战,但这并不意味着开发内核就比开发应用程序难多少。
这种差 异使内核成了一只性格迥异的猛兽。一些常用的准则被颠覆了,而又必须建立许多全新的准则。尽管有许多差异一目了然(人人都知道内核可以做它想做的任何 事),但还是有一些差异晦暗不明。最重要的差异包括以下几种:
* 内核编程时不能访问C库。
* 内核编程时必须使用GNU C。
* 内核编程时缺乏像用户空间那样的内存保护机制。
* 内核编程时浮点数很难使用。
* 内核只有一个很小的定长堆栈。
* 由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发。
* 要考虑可移植性的重要性。
让我们仔细考察一下这些要点,所有这些 东西在内核开发中必须时刻牢记。
1 没有libc库
与用户空间的应用程序不同,内核不能链接使用标准C函数库(其他的那些库也不 行)。造成这种情况的原因有许多,其中就包括先有鸡还是先有蛋这个驳论。不过最主要的原因还是在于速度和大小。对内核来说,完整的C库太大了-即便是从中 抽取一个合适的子集-大小和效率都不能被接受。
别着急,大部分常用的C库函数在内核中都已经得到实现了。比如说操作字符串的函数组就位于 lib/string.c文件中。只要包含头文件,就可以使用它们。
头文件
当我们在这里谈 及头文件时,都指的是组成内核源代码树的内核头文件。内核源代码文件不能包含外部头文件,就像它们不能用外部库一样。
在所有没有实现的函 数中,最著名的就数printf()函数了。内核代码虽然无法调用printf(),但它可以调用printk()函数。 printk()函数负责把格式化好的字符串拷贝到内核日志缓冲区上,这样,syslog程序就可以通过读取该缓冲区来获取内核信息。printk()的 用法很像printf():
printk("Hello world! A string: %s and an integer: %d\n", a_string, an_integer);
printk()和printf()之间的一个显著区别在于printk()允许你 通过指定一个标志来设置优先级。Syslog会根据这个优先级标志来决定在什么地方显示这条系统消息。下面是一个使用这种优先级标志的例子:
printk(KERN_ERR "this is an error!\n");
2 GNU C
像所有自视清高的Unix内核一样,Linux内核是用C语言编写 的。让人略感惊讶的是,内核并不完全符合ANSI C标准。实际上,只要有可能,内核开发者总是要用到gcc提供的许多语言扩展部分。(gcc是多种GNU编译器的集合,它包含的C编译器既可以编译内核, 也可以编译Linux系统上用C写的其他代码。)
内核开发者使用的C语言涵盖了ISO C995标准和GNU C扩展特性。这其中的种种变化把Linux内核推向了gcc的怀抱。尽管目前出现了一些新的编译器如Intel C,已经支持了足够多的gcc扩展特性,完全可以用来编译Linux内核了。Linux内核用到的ISO C995标准的扩展没有什么特别之处,而且C99[1]作为C语言官方标准的修订本,不可能有大的或是激进的变化。让人感兴趣的是,多数GUN C与标准C有区别,而通常人们也不熟悉的这些差异。下面我们给出内核代码中所使用到的C语言扩展中,大家会感兴趣的部分吧。
2.1. 内联(inline)函数
GNU 的C编译器支持内联函数。Inline这个名称(inline翻译成内联似乎并不贴切,直译应该是"在字里行间展开"的意思,不过约定俗成,这里我们也把 它翻译成"内联")就可以反映出它的工作方式,函数会在它所调用的位置上展开。这么做可以消除函数调用和返回所带来的开销(寄存器存储和恢复),而且,由 于编译器会把调用函数的代码和函数本身放在一起进行优化,所以也有进一步优化代码的可能。不过,这么做是有代价的(天下没有免费的午餐),代码会变长,这 也就意味着占用更多的内存空间或者占用更多的指令缓存。内核开发者通常把那些对时间要求比较高,而本身长度又比较短的函数定义成内联函数。如果你把一个大 块头的程序做成了内联函数,却并不需要争分夺秒,反而反复调用它,想想是否值得?
定义一个内联函数的时候,需要使用static作为关键字,并且 用inline限定它。比如:
static inline void dog(unsigned long tail_size)
内联函 数必须在使用之前就定义好,否则编译器就没法把这个函数展开。实践中一般在头文件中定义内联函数。由于使用了static作为关键字进行限制,所以编译时 不会为内联函数单独建立一个函数体。如果一个内联函数仅仅在某个源文件中使用,那么也可以把它定义在该文件开始的地方。
在内核中,为了类型安全起 见,优先使用内联函数而不是复杂的宏。
2.2 内联汇编
gcc编译器支持在C函数中嵌入汇编指令。当然,在内核编程的时候,只有知道对应 的体系结构,才能使用这个功能。
Linux的内核混合使用了C和汇编语言。在偏近体系结构的底层或对执行时间要求严格的地方,一般使用的是汇编语 言。而内核其他部分的大部分代码是C语言写的。
3. 分支声明
对于条件选择语句,gcc内建了一条指令用于优化,在一个条件经常出现,或 者该条件很少出现的时候,编译器可以根据这条指令对条件分支选择进行优化。内核把这条指令封装成了宏,比如likely()和unlikely(),这样 使用起来比较方便。
例如,下面是一个条件选择语句:
if (foo) {
/* .. */
}
如果想要把 这个选择标记成绝少发生的分支:
/* 我们认为foo绝大多数时间都会为0.. */
if (unlikely(foo)) {
/* .. */
}
相反,如果我们想把一个分支标记为通常为真的选择:
/* 我们认为foo通常都不会为0 */
if (likely(foo)) {
/* .. */
}
在你想要对某个条件选择语句进行优化之前,你一定要搞清楚其中 是不是存在这么一个条件,在绝大多数情况下都会成立。这点十分重要:如果你的判断正确,确实是这个条件占压倒性的地位,那么性能会得到提升,如果你搞错 了,性能反而会下降。在对一些错误条件进行判断的时候,常常用到unlikely()和 likely()。你可以猜到,因为if语句往往判断一种特殊情况,因此unlikely()在内核中得到广泛使用。
3. 没有内存保护机制
如 果一个用户程序试图进行一次非法的内存访问,内核会发现这个错误,发送 SIGSEGV,并结束整个进程。然而,如果是内核自己非法访问了内存,那后果就很难控制了。(毕竟,有谁能照顾内核呢?)内核中发生的内存错误会导致 oops,这是内核中出现的最常见的一类错误。在内核中,你不应该去访问非法的内存地址,引用空指针之类的事情,否则它可能会死掉,却根本不知会通知你一声-在内核里,风险常常会比外面大一些。
此外,内核中的内存都不分页。也就是说,你每用掉一个字节,物理内存就减少一个字节。所以,在你想往内核 里加入什么新功能的时候,要记住这一点。
4. 不要轻易在内核中使用浮点数
在用户空间的进程内进行浮点操作的时候,内核会完成从整数 操作到浮点数操作的模式转换。在执行浮点指令时到底会做些什么,因体系结构不同,内核的选择也不同,但是,内核通常捕获陷阱并做相应处理。
和用户 空间进程不同,内核并不能完美地支持浮点操作,因为它本身不能陷入。在内核中使用浮点数时,除了要人工保存和恢复浮点寄存器,还有其他一些琐碎的事情要 做。如果要直截了当的回答,那就是:别这么做了,不要在内核中使用浮点数。
5. 容积小而固定的栈
用户空间的程序可以从栈上分配大量 的空间来存放变量,甚至巨大的结构体或者是包含许多数据项的数组都没有问题。之所以可以这么做,是因为用户空间的栈本身比较大,而且还能动态的增长.
内 核栈的准确大小随体系结构而变。在x86上,栈的大小在编译时配置,可以是4KB也可以是8KB。从历史上说,内核栈的大小是两页,这就意味着,32位机 的内核栈是8KB,而64位机是16KB,这是固定不变的。每个处理器都有自己的栈。
6 同步和并发
内核很容易产生竞争条件。和单线 程的用户空间程序不同,内核的许多特性都要求能够并发的访问共享数据,这就要求有同步机制保证不出现竞争条件,特别是:
* Linux是抢占多任务操作系统。内核的进程调度程序即兴对进程进行调度和重新调度。内核必须对这些任务同步。
* Linux内核支持多处理器系统。所以,如果没有适当的保护,在两个或两个以上的处理器上运行的代码很可能会同时访问共享的同一个资源。
* 中断是异步到来的,完全不顾及当前正在执行的代码。也就是说,如果不加以适当的保护,中断完全有可能在代码访问共享资源的当间到来,这样,中段处理程序就 有可能访问同一资源。
* Linux内核可以抢占。所以,如果不加以适当的保护,内核中一段正在执行代码可能会被另外一段代码抢占,从而有可能导致几段代码同时访问相同的资源。
常 用的解决竞争的办法是自旋锁和信号量。
7. 可移植性的重要性
尽管用户空间的应用程序不太注意移植问题,然而Linux却是 一个可移植的操作系统,并且要一直保持这种特点。也就是说,大部分C代码应该与体系结构无关,在许多不同体系结构的计算机上都能够编译和执行,因此,必须 把体系结构相关的代码从内核代码树的特定目录中适当地分离出来。诸如保持字节序、64位对齐、不假定字长和页面长度等一系列准则都有助于移植性。
8 小结
内核的确是一头独一无二的猛兽:没有内存保护,没有靠得住的libc,小的堆栈,庞大的源码树。Linux内核遵循它自己的游戏规则,以大 人物的架势运行,运行足够长的时间后才停止,打破了我们惯以为常的习俗。尽管如此,内核不外乎就是一个程序,它与我们司空见惯的程序没有多大区别。不必望 而生畏:直面它、呼唤它、摆布它。
意识到内核并不像咋看起来那样使人畏惧,这就是良好的开端。不过,要梦想成真,你必须全身心地投入,阅读源 码、剖析源码,并毫不气馁。
摘自我们翻译的《Linux内核设计与实现》一书
[1]ISO C99是ISO C的最新修订版。C99相对于前一个修订版作了许多加强,ISO C90引入了命名结构体初始化和complex数据类型。后者你不能在内核内安全地使用。
发表评论
-
Android 目录结构
2012-01-20 12:19 694Android 目录结构 2010年11月08日 In ... -
Linux ARM交叉编译工具链制作过程
2012-01-20 12:19 2425Linux ARM交叉编译工具链制作过程 2010年11月2 ... -
【zz】静态库与动态库搜索路径
2012-01-20 12:19 1225【zz】静态库与动态库 ... -
实现一个最简单的嵌入式操作系统
2012-01-20 12:19 757实现一个最简单的嵌入 ... -
Python Gossip:简介模组
2012-01-19 17:00 818Python Gossip:简介模组 2010年09月18日 ... -
python sys模块详解!
2012-01-19 17:00 3918python sys模块详解! 2011年06月28日 ... -
C++ 扩展和嵌入 Python
2012-01-19 17:00 869C++ 扩展和嵌入 Python 2011年02月17日 ... -
话说Python:非主流编程语言
2012-01-19 17:00 1021话说Python:非主流编程语言 2010年07月06日 ... -
全能选手 看看Python应乎潮流的72变
2012-01-19 17:00 727全能选手 看看Python应乎潮流的72变 2010年10月 ... -
张志晨VB实例教程之打开word方法种种
2012-01-17 06:45 1071张志晨VB实例教程之打开word方法种种 2011年08月3 ... -
暂时放一放./..脑子要炸了
2012-01-17 06:45 562暂时放一放./..脑子要炸了 2010年11月10日 f ... -
VB制作快捷打开电脑里的应用软件比如:我的电脑 网上邻居 等....
2012-01-17 06:45 615VB制作快捷打开电脑里的应用软件比如:我的电脑 网上邻居 等. ... -
vb代码2
2012-01-17 06:45 596vb代码2 2010年11月21日 ... -
天铭本期热招岗位7.19-7.25
2012-01-17 06:45 3天铭本期热招岗位7.19-7. ... -
世界各国驻中国大使馆名录(全)
2012-01-16 05:35 1176世界各国驻中国大使馆名录(全) 2009年10月07日 ... -
Flex:学习标准(转载)
2012-01-16 05:34 581Flex:学习标准(转载) 2009年12月27日 转载 ... -
藏经阁
2012-01-16 05:34 668藏经阁 2009年07月21日 藏经阁 ... -
FlashBuilder4 (FlexBuilder4)中文版下载
2012-01-16 05:34 1330FlashBuilder4 (FlexBuilder4)中 ... -
Singleton模式--个人理解
2012-01-16 05:34 591Singleton模式--个人理解 ...
相关推荐
设计一个驱动程序是开发内核级Rootkit的第一步。驱动程序需要包含加载Rootkit核心代码的功能,并且具备一定的隐蔽性和健壮性。 ##### 4.2 实现隐蔽功能 在驱动程序中实现必要的隐蔽功能,如安装钩子函数、修改内核...
21. 模块与应用程序空间:模块运行在内核空间,应用程序运行在用户空间。 22. 浮点运算:在Linux中,浮点运算通常由硬件(如浮点单元)实现,内核和应用程序都能使用。 23. 模块使用库函数:模块程序可以使用静态...
"基于Chrome内核的WPF浏览器开发"是一个项目,它利用了Google Chrome的开源项目Chromium,构建了一个Windows Presentation Foundation (WPF) 应用程序中的浏览器组件。这个项目的核心在于将Chromium的强大浏览能力与...
### 大循环程序与带内核程序的比较研究 #### 摘要 本文通过在S3C44B0X开发板上实现...这表明在嵌入式系统开发中引入轻量级的操作系统是非常有益的,不仅能够提高系统的实时性能,还能简化编程复杂度,提高开发效率。
《嵌入式Linux应用程序开发标准教程(第2版)》是华清远见出版的一本针对嵌入式领域专业开发者的权威教程。这本书涵盖了从Linux基础知识到高级应用开发的多个方面,旨在帮助读者全面掌握在嵌入式环境中使用Linux进行...
《嵌入式Linux应用程序开发标准教程(第2版)》主要分为3个部分,包括Linux基础、搭建嵌入式Linux环境和嵌入式Linux的应用开发。Linux基础部分从Linux基础、基本操作命令讲起,为Linux初学者能快速入门提供了保证。...
通过对Linux操作系统的基本结构、ARM架构特点的理解,以及对驱动开发流程的掌握,可以有效地完成高质量的驱动程序开发工作。特别是在同步串行接口这样的特定应用场景下,开发者需要深入研究硬件细节,并结合实际需求...
《物联网操作系统LiteOS内核开发与实践》是一份详尽的教学资料,主要涵盖了物联网操作系统 LiteOS 的核心原理、设计思路以及实际开发应用。LiteOS 是华为公司推出的一款轻量级物联网操作系统,它针对低功耗、高效率...
在手持设备环境的搭建及程序开发中,C#是一种常用的语言,尤其在开发手持终端设备、PDA(个人数字助手)以及条码数据采集器等移动设备应用时。本项目"WinCEDemo-master"提供了使用C#进行此类开发的相关源码和数据集...
能学到什么:①移动学习开发、微信小程序开发等都是怎么在Spring中体现的;②平台总体的架构、界面、内容、功能都是如何设计和实现的。 阅读建议:此资源以开发微信小程序的词汇学习平台学习其原理和内核,不仅是...
- PCI设备驱动程序开发。 8. **字符设备驱动程序设计** - 字符设备的特点与应用场景。 - 字符设备驱动的开发步骤。 9. **块设备驱动程序** - 块设备的定义与用途。 - 常见的块设备类型,如硬盘、SSD等。 - ...
在"windows应用程序捆绑内核编程实例第四章"中,可能会详细讲解如何使用C++或C#等编程语言来实现邮槽、管道和套接字的编程接口,包括创建、读写、错误处理和通信过程中的同步与互斥等技术。通过实际的代码示例(ch04...
12. **调试技巧**:教授如何使用gdb、SystemTap、kdb等工具对内核进行调试,帮助读者解决内核级问题。 通过学习这些内容,读者不仅可以掌握Linux内核的运作原理,还能学会如何根据需求定制和改进Linux系统,提升在...
"花香9.0内核商业程序"是一款在2009年面向市场推出的商业级内容管理系统(CMS),专门设计用于构建美观且功能丰富的网站。该系统的核心特点在于其内置了"digging"功能,类似于社交网络中的点赞或推荐机制,用户可以...
基于嵌入式Linux的应用程序开发 嵌入式Linux是指在嵌入式系统中使用的...嵌入式Linux的应用程序开发需要考虑到嵌入式系统的特点和限制,选择合适的开发工具和技术,确保开发的应用程序能够在嵌入式系统中稳定运行。
本书首先介绍了嵌入式Linux的基础知识,包括嵌入式系统的特点、Linux内核的工作原理以及Linux发行版的选择。这些内容对于初学者来说至关重要,能够帮助他们建立对嵌入式Linux系统的整体理解。 接着,书中详细讲解了...
LiteOS-A 可以在多种硬件平台上运行,手册会指导开发者如何将LiteOS-A 移植到新的硬件平台,包括硬件初始化、驱动程序开发以及系统调优。 六、鸿蒙系统上的 LiteOS-A集成 在鸿蒙操作系统中,LiteOS-A 与其他组件...