内核这东西不是一篇博客,日志能说清楚的,但是简要总结一下有利于后面的学习。。。。。。
一 内核的演变和发展
Linux是unix的一种克隆系统。它的发展依赖于五大支柱:unix系统(分时操作系统),minx系统,GNU计划,Posix标准和internet。因为从0.95以后对内核的许多改进和扩充均已为其他人士,而Linus的主要任务开始变成维护内核和选择补丁程序。
从1991的0.1版本到2003年的2.6版本,发生了飞跃性的变化,2.6能够支持那些没有硬件控制的内存管理方案的MMU-less系统(以前主要是uClinux)),添加了一套新的媒体驱动程序。
Linux的开发一直朝着支持更多的CPU,硬件体系结构和外部设备,支持更广泛的应用,提供更好的性能的方向发展。具有很多的发行版。如redhat,ubuntu,fedora等。
二 Linux2.6的新特点
虽然现在已经发展到3.14版本了(2014年),但2.6版本是linux的里程碑,实现了历史性的转折。主要体现在以下几个方面:
(1) 新的调度器:使用新的进程调度算法,在高负载的时候性能得到大幅度提升,在多处理器的时候也可以很好的扩展。
(2) 内核抢占:一个内核任务可以被抢占,从而提高实时性,增强系统的用户交互性。
(3) 改进的线程模型:线程操作速度得以提高,可以处理任意数目的线程,最大可以到20亿。
(4) 虚拟内存的变化:融合了R-MAP技术(反响映射),显著改善虚拟内存在一定程度负载下的性能。
(5) 文件系统:增加了对日志文件系统的支持,对扩展属性(可以给指定的文件在文件系统中嵌入元数据)以及posix标准访问控制的支持。
(6) 音频:新的ALSA体系结构取代了OSS,能够支持USB和MIDI设备。
(7) 总线:IDE/SCSI子系统经过大幅度的重写,能够直接通过IDE驱动程序来支持IDE CD/RW,不必再用模拟的SCSI驱动程序
(8) 电源管理:支持ACPI(高级电源配置管理界面),使得CPU在不同的负载下工作于不同的时钟频率。
(9) 联网和IPSec:删除了内置的HTTP服务器khttpd,加入了NFSv4客户机/服务器的支持,改进了IPV6.
(10) 用户界面层:改写了帧缓冲和控制台层,人机界面层增加了对几乎所有接口设备的支持。
(11) 设备驱动:增加了内存池,sysfs文件系统,内核模块从.o变为.ko。驱动模块编译方式,模块使用计数,加载和卸载都有变化
三 内核的组成
1 目录结构(仅写出难以一眼识别的目录)
(1) block:块设备驱动程序IO调度
(2) crypto:常用加密算法和散列算法,还有一些压缩和CRC校验算法
(3) ipc:进程间通信代码
(4) kernel:最核心部分,包括进程调度和定时器。平台有对应的目录。
(5) mm:内存管理代码。平台有自己的目录。
(6) scripts:内核配置的脚本文件
(7) usr:实现了用于打包和压缩的cpio等
2 内核的组成部分:
(1) 进程调度(SCHED):多个进程对CPU的访问,使得多个进程,使得他们能在CPU中微观串行,宏观并行的执行。处于系统的中心位置,其他子系统都依赖他,因为每个子系统都需要挂起或者恢复进程。驱动请求的资源不能得到满足的时候就会进入睡眠,调用其他进程,直到子集自己请求的资源被释放,才会被唤醒而进入就绪状态。睡眠可分为可被打断的睡眠和不可被打断的睡眠。设备驱动中,如果需要几个并发执行的任务,可以启动内核线程,相应的函数为pid_t kernel_thread(int(*fn)(void*),void*arg,unsigned long flags);
(2) 内存管理(MM):控制多个进程安全的共享主内存区域。一般而言,每个进程享有4GB的内存空间,0—3GB是用户空间,3—4GB是内核空间,内核空间对常规内存,IO设备内存,以及高端内存存在不同的处理方式。
(3) 虚拟文件系统(VFS):隐藏了各种硬件的具体细节,为所有的设备提供了统一的接口。,且独立于各个具体的文件系统。用超级块(super block)存放文件系统相关信息,用索引节点(inod)存放文件的物理量,使用目录项(dentry)存放文件的逻辑信息。
(4) 网络接口(NET):提供了对各种网络标准的存取和各种网络硬件的支持。分为网络协议和网络驱动程序。网络协议负责实现每一种可能的网络传输协议,驱动程序负责与硬件设备通信。
(5) 进程间通信(IPC)包含信号量,共享内存,管道等通信方式,这些机制可以协助多个进程,多个资源的互斥访问,进程间的同步和消息传递
(6) 依赖关系:
-----SCHED和MM:互相依赖。在多道程序环境下,程序要运行必须为之创建进程,而创建进程的第一步,就是将数据和程序装入内存。
-----IPC和MM:IPC子系统要依赖MM子系统提供共享内存通信机制,这种机制允许两个进程除了拥有自己的私有空间,还可以存取共同的内存区域。
-----VFS和NET:VFS利用NET支持NFS,也利用MM支持RAMDISK设备。
----MM和VFS:MM利用VFS支持交换,交换进程(swap)定期由调度程序调度,这也是内存管理依赖于SCHED的唯一原因。当一个进程存取的内存映射被换出的时候,MM向文件系统发出请求,同时挂起当前进程。
----除此之外,他们还依赖与一些共同的资源。比如分配和释放空间的函数,打印函数,及系统提供的调试例程。
3 内核空间和用户空间
他们用来区分程序执行的不同状态,使用不同的地址空间。CPU在不同的模式有不同的功能和权限。高层程序不能使用低级功能,必须先切换到低级模式。ARM有7种工作模式,分别是USR,SVC,FIQ,IRQ,SYS,ABT,UND。Arm Linux的系统调用通过软中断SWI从用户态USR切换到内核态SVC。且linux一般只使用CPU的两个特权级,内核可以进行任何操作,而应用程序则被禁止对硬件的直接访问,以及对内存的未授权访问。
四 内核的编译和加载
1 编译
Make zImage
Make Modules
2 配置系统
由3部分组成
(1) Makefile:定义编译规则,
(2) Kconfig:给用户提供配置选择的功能
(3) 配置工具:配置命令解析器,配置用户界面。都用脚本语言编写的。
3 当运行make menuconfig的时候,配置工具会首先分析对应体系结构的kconfig文件(arch/xxx/Kconfig),这个文件中除了包含与自身体系结构相关的配置选项和配置菜单以外,还通过source语句递归的引入下一层的Kconfig,从而得到一个树形菜单配置界面。进而生成.config文件,里面记录了哪些部分将被编译入内核,哪些部分将被编译为模块。
4 内核中增加程序:
(1) 将源代码拷贝到linux源代码的相应目录
(2) 在相应目录的Kconfig中增加关于新代码对应项目的编译配置选项
(3) 在相应目录的Makefile里面增加对新代码的编译条目
5 Makefile
(1) 目标定义:用来定义哪些被编译(动态或者静态)
Obj-$(CONFIG_TEST) += test.o
(2) 多文件模块的定义
应采用模块名加-y或objs的后缀的形式来定义模块的组成文件。
Obj-$(CONFIG_TEST1) += test1.o
Test1-y := file1.o file2.o file3.o \
File4 .o file5.o
(3) 目录层次的迭代
Obj-$(CONFIG_TEST2) += test2/
6 Kconfig
(1) 菜单入口
--config关键字定义新的配置选项。之后的几行定义了相关属性
---指定类型:bool string hex int tristate类型定义后可以紧跟输入提示
---输入提示:prompt <prompt> [if <expr>]if用来表示依赖关系
---默认值:default <expr> [if <expr>]一个配置选项可以定义多个默认值,这种情况下,只有第一个被定义的只是可用的
---依赖关系:depends on <expr>或者requires <expr>如果有多重依赖关系,他们之间用&&间隔,依赖关系也可以用到该菜单中所有的其他选项。即如果depends出现在一个菜单的开头则,改菜单的所有子菜单都依赖于相应的选项,否则就是单独某一个子菜单依赖于该选项
---选择关系:select <symbol> [if <expr>]A如果选了B,则在A被选中的情况下,B自动被选中
---expr由symbol,两个symbol相等,两个sysmbol不相等,以及expr的赋值,yuhuofei运算构成。
---symbol分为由菜单入口定义配置选项定义的非常熟symbol,和作为expr组成部分的常数symbol
---数据范围:range <symbol> <symbol> [if <expr>]
---帮助信息:help 或者---help---
开始
。。。。。。。。。。。。。。。。。。
结束
(2) 菜单结构
Menu “xxxxxxxxxxxxxxxxxxxxxxxxxxx”
[depends on xxxx]
Config xxxxxxxx
…………………………………..
End menu
Menu后面的字符串仅仅是一个提示信息,无对应的真实的配置选项,也不具备三种状态,与config是不同的。
(3) 还可以分析依赖关系来生成菜单结构。以及choice。。。。。。。。。。。。。endchoice的结构
Kconfig通过source来引用底层目录的Kconfig,为了使自己的Kconfig起作用,应该在arch/arm/Kconfig里面用source语句来调用自己的Kconfig。
Makefile则通过obj表达式来引用底层的makefile
五 bootloader
---可以在系统上电或者复位的时候以某种方式,(这些方式包括被BIOS引导执行,直接在Norflash,或者nandflash中)直接读出代码,并通过MCU自动拷入内部或者外部RAM执行)
---将各种存储介质中的操作系统加载到RAM,并把控制权交给操作系统源代码执行。
完成上述功能的代码或者微系统就叫做bootloader,版本很多种,常见的有LILO,GRUB,以及u_boot等。
六 内启动简易流程
内核映像文件有一部分是未压缩的加压缩代码,嵌在zImage的开头部分。
1. 内核映像被加载到RAM,bootloader释放控制权。
2. 从arch/xxxx/boot/head.s的start汇编例程开始执行一些基本的硬件设置。
3. 调用arch/xxxx/boot/compressed/head.s的start_up例程,设置一些基本的运行环境。设置堆栈,清除BSS段,调用解压缩函数解压内核。
4. 内核被解压到内存中以后,调用arch/xxxx/kernel/head.s中的startup_32例程,(即清除程序或者称之为进程0),启用内存分页机制。
5. 为任何可选的浮点单元(FPU)检测CPU类型,并将其存储起来以后使用。
6. 调用init/main.c里的start_kernel函数,进入与体系结构无关的linux内核部分
Start_kernel会调用一系列初始化函数来设置中断,进一步的分配内存。
7. arch/xxxx/kernel/process.c里的kernel_thread被调用,启动第一个核心线程。
8. 第一个核心线程执行init()函数,原执行序列调用cpu_idle()等待调度。
Init()完成外设和驱动程序的加载和初始化,挂接根文件系统。具体表现为:
Init打开/dev/console设备,重定向到stdin stdout stderr到控制台,之后他搜索文件系统中的init程序,并用execve()系统调用执行init程序。而搜索init程序的默认顺序为/sbin/init /bin/init /etc/init /bin/sh 一般情况下,由命令行参数指定init程序。
9. 进入根文件系统,init()对应的线程(由start_kernel()创建的线程)进入用户模式,执行应用层的程序。内核启动完毕。