热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

第一次作业:基于Linux源代码分析进程模型

作业内容:挑选一个开源的操作系统,深入源码分析其进程模型,具体包含如下内容:操作系统是怎么组织进程的进程状态如何转换(给出进程状态转换图)进程是如何调度的谈谈自己对该操作系统进程模

作业内容:

挑选一个开源的操作系统,深入源码分析其进程模型,具体包含如下内容:

  • 操作系统是怎么组织进程的
  • 进程状态如何转换(给出进程状态转换图)
  • 进程是如何调度的
  • 谈谈自己对该操作系统进程模型的看法

 

1.进程

           进程的概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。每个进程是有生命周期的,在用户空间,进程是由PID标识。在生命周期中PID是唯一且不变的。在linux内核中,进程是由task_struct结构表示(linux内核存储进程信息的固定格式),此结构中包含此进程所需要的所有数据,如进程的状态、运行的时间、堆栈、父进程、执行的线程等。linux将多个进程放在任务队列的双向链表中。

         进程的创建:当启动linux时,加载完内核后,会创建第一进程init。之后所有的进程都是由init或父进程向内核发起调用fork()自身而来,再而克隆clone()自身的数据给子进程。

          进程的优先级:系统中同时有大量的进程需要执行时,是需要优先级来先后处理的。task_struct中是有记录优先级的,内核是如何知道哪个优先级高哪个低,遍历。内核2.6将进程优先级划分位固定的个数。

Linux环境下分别使用getpid()和getppid()函数来得到进程ID和父进程ID,分别使用getuid()和geteuid()函数来得到进程的用户ID和有效用户ID,分别使用getgid()和getegid()来获得进程的组ID和有效组ID,其函数原型如下:

#include
pid_t getpid(
void); //获取进程ID
pid_t getppid(void); //获取父进程ID

uid_t getuid(
void); //获取用户ID
uid_t geteuid(void); //获取有效用户ID

gid_t getgid(
void); //获取组ID
gid_t getegid(void); //获取有效组ID

 

      

2.查看进程

《第一次作业:基于Linux源代码分析进程模型》

 

3.操作系统是怎么组织进程

 

             task_struct的 real_parent成员指向父进程,parent成员指向“养父进程”;children成员表示该进程的子进程链表;sibling成员表示该进程的兄弟进程链表。系统启动后创建了第一个进程:进程0(swapper,也叫idle),进程0创建了第一个用户进程(进程1/sbin/init)和第一个内核进程(进程2kthreadd),之后进程0进入idle状态,当没有进程可以被调度的时候运行该进程,不做具体的事情。进程1的主要作用是处理僵尸进程。当某个父进程比子进程提前消亡时,父进程会给子进程重新寻找“养父进程”,一般就是进程1,由进程1负责处理该子进程的消亡。当创建一个新进程时,新进程的父进程为当前进程或者线程,并且把新进程加入到父进程的子链表中。如果使用CLONE_THREAD标志创建一个新进程时,新进程的父进程为当前进程或线程的父进程,并且把新进程加入到父进程的子链表中。CLONE_THREAD标志创建一个新线程时,新线程的父进程为当前线程组长的父进程,所以线程组看起来像是一个独立的进程。此时,因为创建的是线程,所以不需要把新线程加入到任何子链表中。

 

4.进程状态如何转换

          由于进程的不断创建,系统的资源已经不能满足进程运行的要求,这个时候就必须把某些进程挂起(suspend),对换到磁盘镜像区中,暂时不参与进程调度,起到平滑系统操作负荷的目的。 
