【Linux】操作系统与进程

举报
修修修也 发表于 2024/10/25 15:44:05 2024/10/25
【摘要】 🦄个人主页:修修修也 🎏所属专栏:Linux ⚙️操作环境:Xshell (操作系统:CentOS 7.9 64位)​编辑目录📌 操作系 统 🎏 操作系 统 的概念 🎏 设计 操作系 统 的目的 🎏 操作系 统 对进 程 的管理 🕹️ 操作系 统为 什么要 对进 程 进 行管理 ? 🕹️ 操作系 统 如何 对进 程 进 行管理 ? 📌 进 程 🎏 进 程的概念 🎏 进 ...

🦄个人主:修修修也

🎏所属专栏:Linux

⚙️操作:Xshell (操作系:CentOS 7.9 64位)

​编辑


📌 操作系

🎏 操作系 的概念

🎏 设计 操作系 的目的

🎏 操作系 对进 的管理

🕹️ 操作系 统为 什么要 对进 行管理 ?

🕹️ 操作系 如何 对进 行管理 ?

📌

🎏 程的概念

🎏 程的描述 ——PCB(process control block)

🕹️ Linux下的PCB——task_struct

🕹️ task_struct内容分

🎏 程管理

🕹️ 组织进

🕹️

🕹️ 统调 示符

🎏

🕹️ fork()函数

🕹️ fork()函数的两个返回

🕹️ 一步探究 fork()函数

🎏 程状

🕹️ 操作系 统层 程的状

🕹️ Linux系 统层 程的状

🎏

🕹️ 基本概念

🕹️ PRI和NI的概念

🕹️ /修改 命令

结语


📌操作系

🎏操作系的概念

        任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:

内核(程管理,内存管理,文件管理,驱动管理)

其他程序(例如函数shell程序等等)


🎏设计操作系的目的

• 与硬件交互,管理所有的硬件

• 为用户程序(应用程序)提供一个良好(, 高效, 安全)的​编辑


🎏操作系对进的管理

🕹️操作系统为什么要对进行管理?

        进程是操作系统进行源分配和度的基本。操作系统通过管理进程来实现对资源的分配和调度。


🕹️操作系如何对进行管理?

        操作系统通过下面两个行为来完成对进程的管理:

1. 描述

2. 组织进

        描述进程主要是的各种重要的属性描述出来, 比如将进程id, 父进程id, 进程状态, 进程优先级等属性合为一个结构体来描述一个进程。这就像我们将学生的各种属性, 如姓名, 性别, 学号, 班级, 年龄, 成绩等合为一个结构体来描述一个学生一样。

        组织进程则是通过按照进程的各种属性来将进程组织运行起来。这也很好理解, 比如我们要组织一个班级进行野炊活动, 我们就按照各个同学的特长将同学们分组, 一组去拾柴, 一组去收拾场地, 一组去收集食材, 一组处理食材。这样组织同学们共同完成野炊这个任务, 就类似于操作系统组织进程完成用户的任务一样。

        注意, 操作系统管理的只是进程的属性, 通过对进程属性结构的增删查改来管理进程, 而并非去管理每个进程本身有什么行为。


📌

🎏程的概念

        进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。

        在早期面向进程设计的计算机结构中,程是程序的基本, 如正在执行的程序;

        在当代面向线程设计的计算机结构中,程是线程的容器。程序是指令、数据及其组织形式的描述,程是程序的


🎏程的描述——PCB(process control block)

程信息被放在一个叫做程控制数据中,可以理解为属性的集合

• 课本上称之为PCB(process control block),Linux操作系下的PCB是: task_struct

🕹️Linux下的PCB——task_struct

• 在Linux中描述程的构体叫做task_struct。

• task_struct是Linux内核的一种数据,它会被装载到RAM(内存)里并且包含着程的信息

• task_struct非常大, 感兴趣的朋友可以看一下它的源码 : task_struct英文源 原文


🕹️task_struct内容分

示符: 描述本进程的唯一示符,用来区别其他进程。

: 任务状态,退出代码,退出信号等。

: 相对于其他进程的优先级。

程序数器: 程序中即将被执行的下一条指令的地址。

内存指: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。

I/O状信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

• 其他信息


🎏程管理

🕹️组织进

        Linux内核中, 最基本的组织进程task_struct的方式是采用双向链表组织。

        但实际上Linux内核中组织进程的方式错综复杂, 可能是好几种数据构互相嵌套在一起


🕹️

        如下代码,我们先在Linux中创建一个进程(程序):

#include<stdio.h>

#include<unistd.h>

