linux kernel 基础知识总结

1, 虚拟地址、物理地址、总线地址区别

虚拟地址是由程序产生的由段选择符和段内偏移地址组成的地址。这两部分组成的地址并没有直接访问物理内存,而是要通过分段地址的变换处理后才会对应到相应的物理内存地址。

逻辑地址指由程序产生的段内偏移地址。有时把逻辑地址当成虚拟地址,两者并没有明确的界限。

线性地址是指虚拟地址到物理地址变换的中间层, 是处理器可寻址的内存空间(称为线性地址空间)中的地址。程序代码会产生逻辑地址,或者说段中的偏移地址,加上相应段基址就生成了一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换产生物理地址。若是没有采用分页机制,那么线性地址就是物理地址

物理地址是指现在 CPU 外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果。

虚拟地址到物理地址的转换方法是体系结构相关的,一般分段与分页两种方式。以X86CPU为例:

分段分页都是支持的。内存管理单元负责从虚拟地址到物理地址的转化。逻辑地址是段标识+段内偏移 MMU(内存管理单元) 通过查询段表,可以把逻辑地址转换为线性地址。

如果CPU没有开启分页功能,线性地址就是物理地址;如果CPU开启了分页功能,MMU还需要查询业表来将线性地址转换为物理地址;

逻辑地址(段表)--------> 线性地址(页表)------------->物理地址。

映射是一种多对一的关系,即不同的逻辑地址可以映射到同一个线性地址上;

不同的线性地址也可以映射到同一个物理地址上。而且同一个线性地址在发生变化后,也可能被重新装载到另外一个物理地址上,所以这种多对一的关系也会随时间发生变化。

2,ARM/ARM64/X86的分页机制

64位系统使用三级还是四级分页取决于硬件对线性地址的位的划分。

https://blog.csdn.net/kickxxx/article/details/8622450/

https://blog.csdn.net/yyt7529/article/details/4325822

3,linux 页属性;

进程管理

进程、线程、进程组

根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

进程组

就是进程组,进程组就是多个进程的集合,其中肯定有一个组长,其进程PID等于进程组的PGID

Virtual Memory Areas(VMA)

内核栈:

进程用户栈和内核栈的切换: 当进程由于中断或系统调用从用户态转换为内核态时,进程所使用的栈也要从用户栈切换到内核栈。系统调用实质就是通过指令产生中断(软中断)。进程由于中断而陷入到内核态,进程进入内核态之后,首先把用户态的堆栈地址保存在内核态堆栈中,然后设置堆栈寄存器地址为内核栈地址,这样就从用户栈转换成内核栈。

  内核在创建进程的时候,在创建task_struct的同时会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存 在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内 容是内核栈空间地址,使用内核栈。

  内核为每个进程分配task_struct结构体的时候,实际上分配两个连续的物理页面,底部用作task_struct结构体,结构上面的用作堆栈。使用current()宏能够访问当前正在运行的进程描述符。

 注意:这个时候task_struct结构是在内核栈里面的,内核栈的实际能用大小大概有7K。

 


Linux进程的状态
在Linux系统中,一个进程被创建之后,在系统中可以有下面5种状态。进程的当前状态记录在进程控制块的state成员中。

就绪状态和运行状态
就绪状态的状态标志state的值为TASK_RUNNING。此时,程序已被挂入运行队列,处于准备运行状态。一旦获得处理器使用权,即可进入运行状态。

当进程获得处理器而运行时 ,state的值仍然为TASK_RUNNING,并不发生改变;但Linux会把一个专门用来指向当前运行任务的指针current指向它,以表示它是一个正在运行的进程。

可中断等待状态
状态标志state的值为TASK_INTERRUPTIBL。此时,由于进程未获得它所申请的资源而处在等待状态。一旦资源有效或者有唤醒信号,进程会立即结束等待而进入就绪状态。

不可中断等待状态
状态标志state的值为TASK_UNINTERRUPTIBL。此时,进程也处于等待资源状态。一旦资源有效,进程会立即进入就绪状态。这个等待状态与可中断等待状态的区别在于:处于TASK_UNINTERRUPTIBL状态的进程不能被信号量或者中断所唤醒,只有当它申请的资源有效时才能被唤醒。

这个状态被应用在内核中某些场景中,比如当进程需要对磁盘进行读写,而此刻正在DMA中进行着数据到内存的拷贝,如果这时进程休眠被打断(比如强制退出信号)那么很可能会出现问题,所以这时进程就会处于不可被打断的状态下。

停止状态
状态标志state的值为TASK_STOPPED。当进程收到一个SIGSTOP信号后,就由运行状态进入停止状态,当受到一个SIGCONT信号时,又会恢复运行状态。这种状态主要用于程序的调试,又被叫做“暂停状态”、“挂起状态”。

