从根上理解操作系统(二)

article/2025/9/9 8:00:07

目录

进程

1、进程到底是什么

 2、怎么知道时间片用完的

3、进程切换

4、进程初始化

5、任务堆栈

6、系统调用


进程

       程序是一个可执行文件,而进程是一个执行中的程序实例,所以可以认为

进程 = 进程资源 + 执行序列

        利用分时技术,在操作系统上同时可以运行多个进程。分时技术的基本原理是把CPU的运行时间划分成一个个规定长度的时间片,让每个进程在一个时间片内运行。当进程的时间片用完时,系统就利用调度程序切换到另一个进程去运行。那么有几个问题需要探讨一下:1、进程到底是什么;2、怎么知道时间片用完的。

1、进程到底是什么

        内核程序通过进程表对进程进行管理,每个进程在进程表中占用一项。任务表项是一个task_struct任务结构指针,也被称作任务控制块PCB,保存着控制和管理进程的所有信息,如下:

       

 2、怎么知道时间片用完的

         这里其实就是进程调度的概念了。我们从上一节的PCB中可以看到,有一个字段叫count(滴答数),滴答数会随着时间不断递减,那是怎么做到的呢?

        这就不得不说计算机中的一个设备——定时器。这个定时器每隔一段时间就会向 CPU 发起一个中断信号。定时器的信号是由晶振分频产生的。在 linux-0.11 中,定时器的间隔时间被设置为 10 ms,也就是 100 Hz。发起的中断叫时钟中断,其中断向量号被设置为了 0x20。一切的源头,就源于这个每 10ms 产生的一次时钟中断。

        操作系统在启动的时候,会创建一个中断向量表,当时钟中断0x20产生时,CPU会查找中断向量表中 0x20 处的函数地址,这个函数地址即中断处理函数,并跳转过去执行。

_timer_interrupt:...// 增加系统滴答数incl _jiffies...// 调用函数 do_timercall _do_timer...

        该函数主要做了两件事:1、将系统滴答数这个变量 jiffies 加一;2、调用了另一个函数 do_timer。

        jiffies是内核的全局变量,记录的是计算机启动以来时钟中断的数量。在启动的时候,该变量为0,每来一次时钟中断,该值加1。HZ代表1秒会产生多少次时钟中断,所以计算机启动的时间就可以通过jiffies/HZ获得。

         do_timer函数首先将当前进程的时间片 -1,然后判断如果时间片仍然大于零,则什么都不做直接返回。如果时间片已经为零,则调用 schedule(),即进程调度。

void do_timer(long cpl) {...// 当前线程还有剩余时间片,直接返回if ((--current->counter)>0) return;// 若没有剩余时间片,调度schedule();
}

        这里可能会有一个疑问:如果一个counter值等于0之后,会调度其他进程,那么总会达到一个结果,就是所有的进程的counter都为0。如果所有进程的counter值都为0,那么就会对所有进程的counter重新赋值。

3、进程切换

        在PCB数据结构中,我们看到有一个任务状态段字段tss,这里用于存储当前进程所有寄存器值。

        当进行进程调度的时候,底层主要经历了如下过程:

1. 通过 ljmp 跳转指令跳转到新进程的偏移地址处。

2. 将当前各个寄存器的值保存在当前进程的 TSS 中,并将新进程的 TSS 信息加载到各个寄存器。(这部分是执行 ljmp 指令的副作用,并且是由硬件实现的)

