【操作系统内核】进程

news2024/11/18 8:35:54

【操作系统内核】进程

进程的组成

进程的运行,需要考虑 磁盘 => 内存 => CPU => 内核 => 进程切换 这个过程

  1. 首先,程序运行要将可执行文件加载到内存,所以进程要读取可执行文件(运行后可能还需要读取其他文件的数据),需要知道:

① 文件系统的信息,fs_struct

② 打开的文件的信息,files_struct

20230805102419
  1. 其次,进程要访问内存,Linux要求它有一块自己的虚拟地址空间,所以进程中需要有一个mm_struct实例:

① vm_area_struct:内存映射,如mmp

② 页表:pgd存储页表目录的地址

20230805101045
  1. 程序加载到内存后,CPU需要知道下一条执行指令的内存地址,这个内存地址存储在CPU的程序计数器中; 此外,进程还需要其他,CPU寄存器的值,也就是CPU上下文(也称为CPU的硬件上下文),包括:

① 指令指针寄存器 (eip/rip): 存储进程的下一条指令

② 通用寄存器
eax、ebx、ecx、edx、esp、ebp、esi、edi(32位)
rax、rbx、rcx、rdx、rsp、rbp、rsi、rdi(64位)

③ 段寄存器
cs、ds、ss、es、fs、gs

④ 标志寄存器

20230805102602
  1. 每个进程都在运行在用户态和内核态,为实现CPU的上下文切换,每个进程都应该有:

① 一个用户栈

② 一个内核栈

进程的运行流程,无非就是函数链的调用,每调用一次函数,就把函数压栈;但是有一个特殊情况,就是用户态的函数调内核态的函数,如用户态函数c()调用内核态函数d(),将发生第一次CPU上下文切换

此时,内核需要将用户态的信息(通过pt_regs这个结构保存)保存到内核态的函数栈的底端,包含:

① 用户态栈顶指针、系统调用方法参数:调用系统函数的函数

② 用户态栈顶指令指针: 用户态执行的下一个指令

③ 通用寄存器

然后,将函数d()压入内核态函数栈

当内核函数调用完成后,将恢复用户态信息,发生第二次CPU上下文切换

20230805125309
  1. 调度信息

一个CPU核只能运行一个进程,但如果我要运行的程序多于CPU核,将采用
时分共享的方式:一个进程运行一段时间后,切换到另一个进程运行,使得每个进程都觉得自己拥有一个CPU

而要实现进程切换,就需要内核决定切换的策略,假设只有一个CPU并在运行某一个进程,首先,在进程切换时,内核的调度程序需要拿到CPU的执行权限。

时分共享策略通过产生一个时钟中断,使得内核态能去调度其他进程

20230805130732

两个进程切换过程(详细过程见参考链接):
① 切换CPU上下文,如CPU的rip指针应该指向第二个进程的下一条执行指令
② 切换虚拟地址空间,如切换页表
③ 切换内核栈

而内核调度器去调度进程时,需要根据进程的调度信息(如调度算法、优先级等)去调度

据此,可以将进程抽象为结构体task_struct

20230805131548

当然,这里只讲述一些基础的信息,task_srtrcut的组成不止如此

进程的数据结构

除了上面讲到的组成,信息处理也是进程数据结构中重要的组成:

// 0. 标志信息
pid_t pid;
pid_t tgid;
struct task_struct *group_leader;

// 1. 文件和文件系统
struct fs_struct  *fs;
struct files_struct  *files;

// 2. 内存管理
struct mm_struct *mm;

// 3. 内核栈
struct thread_info    thread_info;
void  *stack;

// 4. 调度信息
//调度器类
const struct sched_class  *sched_class;
//调度实体
struct sched_entity    se;
struct sched_rt_entity    rt;
struct sched_dl_entity    dl;
//调度策略
unsigned int      policy;
//可以使用哪些CPU
int  nr_cpus_allowed;
cpumask_t  cpus_allowed;
struct sched_info sched_info;
//是否在运行队列上
int   on_rq;
//优先级
int   prio;
int   static_prio;
int   normal_prio;
unsigned int  rt_priority;

// 5.运行统计信息
u64        utime; //用户态消耗的CPU时间
u64        stime; //内核态消耗的CPU时间
unsigned long      nvcsw;//自愿(voluntary)上下文切换计数
unsigned long      nivcsw;//非自愿(involuntary)上下文切换计数
u64        start_time; //进程启动时间,不包含睡眠时间
u64        real_start_time; //进程启动时间,包含睡眠时间

// 6. 信号处理
struct signal_struct    *signal;
struct sighand_struct    *sighand;
sigset_t  blocked;
sigset_t  real_blocked;
sigset_t  saved_sigmask;
struct sigpending  pending;
unsigned long   sas_ss_sp;
size_t  sas_ss_size;
unsigned int  sas_ss_flags;

// 7.进程状态
volatile long state;
int exit_state;
unsigned int flags;

// 8.亲缘关系
struct task_struct __rcu *real_parent;
struct task_struct __rcu *parent;
struct list_head children;
struct list_head sibling;
 
