可重入 (Reentrant Kernel)

多个进程可以在内核里同时执行. 当一个进程被挂起时, 它的所有运行状态都被保存下来, 在下次被恢复执行时, 就从之前的状态继续执行.

当一个函数只修改本地的变量时, 它是一个可重入的函数. 当内核里的函数不可重入时, 就得用锁机制, 来保证同时只有一个进程来调用这个不可 重入的函数.

同步与临界区 (Synchronization and Chritical Regions)

要安全地访问一个全局变量, 需要原子操作.

Kenrle preemption disabling

对于单核系统, 如果是非抢占式的, 内核不能任意挂起一个进程, 所以内核里的数据结构 不会因为中断或异常处理器更新.

Interrupt disabling

对于内核系统, 可以在进入临界区之前禁用所有的硬件中断.

信号量 (Semaphores)

对于单核和多核系统, 信号量都很有效. 信号量是跟一个数据结构关联在一起的计数器.

每个信号量可以被视为:

  • 一个整数变量
  • 一列等待的进程
  • 两个原子操作 up(), down()

down() 方法减少信号量的值, 当其值小于0时, 就会把运行的进程加到信号量列表时, 同时阻塞该进程. up() 方法增加信号量的值, 如果新的值大于等于0, 重新激活信号量列表里的一个或者 多个进程.

自旋锁 (Spin locks)

对于多核的系统, 信号量效率并不高, 因为同时只有一个进程可以访问该信号量, 其它 要访问的进程都被阻塞.

自旋锁与信号量类似, 但自旋锁不维护一个进程列表, 当一个进程发现这个锁被其它 进程锁住之后, 这进程就在原地”自旋”, 执行一个无限循环的指令, 直到这个锁被其它 进程释放后打开.

避免死锁 (Avoiding deadlocks)

可以使用预先定义好的顺序, 来请求锁, 以避免死锁的出现.

信号与进程间通信 (Signals and Interprocess Communication)

信号被用作, 内核给进程发送系统事件. 信号是整数值.

共享内存是进程间交换和共享数据最快速的方法.

进程管理 (Process Management)

fork(), _exit() 用来创建和终止进程, 而 exec() 系列的系统调用用来执行一个 新的可执行文件.

_exit() 系统调用来终止一个进程, 内核会释放该进程拥有的资源, 并且给该进程的 父进程发送 SIGCHLD 信号.

进程组和登录会话 (Process groups and login sessions)

为了更方便地管理作业(job), 引入了进程组, 比如: $ ls | sort | more shell 把这三个进程作为一个作业(job) 来看待. 每个进程的描述符里都包含进程组的ID (process group ID). 每个进程组都包含 process group leader, process group leader 的进程ID与其 process group ID 相同.

一个进程组里的所有进程都必须位于同一个 login session.

内存分配 (Memory Management)

内存分配是内核里最复杂的活动.

虚拟内存 (Virtual Memory)

虚拟内存是一般应用的内存与硬件内存管理单元 (Meomry Management Unit, MMU) 之间的 一个逻辑层.

CPU 中有相关的电路来将虚拟内存转为物理内存地址. 物理内存被分隔成了 page frames, 每个有4k或者8k. 内存页表(Page Tables) 用来实现虚拟地址与物理地址间的映射.

内核内存分配器 (Kernel Memory Allocator, KMA)

目前内核的 KMA 采用的是 Buddy system 结合 Solaris’s Slab allocator

进程地址空间处置 (Process Virtual Address Space Handling)

内核通常把一个进程的虚拟地址空间存储为一个内存区描述符列表, 包括:

  • 程序代码
  • 初始化了的数据
  • 未初始化的数据
  • 初始化了的栈 (比如, User Mode stack)
  • 共享库的可执行代码和数据
  • 堆(heap)

内存分配策略(demand paging), 一个程序开始执行时, 没有物理内存. 当它访问不存在的 内存页时, MMU 产生异常, 异常处理函数找到受影响的内存区域, 分配一个空白的内存页, 然后用合适的数据初始化它.

当进程使用 malloc(), brk() 来动态地请求内存时, 内核只是更新一下进程堆的大小.

写时复制(Copy on Write), 当一个父进程 fork() 出子进程时, 父进程的内存页 (page frame) 被以只读的形式分给了子进程, 当子进程试图修改内存时, 就会产生异常. 异常处理器分配一个新的内存页给子进程, 然后用之前的内存页的内容来初始化新的 内存页.

缓存 (Caching)

物理内存的一部分作为硬盘以及其它块设备的缓存. 因为这些设备通常读写都比较慢.

设备驱动 (Device Driver)

内核提供了一个统一的简单的接口给设备驱动. 设备驱动可以作为一个模块动态载入和卸载.

                Process1   Process2
                   +           +
                   |           |
        +----------+-----------+-------------+
        |         System Call Interface      |
        +------------------------------------+
Kernel  |         Virtual File System        |
        +------------------+-----------------+
        |    Char Device   |   Block Device  |
        +------------------+-----------------+
        | tty       Sound         Disk       |
        |Driver     Driver        Driver     |
        |  X          X             X        |
        +--X----------X-------------X--------+
           X          X             X
         tty1        Mic.         Disk 1
         tty2        Speaker      Disk 2
         tty3                     Disk 3