LINUX 内核设计于实现 阅读记录(2025.01.14)

news/2025/1/20 1:30:31/

文章目录

  • 一、内核历史
    • 1、内核简介
    • 2、LINUX 内核与 UNIX 内核比较
    • 3、LINUX内核版本命名
  • 二、从内核出发
    • 1、获取内核源码
      • (1)查看Linux内核版本 uname -r
      • (2)下载源码 https://www.kernel.org/
      • (3)编译内核
    • 2、内核源码树
    • 3、编译源码
    • 4、内核开发的特点
      • (1)GNU C 和 ANSI C
      • (2)内核没有内存保护机制,而且不分页
      • (3)最好不要在内核中使用浮点数
      • (4)内核程序的栈空间比较小,在编译时设置
      • (5)内核支持抢占式调度
      • (6)内核应该又高度的可移植性
    • 5、C语言扩展的特性
      • (1)内联函数 inline
      • (2)内联汇编
      • (3)分支声明
  • 三、进程管理
  • 四、进程调度
    • 1、多任务
    • 2、LINUX的进程调度
    • 3、策略
    • 4、LINUX调度算法
      • (1)调度器类
      • (2)CFS
    • 5、调度的实现
      • (1)时间记账
      • (2)进程选择
      • (3)调度器入口
      • (4)进程的休眠
      • (5)抢占和上下文切换
      • (6)实时调度策略
      • (7)调度器相关的系统调用
  • 五、系统调用
  • 问题记录
    • 1、用户进程和内核进程有什么区别?用户进程可以被直接调度吗?是否也存在与用户进程对应绑定的内核进程
    • 2、实时进程和普通进程有什么区别?如何创建一个实时进程
    • 3、如何理解接口设计--->提供机制而不是策略?
    • 4、cortex-m3/4 执行一个函数时 他的寄存器中是啥内容?

一、内核历史

1、内核简介

LINUX内核是一个大的完整的C程序,提供可热插拔的模块。

2、LINUX 内核与 UNIX 内核比较

3、LINUX内核版本命名

主版本号 次版本号 修订版本号

二、从内核出发

1、获取内核源码

(1)查看Linux内核版本 uname -r

在这里插入图片描述

(2)下载源码 https://www.kernel.org/

(3)编译内核

解压 xz 包

xz -d linux-5.15.176.tar.xz

解压 tar 包

tar xvf linux-5.15.176.tar

依赖库下载

sudo apt install make gcc libncurses-dev flex bison libssl-dev libelf-dev

清理源码

make mrproper

清理目标文件

make clean

配置内核选项
编译、安装

2、内核源码树

3、编译源码

4、内核开发的特点

(1)GNU C 和 ANSI C

(2)内核没有内存保护机制,而且不分页

(3)最好不要在内核中使用浮点数

(4)内核程序的栈空间比较小,在编译时设置

(5)内核支持抢占式调度

(6)内核应该又高度的可移植性

LINUX作为一个可移植的操作系统,其实现代码大部分应该是于体系结构无关的,于体系结构相关的代码应该分离出来。

5、C语言扩展的特性

(1)内联函数 inline

C99 和 CNU C 均支持 内联函数,内联函数在函数调用处展开,可以消除函数调用和返回带来的开销(寄存器的恢复和存储)。但这会增加程序占用的空间和指令缓存。
一般在要求时间、简短的函数定义为nline。
语法

static inline fun(){}

需要static修饰,必须在使用之前定义好否则无法展开,内核中优先使用内联函数而不是宏定义

(2)内联汇编

gcc支持汇编代码,用 asm声明

(3)分支声明

gcc 将分支声明封装成宏 ==》likely() unlikely(),表明一个分支是经常出现还是极少出现。
在这里插入图片描述

三、进程管理

  1. 进程 (任务) 是处于执行期间的程序、是正在执行的程序的实时结果。
  2. 线程是在进程中活动的对象,线程拥有独立的计数器、栈、和寄存器。
  3. 内核调度的线程而不是进程 Linux对线程进程并不特别区分,线程是特殊的进程(轻量进程)。
  4. 进程提供两种虚拟机制:虚拟处理器和虚拟内存,虚拟处理器使得进程感觉自己独享处理器,虚拟内存使进程感觉自己独享整个内存。
  5. 线程共享进程的虚拟空间,但是线程有自己的虚拟处理器线程1v1模型的原理)
  6. fork()系统调用从内核返回两次,一次返回父进程,一次返回主进程