// 9.进程权限
const struct cred __rcu *real_cred;         
const struct cred __rcu  *cred;
...

0、1、2号进程

计算机在启动时,会先运行0号进程(idle进程),建立它的堆栈,并运行它:

① 配置实时时钟

② 挂载根文件系统

③ 创建 1 号进程(init 进程)

④ 创建 2 号进程 (kthreadd进程)

0号进程

内核进程:只运行在内核态,只能用大于PAGE_OFFSET的虚拟地址空间(只使用内核态的虚拟地址空间)

普通进程:可运行在内核态和用户态,可以使用所有虚拟地址空间(在内核态使用内核态地址空间,在用户态使用用户态地址空间)

0号进程是一个内核进程,也只有没有任何可运行的进程时,才会运行0号进程

1号进程

共享0号进程的所有数据结构,一开始是内核进程,先执行init()函数完成内核初始化,然后调用exec()装入可执行程序init,变成一个普通进程

它是所有用户态进程的祖先

2号进程

kswapd: 一直在后台运行,执行物理页面的回收,交换出不用的页帧(将最近不用的内存块移动到磁盘)

pdflush:刷新 “脏” 缓冲区的内容到磁盘以回收内存

是所有内核进程的祖先

进程间的关系

除了 0 号进程,一个进程都是由一个父亲进程创建

如果一个进程创建了多个进程,那么子进程之间是兄弟关系

task_struct中维护节点关系的字段:

20230805134500
  • parent:指向其父进程。当它终止时,必须向它的父进程发送信号
  • children: 表示链表的头部。链表中的所有元素都是它的子进程。
  • sibling: 用于把当前进程插入到兄弟链表中。
  • real_parent 和 parent:
    • 通常情况下,real_parent 和 parent 是一样的,但是也会有另外的情况存在
    • bash 创建一个进程,那进程的 parent 和 real_parent 就都是 bash。
    • 如果在 bash 上使用 GDB 来 debug 一个进程,这个时候 GDB 是 parent,bash 是这个进程的 real_parent。

如登录一个linux的shell终端,会先创建一个sshd的进程,sshd进程才创建一个pts进程,pts进程再创建一个bash进程,如果在这个bash去拉起一个进程(如ps -ef、ls这些命令就会拉起一个进程,通过which ls命令可查看ls的可执行文件路径),
那么这个进程的父进程就是这个bash,关闭这个bash后,它的所有子进程都会被kill

20230805133438

内核将task_struct之间通过双向链接的形式组织,后续fork的进程插入链表的表尾:

20230805142124

进程的创建

比如我们写一段代码,生成可执行文件,然后在bash上运行,则这个进程属于bash进程的一个子进程:

20230805142629

内核调用sys_fork()创建一个进程并加入到双向链表

sys_fork()的本质: 在内核创建一个task_struct实例(拷贝父进程的task_struct),然后将之维护到各种链表队列(用于管理或调度进程)

  1. 第一次系统调用:
  • bash调用fork()陷入内核态

    • 进入父进程保存用户态寄存器、程序计数器的值到内核栈
    • 从slab分配器中分配一个task_struct实例,创建子线程内核栈,拷贝父进程内核栈并设置thread_info
    • 拷贝父进程的实例,调度信息,进程运行信息等,创建进程时主要关注mm_struct
    20230805144541
  • 拷贝完成后,分配PID,建立进程亲缘关系,将task_struct加入进程链表,

  • 创建进程完成,唤醒新线程(task_running)

  1. 第二次系统调用:
  • 子进程被唤醒后,调用execvp()加载磁盘上的可执行文件,由于磁盘IO操作需要内核态完成,所以需要再次陷入内核态

  • 内核态调用load_elf_binary()加载代码段和数据段到内存,最主要的是设置子进程的虚拟地址空间

    • 修改mmp
    • 初始化函数栈(会比父进程的小)
    • 将可执行文件的代码部分映射到内存(写时复制,内存映射,因为代码段是进程私有的,不能共享)
    20230805145920
    • 设置堆的brk以及堆的vm_area_struct(堆也会比较小)
    • 将依赖的so映射到内存中的内存映射区域
  • 由于内核栈相同,切换会用户态后,子进程的rip指针和父进程的rip指针会指向同一行指令,且执行的代码段也相同,需要根据pid是否等于0区分父进程或子进程

Tip:

exec()函数:

20230805151606

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1196645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JUC包下面的四大天王+线程池部分知识

一)Semphore:限流器用我就对了 Java中信号量Semphore是把操作系统原生的信号量封装了一下,本质就是一个计数器,描述了 可用资源的个数,主要涉及到两个操作 如果计数器为0了,继续Р操作,就会出现阻塞等待的情况 P操作:申…

徒步“三色”泸溪 共赏冬日胜景

(金笛 胡灵芝)11月11日,“中国体育彩票”2023年“走红军走过的路”徒步穿越系列活动(泸溪站)暨泸溪文旅推荐活动在泸溪县举行,来自全国各地千余名户外爱好者通过徒步的方式,传承红色基因&#x…

