linux内核

linux内核

内核通过文件的方式暴露自身的接口,其实这些文件并不存在

系统调用

为应用程序提供系统服务,只能由操作系统内核来执行,应用程序作为调用方,必须执行一个陷阱或系统调用指令将控制权转移到操作系统,操作系统通过参数检查,找出所需要的调用进程

系统调用的参数逆序压栈,将系统调用编号放入特定寄存器中,然后通过汇编执行TRAP指令陷入,将用户态切换到内核态,开始执行内核代码,内核会开始检查调用编号并发出正确的系统调用处理命令,编号会跳转到系统调用处理器的指针表来完成,通过句柄执行,句柄执行完后,将控制返回给用户空间的库过程,从用户的角度来说这与函数调用的过程几乎无异,清理堆栈继续执行

进程调度算法

在linux调度算法中,将进程分为两种类型,即:I/O消耗型和CPU消耗型 因此为了提高响应速度,I/O消耗程序应该有较高的优先级,才能提高它的交互性

对于普通进程,Linux采用动态优先调度,选择进程的依据就是进程counter的大小,表明可供使用时间片的次数 对于实时进程,Linux采用了两种调度策略,即FIFO(先来先服务调度)和RR(时间片轮转调度)

内核与进程通信

  1. 用户上下文环境
  2. 软硬中断
  3. netlink 套接字,用户创建套接字并将进程 ID 发送给内核

中断

因为操作系统还需要管理硬件,因此通过中断的方式来处理硬件的请求 中断可分为四类:中断、故障、陷阱、终止 中断是来自IO设备的异步请求

锁机制

  • 原子操作:原子操作比普通操作效率要低,因此必要时才使用,且不能与普通操作混合使用,如果是单核处理器,则原子操作与普通操作相同
  • 自旋锁:等待解锁的进程将反复检查锁是否释放,而不会进入睡眠状态(忙等待),所以常用于短期保护某段代码,X86里面通过汇编指令锁总线的方式来实现
  • 信号量与互斥量:竞争信号量与互斥量时需要进行进程睡眠和唤醒,代价较高,所以不适于短期代码保护,适用于保护较长的临界区,互斥量用于线程的互斥,信号线用于线程的同步

CAS

本质上是乐观的,如果长时间不进行操作会浪费 CPU 时间 可以用double CAS,双保险CAS解决ABA问题,例如在32位系统上,我们要检查64位长的内容

一次用CAS检查双倍长度的值,前半部是指针(也可以是版本号),后半部分是一个计数器 只有这两个都一样,才算通过检查,要赋新的值。并把计数器累加1 这样一来,ABA发生时,虽然值一样,但是计数器就不一样(但是在32位的系统上,这个计数器会溢出回来又从1开始的,这还是会有ABA的问题)

定时任务 crontab

早期的定时任务是每分钟检查一次,但是这样太浪费资源 维护一个小顶堆,堆顶是最先执行的任务,可以休眠直到需要执行任务的时候再唤醒执行

进程休眠

sleep() 流程如下:

  1. 挂起进程(或线程)并修改其运行状态
  2. 用sleep()提供的参数来设置一个定时器
  3. 当时间结束,定时器会触发,内核收到中断后修改进程(或线程)的运行状态。例如线程会被标志为就绪而进入就绪队列等待调度

pause() 休眠直到进程收到信号

内核线程

由于在内核中进程和线程不做区分,因此也可以将其称为内核进程。毫无疑问,内核线程在内核中也是通过task_struct结构来表示的 内核线程和普通进程一样也是内核调度的实体,只不过他们有以下不同:

内核线程永远都运行在内核态,而不同进程既可以运行在用户态也可以运行在内核态。从另一个角度讲,内核线程只能之用大于PAGE_OFFSET(即3GB)的地址空间,而普通进程则可以使用整个4GB的地址空间。

内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,mm指针被设置为NULL;它只在 内核空间运行,从来不切换到用户空间去;并且和普通进程一样,可以被调度,也可以被抢占