4、进程初始化

        创建进程,实际上就是创建了一个PCB,并分配了一段内存用于存储该进程的代码程序。创建进程的过程也是复制的过程,牵涉到进程数据结构中信息的设置

  1. 系统首先为新进程在内存中申请一页内存来存放其任务数据结构信息,并复制当前进程任务数据结构中所有内容作为新进程任务数据结构的模板
  2. 对已复制的任务数据结构内容进行修改,把当前进程设置为新进程的父进程
  3. 根据当前进程环境设置新进程任务状态段tss
  4. 新建进程内核态堆栈指针tss.esp0被设置成新进程任务数据结构所在内存页面的顶端(见下图)
  5. 设置新进程代码段、数据段的基址和段限长,复制当前进程内存分页管理的页目录项和页表项。
  6. 在GDT中设置新进程的tss和LDT描述符项,其中基地址信息指向新进程的tss和ldt
  7. 最后将新进程设置成可运行状态,并向当前进程返回新进程号

       所有涉及”页“的概念,下一章进行介绍。

        fork()的执行过程中,内核并不会立刻为新进程分配代码和数据内存页。只有当以后执行过程中如果其中有一个进程以写的方式访问内存时,被访问的内存页面才会在写操作前被复制到新申请的内存页面中。

        子进程虽然复制了父进程所有的地址空间中的内容,但是二者具有独立的地址空间,执行时互不干扰。

        为了说明这些不同,首先看一看 UNIX 操作系统。在 UNIX 中,正如以前所述,每个进程都用一个唯一的整型进程标识符来标识。通过系统调用 fork(),可创建新进程。新进程的地址空间复制了原来进程的地址空间。这种机制允许父进程与子进程轻松通信。这两个进程(父和子)都继续执行处于系统调用 fork() 之后的指令,但有一点不同:对于新(子)进程,系统调用 fork() 的返回值为 0;而对于父进程,返回值为子进程的进程标识符(非零)。

        通常,在系统调用 fork() 之后,有个进程使用系统调用 exec(),以用新程序来取代进程的内存空间。系统调用 exec() 加载二进制文件到内存中(破坏了包含系统调用 exec() 的原来程序的内存内容),并开始执行。采用这种方式,这两个进程能相互通信,并能按各自方法运行。父进程能够创建更多子进程,或者如果在子进程运行时没有什么可做,那么它采用系统调用 wait() 把自己移出就绪队列,直到子进程终止。因为调用 exec() 用新程序覆盖了进程的地址空间,所以调用 exec() 除非出现错误,不会返回控制。

        从上面的解释也可以看出来,创建一个新的子进程和加载运行一个执行程序文件是两个不同的概念:当创建子进程时,它完全复制了父进程的代码和数据区,并会在其中执行子进程部分的代码,一般在子进程中运行exec()系统调用会执行块设备上的程序,在进入exec()后,子进程原来的代码和数据区就会被清掉,CPU就会立刻产生代码页面不存在的异常,此时内存管理程序就会从块设备上加载相应的代码页面,然后CPU重新执行引起异常的指令,到此时新程序的代码才真正开始被执行。   

5、任务堆栈

        为了解决不同CPU特权级共享使用堆栈带来的保护问题,执行0级的内核代码和执行3级的用户代码需要使用不同的栈,所以每个任务都有两个堆栈:用户态堆栈和内核态堆栈。除了处于不同CPU特权级中,这两个堆栈之间的主要区别在于任务的内核态堆栈很小。

        每个任务有自己的64M地址空间,当一个任务刚被创建时,它的用户态堆栈指针被设置在其地址空间的靠近末端的部分。堆栈实际使用的物理内存则是由CPU分页机制确定。

        每个任务都有自己的内核态堆栈,用于任务在内核代码中执行期间。其所在线性地址中的位置由该任务TSS段中ss0和esp0两个字段指定。ss0事实上任务内核态堆栈的段选择符,esp0是堆栈栈底指针。因此每当任务从用户代码转移进入内核代码中执行时,任务的内核态总是空的。       

        每个段定义了内存中的某个区域以及访问的优先级等级等信息。

