1.1 内核的任务
- 增强的计算机
- 资源管理程序
- 库
1.2 实现策略
- 微内核:其他功能在独立进程中实现,通过通信接口与中心内核通信。开销大,实用性低。
- 宏内核:构建系统内核的传统方法。通过模块可以实现热拔插,兼顾效率和模块化。
1.3 内核组成部分
1.3.1 进程、进程切换、调度
传统上,UNIX操作系统下运行的应用程序、服务器及其他程序都称为进程。每个进程都在CPU
的虚拟内存中分配地址空间。各个进程的地址空间是完全独立的,因此进程并不会意识到彼此的存在
内核要做两件事:进程切换,调度(为每个进程分配CPU时间)
1.3.2 unix进程
内核启动init程序作为第一个进程,该进程负责进一步的系统初始化操作,并显示登录提示符或图形登录界面。init是进程树的根。
创建进程
- fork:创建当前进程的副本。linux使用写时复制提高效率
- exec:将新程序加载到当前进程的内存中执行
线程 (轻量级进程)
线程和主程序共享同样的地址空间
Linux用clone方法创建线程 (启用了精确的检查,以确认哪些资源 与父进程共享、哪些资源为线程独立创建)
命名空间
不同的进程可以看到不同的系统视图。
每个命名空间可以包含一个特定的PID集合,或可以提供文件系统的不同视图,在某个命名空间中挂载的卷不会传播到其他命名空间中。
也就是容器 – 比虚拟机开销低,因为可以用一个内核来管理多个容器。
1.3.3 地址空间与特权级别
IA-32:3GB用户空间,1GB内核空间
每个用户进程都认为自身有3 GiB内存, 而虚拟地址空间顶部的内核空间总是同样的,无论当前执行的是哪个进程。
64位:理论可以使用全部64位作为虚拟地址,实际一般为42位或47位。虚拟地址空间会包含一些不可寻址的洞。
特权级别:因特尔CPU支持4个级别,而linux只有2级:核心态和用户态。用户态禁止访问内核空间。通过系统调用、异步硬件终端切换到核心态。在中断处理程序(中断上下文)中内核不能访问用户空间部分。中断上下文也不能睡眠。
内核线程:除了普通进程,系统中还有内核线程在运行。不与任何特定的用户空间进程相关联,也无权处理用户空间。
在多处理器系统上,许多线程启动时指定了CPU,并限制只能在某个特定的CPU上运行。
虚拟和物理地址空间
内核和CPU必须考虑如何将实际可用的物理内存映射到虚拟地址空间的区域
页表:为物理地址分配虚拟地址。可以决定哪些内存区域在进程之间共享,哪些不共享。
多级分页:节省页表使用的内存。将虚拟地址划分为多个部分。linux在64位上使用4级页表(分成5段),IA-32上为2级页表。
缺点:每次访问内存时,必须逐级访问。可使用CPU中的MMU(内存管理单元)来加速页表访问,还有TLB(地址转换后备缓冲器,CPU高速缓存)加速查询。、
内存映射:将任意来源的数据传输到进程的虚拟地址空间中。可以像普通内存那样用通常的方法访问。但任何修改都会自动传输到原数据源。
1.3.5 物理内存的分配
内核可以只分配完整的页帧。将内存划分为更小的部分的工作,则委托给用户空间中的标准库。
- 伙伴系统(buddy system)
内存经常需要分配连续页来提高效率
伙伴系统把连续的页帧分为1,2,4,8,16…大小的连续快,如果请求2k大小的没有,就去找一块2k+1的分裂。在释放时如果两块相同大小的可以合并,则合并。
通过伙伴系统可以在某种程度上减少碎片化,但无法完全消除。 - slab缓存
用于内核中分配小的内存块
kmalloc,kfree
依赖于伙伴系统分配页
- 页面交换和页面回收
页面交换通过利用磁盘空间作为扩展内存
缺页异常机制:换出的也通过特别的页表项标识,访问缺页的时候CPU调用内核处理异常,内核把页从硬盘上加载到内存后继续执行程序。对程序完全透明。
页面回收用于将内存映射被修改的内容与底层的块设备同步,为此有时也简称为数据回写。
1.3.6 计时
jiffies:时间坐标,值美标递增100~1000次(与架构有关)
如硬件支持,还能支持到纳秒级
1.3.7 系统调用
用户请求内核去完成一些事情。
在发出系统调用时,处理器必须改变特权级别,从用户状态切换到核心态。
1.3.8 设备驱动、块设备和字符设备
万物皆文件
外设可以通过/dev目录访问
字符设备:连续的数据流,一般不支持随机访问
块设备:随机访问,数据的读写只能以块为单位(通常是512B)
1.3.9 网络
网卡不能利用设备文件访问(特例),因为内核必须针对各协议层的处理,对数据进行拆包与分析,然后才能将有效数据传递给应用程序
1.3.10 文件系统
文件系统使用目录结构组织存储的数据,并将其他元信息(例如所有者、访问权限等)与实际数据关联起来。
Ext2基于inode,即它对每个文件都构造了一个单独的管理结构,称为inode,并存储到磁盘上。inode包含了文件所有的元信息,以及指向相关数据块的指针。
目录可以表示为普通文件,其数据包括了指向目录下所有文件的inode的指针,因而层次结构得
以建立。
内核必须提供一个额外的软件层,将各种底层文件系统的具体特性与应用层(和内核自身)隔离
开来。该软件层称为VFS。
1.3.11 模块和热插拔
模块用于在运行时动态地向内核添加功能,如设备驱动程序、文件系统、网络协议等
模块在本质上不过是普通的程序,只是在内核空间而不是用户空间执行而已。
模块可以访问内核中所有的函数和数据
1.3.12 缓存
内核使用缓存来改进系统性能,把数据暂时保存在内存中,减少访问块设备的次数。linux只使用页缓存。(相对的还有块缓存,linux已不用)
1.3.13 链表处理
linux双链表
struct list_head {
struct list_head *next, *prev;
};
可以把它放到任何别的struct中。如果一个struct需放入多个list,则需要定义多个list_head。
接口
list_add(new, head)
list_add_tail(new, head)
list_del(entry)
list_empty(head) //test empty
list_splice(list, head) //concat list after head
list_entry(list_head_ptr, type, member) //given an entry, return the struct it is embedded
list_for_each(pos, head) //iterate over list
1.3.14 对象管理和引用计数
一般性的内核对象机制可用于执行下列对象操作:
- 引用计数
- 管理对象链表(集合)
- 集合加锁
- 将对象属性导出到用户空间(通过sysfs文件系统)
内核对象可以通过嵌入一个kobject对象来实现上述功能
struct kobject {
const char *k_name; // object name
struct kref kref; //ref counter (an atomic counter)
struct list_head entry;
struct kobject *parent;
struct kset *kset; //the set which the object belongs to
struct kobj_type *ktype; //type infos, including destructor
struct sysfs_dirent *sd; //reference to sysfs
};
struct kset {
struct kobj_type *ktype; //type of the tiems
struct list_head list; //items in this set
...
struct kobject kobj; //kset is also an kernel object
struct kset_uevent_ops *uevent_ops;
1.3.15 数据类型
- 内核使用typedef来定义各种数据类型,以避免依赖于体系结构相关的特性。
- 大端序和小端序:linux必须两种都支持
- per-cpu变量
通过 DEFINE_PER_CPU(name, type)声明
通过get_cpu(name, cpu)访问,其中cpu通过smp_processor_id()得到 - 访问用户空间
在没有进一步预防措施的情况下,不能轻易访问这些指针指向的区域间。内核需要确保指针所指向的页帧确实存在于物理内存中。
参考
[1] 深入linux内核架构第一章