Linux Kernel LKD3 阅读笔记
概述
《Linux Kernel Development.3rd.Edition》重读笔记。
The most important step on the road to Linux development is the realization that the kernel is not something for fear.
Only through actually reading and experimenting with the code can you ever understand it. The source is freely available, use it!
进程管理
进程 = 程序 + 环境
进程创建
slab 分配器:通过预先分配和重复使用 task_struct,可以避免动态分配和释放所带来的资源消耗,可以加快进程创建的效率。
注意写时拷贝技术的作用是什么?(内核创建子进程时,并不复制整个进程地址空间,而是父子进程共享同一个拷贝。只有当子进程需要写入页的时候,才真正地进行拷贝)。fork 后父子进程的区别仅仅在于 PID、PPID、某些资源和统计量。
注意分析 fork 系统调用的流程:fork/clone -> do_fork -> _do_fork -> copy_precess。
进程调度
Linux CFS调度策略的特点:
- Nice值并不直接对应于进程的运行时间片(timeslices)。
- Old approaches assign absolute timeslices yields a constant switching rate but variable fairness. The approach taken by CFS is a radical (for process schedulers) rethinking of timeslice allotment: Do away with timeslices completely and assign each process a proportion of the processor. CFS thus yields constant fairness but a variable switching rate.
CFS不再依赖传统Unix系统的时间片机制,它实现了一种变化的、动态的公平策略而不是静态的不变的公平策略。调度器根据进程的nice值保证其占用处理器资源的比例(但实际比例因系统负载而定)而不是具体的timeslices,nice值作为分配占用CPU资源比例的权重值(weight),但不具有绝对意义,即对同一个进程不同的nice值可能分配相同的比例值。具体的比例分配要根据系统中所有进程的nice值的相对关系。
The nice value, instead of yielding additive increases to timeslices, yield geometric differences.
CFS计算实际timeslices的方法:CFS定义了目标延迟(targeted latency)的概念来接近完美的多任务系统,更小的target可以获得更好的交互性以及更接近完美的多任务系统,但意味着更昂贵的切换开销和降低系统吞吐率。CFS设置了timeslices的下限(minimum granularity)。
CFS is called a fair scheduler because it gives each process a fair share-a proportion-of the processor’s time. CFS不能实现绝对完美的公平,它是一种尽量接近完美的公平(CFS is perfectly fair!)。
CFS算法的具体实现要围绕vruntime变量,在一个理想的完美的多任务系统中,所有进程的vruntime值应该是相同的。那么可能要问不同优先级的进程怎么会有相同的运行时间呢?要明白的是vruntime的值是经过处理的(normalized or weighted),它并不代表实际的运行时间。CFS算法的核心就是每次选择vruntime值最小的进程来运行,它利用红黑树结构(rbtree)来管理所有可运行的进程列表,树中节点的key值就是进程的vruntime值,而最小key值的进程位于树中最左边的叶子上。vruntime会被不间断地更新,update_curr函数被调用来完成这项任务,而update_curr会被系统timer调用或者当某个进程任何时候状态发生变化时。
schedule()函数是进程调度的主入口。
系统调用
The existence of these interfaces and the fact that applications are not free to directly do whatever they want, is key to providing a stable system.
In Linux, system calls are the only means user-space has of interfacing with the kernel; they are the only legal entry point into the kernel other than exceptions and traps.
A meme related to interfaces in Unix is “Provide mechanism, not policy.”. In other words, Unix system calls exist to provide a special function in an abstract sense.
Linux实现系统调用的方式是利用软中断(software interrupt),在机器代码层面会利用一些特别异常指令,如MIPS的trap、syscall等指令,x86和arm也有类似的指令。在一些x86架构上,软中断的指令为int $0x80,后来提供了专门的sysenter指令。Regardless of how the system call handler is invoked, however, the important notion is that somehow user-space causes an exception or trap to enter the kernel. 然后进入内核空间执行异常Handler,这里的Handler也可以专门命名为syscall Handler。
接下来,自然要做的就是将系统调用号及调用参数传递给内核syscall Handler。x86上的做法是将调用号放入特殊的寄存器eax,其他架构类似。参数的传递可以通过寄存器,参数过多时可以使用某个寄存器指向用户空间,在那里可以获得参数。
Q1: Are you needlessly limiting the function?
Design the system call to be as general as possible.
The purpose of the system call will remain constant but its use may change.
Q2: Is the system call portable?
Provide mechanism, not policy.
内核一定会对系统调用的参数进入检查,Note kernel code must never blindly follow a pointer into user-space! 其中之一是对指针的合法性检查,指针所指向的区域必须是用户空间,且是该进程的地址空间,该空间具有相应的操作权限;另外是对合法权限的检查,例如reboot系统调用会检查当前用户是否为root用户。
中断
中断控制器可以实现多路复用功能,因为连接到CPU芯片的中断信号线往往只有一条,而外围设备却有很多。The important notion is that a specific interrupt is associated with a specific device, and the kernel knows this. The hardware then issues interrupts to get the kernel’s attention.
异常(Exception)往往又称为同步中断(synchronous interrupts),比如缺页(Page Fault)及上一节的软中断,它的处理机制和来自硬件的异步中断类似。
设备的中断处理程序(interrupt service routine,ISR)是设备驱动的一部分,设备驱动是内核中管理设备的代码。Interrupt Handler运行在一个特殊的context中,叫做interrupt context,在那里,代码不能被阻塞,代码会尽量保持小巧,并保证尽量短时间内完成任务。但当任务很重时,handler必须作出选择,Linux实现了Top Halves和Bottom Halves机制来提供这种选择。仔细思考这种机制,我们会发现它也是某种异步编程机制,繁重的任务Bottom Halves会延迟,并在合适的时候执行。
每一个中断类型都有一个中断号,且绑定零个、一个或者多个中断Handler,多个Handler可以共享某个中断线。request_irq负责注册Handler,特别注意的是它可能会睡眠或阻塞。
中断控制,便于进程间同步。local_irq_disable 和 local_irq_enable;local_irq_save 和 local_irq_store。中断屏蔽。
特别注意中断系统与进程调度间的密切关系。
内核同步机制
不管是禁止中断还是禁止内核抢占,都没有提供任何保护机制来防止来自其他处理器的并发访问。Linux 支持 SMP,因此,内核代码一般都需要获取某种锁,防止来自其他处理器对共享数据的并发访问,获取这些锁的同时也伴随着禁止本地中断。
RCU
定时器和时间管理
内核为什么需要timer interrupt? 一个Tickless的操作系统可以起到降低功耗的作用!
Jiffies记录的是自启动后tick的个数,每一秒有HZ个jiffies,注意jiffies要标记为volatile变量。