引起进程挂起的原因是多样的,主要有: 
1. 系统中的进程均处于等待状态,处理器空闲,此时需要把一些阻塞进程对换出去,以腾出足够的内存装入就绪进程运行。 
2. 进程竞争资源,导致系统资源不足,负荷过重,此时需要挂起部分进程以调整系统负荷 ,保证系统的实时性或让系统正常运行。 
3. 把一些定期执行的进程(如审计程序、监控程序、记账程序)对换出去,以减轻系统负荷。 
4. 用户要求挂起自己的进程,以便根据中间执行情况和中间结果进行某些调试、检查和改正。 
5. 父进程要求挂起自己的后代进程,以进行某些检查和改正。 
6. 操作系统需要挂起某些进程,检查运行中资源使用情况,以改善系统性能;或当系统出现故障或某些功能受到破坏时,需要挂起某些进程以排除故障。

        引起进程状态转换的具体原因如下:

  • 等待态—→挂起等待态:如果当前不存在就绪进程,那么至少有一个等待态进程将被对换出去成为挂起等待态;操作系统根据当前资源状况和性能要求,可以决定把等待态进程对换出去成为挂起等待态。
  • 挂起等待态—→挂起就绪态:引起进程等待的事件发生之后,相应的挂起等待态进程将转换为挂起就绪态。
  • 挂起就绪态—→就绪态:当内存中没有就绪态进程,或者挂起就绪态进程具有比就绪态进程更高的优先级,系统将把挂起就绪态进程转换成就绪态。
  • 就绪态—→挂起就绪态:操作系统根据当前资源状况和性能要求,也可以决定把就绪态进程对换出去成为挂起就绪态。
  • 挂起等待态—→等待态:当一个进程等待一个事件时,原则上不需要把它调入内存。但是在下面一种情况下,这一状态变化是可能的。当一个进程退出后,主存已经有了一大块自由空间,而某个挂起等待态进程具有较高的优先级并且操作系统已经得知导致它阻塞的事件即将结束,此时便发生了这一状态变化。
  • 运行态—→挂起就绪态:当一个具有较高优先级的挂起等待态进程的等待事件结束后,它需要抢占 CPU,,而此时主存空间不够,从而可能导致正在运行的进程转化为挂起就绪态。另外处于运行态的进程也可以自己挂起自己。
  • 新建态—→挂起就绪态:考虑到系统当前资源状况和性能要求,可以决定新建的进程将被对换出去成为挂起就绪态。
  • 《第一次作业:基于Linux源代码分析进程模型》

5.进程是如何调度的

 Linuz 中进程调试都是非强占式的,系统采用相当简单的基于优先级的调试算法。进程只能在核心态等待,当进程执行系统调用时它会从用户态“陷入”核心态,这时内核代表这个进程执行。当进程在核心态等待某事件时系统会将它挂起,而后让另一个进程运行。Liunz 的调度函数是schedule,定义在kernelfsched.c 中。Schedule 函数完成以下工作:

  (1)定义两个指针。prev 是正在运行的进程,next 是调度程序选择下一个将要运行的进程。这两个指针可能会相同。

  (2) 处理调度任务队列。首先检查任务是否在队列中,如果有等待处理的任务则顺序处理其中的每一个任务,然后将队列清空。

  (3) 检查任务是否在中断队列中,如果在,则不做本次调度,直接返回。   (4) 底半处理。就是中断处理的后半部分。

  (5) 调度运行进程队列。与Minix 不同的是Linuz 没有将可运行进程按其优先级分成多个队列,而是把系统中所有可运行进程(状态为TASK_RUNNING) 都排在同一个队列中,这实际上是一个双向链表。 (6) 选择下一个要运行的进程。从init_task.nezt开始,顺序搜索运行进程队列,对每一个进程计算其weight 值,选择weight 值最大的运行。weight 值的计算规则是: 1.如果当前进程声明要放弃CPU (进程调度策略中设置了SCHED_YIEID 位),则其weigt=0。2.如果进程是实时进程,则weight=rt_priority+1000。3.如果进程的counter’值为0,则weight=0。4.如果进程的counter不为0,则weight=countertpriority; 如果进程是当前进程,则其weight 值再加1,即当前进程优先。
(7) 切换。保存当前进程结构(task_struct )中的上下文,就是当前进程运行到调度程序结束时处理器的上下文。新加载进程的上下文也是它上次运行到调度地的快照,包括进程的程序计数器和各寄存器的内容。

 

1.创建一些局部变量

struct task_struct *prev, *next;//当前进程和一下个进程的进程结构体
unsigned long *switch_count;//进程切换次数
struct rq *rq;//就绪队列
int cpu;

 

2.关闭内核抢占,初始化一部分变量