1、进程描述符和任务结构

(1)进程描述符:task_struct

结构体描述符存放在 /linux/sched.h

(2)内核中任务的组织结构

  1. 内核把进程存放在任务队列中,这个任务队列是双向循环链表的结构。
  2. task_struct比较大,在32位机中大约为1.7KB,它完整的描述了一个进程的所有状态。
  3. 通过预先分配和重复使用task_struct避免动态分配和释放带来的资源消耗
  4. Linux 通过slab分配器来分配task_struct,达到对象复用和缓存着色
  5. 在栈顶创建一个 thread_info的结构体,该结构体中有指向tsak_struct的指针。通过计算栈偏移量来寻找
  6. 内核通过PID标识一个进程
  7. 通过current宏查找当前执行的进程进程描述符

(3)进程状态

进程描述符的state域描述了进程的当前状态。
进程有5个状态
TASK_RUNNING:运行或就绪
TASK_INTERRUPTIBLE:可中断(阻塞)
TASK_UNINTERRUPTIBLE:不可中断(阻塞),但不可被唤醒,例如sleep期间
__TASK_TRACED:被其他进程追踪
__TASK_STOPPED:进程停止,如收到STOP信号。
在这里插入图片描述
设置进程状态

set_task_state(task,state)  == set_current_state(state)

(4)进程上下文(待补充,理解不到位)

系统调用和异常是内核留给用户的接口,当系统调用时,陷入内核,内核代表进程执行,并处于进程的上下文中。
上下文:进程程序执行的环境(CPU寄存器保存的值)
上下文切换:将不通进程的内容写入寄存器并保存旧的寄存器值到前一个进程的栈中。
内核代表进程执行:内核访问内核资源,并将结果通过系统调用传递给用户进程
https://blog.csdn.net/zysharelife/article/details/7276998

(5)进程家族树

  1. 所有的进程都是PID为1 的进程(init)的后代,内核在启动的最后阶段启动init进程进程读取系统的初始化脚本
    在这里插入图片描述
  2. 进程间的关系存放在进程描述符中,通过parent指针指向父进程,通过children链表指向子进程

linuxfont_111">2、进程线程的创建(理解linux线程进程的重点)

在LINUX的内核中不分进程或者线程,在创建进程时调用clone函数,为进程分配资源,通过exec函数族加载一个程序到进程的地址空间,并采用写时拷贝来加快进程的创建速度。
在创建线程时 调用pthread_create,这个函数还是调用了clone函数。
创建进程和创建线程最终都是调用了clone函数,不同之处在于调用时的参数不通,在创建线程
在这里插入图片描述
共享父进程的内存、文件描述都、信号等
在创建进程
在这里插入图片描述
所以由此来看,线程进程没有本质上的区别,在内核中都是以task_struct标识,不同之处在于task_struct的域信息不同。
这也是linux线程也可以参加系统调度的原因。线程可以看作共享父进程一些资源的进程

在不同的系统中实现多线程的机制是不同的。
一个进程的创建分为fork和exec两步,一个进程的回收也分为资源回收和进程描述符回收两部分。

3、孤儿进程

进程先于自己终结的进程叫做孤儿进程,系统会为孤儿进程找一个同组进程或init进程作为新的父进程
这个过程为

  1. 先在同组遍历
  2. 找不到合适进程,就将父进程设置为init进程,找到则将找到的进程设置为父进程
  3. 遍历所有子进程,设置父进程

四、进程调度

进程调度程序可以看作内核的子系统,负责为可运行程序分配CPU时间。
调度器的通用概念:时间片 和 优先级

1、多任务

多任务系统分类:

  1. 非抢占式多任务 cooperative multitasking
  2. 抢占式多任务 preemptive multitasking (UNIX 使用):在分配的时间片未耗尽时打断进程的执行,去执行另一个进程

2、LINUX的进程调度

LINUX 2.5 ==>> O(1)调度程序:静态时间片算法、对于每个CPU的任务队列,但是对时间敏感的(交互进程)响应不理想。所以在服务器运行尚可,在桌面环境运行不太理想。(O1 表示时间复杂度)
LINUX 2.6 ==>> 反转楼梯最后期限调度算法 RSDL,被称为完全公平的算法 CFS。

