因为现代操作系统是多处理器计算的架构,必然更容易遇到多个进程,多个线程访问共享数据的情况,如下图所示:图中每一种颜色代表一种竞态情况,主要归结为三类:进程与进程之间:单核上的抢占,多核上的SMP;进程与中断之间:中断又包含了上半部与下半部,中断总是能打断进程的执行流;中断与中断之间:外设的中断可以路由到不同的CPU上,它们之间也可能带来竞态;本章主要是学习的内容如下:原子锁解决什么问题,有什么缺陷自旋锁解决什么问题,原理和应用场景睡眠锁解决什么问题,mutex和Semaphore原因和应用场景各个锁之间的区别和联系1原子操作1.1产生起源我们的程序逻辑经常遇到这样的操作序列:1、读一个位于me
下面是MTK的调度器的演变过程,我们顺着这个过程来学习下历代的调度器在手机系统上的演变过程1.SMP随着计算机技术(特别是以芯片为主的硬件技术)的快速发展,CPU架构逐步从以前的单核时代进阶到如今的多核时代,在多核时代里,多颗CPU核心构成了一个小型的“微观世界”。每颗CPU各司其职,并与其他CPU交互,共同支撑起了一个“物理世界”,这个最常用的时SMP架构,对称多处理器系统,指的是一种多个CPU处理器共享资源的电脑硬件架构。对于此时task分配主要是基于linux2.6中的调度策略CFS,SMP是基于该调度策略平均的balance各个core上的负载情况。如下图所2big.LITTLE对于移
linux进程管理—负载均衡前面主要是学习进程的调度管理,默认都是在单CPU上的调度策略,在O(1)调度后,为了减小CPU之间的干扰,就会为每个CPU上分配一个任务队列,运行的时候可能会出现有的CPU很忙,有的CPU很闲,为了避免这个问题的出现,甚至最极端的情况是,一个CPU的可运行进程队列拥有非常多的进程,而其他CPU的可运行进程队列为空,这就是著名的一核有难,多核围观,Linux内核实现了CPU可运行进程队列之间的负载均衡。1什么是CPU负载提到负载,我们首先会想到命令uptime或者top命令输出系统的平均负载(loadaverage),例如uptime的输出结果uptime14:44:
首先看看维基百科对实时操作系统的定义:实时操作系统(Real-timeoperatingsystem,RTOS),又称即时操作系统,它会按照排序运行、管理系统资源,并为开发应用程序提供一致的基础。实时操作系统与一般的操作系统相比,最大的特色就是“实时性”,如果有一个任务需要执行,实时操作系统会马上(在较短时间内)执行该任务,不会有较长的延时。这种特性保证了各个任务的及时执行。实时操作系统(Real-timeOS)是相对于分时操作系统(Time-SharingOS)的一个概念。在一个分时操作系统中,计算机资源会被平均地分配给系统内所有的工作。在分时系统中,各项任务需要花多长时间来完成,这一点并不
CFS的调度器颗粒是进程,但是在某些应用场景中,用户希望的调度颗粒是用户组,例如下面的用户场景,主要用到服务器场景中A用户要跑一个任务,因为要调用一个库,只能单线程跑B用户跑并行的任务,创建了100个线程跑那么对于CFS会发生什么呢?用户A可使用的CPU时间越来越少。这显然是不公平的对于服务器中,我们希望这两个用户可以平均的分配CPU时间,所以这在调度颗粒为进程的CFS是很难做到的,拥有进程数量多的用户将被分配比较多的CPU资源,因此,我们引入组调度(GroupScheduling)的概念。我们以用户组作为调度的单位,这样用户A和用户B各获得50%CPU时间。用户A中的每个进程分别获得5.5%
CFS是内核使用的一种调度器或调度类,它主要负责处理三种调度策略:SCHED_NORMAL、SCHED_BATCH和SCHED_IDLE。调度器的核心在挑选下一个运行的进程时有可能会遍历所有的调度类别。实际上系统大多数进程通常都是CFS调度类负责处理的,因此为了优化下一个进程的挑选调度器核心会先判断当前进程是否采用了CFS调度策略,若是,则直接调用CFS代码来挑选下一个进程,若不是或CFS代码未能挑选到一个合适的进程,则会调用各个调度类的挑选函数来寻找一个合适的进程。若CFS代码寻找到了合适的下一个运行的进程,则直接返回该进程的实例而不会再遍历调度类。本文将主要关注下列的CFS活动和行为:将一
上一章学习了调度的方式,分为主调度器和周期性调度器,明白了进程切换分为自愿(voluntary)和强制(involuntary)两种。自愿切换:是指任务由于等待某种资源,将state改为非running状态后,主动调用schedule让出CPU任务因为等待IO操作完成或者其它资源而阻塞。任务显式地调用schedule前,把任务运行态设置成TASK_UNINTERRUPTIBLE。保证任务阻塞后不能因信号到来而引起睡眠过程的中断,从而被唤醒。Linux内核各种同步互斥原语,如Mutex,Semaphore,wait_queue,R/WSemaphore,及其他各种引起阻塞的内核函数。等待资源和特
对于调度器,一个很重要的是调度时机问题,在什么情况下,什么时候发生调度?也就是说在什么情况下,什么时候,把现在占用CPU的进程替换下来,根据进程生命周期的图示本章主要关注的是上图中schedule的调度时机,主要有两部分组成,一种是直接,比如上图中的进程等待资源的到位需要睡眠,或者处于其他的原因主动放弃CPU资源;另外一种是时间片耗尽而放弃调度,本章主要是结合源码,学习内容包括schedule()函数什么时候被调用主调度器和周期性调度器如何工作内核对于调度的各个场景是如何工作的1.核心调度器进程调度分为资源和强制调度两种自愿调度:是指由于等待某种资源,将进程的状态改成非RUNNING状态后,如
我们知道当调用schedule函数进行主动调度时,首先会调用通过调度类找到下一个要被调度的进程,然后将当前进程切换状态放入对应调度类的调度队列里面,等待再次被唤醒。而对于被调度的这个队列我们就要对其进行上下文切换,上一章节我们学习了上下文切换的时候的基本原理后,本章主要是学习在最新的内核上基于ARM架构学习完整的进程上下文切换的过程,本文的内核版本号为linux4.9.88。1context_switch代码分析在操作系统中把当前正在运行的进程挂起并恢复以前挂起的某个进程的执行,这个过程叫进程切换或者进程上下文,linux内核实现进程切换的核心代码在kernel/sched/core.c中有一
学习了操作系统的基本原理和调度的相关知识,开始学习进程的上下文切换,本章主要要了解一下内容:用户级和内核级上下文切换的原理前面章节学习了进程由哪些部分组成,那么进程自身的上下文切换有哪些部分组成了何时发生进程的上下文切换进程间切换的线程和断点保存在哪里,结合linux0.11讲解进程切换的五部曲1用户级线程上下文切换上文([进程管理(二)----线程的基本概念]((9条消息)进程管理(二)----线程的基本概念_奇小葩-CSDN博客))中,我们讨论了何为多线程,而线程又分用户级线程和内核级线程,这节我们先来讨论一下何为用户级线程以及用户级线程的底层原理。用户级线程的切换是由我们用户来主动控制的
在O(n)和O(1)调度器中,时间片是一个很重要的概念,它决定了一个任务能够运行多长时间而不被抢占。对于内核,时间片的机制是这样的,每当系统时钟中断来临,调度器从当前任务的时间片中减去一个时钟周期,直至时间片耗完,这个时候当前任务返回用户空间会,换成其他任务执行,所以时间片的划分是调度器设计上重要的问题。为了解决上面问题,以往的调度器把动态优先级和时间片绑定在一起,高优先级的进程获得长的时间片,而低优先级的进程获得短的时间片,调度器优先调度具有时间片长的任务。但是这样分配不合理,时间片长的进程并不一定是当下最需要CPU时间的任务。但是这样的算法还是存在一定的问题高优先级任务的应用会比低优先级任
上一章学习了O(n)调度器的设计,以及它的核心算法,其主要思路如下:O(n)调度器采用一个Runqueue运行队列来管理所有可运行的进程,在主调度schedule函数中选择一个优先级最高,也就是时间片最大的进程来运行,同时也会对喜欢睡眠的进程做奖励,去增加此类进程的时间片当Runqueue运行队列中无进程可选择时,则会对系统中所有的进程进行依次重新计算时间片的操作针对这个调度,虽然简单,但是也存在去缺点时间复杂度为O(n),当系统中就绪队列中的进程数目增多,那么调度器的运算量就会线性增长,为每个进程计算其时间片的过程太耗费时间多核处理器扩展问题,多处理器的进程在同一个就绪队列中,因此调度器对它
1调度器概述任务调度器是操作系统中一个很重要的功能部件,主要功能是把系统中的task调度到各个CPU上去执行,满足如下的性能需求:调度器必须是公平的:(对于分时的进程,每个任务都应该有机会执行,不能饿死,保证每个进程得到合理的CPU时间)快速的进程响应时间:(对于交互式进程,需要和用户进行交流,因此对调度延迟比较敏感)高系统的吞吐量:(对于批处理进程进程,属于那种在后台的默默奉献的,因此它更注重吞吐量的需求)功耗要小:(对于移动式终端,功耗的需求其实一直以来都没有特别被调度器重视,当然在linux大量在手持设备上应用之后,调度器不得不面对这个问题了)对于操作系统,为了达到这些设计目标,调度器必
1linux进程分类当涉及有关调度的问题时,传统上把进程分类为”I/O受限(I/O-dound)”或”CPU受限(CPU-bound)”.类型别名描述示例I/O消耗性I/O-Bound频繁进行I/O处理,并花很多时间等待I/O操作完成键盘等待输入、写磁盘CPU消耗性CPU-Bound花费大量时间进行数字计算,一直占用CPU大量数学计算另外一种分法把进程区分成三类类型描述示例交互式进程此进程经常需要与用户进行交互,因此需要花费很多时间等待键盘、鼠标、触摸屏等操作,当接受了用户的输入后,进程必须被很快唤醒,否则用户就会抱怨系统卡顿,这类进程的特点就是响应时间越短越好shll,文本编辑程序和图形应用
1.为什么需要调度进程调度的概念比较简单,我们假设在一个单核处理器的系统中,同一时刻只有一个进程可以拥有处理器资源,那么其他的进程只能在就绪队列中等待,等到处理器空闲之后才有计划获得处理器资源来运行。在这种场景下,操作系统就需要从众多的就绪进程中选择一个最合适的进程来运行,这个就是调度器需要做的事情。作为一个通用的操作系统,需要兼顾各种类型的进程,包括交互式进程、批处理进程、实时进程等。其特征如下:交互式进程:与人机交互的进程,例如鼠标、键盘、触摸屏等相关的应用,这类进程的特点是系统响应时间越短越好,否则用户就会抱怨系统卡顿批处理进程:此类进程默默运行,可能会占用比较多的系统资源,对响应时间没
在Linux系统中,前面我们接触了用户进程或用户进程,但是在实际的也是有内核线程的存在,例如我们在内存管理章节中熟悉的内存回收进程kswapd,软中断等。本章主主要包括内核线程的创建和结束的完整过程。1.Linux线程管理Linux内核在启动的时候,是没有线程的概念,当内核初始化完成后将启动一系列的线程,之后,CPU执行流就绑定在一个线程中运行,内核线程和用户线程的区别如下图所示:每一个线程创建之初都是内核线程;创建之后如果与具体的进程上下文绑定,那线程就成了用户线程如果绑定的内核线程,那么执行内核线程的服务代码,对于内核线性是没有地址空间的概念,准确的来说是没有用户地址空间的概念,使用的是所
fork,vfork,clone都是linux系统调用,这三个函数分别调用sys_fork,sys_vfork,sys_clone,最终都会调用到do_fork函数。差别就在于参数的传递和一些准备工作的不同,上一章节已经详细学习了fork的流程,本章主要专注学习这三个接口函数的使用方法和差异点。1进程的四要素linux进程所必须的四个要素:程序代码,有一段程序供其执行:代码不一定是进程专有,可以与其它进程共享有自己专用系统堆栈空间:有进程控制块(task_struct):有独立的存储空间:以上4条,缺一不可。如果缺少第四条,那么就称其为"线程"。如果完全没有用户空间,称其位
上一章学习了进程的创建,在用户空间可以使用fork接口来创建一个用户进程,或者使用clone接口来创建一个用户线程,它们在内核空间都会调用do_fork函数来实现,但是我们有两个疑问未得到解答fork接口,它可以是父、子进程都会返回,那么它会返回两次,其中父进程的返回值是子进程的PID,而子进程返回0,这个过程是如何的呢?子进程第一次返回用户空间时,它的返回在哪里呢?进程如何完成终止1.fork的执行过程当调用_do_fork()函数创建子进程后,子进程会加入到内核的调度器中,在调度器中参与调度。那么子进程在稍后的某一时刻得到调度和执行,因此fork函数也会有两次返回,一次是父进程的返回,另外
对于进程,除了0号进程,其他的所有进程(无论是内核线程还是普通线程)都是通过fork出来的,而创建进程是在内核中完成的要么在内核空间直接创建出所谓的内核线程要么是应用空间通过fork/clone/vfork这样的系统调用进入内核,再内核空间创建同上一章,我们完成的分析了fork的整个过程,fork分为两部分,一部分是初始化进程控制块,另外一部分是进程管理部分。本章的重点学习以下内容子进程如何构建自己的内存管理父子进程如何共享地址空间写时复制如何发生1.写时复制技术在传统的unix操作系统中,创建新建成时就会复制父进程所拥有的所有资源,这样进程的创建就变的很低效。其原因如下每次创建子进程时,都要
在最新的版本的POSIX标准中,定义了进程创建和终止的操作,进程创建包括fork()和execve(),进程终止包括wait(),waitpid(),kill()以及exit()。Linux系统为了提高效率,把POSIX标准的fork()扩展为vfork和clone。前面一章我们学习了用GCC将一个最简单的程序(如helloworld程序)编译成ELF文件,在shell提示符下输入该可执行文件并且按回车后,这个程序就开始执行了。起始这里shell会调用fork()来创建一个新进程,然后调用execve()来执行这个新程序。该函数负责读取可执行文件,将其装入子进程的地址空间并开始执行,这时候父子
我们常见的一个应用场景是,在shell中输入命令,然后等待命令返回。如果以进程创建和终止的角度来看,shell首先会读取命令,解析命令,创建自建成并执行命令,然后父进程在等待子进程终止,其如下图示对于用户空间的一个进程,首先我们需要编写对应的.c/.h文件,然后经过编译器编译成二进制的可执行文件,装载到硬盘上开始执行,最终生成用户进程,这里面涉及到很多细节,本章主要针对这些内容进行深入学习,主要包括以下内容对于程序员如何从文本文件到可执行的程序操作系统如何完成对于可执行文件加载1程序的编译当我们把程序写完了,是否就万事大吉了,可是CPU是不能执行文本文件里的指令,CPU需要能够执行机制指令,比
我们知道,对于内核提供的进程管理子系统,将来肯定是要运行各种各样的进程,对于我们做Linux内核开发的同学来说,大家熟悉Linux下有3个特殊的进程,其主要内容如下:Idle进程(PID=0),本章主要讲解进程0是什么?Init进程(PID=1),本章主要讲解进程1是什么?kthread(PID=2),本章主要讲解进程2是什么?1进程初始化(0号进程)内核的启动从入口函数start_kernel()开始;在init/main.c文件中,start_kernel相当于内核的main函数;这个里面是各种各样初始化函数,用来初始化各个子系统。对于操作系统,开机的时候首先会创建第一个进程,也就是唯一一
在进程创建时,内核会为进程创建一系列数据结构,其中最重要的就是上章学习的task_struct结构,它就是进程描述符,表明进程在生命周期内的所有特征。同时,内核为进程创建两个栈,一个是用户栈,一个是内核栈,分别处于用户态和内核态使用的栈。本章主要包括以下内容内核栈的概念thread_info的用途1内核态内核栈在每个进程的生命周期内,经常会通过系统调用(SYSCALL)或者中断进入内核。在执行系统调用后,这些内核代码所使用的栈并不是原先用户空间的栈,而是一个内核空间的栈,这个栈被称作进程的“内核栈”。由用户态切换到内核态,内核将用户态时的堆栈寄存器的值保存在内核栈中,以便从内核栈切换回进程栈时
Linux内核涉及到进程和程序的所有算法都围绕一个名为task_struct的数据结构建立,对于Linux内核把所有进程的进程描述符task_struct数据结构链成一个单链表(task_struct->tasks),该数据结构定义在include/sched.h中。这是系统的中主要的一个数据结构,其进程管理task_struct的结构图如下1任务ID每一个任务都应用有一个ID,作为这个任务的唯一标识。pid是进程的ID,tgid是线程的groupID,group_leader是进程组的组长。任何一个进程,如果只有主线程,那么pid是自己,tgid也是自己,group_leader指向的
在学习Linux进程管理的知识之前,我们来从操作系统的发展说起,在每个阶段的发展历程中,每一个新的技术都是解决当前的技术瓶颈。操作系统的进程管理的发展和解决什么问题早期分时系统的内核实现,以linux0.11源码为主1.我从哪里来人工控制时代在20世纪50年代中期,出现了人们历史上的第一代计算机,那个时候还需要程序员亲自手工操作穿孔的纸带卡片,当时的计算机一切数据和操作指令都是通过穿孔纸带输入的。穿孔纸带利用一排孔表示一个字符,用穿孔或不穿孔表示1和0,来将指令和数据导入内存。当时,所有的程序都挨着排好队,等待管理员将其取出,换上下一个开始执行,久而久之,程序员也开始抱怨,排队十分钟,执行三秒
在操作系统中引入进程的目的,是为了使多个程序能并发执行,以提高资源的利用率和系统的吞吐量,那么在操作系统中再引入线程,则是为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发行。本小节主要是学习以下内容什么是线程,为什么要引入线程与进程相比,线程有哪些特点用户线程内核线程1.线程的引入我们以软件生活中,常见的应用为例,我们要编写一个MP4的播放软件,其核心功能模块有以下三个:磁盘文件中读取MP4数据对视频数据进行解码把解码后的视频数据进行播放对于目前采用进程的方法,我们可以采取两种方案方案一:单进程的实现方式方案存在的问题:播放的视频是否能够连贯各个函数之间不是并发执行,影响资源的使
对于内存管理告一段落,今天正式开始进入内存管理的章节,首先从基础学习,主要是包括进程线程基础概念篇,主要包括以下内容为什么要引入进程的概念进程的概念,进程和程序的联系和区别进程控制块进程的状态模型1.为什么要引入进程早期的计算机一次只能执行一个任务,采用批处理的方法,由监督系统完成作业的切换,使得作业一个接一个的被处理,如下图所示首先,由监督器将磁带上的第一个程序装入内存,并把运行的控制权交给作业当该作业批处理完成时,又把控制权交还给监督程序,再由监督程序把磁带上的第二个作业调入内存,计算机系统就这样自动地一个任务有一个任务的进行处理,直到将磁带上的所有作业全部完成对于该批处理系统内存中仅有一