need_resched:
preempt_disable();
//关闭内核抢占
cpu = smp_processor_id();
rq
= cpu_rq(cpu);//与CPU相关的runqueue保存在rq中
rcu_note_context_switch(cpu);
prev
= rq->curr;//将runqueue当前的值赋给prev

 

3.选择next进程

next = pick_next_task(rq, prev);//挑选一个优先级最高的任务排进队列
clear_tsk_need_resched(prev);//清除prev的TIF_NEED_RESCHED标志。
clear_preempt_need_resched();

 

4.完成进程的调度

if (likely(prev != next)) {
//如果prev和next是不同进程
rq->nr_switches++;//队列切换次数更新
rq->curr = next;
++*switch_count;//进程切换次数更新

context_switch(rq, prev, next);
/* unlocks the rq *///进程上下文的切换
/*
* The context switch have flipped the stack from under us
* and restored the local variables which were saved when
* this task called schedule() in the past. prev == current
* is still correct, but it can be moved to another cpu/rq.
*/
cpu
= smp_processor_id();
rq
= cpu_rq(cpu);
}
else//如果是同一个进程不需要切换
raw_spin_unlock_irq(&rq->lock);

static inline void
context_switch(
struct rq *rq, struct task_struct *prev,
struct task_struct *next)
{
struct mm_struct *mm, *oldmm;//初始化进程地址管理结构体mm和oldmm
prepare_task_switch(rq, prev, next);//完成进程切换的准备工作
mm = next->mm;
oldmm
= prev->active_mm;
/*完成mm_struct的切换*/
if (!mm) {
next
->active_mm = oldmm;
atomic_inc(
&oldmm->mm_count);
enter_lazy_tlb(oldmm, next);
}
else
switch_mm(oldmm, mm, next);
if (!prev->mm) {
prev
->active_mm = NULL;
rq
->prev_mm = oldmm;
}
switch_to(prev, next, prev);
//进程切换的核心代码
barrier();
finish_task_switch(this_rq(), prev);
}

 

5.我们看到在context_switch中使用switch_to(prev,next,prev)来切换进程。我们查看一下switch_to的代码。 switch_to是一个宏定义,完成进程从prev到next的切换,首先保存flags,然后保存当前进程的ebp,然后把当前进程的esp保存到prev->thread.sp中,然后把标号1:的地址保存到prev->thread.ip中。 然后把next->thread.ip压入堆栈。这里,如果之前B也被switch_to出去过,那么next->thread.ip里存的就是下面这个1f的标号,但如果next进程刚刚被创建,之前没有被switch_to出去过,那么next->thread.ip里存的将是ret_ftom_fork __switch_canqry应该是现代操作系统防止栈溢出攻击的金丝雀技术。 jmp __switch_to使用regparm call, 参数不是压入堆栈,而是使用寄存器传值,来调用__switch_to eax存放prev,edx存放next。这里为什么不用call __switch_to而用jmp,因为call会导致自动把下面这句话的地址(也就是1:)压栈,然后__switch_to()就必然只能re到这里,而无法根据需要ret到ret_from_fork 当一个进程再次被调度时,会从1:开始执行,把ebp弹出,然后把flags弹出。

 

#define switch_to(prev, next, last) \
do { \
/* \
* Context-switching clobbers all registers, so we clobber \
* them explicitly, via unused output variables. \
* (EAX and EBP is not listed because EBP is saved/restored \
* explicitly for wchan access and EAX is the return value of \
* __switch_to()) \
*/ \
unsigned
long ebx, ecx, edx, esi, edi; \
\
asm
volatile("pushfl\n\t" /* save flags */ \
"pushl %%ebp\n\t" /* save EBP */ \
"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \
"movl %[next_sp],%%esp\n\t" /* restore ESP */ \
"movl $1f,%[prev_ip]\n\t" /* save EIP */ \
"pushl %[next_ip]\n\t" /* restore EIP */ \
__switch_canary \
"jmp __switch_to\n" /* regparm call */ \
"1:\t" \
"popl %%ebp\n\t" /* restore EBP */ \
"popfl\n" /* restore flags */ \
\
/* output parameters */ \
: [prev_sp]
"=m" (prev->thread.sp), \
[prev_ip]
"=m" (prev->thread.ip), \
"=a" (last), \
\
/* clobbered output registers: */ \
"=b" (ebx), "=c" (ecx), "=d" (edx), \
"=S" (esi), "=D" (edi) \
\
__switch_canary_oparam \
\
/* input parameters: */ \
: [next_sp]
"m" (next->thread.sp), \
[next_ip]
"m" (next->thread.ip), \
\
/* regparm parameters for __switch_to(): */ \
[prev]
"a" (prev), \
[next]
"d" (next) \
\
__switch_canary_iparam \
\
:
/* reloaded segment registers */ \
"memory"); \
}
while (0)

