深入理解 Linux 内核读书笔记

  1. 在 ps 命令的输出中很容易识别内核线程,其名称都置于方括号内
  2. linux 内核把虚拟地址空间划分为两个部分:核心态和用户状态。两种状态的关键差别在于对高于 TASK_SIZE 的内存区域的访问
  3. 伙伴系统。 系统中的空闲内存块总是两两分组,每组中的两个内存块称作伙伴。伙伴的分配可以是彼此独立 的。但如果两个伙伴都是空闲的,内核会将其合并为一个更大的内存块,作为下一层次上某个内存块 的伙伴。
  4. 字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类 设备支持按字节/字符来读写数据。举例来说,调制解调器是典型的字符设备。
  5. 块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘是典型的 块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常 是 512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。 编写块设备的驱动程序比字符设备要复杂得多,因为内核为提高系统性能广泛地使用了缓存 机制。
  6. Linux 使用了源于 BSD 的套接字抽象。 套接字可以看作应用程序、文件接口、内核的网络实现之间的代理
  7. 进程可以分为实时进程和非实时进程,硬实时进程有严格的时间限制,某些任务必须在指定的时限内完成。硬实时进程的关键特征是,它 们必须在可保证的时间范围内得到处理。Linux 不支持硬实时处理。 软实时进程是硬实时进程的一种弱化形式。
  8. 抢占式多任务处理(preemptive multitasking),各个进程都分配到一定的时间段 可以执行。时间段到期后,内核会从进程收回控制权,让一个不同的进程运行,而不考虑前一进程所 执行的上一个任务。被抢占进程的运行时环境,即所有 CPU 寄存器的内容和页表,都会保存起来,因 此其执行结果不会丢失。在该进程恢复执行时,其进程环境可以完全恢复。时间片的长度会根据进程
  9. 完全公平调度器(completely fair scheduler)在内核版本 2.6.23 开发期间合并进来。新的代码再 一次完全放弃了 原有的设计原则,例如,前一个调度器中为确保用户交互任务响应快速,需要许多启 发式原则。该调度器的关键特性是,它试图尽可能地模仿理想情况下的公平调度。此外,它不仅可以 调度单个进程,还能够处理更一般性的调度实体(scheduling entity)。例如,该调度器分配可用时间时, 可以首先在不同用户之间分配,接下来在各个用户的进程之间分配。
  10. 进程运行的状态:
    10.1. 运行:该进程此刻正在执行。
    10.2. 等待:进程能够运行,但没有得到许可,因为 CPU 分配给另一个进程。调度器可以在下一次 任务切换时选择该进程。
    10.3. 睡眠:进程正在睡眠无法运行,因为它在等待一个外部事件。调度器无法在下一次任务切换 时选择该进程
  11. 僵尸进程的资源已经释放但在进程表中仍存在对应的表项
  12. 僵尸是如何产生的?其原因在于 UNIX 操作系统下进程创建和销毁的方式。在两种事件发生时, 程序将终止运行。第一,程序必须由另一个进程或一个用户杀死(通常是通过发送 SIGTERM 或 SIGKILL 信号来完成,这等价于正常地终止进程);进程的父进程在子进程终止时必须调用或已经调用 wait4 (读做 wait for)系统调用。 这相当于向内核证实父进程已经确认子进程的终结。该系统调用使得内核 可以释放为子进程保留的资源。 只有在第一个条件发生(程序终止)而第二个条件不成立的情况下(wait4),才会出现“僵尸” 状态。在进程终止之后,其数据尚未从进程表删除之前,进程总是暂时处于“僵尸”状态。有时候(例 如,如果父进程编程极其糟糕,没有发出 wait 调用),僵尸进程可能稳定地寄身于进程表中,直至下 一次系统重启。
  13. 内核抢占(kernel preemption)的选项添加到内核。 该选项支持 在紧急情况下切换到另一个进程,甚至当前是处于核心态执行系统调用(中断处理期间是不行的) 。 尽管内核会试图尽快执行系统调用,但对于依赖恒定数据流的应用程序来说,系统调用所需的时间仍 然太长了。内核抢占可以减少这样的等待时间,因而保证“更平滑的”程序执行。但该特性的代价是 增加内核的复杂度,因为接下来有许多数据结构需要针对并发访问进行保护,即使在单处理器系统上 也是如此。
  14. Linux 内核涉及进程和程序的所有算法都围绕一个名为 task_struct 的数据结构建立,该结构定 义在 include/sched.h 中。
  15. Linux 提供资源限制(resource limit,rlimit)机制,对进程使用系统资源施加某些限制。该机制利 用了 task_struct 中的 rlim 数组,数组项类型为 struct rlimit。
    打开文件的数目(RLIMIT_NOFILE,默认限制在 1 024)。  每用户的大进程数(RLIMIT_NPROC),定义为 max_threads/2。max_threads 是一个全局变 量,指定了在把八分之一可用内存用于管理线程信息的情况下,可以创建的线程数目。在计 算时,提前给定了 20 个线程的小可能内存用量。
  16. 典型的 UNIX 进程包括:由二进制代码组成的应用程序、单线程(计算机沿单一路径通过代码, 不会有其他路径同时运行)、分配给应用程序的一组资源(如内存、文件等)。
    16.1. fork 生成当前进程的一个相同副本,该副本称之为子进程。原进程的所有资源都以适当的方 式复制到子进程,因此 该系统调用之后,原来的进程就有了两个独立的实例。这两个实例的 联系包括:同一组打开文件、同样的工作目录、内存中同样的数据(两个进程各有一份副本), 等等。此外二者别无关联。
    16.2. exec 从一个可执行的二进制文件加载另一个应用程序,来代替当前运行的进程。加载了一个新程序。因为 exec 并不创建新进程,所以必须首先使用 fork 复制一个旧的程序, 然后调用 exec 在系统上创建另一个应用程序。
    16.3. clone 的工作原理基本上与 fork 相同,但新进程不是独立于父进程的, 而可以与其共享某些资源。写时复制,直至新进程对内存页执行写操作才会复制内存页面,这比在 执行 fork 时盲目地立即复制所有内存页要更高效。父子进程内存页之间的联系,只有对内核才是可见的,对应用 程序是透明的可以指定需要共享和复制的资源种类,例如,父进程的内存数据、打开文 件或安装的信号处理程序。 clone 用于实现线程,但仅仅该系统调用不足以做到这一点,还需要用户空间库才能提供完整的 实现。线程库的例子,有 Linuxthreads 和 Next Generation Posix Threads 等
  17. 进程总是会分配一个号码用于在其命名空间中唯一地标识它们。简称 PID。用 fork 或 clone 产生的每个进程都由内核自动地分配了一个新的唯一的 PID 值
  18. task_struct 数据结构提供了两个链表表头,用于实现进程家族关系
  19. 内核线程是直接由内核本身启动的进程。内核线程实际上是将内核函数委托给独立的进程,与系 统中其他进程“并行”执行(实际上,也并行于内核自身的执行)。
    19.1. 内核线程经常称之为(内核)守 护进程。它们用于执行下列任务。
    19.2. 周期性地将修改的内存页与页来源块设备同步(例如,使用 mmap 的文件映射)。
    19.3. 如果内存页很少使用,则写入交换区。
    19.4. 管理延时动作(deferred action)。
    19.5. 实现文件系统的事务日志。
  20. 基本上,有两种类型的内核线程:
    20.1.线程启动后一直等待,直至内核请求线程执行某一特定操作。
    20.2.线程启动后按周期性间隔运行,检测特定资源的使用,在用量超出或低于预置的限制值时采取行动。内核使用这类线程用于连续监测任务。
    调用 kernel_thread 函数可启动一个内核线程。其定义是特定于体系结构的,但原型总是相同的。