int main()

{

while(1)

{

printf("进程正在运行中...\n");

sleep(1);

}

return 0;

}

        然后用gcc编译运行这个程序:​编辑

        可以看到进程正在每隔一秒打印一次提示信息,然后我们在另一个终端使用ps命令查看一下这个进程信息:

ps ajx | head -1 && ps ajx | grep mypro //查看mypro相关的进程

//或

ps ajx | head -1 ; ps ajx | grep mypro //查看mypro相关的进程

​编辑

        还有一种查看进程的方式是通过查看/proc目录来查看进程:

ls /proc //查看当前系统的所有进程

​编辑

        然后我们可以通过查看具体的进程目录来查看进程的信息:

ls /proc/4206 -l

​编辑

        或者也可以通过top命令来查看进程:

​编辑


🕹️统调示符

• 进程id(PID)

• 父进程id(PPID)

​编辑

        可以通过getpid(),getppid()来获取进程id和父进程id,函数手册如下:

​编辑

        函数使用示例:

        我们编写一个进程,然后调用getpid()和getppid()函数来获取它的进程id和父进程id,代码如下:​编辑

        使用gcc编译运行,再在另一个窗口查询进程相关信息:

​编辑

        可以看到getpid()和getppid()获取的进程id和父进程id信息是正确的。


🎏

🕹️fork()函数

        我们前面尝试了在指令级别运行一个进程,即运行我们写的程序; 现在我们来看一下在代码层面如何创建一个进程。

        先来看一下Linux的fork()函数手册:

​编辑​编辑

        我们通过一段代码来观察一下fork()函数调用的现象,代码如下:

​编辑

        编译运行代码,结果如下:

​编辑

        可以看到, fork()函数后的代码竟然被打印了两遍! 这是因为我们在调用fork()函数之前程序只有一个执行流,而调用fork()函数之后程序就开始有两个执行流了,所以两个执行流都执行了打印操作,我们的屏幕上才会看到两个打印的结果。​编辑


🕹️fork()函数的两个返回

        我们可以看到fork()函数拥有两个返回值,一个是返回给父进程的子进程id,一个是返回给子进程的0。如果fork()函数创建子进程失败了,就会给父进程返回-1,并适当设置errno报错.​编辑

        下面我们测试一下接收fork()函数的返回值,代码如下:

​编辑

        编译运行,结果如下:
​编辑

        可以看到, 子进程和父进程分别执行了其对应的不同的代码逻辑,并且我们可以明显发现,这个测试中fork()函数应该是优先执行父进程的,所以父进程的信息先被打印了出来。但实际上父子进程的执行先后是由调度器来决定的, 可能不同系统, 不同进程甚至不同时间测试结果都有可能不一样, 完全取决于调度器如何安排, 是不确定的。

        至此,我们发现fork()函数确实是创建了一个新的进程, 只有两个进程一起运行, 这段代码才可能打印出两个if语句的分支循环结果,否则按照C语言单进程的执行逻辑, 程序一旦进入其中一个死循环就再也不可能出来了, 那么我们将只能看到其中一个死循环在打印。

​编辑


🕹️一步探究fork()函数

        关于fork()函数,有几个问题需要解答一下:

1.什么fork()函数要程返回0,返回子pid?

        我们给父子置不同返回的目的,就是fork()函数之后,可以根据不同的if判断来父子行不同的代片段因此返回不同的返回,是了区分不同的行流,使其可以行不同的代码块。需要注意的是,一般而言,fork()函数之后的代父子共享的!

        而具体们给返回子程的pid的原因方便父程后续对程做管理, 因程的子程可能不止一个,因此需要分别记录下子id方便其后续对程的精准管理

2.fork()函数究竟做了什么?

        fork()函数的主要工作大概有下面几个:

1.         建子PCB

2.         填充PCB对应的内容

3.         程和指向同的代

4.         父子程都有独立的task_struct,可以被CPU度运行

5.         ......(fork()函数的主要工作完成之后,后面的代就是父子共享的了!)

6.         return id ;    (因为这里的fork()函数的return 句已在成功建子程后了,意味着从那之后父子程就会分别拥有一个return ,这样就可以做到父程返回一个,子程返回一个)

3.一个函数是如何做到返回两次的?我们该如何理解?

        如上fork()函数的工作内容所示,因一旦子建好后后面的代就是共享的了,所以return 句自然也成父子共享的,所以他就会通过这个共享的return 句来得到分属于自己的返回这样也就做到了返回两次的效果。