中止状态
状态标志state的值为TASK_DEAD。进程因某种原因而中止运行,进程占有的所有资源将被回收,除了task_struct结构(以及少数资源)以外,并且系统对它不再予以理睬,所以这种状态也叫做“僵死状态”,进程成为僵尸进程。

在进程的整个生命周期中,它可在5种状态之间转换。Linux进程5种状态之间的转换关系如下图所示:

 

中断和异常:

中断是指 CPU 对系统发生某事件时的这样一种响应:

CPU 暂停正在执行的程序,在保留现场后自动地转去执行该事件的中断处理程序;执行完后,再返回到原程序的断点处继续执行。

 

引入原因

中断的引入——为了支持CPU和设备之间的并行操作

异常的引入——表示CPU执行指令时本身出现的问题

 

引发中断或异常的事件

  • 中断——外部事件引起正在运行的程序所不期望的
  • 异常——内部执行指令引起

 

 

Linux 对异常的处理

所有异常处理程序的结构是一致的,都可划分成以下三个部分:

  • 准备阶段

    内核栈保存通用寄存器内容(称为现场信息),这部分大多用汇编语言程序实现

  • 处理阶段

    采用 C 函数进行具体处理。函数名由 do_ 前缀和处理程序名组成,如 do_overflow 为溢出处理函数。大部分函数的处理方式:保存硬件出错码(如果有的话)和异常类型号,然后,向当前进程发送一个信号。

    当前进程接受到信号后,若有对应信号处理程序,则转信号处理程序执行;若没有,则调用内核 abort 例 程,终止当前进程

  • 恢复阶段

    恢复保存在内核栈中的各个寄存器的内容,切换到用户态并返回到当前进程的断点处继续执行

再总结一下:Linux 对中断的处理

PIC 需对所有外设来的 IRQ 请求按优先级排队,若至少有一个 IRQ 线有请求且未被屏蔽,则 PIC 向 CPU 的 INTR 引脚发中断请求

CPU 每执行完一条指令都会查询 INTR,若发现有中断请求,则进入中断响应过程(关中断、保护断点和现场、发中断查询信号),调出中断服务程序执行。

所有中断服务程序的结构类似,都划分为以下三个阶段:

  • 准备阶段

    在内核栈中保存各通用寄存器的内容(称为现场信息)以及所请求 IRQi 的值等,并给 PIC 回送应答信息,允许其发送新的中断请求信号

  • 处理阶段

    执行 IRQi 对应的中断服务例程 ISR (Interrupt Server Routine)。中断类型号为 32 + i

  • 恢复阶段

    恢复保存在内核栈中的各个寄存器的内容,切换到用户态并返回到当前进程的逻辑控制流的断点处继续执行

https://blog.csdn.net/godleading/article/details/52971179

https://blog.csdn.net/ezimu/article/details/54851148

软中断
软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:

产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(即单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)。
可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保其数据结构。

 

tasklet
由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:
a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
b)多个不同类型的tasklet可以并行在多个CPU上。
c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。也就是说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行。

工作队列
从上面的介绍看以看出,软中断运行在中断上下文中,因此不能阻塞和睡眠,而tasklet使用软中断实现,当然也不能阻塞和睡眠。但如果某延迟处理函数需要睡眠或者阻塞呢?没关系工作队列就可以如您所愿了。
把推后执行的任务叫做工作(work),描述它的数据结构为work_struct ,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct ,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events。
工作队列(work queue)是另外一种将工作推后执行的形式。工作队列可以把工作推后,交由一个内核线程去执行—这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。
通常,在工作队列和软中断/tasklet中作出选择非常容易。可使用以下规则:
- 如果推后执行的任务需要睡眠,那么只能选择工作队列。
- 如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时(内核定时器实现)。
- 如果推后执行的任务需要在一个tick之内处理,则使用软中断或tasklet,因为其可以抢占普通进程和内核线程,同时不可睡眠。
- 如果推后执行的任务对延迟的时间没有任何要求,则使用工作队列,此时通常为无关紧要的任务。
实际上,工作队列的本质就是将工作交给内核线程处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高,而工作队列实现了内核线程的封装,不易出错,所以我们也推荐使用工作队列。

等待队列(waitqueue)
linux驱动中,阻塞一般就是用等待队列来实现,将进程停止在此处并睡眠下,直到条件满足时,才可通过此处,继续运行。在睡眠等待期间,wake up时,唤起来检查条件,条件满足解除阻塞,不满足继续睡下去;

内核的抢占:

https://blog.csdn.net/gatieme/article/details/51872618

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值