5.开始抢占

sched_preempt_enable_no_resched();
if (need_resched())
goto need_resched;

参考文献:https://www.cnblogs.com/xiaomanon/p/4195327.html 

http://dict.youdao.com/w/%E8%BF%9B%E7%A8%8B%E6%A0%87%E8%AF%86%E7%AC%A6/ 

 

 

 

转:https://www.cnblogs.com/1014221651a/p/9008427.html


推荐阅读
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • PyCharm下载与安装指南
    本文详细介绍如何从官方渠道下载并安装PyCharm集成开发环境(IDE),涵盖Windows、macOS和Linux系统,同时提供详细的安装步骤及配置建议。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • QUIC协议:快速UDP互联网连接
    QUIC(Quick UDP Internet Connections)是谷歌开发的一种旨在提高网络性能和安全性的传输层协议。它基于UDP,并结合了TLS级别的安全性,提供了更高效、更可靠的互联网通信方式。 ... [详细]
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 本文详细分析了JSP(JavaServer Pages)技术的主要优点和缺点,帮助开发者更好地理解其适用场景及潜在挑战。JSP作为一种服务器端技术,广泛应用于Web开发中。 ... [详细]
  • QBlog开源博客系统:Page_Load生命周期与参数传递优化(第四部分)
    本教程将深入探讨QBlog开源博客系统的Page_Load生命周期,并介绍一种简洁的参数传递重构方法。通过视频演示和详细讲解,帮助开发者更好地理解和应用这些技术。 ... [详细]
  • python的交互模式怎么输出名文汉字[python常见问题]
    在命令行模式下敲命令python,就看到类似如下的一堆文本输出,然后就进入到Python交互模式,它的提示符是>>>,此时我们可以使用print() ... [详细]
  • 火星商店问题:线段树分治与持久化Trie树的应用
    本题涉及编号为1至n的火星商店,每个商店有一个永久商品价值v。操作包括每天在指定商店增加一个新商品,以及查询某段时间内某些商店中所有商品(含永久商品)与给定密码值的最大异或结果。通过线段树分治和持久化Trie树来高效解决此问题。 ... [详细]
  • Java 中的 BigDecimal pow()方法,示例 ... [详细]
  • 本文总结了汇编语言中第五至第八章的关键知识点,涵盖间接寻址、指令格式、安全编程空间、逻辑运算指令及数据重复定义等内容。通过详细解析这些内容,帮助读者更好地理解和应用汇编语言的高级特性。 ... [详细]
  • 探讨如何高效使用FastJSON进行JSON数据解析,特别是从复杂嵌套结构中提取特定字段值的方法。 ... [详细]
  • 本文详细介绍了如何使用Maven高效管理多模块项目,涵盖项目结构设计、依赖管理和构建优化等方面。通过具体的实例和配置说明,帮助开发者更好地理解和应用Maven在复杂项目中的优势。 ... [详细]
  • 本文详细介绍了如何使用Python编写爬虫程序,从豆瓣电影Top250页面抓取电影信息。文章涵盖了从基础的网页请求到处理反爬虫机制,再到多页数据抓取的全过程,并提供了完整的代码示例。 ... [详细]
  • 探讨一个显示数字的故障计算器,它支持两种操作:将当前数字乘以2或减去1。本文将详细介绍如何用最少的操作次数将初始值X转换为目标值Y。 ... [详细]
author-avatar
李慧颖yynd_613
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有