6、系统调用

        系统调用接口是linux内核与上层应用程序进行交互通信的唯一接口。用户程序通过直接或间接调用中断int 0x80,并在eax寄存器中指定系统调用功能号,即可使用内核资源。

        如果一个中断产生时任务正在用户态中执行,那么该中断就会引起CPU特权级从3级变化到0级,此时CPU就会进行用户态堆栈到内核态堆栈的切换。CPU会从当前任务的任务状态段tss中取得新堆栈的段选择符和偏移值,即ss0和esp0。在定位了内核态堆栈之后,CPU就会首先把原用户态堆栈指针ss和esp压入内核态堆栈,随后把标志寄存器eflags的内容和返回位置cs、eip也压入内核堆栈。

        执行iret退出内核程序返回到用户程序时,将恢复用户态堆栈和eflags

        上面讲的是系统调用时,任务堆栈的切换过程。如果任务在用户态时,CPU收到了中断响应,那么就需要先讲任务切换到内核态,再响应中断。如果一个任务正在内核态中运行,那么若CPU响应中断就不再需要进行堆栈切换操作,因为此时该任务运行的内核代码已经在使用内核态堆栈,所以CPU仅把eflags和中断返回指针cs、eip压入当前内核态堆栈,然后执行中断服务过程。

参考:

《linux内核完全剖析》

上帝视角看进程调度 - 知乎
Jiffies


http://chatgpt.dhexx.cn/article/J0xxMQmJ.shtml

相关文章

【ubuntu18.04】meson 安装 及python升级python3.9

发现openh264 支持meson 发现openh264 支持meson 写的也太简单了完全不知道openh264 到底怎么搞meson看起来支持windows和ubuntu先安装python3 参考大神: 注意:meson安装前必须确认是否已经安装python3.5及以上版本;因为meson依赖于python3和ninja 1 安装python3和ninja:su…

KEAZ128 时钟配置

本文介绍如何用KEAZ128评估版(FRDM-KEAZ128Q80)配置为40MHz core freqency/20MHz bus frequency。 1.了解器件时钟特性 参见NXP KEA128 DS(S9KEA128P80M48SF0.pdf ),可以知道这个MCU最高支持48MHz core frequency. 从KEA128 RM&…

《软件工程》整理

目录 Scrum开发 概念 scrum开发流程 Scrum开发 需求获取(拆分) DevOps Server的CMMI模型对需求的层级划分,Epic(长篇故事)、Feature(特性)、Requirement(需求)其中&am…

RTKLIB源码调试.trace文件解析.stat文件解析

.trace文件是调试文件,帮助用户在程序、输入数据时,进行分析的一个很好的提示! .trace文件的生成、trace文件内容的生成,均在execses中! 以单点定位为例,大体流程为: 生成.trace文件&#xf…

sopt:一个简单的python最优化库

