写在前面:
本文是我学习《Linux内核设计与实现》 一书的学习笔记,以书的章节目录为框架,同时加入一定的网上搜索的资料以及自己的理解。供自己学习外,也希望能分享出去,帮助其他学习者。
差异一 内核编程时既不能访问C库也不能访问标准 C 头文件
由于大小和速度的限制,无论是完整的C库还是它的子集,都太大太低效。 所以内核不能链接使用标准C函数库。
大部分常用的C库函数在内核中得到了实现,Linux下的库文件分为共享库和静态库两大类,它们两者的差别仅在程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。 Linux的库一般在/lib 或/usr/lib 目录下,例如操作字符串的函数组就位于lib/string.c 文件中,想要使用它们的话,只要包含 <linux/string.h> 。
在所以没有实现的函数中,最著名的就是printf函数,但是内核代码提供了几乎相同的printk函数。printk相对于printf的一个显著区别在于printk允许通过制定log level来设置优先级。具体的优先级如下表所示。
printk prototype: int printk(const char *fmt,...);
内核基本的头文件位于内核源代码书顶级目录下的include目录下, 例如 include/linux/inotify.h , 内核代码通过形如 <linux/inotify.h>的方式包含内核基本头文件。 (inotify是一种在Linux 2.6新添加的特性,它可以实现对文件系统进行监控,通过触发的方式告诉你文件的变化)。
体系结构相关的头文件集位于内核源代码树的 arch/<architecture>/include/asm 目录下, architecture 表示所用的体系结构,例如x86体系结构的相关头文件就在 arch/x86/include/asm 目录下。 内核代码通过形如<asm/ioctrl.h>的方式包含体系结构相关的头文件。(ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。)
差异二 内核编程必须使用 GNU C
Linux库的命名比较简单,第一个特点是所有的库以lib开头,GCC命令在在-l 选项所指定的文件名前会自动加入lib。第二个特点文件名以.a结尾的库是静态库 。第三个特点文件名是.so的库为共享库(共享库是在运行的时候动态加载的 ) 。默认情况下,GCC在链接时优先使用共享库,只有当共享库不存在时才考虑使用静态库。
内联函数 - inline关键字定义,一般定义在头文件中(若是内联函数仅仅在某个源文件中使用,也可以把它定义在文件开始的地方。),形如下面的例子。
static inline void wolf (unsigned long tail_size)
它会在调用它的地方展开,因此内联函数在使用前必须定义好,否则编译器没法展开。这样可以消除函数调用和返回所带来的开销,包括寄存器的存储和恢复。并且编译器会将调用函数的代码以及函数本身放在一起优化。这样做的代价就是占用更多的内存空间或者占用更多的指令缓存。内核开发这通常会把那些对时间要求较高,而本身长度比较短的函数定义成内联函数。
很自然的,人们会把内联函数与宏定义做比较,一个显著的区别在于,宏定义在预处理是展开,因此不检查函数参数,返回值什么的,只是展开,相对来说,内联函数在编译时展开,因此会检查参数类型,所以更安全。此外,内联函数能有效避免使用宏定义可能产生的由优先级引起的错误以及有宏定义中有多个相同操作数且有自增操作时,回到导致展开的代码不符合预期。因此内联函数更被推荐使用。
GCC 编译器支持 在C函数中嵌入会变指令(这样做的前提是知道对应的体系结构)。我们通常使用asm()指令嵌入汇编代码,直接对寄存器进行操作。
asm(
"mov r0, r0\n\t"
"mov r0, r0\n\t"
"mov r0, r0\n\t"
"mov r0, r0"
);
对于条件选择语句,gcc内建了一条指令用于优化,当一个条件经常出现/很好出现的时候,编译器可以根据这条指令对条件分之选择进行优化,这条指令被内核封装成了 likely 与 unlikely 宏。
例如当error错误分支很少发生,我们可以定义:
if(unlikely(error)){
}
相反如果我们想把一个分支标记为通常为真的选择,可以定义:
if(likely(success)){
}。
使用likely一定要非常小心,如果某个条件不是占压倒性的低位时,使用likely反而会降低性能。而unlikely则被广泛使用去标记异常分支。
差异三 内核没有内存保护机制
当用户进程试图非法访问时,内核会发现这个错误,发送SIGSEGV信号,并结束整个进程,而内核自己非法访问内存时,会导致oops错误。因此,在内核中不应该去访问非法的内存地址,引用空指针之类的事情,否则它可能死掉去根本不通知你一声。
此外,内核中的内存不分页,也就是说,内核中用掉每一字节,物理内存就会少掉一个字节。
差异四 不要轻易在内核中使用浮点数
差异五 内核有容积小且固定的栈
用户空间的栈比较大,且可以动态增长,所以用户进程可以从栈上分配大量的空间存放变量,甚至包含巨大的结构体和数组。而内核的栈随体系结构而变化,到那时固定而小,每个处理器都有自己的栈。
差异六 同步和并发
Linux内核支持SMP,两个或多个处理器上执行的中断代码可能会同时访问共享的资源。
Linux内核可以抢占,内核中一段正在执行的代码可能被另一段代码抢占,从而导致几段代码访问相同资源。
中断是异步到来的,不会顾及到正在执行的代码。如果不加以适当的保护,中断完全可能会在代码访问资源时到来,这样中断处理函数就可能访问同一资源。
为防止上述的问题,自旋锁以及信号量是常用的解决并发中的资源竞争问题的方法。
网友评论