C语言--输入10个数字,要求输出其中值最大的元素和该数字是第几个数

今天小编带大家了解一下什么是“打擂台”算法。 一.思路分析 可以定义一个数组arr,长度为10,用来存放10个数字,设计一个函数Max,用来求两个数中的较大值, 定义一个临时变量tmparr[0],保存临时最大的值,下标…

邻接表储存图实现广度优先遍历(C++)

目录 基本要求: 邻接表的结构体: 图的邻接表创建: 图的广度优先遍历(BFS): 邻接表的打印输出: 完整代码: 测试数据: 结果运行: 通过给出的图的顶点和…

归并排序 merge Sort + 图解 + 递归 / 非递归

归并排序(merge sort)的主要思想是:将若干个有序序列逐步归并,最终归并为一个有序序列二路归并排序(2-way merge sort)是归并排序中最简单的排序方法 (1)二路归并排序的递归实现 // 二路归并排序的递归实现 void merge(vector&l…

EFCore: The ConnectionString property has not been initialized.

使用NuGet的程序包管理控制台执行命令“update-database”的时候报出该错误 经过检查发现是optionsBuilder.UseSqlServer(strConn);中的strConn没有写

数据库01-慢查询优化

目录 MySQL优化 慢查询 如何定位慢查询? 如何分析慢查询? MySQL优化 MySQL 优化是数据库管理和应用性能调优的一个重要方面。以下是一些常规性的 MySQL 优化经验和适用场景: 索引优化: 确保表的字段上有适当的索引&#xff0…

计算机毕业设计选题推荐-农产品销售微信小程序/安卓APP-项目实战

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

C语言全部关键字解析

前言 C语言具有以下关键字: 这些关键字如下: 关键字autobreakcasecharconstcontinuedefaultdodoubleelseenumexternfloatforgotoifintlongregisterreturnshortsignedsizeofstaticstructswitchtypedefunionunsignedvoidvolatilewhile 对于这些关键字,大…

牛客网刷题笔记131111 Python实现LRU+二叉树先中后序打印+SQL并列排序

从学校步入职场一年多,已经很久没刷过题了,为后续稍微做些提前的准备,还是重新开始刷刷题。 从未做过计划表,这回倒是做了个计划表,希望能坚持吧。 刷题比较随性且量级不大,今天就写了2个算法2个sql&#x…

第四节(2):修改WORD中表格数据的方案

《VBA信息获取与处理》教程(10178984)是我推出第六套教程,目前已经是第一版修订了。这套教程定位于最高级,是学完初级,中级后的教程。这部教程给大家讲解的内容有:跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互联网…

Qt界面设计时使各控件依据窗口缩放进行自适应填充的方法——使用布局、Spacer等控件

Qt界面设计时使各控件依据窗口缩放进行自适应填充的方法—使用布局、Spacer等控件 Chapter1 Qt界面设计时使各控件依据窗口缩放进行自适应填充的方法—使用布局、Spacer等控件Chapter2 Qt Creator中布局器详解01. 概述02. 开发环境03. 布局器概述04. 布局属性设置05. 弹簧条属性…

简单版本管理服务编写

说明: 制作android应用内更新的时候,经常会用到版本检查,下载,安装,这时候需要写一个版本管理服务。 本文说明了自己编写版本服务的简单经过。 解决方案: 该软件实现如下功能: 创建后台接口:版本软件上传…

基于PHP的设云尘资讯网站设计与实现

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下,你想解决的问…

保姆级自定义GPTs教程,无需任何代码!

11月10日,OpenAI正式宣布向所有ChatGPT Plus用户开放GPTs功能,一个人人都能开发自定义ChatGPT助手的时代降临。 GPTs支持无代码、可视化点击操作,这意味着即便你没有任何编程经验,只要有数据、脑洞大开的想法,就能开发…

探索微信小程序框架的精华——高质量的优秀选择

目录 引言: 1. 框架性能 2. 开发者工具支持 3. 文档和社区支持 4. 扩展能力 5. 使用率和稳定性 结语: 引言: 微信小程序作为一种轻量级、高效便捷的应用形式,已经在移动应用领域占据了重要地位。而其中,选择一个…

PostMan授权认证使用

Authorization 对于很多应用,出于安全考虑我们的接口并不希望对外公开。这个时候就需要使用授权(Authorization)机制。 授权过程验证您是否具有访问服务器所需数据的权限。 当发送请求时,通常必须包含参数,以确保请求具有访问和返回所需数据…

C/C++数据结构之链表题目答案与解析

个人主页:点我进入主页 专栏分类:C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 数据结构初阶 欢迎大家点赞,评论,收藏。 一起努力,一起奔赴大厂。 目录 1.前言 2.题目…

C++学习笔记(二):C++是如何运行的

C是如何运行的 include 预处理语句&#xff0c;在编译前就会被处理。 main函数 程序入口。 #include <iostream>int main() {std::cout << "Hello World!" << std::endl;std::cin.get();return 0; }Visual Studio 解决方案平台指的是编译的代码的…