sopt:一个简单的python最优化库引言 最近有些朋友总来问我有关遗传算法的东西,我是在大学搞数学建模的时候接触过一些最优化和进化算法方面的东西,以前也写过几篇博客记录过,比如遗传算法的C语言实现(一):以非线性函数求极值为例和C语言实现粒子群算法(P…

python古诗词生成_Python一日一练02----诗词生成器

要求 编写一段程序,可以自动生成小诗。 格式如下 源码 import random import sys articles ["the", "a", "another", "her", "his"] subjects ["cat", "dog", "horse", "m…

LSTM古诗词生成

Le LSTM古诗词生成 一、简介 基于LSTM的古诗词生成,设计神经网络模型,使模型学习数据是6291首古诗,没有专门的验证数据和测试数据,感觉不会预测正确。边学习边生成古诗,从生成的古诗来看学习的效果。 涉及到的模块有Py…

宋词自动生成

利用宋词语料库,通过单双词的组合与模板的匹配,实现指定词牌宋词的生成 import random import tkinter as tk import re from tkinter import messageboxlist []class Window:# 界面设计def __init__(self, root):label1 tk.Label(root, text输入词牌…

基于Python的宋词生成器

资源下载地址:https://download.csdn.net/download/sheziqiong/85631523 1. 背景 我有两个爱好,一个是传统文化,另一个是高新技术。 传统文化,我喜欢唐诗宋词、笔墨丹青,高新技术我则从事前沿的IT编程,喜…

基于java的古诗词生成管理系统

10161-古诗词生成管理系统 开发工具 eclipse tomact mysql jdk 功能详情: 古诗搜索、古诗问答、机器回复、古诗管理(添加古诗、古诗分类、古诗标签、古诗列表)、用户管理

田字格字帖生成器、孩子取名系列工具

大家好,我是小寻,欢迎关注公众号:工具优选,免费领取优质项目源码和常用工具,还可以加入我的交流群! 这是是一款2013年上线的在线小工具集,包括了 13 款中文学习工具,有田字格字帖、拼音田字格、古诗词字帖…

有趣的深度学习——使用TensorFlow 2.0 + RNN 实现一个古体诗生成器

一、前言 很早之前,我曾经写过一个古体诗生成器(详情可以戳TensorFlow练手项目二:基于循环神经网络(RNN)的古诗生成器),那个时候用的还是Python 2.7和TensorFlow 1.4。 随着框架的迭代,API 的变更&#x…

现在还可以一键自动生成古诗词,你知道吗?

人类在漫长的历史长河中,一直在探索着各种各样的美好,不断地追求着更高的境界。而如今,随着科技的不断发展,人工智能已经成为了我们得力的伙伴之一,为我们带来了更多的便利和可能性。尤其是在艺术和文化领域&#xff0…

古诗词在线起名 - 一刀工具箱

古诗词名字生成器帮助你在线生成古诗词名字,包含:诗经、楚辞、唐诗、宋词、辞赋等古风的名字,这些名字都非常的优美好听,希望你们能够喜欢这款古诗词起名工具。 代码片段 async subName() {let name_arr Object.keys(this.userN…

NLP入门 - 基于Word Embedding + LSTM的古诗生成器

一共实现三个功能: 1. 续写五言诗 2. 续写七言诗 3. 写五言藏头诗 之前用这个做Intro to Computer Science的期末项目折腾太久,不想赘述,内容介绍及实现方法可参考期末presentation的slides: https://docs.google.com/presen…

基于古诗词的名字生成器

数据集 因为数据量庞大,使用本地的 CSV 数据进行测试。 后续改进 CSV 文件保存到 mongodb 数据库,便于聚合查询。 数据分词 我们需要一个分词器将这些数据进行分词,用到的是 Golang 版的 jieba 库如下: "github.com/go-e…

基于LSTM + keras 的古诗生成器

1.语料准备:包含 5.5 万首唐诗、26 万首宋诗、2.1 万首宋词和其他古典文集。诗人包括唐宋两朝近 1.4 万古诗人,和两宋时期 1.5 千古词人。数据来源于互联网。每行一首诗,标题在预处理的时候已经去掉了。2.模型参数配置:预先定义模…

唐诗生成器

使用唐诗语料库,经过去噪预处理、分词、生成搭配、生成主题等过程,生成唐诗。 csdn下载地址:http://download.csdn.net/detail/lijiancheng0614/9840952 github上repository地址:https://github.com/lijiancheng0614/poem_genera…

JQuery中的val()函数

JQuery中的val()函数相当于Javascript中的value属性,可以用来设置和获取元素的值。 下面用一个简单的邮箱登陆界面来举个例子: 在默认情况下邮箱的地址输入框和密码输入框都有相应的提示。 要求:当鼠标聚焦在邮箱地址输入框时,提…

【Python】sklearn中的cross_val_score()函数参数

sklearn 中的cross_val_score函数可以用来进行交叉验证,因此十分常用,这里介绍这个函数的参数含义。 sklearn.cross_validation.cross_val_score(estimator, X, yNone, scoringNone, cvNone, n_jobs1, verbose0, fit_paramsNone, pre_dispatch‘2*n_job…