3、策略

策略决定进程调度的时机,策略决定系统的运行效率。

(1)IO消耗型进程和CPU消耗型进程

IO消耗型进程:处理IO请求,每次运行的时间短,等待的时间长
CPU消耗型进程:多数时间在执行代码或计算数据,对于这种进程应该尽量降低它的调度频率,让其运行时间延长。
调度策略要在两者中寻求一个平衡。
LINUX 更加倾向

(2)进程优先级

LINUX采用两种范围的优先级:nice值实时优先级,两个没有关联。
nice:值范围是 -20 ~ 19 值越低优先级越高,默认0,代表可获得的时间片比例。
实时优先级:0 ~ 99 值越高优先级越高 ,实时进程的优先级高于普通进程
在这里插入图片描述

(3)时间片

时间片是一个常值,但是这个值的指定却不好确定,时间片太大,就会导致实时性降低,时间片太小就会消耗大量时间在进程的上下文切换中。
但是LINUX的CFS调度算法并不直接将时间片分配到进程,而是将CPU的使用比例分配给进程,这样不同的进程就会的到不通的CPU时间,这个比例的值收到nice值的影响。
LINUX的抢占式调度收到进程优先级和是否有时间片决定,当来了一个新的可运行进程,如果新进程的CPU使用(消耗)比例小于当前进程且优先级更高,当前进程就会被新进程抢占。
CPU使用(消耗)比例:一定时间内进程使用CPU的时间的比值。

4、LINUX调度算法

(1)调度器类

在Linux中调度器以模块的方式提供,被称为调度器类,这样做的目的是为了给内核提供多种不通算法的调度器,每种调度器管理自己范围内的进程,每次有多个可运行进程时,先比较他的调度器的优先级,优先级高的调度器先执行自己的进程
CFS只是针对普通进程的调度器。

(2)CFS

CFS允许所有进程(n个)平分所有的CPU时间 1/n,循环调度
CFS抢占时以新进程的运行时间是否小于当前进程的运行时间为判断条件。
CFS不是依靠nice值计算时间片,而是用nice值进程获得CPU时间的权重。
CFS规定了获取CPU时间比例的最小标准==>最小粒度
CFS规定目标延迟:每个可运行任务在处理器上至少运行一次所需的最短时间,即最小粒度
在CFS中进程所获得的CPU时间由它自己和其他所有的程的nice值得相对差值决定。

5、调度的实现

(1)时间记账

所有的调度器都必须为进程记录运行时间。
CFS使用调度器实体结构 struct sched_entity 来追踪进程运行记账。

struct sched_entity {/* For load-balancing: */struct load_weight      load;struct rb_node          run_node;struct list_head        group_node;unsigned int            on_rq;u64             exec_start;u64             sum_exec_runtime;u64             vruntime;u64             prev_sum_exec_runtime;u64             nr_migrations;#ifdef CONFIG_FAIR_GROUP_SCHEDint             depth;struct sched_entity     *parent;/* rq on which this entity is (to be) queued: */struct cfs_rq           *cfs_rq;/* rq "owned" by this entity/group: */struct cfs_rq           *my_q;/* cached value of my_q->h_nr_running */unsigned long           runnable_weight;#endif#ifdef CONFIG_SMP/** Per entity load average tracking.** Put into separate cache line so it does not* collide with read-mostly values above.*/struct sched_avg        avg;#endif};

struct sched_entity 作为一个se成员嵌入到 tsak_struct 中。
调度器实体的 vruntime成员以纳秒为单位记录进程运行的虚拟时间(获取cpu的时间),vruntime的更新由系统周期性的调用updata_curr来实现,根据vruntime可以准确的测量出进程的运行时间,确定下一个运行进程是哪一个。

(2)进程选择