4.一个量怎么会存有不同的内容?如何理解?

       父子程而言,他程代是共用的,而它程数据采用写的方式, 拷出的程数据互不干, 可以分使用实际量已不是一个量了,而是父程的量和写出的子两个,因此可以存不同的内容​编辑

        这样安排的主要原因是相互具有独立性, 进程代码因为不会被进程更改,所以父子共享是没问题的,但进程数据是可能被进程更改的,如果父子进程间可以互相影响数据,那么就很容易导致出错的情况。

        这就好比我们现实生活中父子可以在一栋房子里生活, 不会因为父亲住了房子就坏了,儿子不能住了,也不会因为儿子住了导致房子有什么变化导致父亲不能住了。但是父子间不能共用一个钱包,否则可能父亲刚想给家里买一台空调,就发现钱包的钱已经被孩子拿去买乐高了。这样会互相干扰,就会导致出事。


🎏程状

🕹️操作系统层程的状

        进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。在三态模型中,进程状态分为三个基本状态,即运行态,就绪态,阻塞态。在五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态。

🌳运行状

        程占有理器正在运行或正于运行列中。

🌳阻塞状

        程不具运行条件,正在等待某个事件的完成。

🌳挂起状

        程的挂起状是指算机系中,一个程因某些原因而暂时不能继续执行,但仍然保持在程表中,并且有可能在将来恢复行的状


🕹️Linux系统层程的状

        Linux内核中源码定义的进程状态:

/*

* The task state array is a strange "bitmap" of

* reasons to sleep. Thus "running" is zero, and

* you can test for combinations of others with

* simple bit tests.

*/

static const char * const task_state_array[] = {

"R (running)", /* 0 */

"S (sleeping)", /* 1 */

"D (disk sleep)", /* 2 */

"T (stopped)", /* 4 */

"t (tracing stop)", /* 8 */

"X (dead)", /* 16 */

"Z (zombie)", /* 32 */

};

​编辑

🌳R : 运行状( running )

并不意味着程一定在运行中,它表明程要么是在运行中要么在运行列里。

        但是要注意,我们查看进程时,不能只根据表面来推测判断它处于什么状态,而应该更严谨的去分析进程的行为再做判定。

        比如,我们创建一个程序,代码如下:

​编辑

        然后我们运行进程,查询该进程的状态:

​编辑

        可以发现明明左边进程一直在运行,但是右边查询进程属性的结果却显示进程在等待状态。这是因为我们在进程中调用了printf()函数,即调用了硬件设备显示器来向屏幕上打印内容,而当硬件显示器在打印内容时,进程就是处于等待硬件工作的状态的, 并且硬件显示器向屏幕打印的时间相对CPU运行时间来说很慢,两者是数量级的差别,硬件几乎占了99.99%的时间,所以我们在查询进程状态时,大部分时间查到的都是进程在等待打印的时候。

        当我们将while循环中的打印语句删除,再运行,查询进程状态:

​编辑

        可以看到,进程当前处于R运行状态:

​编辑

        小tips:

        带+的状态表示是在前台运行的进程,如果我们在运行进程指令后面加一个 & ,那么进程就会在后台运行,状态后面就不会有+号。(杀后台进程用kill -9 [pid]来完成)

🌳S : 睡眠状( sleeping )

意味着程在等待事件完成(里的睡眠有候也叫做可中断(浅度)睡眠(interruptible sleep))。浅度睡眠状意味着该进程当前是可以相操作系的操作的,比如可以直接被操作系统杀死。

🌳D : 磁休眠状( Disk sleep )

候也叫不可中断(深度)睡眠状uninterruptible sleep),在个状程通常会等待IO的束。并且该进程不会响任何操作系, 即操作系无法将其死或者是行其他任何操作。这样做主要是了防止操作系将某些重要的正于等待状误杀

🌳T : 停止状( stopped )

可以通过发 SIGSTOP 信号给进程来停止(T)程。个被停的程可以通过发 SIGCONT 信号让进继续运行。

         可以通过给进程发kill -18/19 [pid]来使进程恢复运行/暂停.

         T状态和S状态的区别是:两者都可以是为了等待某种资源而暂停,但T状态更为自由一些,它也可以不是因为等某种硬件资源,而是单纯的就是不想进程再运行,所以就可以将进程暂停。

🌳Z : 僵死状( zombie )

僵死状Zombies)是一个比特殊的状。当程退出并且父程(使用wait()系统调,后面)没有取到子程退出的返回代码时就会生僵死(尸)

僵死程会以止状保持在程表中,并且会一直在等待父取退出状

所以,只要子程退出,父在运行,但父没有取子程状,子程就会Z状

