目录
前面所讲的是一些Linux基本概念、命令行操作和基本的系统管理技能。已经了解Linux的文件系统、用户和权限管理以及常用工具和命令。下面来学习体系结构和进程管理。
一、认识冯诺依曼体系结构
1.1 概念
冯·诺依曼体系结构(Von Neumann architecture)是一种计算机体系结构的基本框架。它是现代计算机设计的基础,我们常见的计算机,如笔记本、不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
1.2 组成
具体来说,冯诺依曼体系结构包括以下几个部分:
- 存储器:存储器包括计算机的主存储器和辅助存储器,用于存储数据和程序。
- 运算器:运算器是计算机的核心部件,用于执行算术和逻辑运算。
- 控制器:控制器用于控制计算机的执行流程,包括从存储器中读取指令、解码指令、执行指令等。
- 输入输出设备:输入输出设备用于与外部环境进行交互,如键盘、鼠标、显示器、打印机等。
注:
- 这里的存储器指的是内存。硬盘、磁盘等属于外设。
- 计算机里面的几乎所有的设备,都有数据存储的能力!只不过存储能力有大有小。
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)。
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
1.3 存储分级
以下是计算机存储器的金字塔结构:
金字塔结构以CPU为中心,越往上,越靠近CPU,存储效率越高,造假越贵,反之亦然。
1.4 有关冯诺依曼的问题
为什么要谈存储?
原因:
- 因为设备交互的本质就是将数据从一个拷贝到另一个设备,所以存储的效率就决定了拷贝的效率,也就决定了设备交互的效率。
为什么当代的CPU不直接与外设交互?
原因:
- CPU这个设备,它的处理数据的速度是非常快的,然后是内存,然后是各种外设(硬盘)。根据木桶原理,如果CPU直接与外设交互,由于CPU速度很快,外设速度很慢,外设就成了木桶较短的那个板子,整机的效率会和外设的速度保持一致,导致计算机整体的效率降低。
- 如果CPU与内存交互,内存的速度相较硬盘更快,相当于把木桶最短的板子提高了,即计算机整体的效率提高了。
为什么不全用速度更高的寄存器呢?
原因:
- 因为很贵且没必要。如果我们全部用便宜的存储介质,计算机价格是便宜了,但也基本用不了。所以当代的计算机的要求就是效率不差,并且不贵。这就需要在数据层面上,CPU优先要和内存直接打交道。这也是基于冯诺依曼体系结构的计算机的本质:用比较少的钱,做出来效率不错的计算机!
程序在运行前,必须先加载(拷贝)到内存。
原因:程序=代码+数据,最终都要CPU来执行处理。CPU需要先读取到这些代码和数据,而CPU是与内存进行数据交换的,所以程序需要先加载到内存,加载到内存后,CPU可以通过指令寄存器获取指令并执行。形成的exe本质也是一个文件,只能在磁盘(外设)中保存,也需要从内存传输出去。(输入设备的数据传到内存时,哪个数据放在哪块内存,是由操作系统来决定的。)
二、操作系统
2.1 概念和功能
操作系统是一种系统软件,它是计算机系统中的核心部分之一,能够管理计算机硬件和其他软件资源,为应用程序提供运行环境和服务。
功能:操作系统将软硬件资源管理好(手段),给用户提供良好的(稳定、高效、安全、易用)使用环境(目的)。
2.2 如何理解操作系统的 "管理"
将管理总结成六个字:先描述再组织。
先描述:把被管理对象以结构体形式描述出来。
再组织:把描述的结构体定义的对象按照特定的数据结构组织起来。
任何管理工作都可以经过这六个字进行计算机级别的建模。例如C语言的小项目、数据结构、C++,开始写代码的时候,一定总是要先写struct/class,这个过程就是先描述再组织。
计算机的层次结构
先来看最下面的三层:操作系统、驱动程序和底层硬件。
- 操作系统:操作系统是计算机系统的核心软件,负责管理和控制计算机的硬件资源和软件资源。它提供了一个抽象的接口,使得应用程序可以方便地与硬件进行交互。
- 驱动程序:驱动程序是一种特殊的软件,用于与特定硬件设备进行通信和控制。不同类型的硬件设备需要相应的驱动程序来进行管理和操作,例如打印机驱动程序、显卡驱动程序等。
- 底层硬件:底层硬件是指计算机系统中的实际物理设备,包括内存、硬盘、显示器、键盘、鼠标等。底层硬件是计算机系统的基础,提供计算和存储能力,以及与外部世界的交互接口。
这里的管理者是操作系统,被管理者就是底层硬件。操作系统对数据做管理,不是要将硬件怎么样,而是对硬件中的数据做管理。操作系统不会直接和硬件打交道,所以才有了驱动程序。驱动程序来获取底层各个硬件的数据,然后操作系统内就可以拿到底层硬件的数据。
为了对这些杂七杂八的数据进行管理,操作系统就需要先描述再组织。
操作系统需要对所有的硬件按照struct结构体将硬件信息描述出来(Linux操作系统使用C语言写的),将每种数据采集上来用先描述把值填到结构体。然后操作系统对所有设备形成单链表或者双链表,然后操作系统对设备的管理就变成了对各个硬件的数据结构对象的链表结构的增删查改了。
操作系统内部,一定会存在大量的数据对象和数据结构!
一个设备被操作系统管理的含义:操作系统内存在管理这个设备数据结构的对象。
总之,操作系统、驱动程序和底层硬件之间形成了一个层次化的关系,操作系统作为核心软件管理和控制硬件资源,依赖于驱动程序来与底层硬件进行通信和控制。驱动程序则负责将操作系统的请求转化为硬件能够理解的指令和信号,并将硬件的响应传递给操作系统。这种协作关系使得计算机系统能够正常运行,并提供各种功能和服务。
注:
- 每一个硬件都需要有与之匹配的驱动程序存在(内存和CPU不需要)
- 驱动程序大部分是操作系统自带的。
操作系统的管理核心是:
1.进程管理
2.内存管理
3.文件/IO管理
4.驱动管理
无论是操作系统的哪一种管理,都需要把概念和上面的概念(先描述再组织)适配。
2.3 操作系统的用户、系统调用和库函数概念
- 操作系统将软硬件资源管理好(手段),给用户提供良好的(稳定、高效、安全、易用)使用环境(目的)。
- 广义上用户是使用计算机的所有人,狭义上是指开发者。
先从开发者理解使用。因为需要先被开发者使用。
但即便是开发者,也不能直接与操作系统进行交互(尽管从技术方面可以实现),因为群众里面有坏人。操作系统不相信我们,但同时也不得不给我们提供服务。(类似于银行)
于是操作系统实现了一些系统调用接口,使得用户获取操作系统的软硬件信息,只能通过这些接口/函数。这样就可以防止操作系统的数据被盗取、篡改,提高了安全性。
例如printf()函数打印在硬件上,不是直接调用硬件打印的,而是通过一些系统调用接口实现的,贯穿了整个操作系统的层次结构。
总结:
- 一般一个用户想访问非常底层OS数据或者访问硬件,必须贯穿整个层状结构!
- 贯穿整个层状结构,就注定用户必定要调用系统调用!
- 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
- 系统调用,使用起来比较麻烦,站在用系统的人(普通人)来看,系统必须提供一些更易用的程序,于是就有了外壳程序(shell,图形化界面)。外壳程序是用C/C++语言写的,一定会调用系统调用!
- 站在开发的人的角度,可以直接调用系统接口,但是系统调用在使用上,功能比较基础,对用户的要求相对也比较高,为了更方便的使用,人们将部分系统调用封装成为各种各样好用的函数,打包形成 库!,从此所有的开发者、用很多功能不用自己去写了,而是直接调用库函数即可!(库由专门的人来做,专业的人做专业的事情,提高开发效率降低开发成本)
三、进程
3.1 基本概念
进程是计算机中正在运行的程序的实例。
- 它包含了程序的执行状态、内存使用情况、打开文件等信息。
- 每个进程都有自己的独立内存空间和资源,它们相互隔离并且可以并发地执行。
进程和程序的区别:
- 程序是一组指令或代码的集合,用于实现某种特定的计算功能,存放在硬盘。程序本身并不会占用计算机的资源或执行任何操作,只有在被操作系统加载、拷贝到内存中并被执行时,才会成为运行中的进程。
- 进程是正在执行的程序的实例。进程包含了程序的执行环境、状态信息、系统资源等,可以独立地运行、调度和控制。每个进程都有自己的内存空间、寄存器、栈、堆栈等,可以相互隔离并且可以并发地执行。
3.2 描述进程-进程控制块PCB
进程 = 可执行程序 + 内核数据结构(PCB)
操作系统内可能会同时存在非常多的“进程”,为了方便操作系统管理所有的"进程",需要先描述,在组织!操作系统是用C语言写的,进程用struct结构体描述进程信息。然后操作系统对进程的管理就变成了对PCB的管理。和进程的可执行程序没有关系。
structXXX
{
//进程的各种属性!
struct XXX *next
}
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
struct PCB
{
// id
// 代码地址&&数据地址// 状态
// 优先级
// 链接字段
struct PCB *next}
task_struct 内容分类:
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据。包括了进程在被调度之前的寄存器状态,以及在进程切换时需要保存和恢复的寄存器值。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
task_struct的格式如下:
struct dlist { struct dlist *next; struct dlist *prev; } struct task_struct { //进程的各种属性 struct dlist list; }
查找 task_struct 结构体的首地址:
(struct task_struct*)( (int)&list - (int)&(task_struct*)0->list )再经过 #define curr(list) (struct task_struct*)( (int)&list - (int)&(task_struct*)0->list )
就可以直接通过curr(list)->pid找到进程的pid。
将结构设置的这么复杂的原因:一个进程的PCB不一定只放在一张链表里。
struct task_struct { dlist list;//系统所有进程所在的链表 dlist queue;//同时这个进程可以在队列中 也可以在其他数据结构中 }
3.3 进程的查看
查看进程方式1: 进程的信息可以通过ps ajx 进程名/进程id 命令显示所有进程的详细信息。
可以结合管道文件和grep命令查看指定进程;
可以用head -1 显示进程列表的标题行;
例如:ps ajx | head -1 && ps ajx | grep mybin
- 进程id(PID) 父进程id(PPID)
- 命令行中,父进程一般是命令行解释器(bash),所以命令行启动的进程都是bash的子进程。
- 可以通过getpid()和getppid()函数获得当前进程的id和父进程id。(需要包含unistd.h库。函数返回值类型为pid_t,无符号整型)
- 启动进程,本质就是创建进程,一般是通过父进程创建的,从父进程复制若干属性。
查看进程方式2: ls /proc/ proc是一个动态的目录结构,存放所有存在的进程,目录的名称;proc是process的简称。
ls /proc/进程id -l 可以查看指定进程的详细信息,
其中存放着它自己的可执行程序的地址的符号链接: cwd 表示当前工作目录,简称当前目录。所以一个进程可以找到自己的可执行程序。
默认情况下,进程启动所处的路径,就是当前路径,但可以通过
int chdir(const char *path) 系统调用,更改一个进程的工作目录。(unistd.h)
并且把生成的可执行程序放在目标目录中。
3.4 进程的创建
通过系统调用创建子进程——fork
启动一个进程,如何理解这种行为?其实本质就是系统多个一个进程,OS要管理的进程也就多了一个,进程 = 可执行程序+task_struct对象(内核对象),创建一个进程,就是系统中要申请内存,保存当前进程的可执行程序+task_struct对象,并将task_struct对象添加到进程列表中!
pid_t fork(void);
32761就是bash(命令行解释器)
注:
- fork之前,只有父进程执行代码,fork之后,父子进程都要执行后续代码。
- fork有两个返回值。
执行成功,将子进程的PID返回给父进程,0返回给子进程。
执行失败,将-1返回给父进程,不创建子进程。 - 通过判断fork的返回值,判断谁是父谁是子,从而让他们执行不同的代码片段。
在之前的学习中,我们没有办法同时进入两个死循环,也没有办法同时执行两个死循环,我们期望使用fork之后,可以实现同时进入和执行两个死循环,并实现目标操作。
- fork干了什么事?
fork创建子进程,操作系统以父进程为模板,为子进程创建PCB,但是创建出来的子进程是没有代码和数据的,要和父进程共享相同代码和部分数据!所以fork之后,父子进程会执行一样的代码。(fork之前的代码子进程也能看到,但是子进程不会再从头执行,因为子进程也继承了父进程的读写位置)- 为什么fork会有两个返回值?
fork函数的过程:
·找到父进程的pcb对象
·malloc(task struct)
·根据父进程pcb,初始化子进程pcb
·让子进程的pcb指向父进程的代码和数据 让子进程放入调度队列中,和进程一样去排队
....//一系列操作
·return XXX
注意:return执行之前,fork的一系列操作已经完成了创建子进程,并将子进程放入调度队列中运行,但是前面又说fork之后代码共享,return语句也是代码,所以return也要被共享,父子进程都要执行return语句。
(真实的情况是操作系统通过一些寄存器做到返回值返回两次,以上讲解便于理解)- 为什么fork的两个返回值,会给父进程返回子进程pid,给子进程返回0?
为了区分父子进程,需要在返回值上有所区别。 如果返回的是正整数,父进程可以根据这个 pid 来识别子进程,并进行相关操作。 如果返回的是 0,子进程通过这个返回值可以判断自己是子进程,并且开始执行它自己的逻辑。
对于任何一个子进程,它的父进程是唯一的,而父进程可以根据这个 pid 来识别子进程。- fork之后,父子进程谁先运行?
创建完成子进程,只是一个开始,创建完成子进程之后,系统的其他进程、父进程、和子进程,接下来要被 调度执行 的。当父子进程的PCB都被创建并在 运行队列中排队的时候,哪一个进程的PCB先被选择调度,那个进程就先运行!!
这个结果用户不能确定!由操作系统自主决定!(由各自PCB中的调度信息(时间片,优先级等)+调度器算法共同决定)- 如何理解同一个变量,会有不同的值?
进程之间运行的时候,是具有独立性的!无论是什么关系!
进程的独立性,首先是表现在有各自的PCB进行之间不会互相影响。代码本身是只读的,不会影响。数据,父子是会修改的,所以各个进程必须想办法各自私有一份数据(写时拷贝)。
fork() -> return X -> id :返回的本质是写入,id是父进程定义的变量,保存的是数据,返回的时候,发生了写时拷贝,所以同一个变量会有不同的值!
3.5 其它的进程操作
终止进程:kill 命令,向进程发送信号,以控制其行为或终止进程。
常用选项:kill -9 进程id 强制终止目标进程。
3.6 进程状态
可以先简单理解进程状态就是PCB中的一个字段(例如int status),所谓的状态变化,就是修改状态变量(status)。进程状态字段通常用于记录进程当前的状态,以便操作系统在调度和管理进程时能够了解其状态。不同的操作系统可能会有不同的字段名称和状态定义,但进程状态通常都是 PCB 中的一个重要字段之一。
3.6.1 运行状态
考虑单CPU的情况:在一般的操作系统内部,一个CPU都会在系统层面上配备一个属于该CPU的调度队列/运行队列,该运行队列也是一个结构体类型,里面包含若干节点和pcb *head等,一个程序被加载到内存时,操作系统会创建相应的pcb,如果这个进程要运行,就需要将它的pcb链入到运行队列中。所以操作系统要调度进程运行时,只需要在运行队列中寻找即可。
只要在运行队列中的进程,状态都是运行状态。
(表示“我”已经准备好了,可以随时被调度)
3.6.2 阻塞状态
进程状态变化的本质:1.更改pcb status整数变量。 2.将pcb 链入不同的队列中。
阻塞状态是指进程由于某些原因而无法继续执行,暂时处于等待状态的情况。在阻塞状态下,进程通常会暂停执行,直到某些条件得到满足后才能继续执行。这些条件可能包括等待 I/O 操作完成、等待某些资源可用、等待信号、等待子进程状态改变等。
例如一个进程如果需要从键盘中读取一个数据,而用户又迟迟没有输入,那么这个进程将会处于阻塞状态。操作系统有非常多的队列,状态变化过程就是修改一个进程pcb的状态变量,并将该pcb链入其他队列。
当一个进程阻塞时发生的现象:进程卡住了,pcb没有在运行队列中,状态不是running,CPU不调度该进程。
3.6.3 挂起
如果一个进程当前被阻塞了,注定了这个进程在它所等待的资源没有就绪的时候,该进程是无法被调度的。如果此时,恰好OS内的内存资源已经严重不足了,怎么办?
操作系统会将该进程的代码和pcb交换到磁盘中,这会节省一部分内存空间供操作系统使用。
将内存数据进行置换到外设,是针对所有阻塞进程的。
不用担心慢的问题,因为这个是必然的,主要关心的是,让OS继续执行下去。
swap分区--OS内的数据会被交换到这里。
当进程被OS调度,曾经被置换出去的进程代码和数据,又要重新被加载进来。
以上操作全部由OS自动执行。
3.7 Linux中进程的状态
上面讲的是操作系统这一学科的进程状态,Linux中实际的进程状态存在一些不同。
3.7.1 "R" 运行状态
在Linux操作系统中,进程的"R"状态表示进程正在运行或在运行队列中等待。
当一个进程处于"R"状态时,它可能正在执行以下操作之一:
1. 正在执行:进程正在CPU上执行其指令。
2. 可运行:进程已经准备好执行,但由于其他进程正在运行,它正在等待调度器给它分配CPU时间。
3. 运行队列中的等待:进程可能因为系统负载较高,导致多个进程竞争CPU时间,因此它在运行队列中等待。
在Linux中,"R"状态是进程的一种动态状态,进程可以在"R"状态和其他状态(如"S"睡眠状态或"D"不可中断睡眠状态)之间切换。例如,一个进程可能会因为等待某些资源(如输入/输出操作完成)而进入睡眠状态,当这些资源变得可用时,进程将被唤醒并返回到"R"状态。
用"ps"命令查看进程的状态时,可能出现"R+"状态。其中"+" 表示进程是前台进程组的成员。
前台进程和后台进程:
前台进程是指当前用户正在与之交互的进程。它是用户直接控制和输入的进程,通常是终端(shell)中正在运行的程序。用户可以通过键盘输入与前台进程交互,并且前台进程的输出会直接显示在用户的终端上。
前台进程相对的是后台进程。后台进程是那些启动后不在用户当前工作终端上接收输入和显示输出的进程。后台进程通常在执行不需要用户交互的任务时使用,例如,在后台运行编译作业或下载文件。
直接以后台方式运行命令: 在命令后面加上&符号,可以直接将命令以后台方式运行。例如:command &。在Linux中,每个终端会话都有一个前台进程组,其中包含一个或多个进程。这些进程接收来自用户的输入信号(如键盘输入),并且它们的输出会显示在用户的屏幕上。只有前台进程组中的进程才能读取终端输入和向终端写入输出。
3.7.2 "S" 休眠状态
在Linux操作系统中,进程的"S"状态表示进程正在睡眠(Sleep)(浅度睡眠),即进程正在等待某个事件的发生。浅度睡眠会对外部信号做出相应。
进程因为等待某些资源或事件而睡眠,但可以被信号唤醒。例如,一个进程可能在等待用户输入或等待磁盘I/O完成。
3.7.3 "D" 休眠状态
在Linux操作系统中,进程的"D"状态表示进程处于不可中断睡眠状态,也称为磁盘睡眠状态(深度睡眠)。当一个进程处于"D"状态时,它通常在等待某些不可中断的磁盘I/O操作完成,如直接磁盘访问或等待某些硬件设备的响应。专门针对磁盘设计的。
不可中断睡眠状态意味着进程不会响应任何信号,包括用于终止进程的信号。这是因为进程正在执行对系统稳定性至关重要的操作,中断这些操作可能会导致系统数据损坏或硬件故障。
"S"状态和"D"状态都是阻塞状态。
3.7.4 "T" 停止状态
在Linux操作系统中,进程的"T"状态(Stopped)表示进程被信号停止。
当一个进程接收到'SIGSTOP'、'SIGTSTP'(通常是Ctrl+Z组合键)、'SIGTTIN'或'SIGTTOU'信号时(可以用”kill -信号 PID"执行命令),它将进入停止状态,处于停止状态的进程暂时不会被执行,但它的资源(如内存和文件描述符)通常仍然被保留。
停止的进程可以通过发送`SIGCONT`信号来恢复执行。一旦收到`SIGCONT`信号,进程将回到可运行状态("R"状态),并继续执行。
在Linux操作系统中,进程的"T"状态(tracing stop)表示debug程序时,追踪程序,遇到断点,进程暂停。
3.7.5 "Z" 僵尸状态
在Linux操作系统中,进程的"Z"状态表示进程是一个僵尸进程(Zombie)。僵尸进程是指已经结束执行但仍然在进程表中占据一个位置的进程。
要创建一个进程,一定是因为需要完成某种任务。进程=内核PCB+进程的代码和数据,它们都要占据内存空间。进程退出的核心工作之一:将 PCB && 自己的代码和数据释放掉。进程在退出的时候,要有一些退出信息,表明自己把任务完成的怎么样。当一个进程在退出的时候,退出信息会由OS写入到当前退出进程的PCB中,可以允许进程的代码和数据空间被释放,但是不能允许进程的PCB被立即释放!因为要让OS或者父进程,读取退出进程的PCB中的退出信息,得知子进程推出的原因。
子进程已经执行完毕并退出,但其父进程尚未通过调用wait()或waitpid()系统调用来读取其退出状态的情况下,OS必须维护这个进程的PCB结构,此时该进程的状态即为僵尸状态。
通常,父进程应该负责回收其子进程的状态信息。如果父进程没有正确地回收子进程,那么子进程就会一直处于僵尸状态。
虽然单个僵尸进程对系统的影响很小,但如果系统中存在大量的僵尸进程,它们可能会耗尽进程表的空间(内存泄漏),影响新进程的创建。
3.8 孤儿进程
在Linux操作系统中,孤儿进程是指父进程已经结束,但其子进程仍然在运行,并且这些子进程的父进程变成了"init"进程(在Linux系统中,"init"进程的PID是1)或"systemd"(在较新的Linux发行版中,"systemd"通常作为第一个进程运行),子进程的PPID变为1。
这些进程会自动接管孤儿进程,并在孤儿进程结束时回收它们的状态信息,因此这些进程不会变成僵尸进程。孤儿进程的管理通常不需要用户或管理员干预。
孤儿进程通常是由于父进程的异常终止或正常终止而创建的。
孤儿进程与僵尸进程的区别:孤儿进程是仍然在运行的进程,它们的父进程已经结束。僵尸进程是已经结束但尚未被其父进程回收的进程。
四、优先级
4.1 概念
进程的优先级(priority)是PCB中的一个int字段,确定了cpu资源分配的先后顺序,数值越小,优先级越大,优先级高的进程有优先执行权利。
优先级 vs 权限
权限决定能还是不能得到某种资源。优先级已经保证能够得到申请的资源,只不过需要等一等。
使用 ps -la 显示进程优先级
4.2 优先级的调整
Linux进程的优先级数值范围:60~99
Linux中默认进程的优先级都是:80
Linux是支持动态优先级调整的,但不运行直接修改PRI的数字。
Linux 进程pcb中存在一个nice值,来达到修改优先级的目的。nice值:进程优先级的修正数据。
PRI(新) = PRI(old) + nice 。 old PRI 每次修改都是从80修改的。
nice值的范围是从-20到19,其中-20是最高优先级,19是最低优先级。
默认情况下,进程的nice值是0。只有特权用户(通常是root)才能设置负的nice值,从而提高进程的优先级。
优先级可以通过以下方式来调整:
- nice命令:用于调整普通进程的nice值,从而改变其静态优先级。
例如,nice -n 10 command会将command命令的nice值设置为10。- renice命令:用于改变正在运行的进程的nice值。
例如,renice 5 -p 1234会将进程ID为1234的进程的nice值设置为5。
为什么要把优先级限定在一定的范围内?
OS 调度的时候,要较为均衡的让每一个进程都要得到调度!
如果优先级不限定在一定的范围,容易导致优先级较低的进程,长时间得不到CPU资源,这种情况叫做"进程饥饿"
五、其他概念
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,由于CPU在任一时刻只能执行一个进程的指令,因此进程必须竞争CPU时间,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。操作系统中通过调度算法来管理这个竞争过程,这些算法会根据进程的优先级、等待时间、执行特性等因素来决定哪个进程将获得CPU时间。
- 独立性: 每个进程都应认为自己独占CPU和内存资源,即使实际上这些资源是在多个进程间共享的。操作系统通过提供虚拟化技术,如虚拟内存,使得每个进程都拥有自己的地址空间,这样进程间就可以互不干扰地运行。
- 并行: 当系统中有多个CPU或者多核处理器时,可以同时执行多个进程,这种情况下我们说进程是并行运行的。每个CPU可以独立地执行一个进程的指令,从而实现真正的并行处理。
- 并发: 在单个CPU系统中,通过操作系统的进程调度,可以给用户一种错觉,即多个进程在同时运行。操作系统快速地在进程间切换,每个进程都得到一小段时间的CPU执行时间,多个进程都得以推进,从而在宏观上看起来像是同时进行的,这称为并发。并发是通过分时来实现的,它提高了资源利用率和系统吞吐量。