从内核出发

原以为这章可以飞快跳过,但还是发现很多新的有意思的点。

获取源码

  1. https://www.kernel.org 压缩源码或者增量补丁

    • bz2
      tar xvjf linux-x-y-z.tar.bz2
    • gz
      tar xvzf linux-x-y-x.tar.gz
  2. https://github.com/torvalds/l...

安装源码

/usr/src/linux
不要把这个源码树用于开发,因为编译用的C库所用的内核版本就链接到这棵树。
正确:建立自己的主目录,然后仅用root进行安装。
关于补丁:
patch -p1 < ../patch-x.y.z

内核源码树

目录描述
arch特定体系结构的源码
block块设备io层
crypto机密API
Documents内核源码文档
drivers设备驱动程序
firmware使用某些驱动程序而需要的设备固件
fsVFS和各种文件系统
include内核同文件
init内核引导和初始化
ipc进程间通信
kernel核心子系统
lib通用内核函数
mm内存管理子系统和VM
net网络子系统
samples实例
scripts编译内核所用的脚本
securityLinux安全模块
sound语音子系统
usr早期用户空间代码
toolsLinux开发工具
virt虚拟化体系

编译

编译Linux之前需要进行配置:CONFIG_XXXX: yes|no|module;

  1. 决定哪些文件可以被编译进内核;module意味着该配置选项被选定,但编译的时候这部分功能的实现代码是以模块的形式生成的(动态安装的独立代码块)。
  2. 通过预处理的命令处理代码;配置选项可以为字符串和证书,指定内核源码可以访问的值,以预处理宏的形式;如制定静态分配数组的大小。
    配置命令:
  3. 逐一遍历
    make config
  4. 图形界面工具
    make menuconfig
  5. gtk+图形工具
    make gconfig
  6. 默认配置
    make defconfig
    配置选项会被放在根目录下.config文件中;每次编译前,更新配置
    make oldconfig
    可以配置CONFIG_IKCONFIG_PROC,把配置压缩放入/proc/config.gz下,可以从/proc下复制出配置文件并且使用它来编译一个新内核:
    zcat /proc/config.gz > .config
    make oldconfig
    配置完成后: make > /dev/null
    提高make效率多核,如16核:
    make -j32 > /dev/null

安装

  1. 查阅启动引导工具的说明,将内核映像拷贝到/boot目录下,按启动要求安装。
  2. 内核模块的安装是自动的,make modules_install,装到/lib/modules下
  3. 编译时也会在内核代码树的根目录下创建一个System.map文件,符号对照表,将内核符号和他们的起始地址对应,需要把内存地址翻译成容易理解的函数名以及变量名。

内核开发

内核编译时不能访问c库文件(libc)和标准c头文件

lib/string.c => <linux/string.h>

  1. 内核开发头文件基本都在源码include目录下,头文件<linux/inotify.h>
  2. 体系相关的头文件arch/x86/include/asm下面<asm/ioctl.h>
    没有实现的printf() -> prink 将格式化的字符串拷贝到日志缓冲区中,syslog程序就可以通过读取该缓冲区来获取内核信息。 可以指定一个优先级标志符,类似于宏定义
    prink(KERN_ERR "this is an error.\n")

使用GNU C

  1. gcc工具包含了多种GNU编译器,可以编译内核。
  2. 内核C语言包含了ISO C99标准和GNU C扩展特性。 与标准c语言不同的大多是GNU C的扩展上。

    • 内联函数inline:

      • 函数在调用位置扩展,消除函数调用和返回的开销,代码变长,占用内存空间或者指令缓存更多;
      • 函数较大,被反复调用,没有时间要求不推荐使用
      • 与static合用
        static inline void wolf(unsigned long tail_size)
      • 使用前定义,推荐在头文件中定义,优先使用inline函数而不是宏
    • 内联汇编

      • 只有知道体系结构才可以使用,偏近底层或对执行时间要求严格
      • asm()
        unsigned int low, hight
        asm volatile("rdtsc" : "=a" (low), "=d" (high));
    • 分支声明

      • gcc优化分支,经常出现likely, unlikely
        if (unlikely (error))
      • 一定要弄清是否真的有偏向,对性能影响巨大

    没有内存保护机制

  3. 用户程序非法内存访问,内核会发现,发送SIGSEGV信号,结束进程
  4. 内核中发生内存访问错误导致oops

    • 非法内存访问
    • 引用空指针
  5. 内核中内存不分页,用一个字节,物理内存就少一个

不要轻易使用浮点数

  1. 用户空间操作浮点数,内核需要从整数模式切换成内核模式,具体实现与体系相关
  2. 在内核中使用浮点数时,需要人工保存和回复浮点寄存器,还要有其他复杂工作

内核只有小而固定的栈

  1. 用户空间可以动态分配大容积的栈,内核不行
  2. 内核栈大小随体系变化,x86,在编译时确定,4KB或者8KB;历史上内核栈大小两页,32位8KB, 64位16KB

注意同步和并发

原因:

  1. Linux是抢占多任务操作系统,调度过程中内核必须和任务同步
  2. 支持对称多处理器系统SMP;两个或多个以上的处理器上执行的内核代码可能同时访问共享的同一个资源
  3. 中断异步到来,可能在访问资源时到来,中断程序可能处理同一个资源
  4. Linux内核可以抢占,内核中正在执行的代码可能被同一段代码抢占,从而导致几段代码同时访问相同的资源。

方法:

  1. 自旋锁
  2. 信号量

可移植性很重要


唠叨的水龙头
1 声望0 粉丝