  1. CFS选择进程时选择vruntime最小的进程,以达到公平。
  2. CFS使用红黑树 rbtree(自平衡二叉查找树) 来存储可运行的进程队列,利用红黑树迅速寻找最小的vruntime。
  3. 最小的vruntime就是最左边的叶子节点,并将该节点存储,这样每次不用查找直接获取它就可以了。
  4. 进程变为可运行状态或fork后被加入CFS的红黑树。
  5. 进程变为阻塞或终止时从CFS红黑树删除。

(3)调度器入口

linux内核调度器入口为 schedule() 函数,schedule()通常要和一个调度类关联,每个调度器有一个自己的任务队列。
在schedule内调用pick_next_task() 函数一次从优先级高到优先级低访问每个调度类,询问最高优先级的进程,最后选择最高优先级的调度类的最高优先级的进程

(4)进程的休眠

进程休眠时将自己设置为阻塞状态,并将自己移除可执行进程红黑树,并加入等待队列。TASK_INTERRUPTIBLE和
TASK_UNINTERRUPTIBLE两种阻塞函数存放在同一个等待队列上。
等待队列由一个简单的链表实现,等待队列有多种,不同的事件发生时唤醒不同的进程等待队列。

(5)抢占和上下文切换

切换上下文
进程上下文的切换调用contex_switch()函数实现,contex_switch内调用两个函数分步骤完成切花

  1. switch_mm()函数将虚拟内存从上一个进程映射到新进程
  2. switch_to() 函数负责将处理器状态从上一个进程切换到新进程,包括旧现场的保存和新现场的恢复。

抢占

  1. 内核提供一个标志need_reched ,内核通过检查need_reched 标志来表明是否需要调用schedule()切换进程
  2. need_reched 标志存放在task_strucr中(不是全局的,应为当前task_struct存放在高速缓存中,访问更快)
  3. 内核抢占和用户抢占不同

(6)实时调度策略

  1. LINUX提供两种实时调度策略SCHED_FIFO SCHED_RR,有对应的调度类
  2. 普通的线程使用 SCHED_NORMAL 调度策略(CFS)
  3. 实时策略的调度类优先级永远比SCHED_NORMAL高
  4. SCHED_FIFO策略:不基于时间片,直到显示的调用schedule或阻塞后才会让出CPU,只能被优先级更高的SCHED_FIFO打断。
  5. SCHED_RR策略:带时间片的SCHED_FIFO。时间片耗尽也会调用同优先级的SCHED_RR。
  6. 实时调度策略永远不可能被优先级低的进程抢占成功。

(7)调度器相关的系统调用

sched函数组
在这里插入图片描述
优先级函数
在这里插入图片描述
在这里插入图片描述

五、系统调用

在这里插入图片描述
在这里插入图片描述

1、与内核通信

系统调用在用户进程和硬件设备之间添加了一个中间层,这个中间层的作用有:

  1. 为用户进程提供硬件的抽象访问接口(一切皆文件)
  2. 保证系统的稳定,裁决访问
    系统调用是用户空间访问内核的唯一手段,也是唯一的内核访问的合法入口

2、使用API而不是直接使用系统调用

使用C口提供的API,API内部进行系统调用与内核交互。
使用API可以提高程序的可移植性。
C库提供相同的API,在不同架构上API内部实现各不相同。

3、系统调用

  1. 通过C库API访问系统调用
  2. 系统调用通常返回一个long型,表示调用结果 负值表示失败,并将结果写入C的全局errno
  3. 必须保证系统调用是可重入的,应为不同的进程可能会调用同一个系统调用
  4. 定义系统调用,getpid的系统调用的实现
    在这里插入图片描述
    SYSCALL_DEFINE0 是一个宏,原型是这样的
    在这里插入图片描述
    asmlinkage 是一个编译指令,它要求从栈中提取该函数的参数,(cortex-m3/4 执行一个函数时 他的寄存器中是啥内容?)
    所有系统调用都要用asmlinkage 修饰
    为了兼容32和64位机,系统调用在内核返回long型,在用户返回int型。
    所有系统调用以 sys_XXX 开头。

(1)系统调用

