前言
"<Linux内核设计与实现>(原书第三版)"---以下称"本书"第2章"从内核出发"部分内容解读
引入
编程这个工作一直以来都是以"理性","逻辑"作为标签的,但很多时候我更希望靠"感觉"编程.感觉是建立在足够熟练和理解基础上的,学习的过程可以当成积累"感觉"的过程.
作为一个初学内核的人来说,"内核"是个怎样的感觉呢?
1>内核本身是个程序---在应用程序的下层,内核直接控制CPU和硬件.
2>内核可以访问绝对地址,并掌握最重要的数据--如可以访问中断寄存器,某些中间数据.
笔者在这里引入一个概念:中间数据.数据分为可以映射入硬件的数据和不能映射入硬件的数据,前者称为硬数据,后者称为中间数据.举个例子,在电脑上看见的像素点(或者一张图一个表格等),他们将被写入显存并显示在屏幕上,这些数据是硬数据.而在编程过程中,表示状态(可以用枚举类型)的数据是中间数据
内核开发特点
本书P15页有具体介绍
无libc库或无标准头文件
本书P15倒数第5段:与用户空间的应用程序不同,内核不能链接使用标准C函数库
---这个很好理解,标准C函数库在内核上层,封装了内核函数库.内核函数库提供了标准C的实现.在不会内核编程的情况下,标准C就是程序员的基础API(只会调用不会定义的函数).
之所以说内核仍然能使用标准C,是因为内核代码和标准C长得很像,例如
//伪代码
//用户空间scanf的函数定义
void scanf(...,...){ //形参略
_scanf(...,...); //内核函数
}
本书P16列举了printf和printk的例子,与此类似
//伪代码
//用户空间怕printf的函数定义
void printf(...,...){ //形参略
printk(...,...); //内核函数
}
本书P16第二段提到了++标志位提供优先级++ .说明在printf的函数体中,有标志位参与(上述伪代码未表达)---++标志位++可能类似于CPU标志位寄存器在内核空间的映射---内核空间有一个字节长度的全局变量来表示它(虽然现在不知道它叫什么,或者它的绝对地址)
GNU C
Linux内核用的语法和标准C有一些区别,在标准C的基础上做了扩充,其中有的内容比较有意思
内联函数
内联函数的条件:代码长度短,调用次数少
写法:static inline + 函数定义(返回值+空格+函数名+(+形参定义+)
内联函数还是在优化代码的时候考虑,平常不要想太多.原因是从使用条件来讲,代码要多短,调用次数多少次才够得上用内联函数,这些问题没有明确说明.所以内联函数的使用方法:++可以在代码优化阶段尝试把一些短且用得少的函数定义成内联函数,再看是否能提高工作效率++.
内联函数的特点:占空间大,反应时间短.其中为什么反应时间短,这个问题笔者在C++学习时没明白,到理解了"可执行文件"的内容后才有了答案.以下给出main函数执行的抽象
int main(void){
fun1();
fun2();
}
程序的执行是在不停调用函数-函数返回.对应的操作大概是
//伪代码
call fun1;
ret;
call fun2;
ret;
这个过程实际上是在"查表"(见笔者前面贴),每执行一次call,就到静态数据区查找对应函数地址,并开始执行编译后的二进制代码,然后返回.如果定义的是内联函数,编译后直接执行,少了调用-返回的过程.但是非内联函数编译后只在静态数据区有一份(查询执行),内联函数每调用一次都使用一份空间.
笔者个人认为内联函数的用处不是很大,但是有必要理解它的工作机制
内联汇编
内联汇编在本书P17只占了几行,但它很重要,意味着程序员可以真正使用操作系统的核心资源,做任何想做的事.
分支声明
编译器给出的优化,具体怎样优化和CPU的运行机制有关可以不管,掌握其用法
分支声明的用法:程序员先判定分支条件的概率,再根据概率调整写法.例如
//C语言代码
//伪代码
if(表达式)
做事A;
else
做事B;
如果表达式不成立(等于0)的概率高,则改为
//伪代码
if(unlikely(表达式)) //表达式为0的概率高
做事A;
else
做事B;
反之如果表达式成立的概率高,把unlikely换成likely
没有内存保护机制
本书P18有描述,容易理解,程序员进入内核编程,控制重要资源,权限大责任也大
容量小而固定的栈
栈的机制是每调用一个函数,在没返回(ret)之前,当前函数的数据将压栈.例如
//伪代码
void fun1(){
...
fun2();
}
void fun2(){
...
fun3();
}
fun1的执行调用了fun2(),fun2()的执行调用了fun3().在fun3()执行完毕后,释放fun3()对应的栈空间.当fun2()执行完毕后,释放fun2()对应的栈空间."递归"将占用了巨大的栈空间.
对内核来说,栈空间属于自己分配,所以实际意义不大,不管是8KB还是16KB都是很大的空间了
响应做法:不要在函数定义时嵌套得太深,让栈的空间尽量小,节约内核资源.
同步和并发
这是操作系统的重点和难点,简单的说如何保证多个进程访问共享数据的准确性.
本书P19讲了:常用的解决竞争的办法是自旋锁和信号量.
可移植性
计算机的书籍中经常看见"平台无关"的说法,例如Java是平台无关的,Qt是平台无关的.他们可以运行在不同的操作系统上.为什么平台无关,因为他们底层的由内核程序实现了统一.
Linux是可移植的,指的是可以运行在不同架构的芯片上.内核程序在不同的芯片指令集上是不同的.但是从内核程序上层的程序,如标准C看来,他们是相同的.