介绍linux内核
linux内核是操作系统的核心
1
内核提供的服务大致分为
- 1 响应中断的中断处理程序
- 2 管理多个进程分享处理器(cpu)时间片的调度程序
- 3 负责进程地址管理的内存管理程序
- 4 负责网络的程序
- 5 负责进程间通信的服务程序
1.1
下面介绍系统中的应用程序与内核通信
如图所示,用户进程所在的用户空间是无法访问内核空间的资源
用户进程访问内核空间需要操作系统定义的系统调用函数来访问
通常流程
用户空间 -> c库 -> 系统调用 -> 内核空间
这种情况,应用进程执行系统调用,这时切换到内核态来执行相关操作,内核又运行在进程上下文,这种交互关系你可以理解为,应用程序陷入内核态
1.2
内核另一大特点,处理硬件中断
内核处于运行状态,硬件发起中断,处理器就要响应中断,打断当前内核的运行,让内核去根据当前中断号找到合适的中断处理程序 处理中断
注意
中断处理程序有自己的处理上下文,和进程上下文没有关系,这么做,好处理解为及时响应硬件中断
这里的上下文 你可以理解为内核活动范围
该图描述了用户空间应用程序,内核空间 以及硬件的结构
- 1 运行于用户空间 执行应用程序
- 2 运行在内核空间 其一处于进程上下文
- 3 运行在内核空间 其二处于中断上下文,不属于任何一个进程
而当cpu空闲时,内核会运行在一个空进程,他是属于内核的
2 unix与linux的区别
引出MMU这个概念,它由硬件系统提供,用于加强对内存的保护,保证每个进程运行在不同的虚地址空间
这里引出了单内核和微内核的概念
1 单内核
你可以理解为单内核的话,整体运行在一个大过程里,内核的所有服务都在一个大内核地址运行,当然 这样服务间的通信,就不会存在跨进程,服务之间的函数调用也会简单高效
大多数的unix系统都是单内核的2 微内核
微内核的话它的定义是部分需要强烈请求特权的服务才会处于内核中,其余的服务处于用户空间,而且对于不同服务分配了不同的地址空间,服务间的通信就涉及到跨进程(IPC),就涉及了进程由用户空间陷入内核态,这样是存在耗时
目前大部分的厂商实际应用的微内核系统,还是会将大部分服务放入内核中,以减少不必要的性能消耗
由上述俩种内核设计引出linux内核的设计概念
linux系统继承了微内核的特点,设计上其引以为傲的模块化设计,抢占式内核,支持内核线程,动态装载内核模块的能力,同时又避免了微内核性能差的问题,将服务放入内核的地址空间以提高性能
3 linux内核的特点
- 1 动态加载内核模块,你可以理解为在linux系统运行中可以动态的添加功能或者删除功能
- 2 linux支持SMP多处理机制
- 3 linux内核支持抢占的操作,高优先级请求打断低优先级任务执行
- 4 支持线程操作
- 5 针对嵌入式设备驱动,提供面向对象的设备模型,热插拔事件,用户设备文件系统sysfs
linux系统本身就是开放的,那么内核中好的设计就会不断开发完善,同时也会有面对实际场景提出的更好的设计解决方案加入其中,同时linux内核中不好的或者没有意义的,就会慢慢被遗弃
4 内核开发的特点
相对于用户空间的应用程序的开发,内核的开发存在着一些差异
- 1 内核编程时,不能访问调用c库和标准的c头文件
原因 c库的占比对于内核来说太大了,影响内核的启动速度,还有代码树的大小
解决方案,在内核中对常用的一些c库函数,内核的include头文件夹都对其进行了实现,可以保证我们在linux内核直接使用linux内部定义的头文件
- 2 内核的编程必须使用GNU C
首先要了解,GNU 他是一个操作系统,它内部的编译工具集是gcc,它包含了多种语言集的编译,可以帮助我们编译linux内核和C语言的一些扩展代码
接下来看下linux内核中设计到的c语言的一些扩展
- 2.1 内联函数
什么是内联函数
inline 关键字修饰函数
他在系统编译调用时对于inline修饰的函数,直接将其替换为函数体执行,减少正常系统函数调用的开销,所以对于简单且经常使用的函数我们可以使用内联函数
- 2.2 内联汇编
我们很清楚,所有的代码在给到计算机执行都是需要将高级语言经过编译解析成为
汇编指令,linux内核是支持混合使用c语言和汇编语言的
什么时候会考虑到汇编语言
首先 linux内核是可以和硬件交互的,那么只要知道其体系结构,我们就可以使用asm函数嵌入汇编指令给到具体硬件
优势
在偏向底层的体系结构或者对执行时长比较严格的地方会考虑
- 2.3 分支声明
对于条件选择的语句,gcc提供了一套指令可以在一定情况下帮助我们提升性能
他们在c中被封装成了宏 likely或unlikely 内核中内存错误会导致oops
什么时候使用
主要是一些特殊的判定情况,且绝大多数时都满足成立,我们才考虑使用
likely用于提示条件表达式为真的可能性更高,而unlikely用于提示条件表达式为假的可能性更高。这样的提示有助于编译器在生成汇编代码时更好地优化分支预测,提高程序的性能。
- 3 没有内存保护机制
理解 用户空间的进程在访问无效内存时,内核会发出SIGSEGV信号,并且结束这个进程,但是内核在访问无效内存时,或者对内存进行非法访问,是会直接死掉,并且你是不知道的,内核中内存错误会通常导致oops
在Linux系统中,内核Oops信息通常会被记录在系统日志中的/var/log/messages或/var/log/syslog等文件中。这些文件包含了系统运行时的各种日志信息,包括内核打印的错误消息、警告和调试信息。
另外,有一些专门的内核日志文件也可能包含Oops信息,例如/var/log/kern.log。这些文件通常用于记录内核相关的信息,包括Oops、内核模块加载信息等。
如果系统发生了Oops,你可以查看这些日志文件,尝试找到包含Oops信息的记录。通过查看这些记录,你可以获取更详细的Oops信息,包括调用栈轨迹、错误原因等,有助于进行故障诊断和解决。
总之,要查看Oops的详细信息,你可以查看系统日志文件,特别是/var/log/messages、/var/log/syslog和/var/log/kern.log等文件
4 在内核中尽量不要使用浮点数
用户空间 整数到浮点数的切换,可以陷入内核态通过对应体系结构,发送对应的指令,但是内核是无法陷入,需要人工保存和浮点寄存器,所以尽量不要使用浮点数5 容积小而固定的栈
用户空间的栈空间是巨大的同时可以递增,但是内核空间的栈空间只能根据不同体系结构分配对应的栈空间6 同步和并发
可以理解为和用户空间的多线程类似,同一时间存在大量线程访问相同的资源,那么如果不能对线程进行合理管控,就会导致程序出错,内核也是如此,linux是抢占式的多任务操作系统,所以我们可以通过自旋锁和信号量,来保证被同时访问的资源7 可移植性
linux系统是可以直接在不同体系结构的设备上编译运行的,所以我们只需要将内核代码树中跟体系结构相关的代码剥离出来就可以。
5 附录
本文主要参考Linux内核设计与实现写的读书笔记