于僵死状程就被成僵尸,其相关源尤其是task_struct构体不能被,也就会致僵尸程会一直占用内存!

        一个进程在退出之后并不是就要立即将自己的所有资源全部释放, 而是操作系统要将该进程的退出信息维持一段时间, 直到该退出进程的相关进程知道了该进程退出的相关信息和原因之后,才会释放该进程的相关信息和资源。从进程退出,到相关进程接收到退出信息之间的这一段状态,就成为进程的僵死状态。

         我们通过一段代码演示一下僵死状态,我们用fork()创建一个子进程,然后让它休眠3秒之后直接退出,同时我们让父进程休眠30秒,这样在子进程退出后由于父进程处于休眠状态就没法立即回收子进程的信息,子进程就会进入僵死状态,代码如下:​编辑

        然后我们编译运行程序,调出监控窗口查看进程状态, 监控命令如下:

while :; do ps axj | head -1 && ps axj | grep zombie;sleep 1; echo "----------------------------------------------";done

        查看结果,可以看到子进程确实从第三秒后就变成了僵死状态,名字后面也被标上了<defunct>:

​编辑

僵尸程的危害:

• 无人回收时进程的退出状态必须被维持下去,因为他要告诉和它相关的进程(父进程),你交给我的任务,我完成的怎么样,又或是遇到了怎样的状况。父进程如果一直不读取子进程的退出信息,那子进程就会一直处于Z状态!

• 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,如果Z状态一直不退出,PCB就一直都要维护

• 如果一个父进程创建了很多子进程,但是不回收,就会造成内存资源的浪费!因为数据结构对象本身就要占用内存,要在内存的某个位置进行开辟空间,这就会导致内存泄漏!

🌳孤儿

        我们刚刚讨论的是子进程比父进程先死亡的情况,但还有一种可能的情况是父程比子程先死亡,种子程就被成孤儿,该进程会被1号(即操作系),代码如下:​编辑

        编译运行,调用监控查看结果:

​编辑

        所以父进程是1号进程(操作系统)的进程就被称为孤儿进程.操作系统领养孤儿进程的主要目的是为了后续回收孤儿进程的退出信息并将其释放,防止存在内存泄漏问题。

🌳X : 死亡状( dead )

个状只是一个返回状,你不会在任列表里看到个状


🎏

🕹️基本概念

cpu源分配的先后序,就是priority)。

高的程有利。配置权对多任务环境的linux很有用,可以改善系性能( 但前提是能公平的,一般情况下是遵守度器,不要擅自修改 )。

可以运行到指定的CPU上, 把不重要的程安排到某个CPU,可以大大改善系整体性能。


🕹️PRI和NI的概念

        我们在linux或者unix系统中,用ps –l命令会输出以下几个内容:

​编辑

UID : 代表执行者的身份

PID : 代表这个进程的代号

PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

PRI :代表这个进程可被执行的优先级,其值越小越早被执行

NI :代表这个进程的nice值

• PRI即进程的优先级,通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高

• NI就是nice值,其表示进程可被执行的优先级的修正数值

PRI越小越快被,加入nice值后,PRI变为:PRI(new) = PRI(old) + nice

• 当nice值为负值时,该程序优先级值将变小,即其优先级会变高,则其越快被执行

• 调整进程优先级,在Linux下,就是调整进程的nice值

• nice其取值范围是-20至19,一共有40个级别

• 需要强调的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。nice值是进程优先级修正的修正数据


🕹️/修改命令

        查看进程优先级的方式和查看进程信息的方式一样,可以使用ps命令或top命令来查看:

        使用ps命令查看进程PRI值和NI值:

​编辑

        使用top命令查看已存在进程的nice值:

​编辑

        修改进程优先级的方式是使用top命令:

•         进入top后按“r”–>输入进程PID–>输入nice值

•         需要注意的是普通用户不能修改nice值,只有root才可以修改进程的nice值

 程的其他概念:

争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰, 因此父子进程间PCB是独立的,代码可以共享, 但数据需要写时拷贝

并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行

编辑

: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发​编辑


结语

希望篇关于 操作系 的博客能大家有所帮助,迎大佬留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学,一起!

相关文章推荐

【Linux】 实现 三个迷你小程序 (倒 计时 ,旋 , 度条 )

【Linux】手把手教你从 零上手 gcc/g++ 编译

【Linux】手把手教你从 零上手 Vim 编辑

【Linux】一文 底搞懂

【Linux】基本指令(下)

【Linux】基本指令(中)

【Linux】基本指令(上)


​编辑

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。