  1. 每个系统调用有一个系统调用号,内核记录系统调用号表,存储在sys_call_table中。(感觉像stm32的中断号和中断向量表),从0 开始。
  2. 系统调用号是每个体系结构的ABI(应用程序二进制接口)
  3. 系统调用号必须定义在**<asm/unistd.h>**中
  4. 系统调用必须被编译进内核,编译之前放进 kernel/ 下的特定文件syss.c
    在这里插入图片描述

(2)系统调用过程

1、在用户进程调用API
2、API中调用系统调用
3、通过内核陷入指令产生异常,进入系统调用处理函数。(处于内核态)
4、提取系统调用参数
5、判断参数有效性、合法性。内核根据系统调用执行系统调用函数 ==> 这就是所说的内核代替用户执行
6、系统调用函数返回结果
7、返回系统调用处理函数
8、返回用户进程

4、如何实现一个系统调用

(1)<asm/unistd.h> 中添加一个系统调用

在这里插入图片描述

(2)在系统调用表添加系统调用

(3)实现系统调用函数

问题记录

1、用户进程和内核进程有什么区别?用户进程可以被直接调度吗?是否也存在与用户进程对应绑定的内核进程

进程 == 单线程

2、实时进程和普通进程有什么区别?如何创建一个实时进程

3、如何理解接口设计—>提供机制而不是策略?

4、cortex-m3/4 执行一个函数时 他的寄存器中是啥内容?

cortex-m3/4寄存器有一下
在这里插入图片描述
在函数执行时
在这里插入图片描述
各个寄存器的作用
在这里插入图片描述


http://www.ppmy.cn/news/1564543.html

相关文章

域名劫持是怎么回事?怎么解决?

域名劫持是一种常见的DNS攻击方式&#xff0c;攻击者通过非法手段获取域名控制权&#xff0c;改变域名解析的目的地址&#xff0c;使得用户在访问该域名时被重定向到攻击者指定的页面&#xff0c;对企业的品牌形象和业务开展造成很大影响。那域名劫持是怎么回事&#xff1f;怎么…

《leetcode-runner》【图解】【源码】如何手搓一个debug调试器——表达式计算

前文&#xff1a; 《leetcode-runner》如何手搓一个debug调试器——引言 《leetcode-runner》如何手搓一个debug调试器——架构 《leetcode-runner》如何手搓一个debug调试器——指令系统 《leetcode-runner》【图解】如何手搓一个debug调试器——调试程序【JDI开发】【万字详解…

【PowerShell专栏】PowerShell脚本执行策略。

PowerShell为了防止恶意的PowerShell的代码在电脑执行,在PowerShell 5.1 后的版本PowerShell的默认运行环境是RemoteSigned,就是经过远程验证过的PowerShell脚本能够执行。 在实际的脚本应用过程中,很多时候我们都会将策略修改为Unrestricted,这其实是一种危险行为,如果是…

R语言的文件操作

R语言的文件操作 引言 在数据科学和分析的过程中&#xff0c;文件操作是不可或缺的一部分。R语言作为一种强大的统计计算和图形作图的编程语言&#xff0c;提供了丰富的文件操作函数&#xff0c;使得用户能够方便地读取和保存数据。本文将详细介绍R语言中的文件操作&#xff…

深入HDFS——数据读取源码

引入 通过核心设计篇章的学习&#xff0c;我们知道数据上传设计的过程是很多的&#xff0c;这也是上一篇数据上传源码内容很多的原因。 今天我们就可以来个简单一些的放松放松&#xff0c;看看数据读取源码是如何实现的。 数据读取过程 还是老样子&#xff0c;实现一个简单…

打算换工作

主要是从事硬件测试工作&#xff0c;熟悉的技能如下&#xff1a; 熟练使用万用表、示波器、频谱仪、smartbits、IQxel、IQview、极致汇仪、可程式恒温恒湿实验箱、jinko电子负载仪、温升测试仪器、振动台等仪表仪器。 熟练使用MTK、Qualcomm、ReakTek、Quantenna等系列芯片的R…

疑难Tips:解决 SQL*Plus 中工具插入中文数据到Oracle数据库报错及乱码问题

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] 解决在 sqlplus 命令行中插入 Oracle 中文数据报错及乱码问题 错误信息&#xff1a;在 sqlplus 命令行中执行下述插入语句&#xff0c;报 ORA-01756: quoted string not properly terminated &am…

学习笔记081——如何备份服务器中MySQL数据库数据?

方法&#xff1a; 可以通过编写sh脚本的方式&#xff0c;结合Linux中的crontab定时任务来实现定时备份数据的功能。 sh脚本如下&#xff1a; #!/bin/bash# 要备份的数据库 DB_NAME"wms" # 数据库账号 DB_USER"root" # 数据库密码 DB_PASSWORD"12345…