yo!这里是Linux进程入门介绍

目录

前言

os定位

进程基本介绍

进程基本操作

查看进程

创建进程

进程状态

理论层面

具体状态

进程优先级

环境变量

地址空间

进程地址空间&&页表介绍 

深入理解地址空间 

后记


前言

        在了解学习过Linux环境下的基本操作以及工具之后,就来到了操作系统的一个大的重点——进程,进程的内容有很多,在讲最主要的进程控制之前,先来初步了解一下进程,包括进程的概念、查看、状态、优先级以及地址空间等相关概念,在了解完之后,可能会对程序有个新的认识,来往下看看吧!

os定位

        我们知道,计算机由一个个硬件组件组成,包括输入输出单元、存储器及cpu中央处理器,大部分都遵守冯诺依曼体系,如图:

yo!这里是Linux进程入门介绍_第1张图片

值得注意的是,这里的存储器指的就是内存,cpu只能对内存进行读写,不能访问外设,外设也只能读取或写入内存,即所有设备只和内存打交道;程序属于文件,存储在磁盘之中,程序要运行,必须先被加载到内存中,所以可以看出硬件很“笨”,那么是谁在协助硬件之间的“打交道”,或者程序是如何加载到内存中,在这之中,仿佛有一个角色在中间协调,让硬件可以更好的工作,更好的发挥作用。

        这个角色就是操作系统(os),是计算机系统中的一个基本的程序集合,实质是一款软件,包括内核(如进程管理、内存管理等)和其他程序(如函数库、shell程序等),主要目的在于与硬件交互,管理所有软硬件资源——向内;并为用户程序提供一个良好的执行环境——向外。综上,os的定位是一款纯正的搞管理软件

进程基本介绍

       进程是计算机中正在执行的程序的实例,是操作系统资源分配的基本单位。每个进程都有独立的地址空间、堆栈、数据段等,它们之间互相独立,互不干扰。操作系统为每个进程分配CPU时间片、内存、文件等系统资源,控制各个进程的执行顺序,保证系统的正常运行。——Inscode AI 创作助手

        所谓“搞管理”,总结就是“先描述,再组织”,举个栗子,校长(学生系统管理员)如何管理所有的学生?肯定不是一个个拉到自己面前进行管理,而是先把每个学生的基本信息统计起来(描述),无论是招入、开除、奖励、惩罚等某个学生,都是针对于这个学生的基本信息进行处理(组织),比如,招入一个学生,那就添加他的基本信息到系统中,开除就删除该学生的信息,奖励或者惩罚是在学生的信息中添上一个标记,然后联系该学生的辅导员通知学生进行某种行为。而os就是上文的校长,进程就是学生,辅导员就是硬件。

        先组织:进程的属性等各种信息作为一个集合被存放到PCB中,Linux操作系统的PCB是task_struct,是一种数据结构,会被装载到内存中并且包含着进程信息,主要包括

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程,称为pid。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

        再描述:所有运行(即加载到内存中)的进程都以链表的形式存在Linux内核里,将其作为一个单位与其他进程进行数据交换、协作及资源的分配。

进程基本操作

  • 查看进程

方法一:通过/proc系统文件夹查看,如图

yo!这里是Linux进程入门介绍_第2张图片

方法二:通过ps指令,如图

 注意:参数 a 表示不仅列当前用户的进程,也列出所有其他用户的进程,参数 x 表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数 j 表示列出与作业控制相关的信息;加上grep指令可以定向查找进程。

方法三:通过调用进程标识符函数getpid()获取当前进程的pid,如图

yo!这里是Linux进程入门介绍_第3张图片

  • 创建进程

        除了运行一个程序就是在创建一个进程的方法,还可以通过系统调用fork()创建进程,介绍如图

yo!这里是Linux进程入门介绍_第4张图片

yo!这里是Linux进程入门介绍_第5张图片

 注意:

        ①fork失败时返回-1,成功时有两个返回值,给父进程返回子进程的pid,给子进程返回0,那么为什么有两个返回值?因为在fork内部完成了创建子进程的逻辑之后,在return之前,子进程就已经存在了,所以父子进程会各自执行各自的return,对于fork之后哪一个先被执行,这是不一定的,是由操作系统的调度器决定。

        ②父子进程代码共享,数据各自开辟空间,采用写时拷贝的方法(后面会说)

        ③通过返回值不同,用if分流,使得父子进程做不同的事。

        既然已经说到创建进程,那就先初步了解一下结束进程,正常一个程序走完了也就是进程结束了,如果遇到死循环可通过ctrl + c或者kill命令结束进程。

yo!这里是Linux进程入门介绍_第6张图片

进程状态

  • 理论层面

        从理论层面,也就是会在书本上出现的状态,包括新建、运行、就绪、阻塞、挂起及退结束状态,不同的书籍命名不同,但基本也就是这几种,如图

yo!这里是Linux进程入门介绍_第7张图片

①新建状态:新建状态,实际当中并不存在此状态,是理论上的一种完善;

②就绪状态:当进程已经准备就绪,等待系统分配资源使其运行时,即在等待CPU分配时间片;

③运行状态:进程正在被CPU执行,这是进程真正执行的状态;

④阻塞状态:当进程正在等待一个事件的完成而无法继续执行时,进程进入阻塞状态,如等待IO操作完成;

注意:系统中不止cpu存在各种资源,网卡、磁盘等资源也存在,比如一个进程要访问在磁盘的文件;且系统中也不止一种队列,即等待cpu调度的运行队列,还有阻塞队列,即等待磁盘等非cpu资源的队列,这个等待访问的过程就是阻塞。

⑤挂起状态:当内存不足或其他情况时,操作系统通过适当的置换进程的代码和数据到磁盘,留出内存运行其他进程,此时被置换出去的进程状态叫做挂起;

注意:1)进程的pcd依旧在内存中,当需要此进程时代码与数据就会被置换回来;

           2)所谓阻塞挂起状态,就是等待非cpu资源就绪的过程中代码和数据被置换到磁盘(反正都是等,在磁盘也是等)。

⑥结束状态:当进程结束或被终止时,进程进入结束状态,其占用的资源被释放。

  • 具体状态

        前面介绍过,进程的pcb中存在着进程的大量属性信息,其中就有进程状态,源码如下,

/*
* 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): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里;

②S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep));

③D磁盘休眠状态(Disk sleep:有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束,只有IO结束或关机或拔电源才能中断;

④T停止状态(stopped):可以通过发送 SIGSTOP 信号给进程来停止(T)进程,这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行,比如在调式时断电处;

⑤X死亡状态(dead):也叫终止状态,这个状态只是一个返回状态,你不会在任务列表里看到这个状态,比如当多个进程需要同时被释放,其中一个正在释放时,其他进程就是终止状态。

eg:

yo!这里是Linux进程入门介绍_第8张图片

注意:带“+”属于前台程序。

./ 可执行程序 运行的就是前台程序,运行前台程序时执行任何命令没有效果,并且可以被ctrl+c终止,

./ 可执行程序 &运行的是后台进程,不带“+”,运行期间可执行其他命令,ctrl+c终止不了,但可用kill指令终止。

        除了以上进程状态之外,还有两种比较特殊的状态,就是僵尸状态和孤儿状态。

僵尸状态:当子进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程,僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就会进入Z状态;

孤儿状态:一个进程的父进程先于它终止,导致该进程成为孤儿进程。在此状态下,该进程将被操作系统的init进程接管,init进程会成为该进程的新的父进程。此外,为了避免产生孤儿进程,父进程可以在创建子进程后调用wait或waitpid等函数(进程控制中会讲)来等待子进程的终止,并及时对其进行处理。

eg:

yo!这里是Linux进程入门介绍_第9张图片

进程优先级

1)介绍

        使用ps -l或者ps -al指令查看进程信息,如下图,对于一个进程的信息,linux下不止显示pid与ppid,还有uid、pri、ni等信息,这里的pri与ni就决定着一个进程的优先级。

PRI:即进程的优先级,也就是就是程序被CPU执行的先后顺序,此值越小,进程的优先级别越高,默认值是80

NI:nice值,表示进程优先级的修正数值;

        PRI的值通过nice值进行修改,公式:PRI=PRI默认值+nice ,比如,若nice值是10,则PRI就加10,但优先级就变低,若nice值是-10,则PRI就减10,则优先级就变高。所以,调整进程优先级,在Linux下,就是调整进程nice值,而nice其取值范围是-20至19,则在PRI默认值的基础上,PRI的范围就是60至99。

注意:每次修改nice值,新的PRI值都是在默认值的基础上修改,与当前PRI值无关。

2)修改优先级

①输入top;

②按r之后输入需要修改进程的pid;

③输入nice值。

eg:

yo!这里是Linux进程入门介绍_第10张图片

yo!这里是Linux进程入门介绍_第11张图片

环境变量

        环境变量是一种在操作系统中使用的一组动态值,可以影响运行进程的行为。在操作系统中,每个进程都有自己的环境变量集合,这些变量在进程运行期间可以动态地被修改和访问。环境变量一般由操作系统或用户设定,并且可以通过命令行工具或程序代码来访问和修改。——Inscode AI 创作助手

        常见的环境变量:PATH(可执行文件搜索路径)、HOME(用户主目录)、SHELL(当前shell)等;

        查看环境变量方法:echo $环境变量名

eg:

         其他与环境变量相关的命令:

  • export: 设置一个新的环境变量

  • env: 显示所有环境变量

  • unset: 清除环境变量
  • set: 显示本地定义的shell变量和环境变量

        通过代码获取环境变量:

1)命令行第三个参数

yo!这里是Linux进程入门介绍_第12张图片

 2)二级指针environ

yo!这里是Linux进程入门介绍_第13张图片

 3)系统调用getenv()(常用)

yo!这里是Linux进程入门介绍_第14张图片

地址空间

  • 进程地址空间&&页表介绍 

yo!这里是Linux进程入门介绍_第15张图片

         上面的图,相信大家在学习c/c++时经常遇到的“地址空间”,其实它并不是严格意义上的地址空间(物理地址),这种地址叫做虚拟地址,也叫线性地址,对于物理地址,用户是看不到的,是由os统一管理。

        地址空间,准确点来说是进程地址空间,实质上是一种内核数据结构,每个进程都有自己的地址空间,其中包含了程序代码、静态数据、堆、栈等部分。进程地址空间的管理是由操作系统负责的,每个进程都有自己的虚拟地址空间与实际的物理地址空间相对应。操作系统通过内存映射机制实现了虚拟地址空间与物理地址空间之间的转换,虚拟地址被转换成物理地址后,进程就可以访问相应的内存,这种映射机制叫做页表,本质也是一种内核数据结构,不仅存在映射关系,还有权限的控制。

注意:

        ①地址空间和页表时每个进程都私有一份;

        ②只要保证每个进程页表映射的是物理内存的不同区域,就能做到进程之间不会相互干扰,进而保证了进程的独立性。

  • 深入理解地址空间 

        每个进程的pcb中都有一份虚拟地址和一份页表,也存在一个指向地址空间的指针指向地址空间,假如存在两个进程父进程和子进程,分布情况如图

yo!这里是Linux进程入门介绍_第16张图片

为什么要有地址空间?

①有效保护了物理内存和各个进程及内核的相关有效数据;

说明:凡是非法访问或映射,os都会识别到并终止此进程,因为地址空间和页表都是os创建并维护的,想使用页表将地址空间映射到物理内存一定是在os的监管之下。

②物理内存分配和进程管理可以做到没有关系,即内存管理模块与进程管理模块解耦合了,同时实现了进程的独立性;

③地址空间加页表的存在可以将内存分布有序化;

说明:页表可以将虚拟地址和物理内存进行映射,那么在进程视角,所有的内存分布都是有序的(即每个进程都不知道其他进程的存在,都认为自己拥有4G内存(32位),并且各个区域划分清楚且有序),但其实通过页表映射到物理内存后,真正的存储空间是由os的内存管理模块决定的。

注意:c/c++上malloc/new申请的其实是虚拟地址空间!

        因为如果申请的是物理空间,不立马使用就会造成浪费,所以使用延迟分配的策略来提高整机的效率,即申请空间是在地址空间上申请的,物理内存可以甚至一个字节都不给,而当你真正访问物理空间时,才执行相应的管理算法(在物理空间申请空间并构建页表映射关系)进行内存的访问,这个过程由os完成,用户、进程完全0感知。

        

重新理解什么是挂起?

        加载的本质就是创建进程,但没有立马将所有程序代码和数据加载到内存中,并创建内核数据结构建立映射,甚至有些情况只有内核结构被创建,之后分批加载程序到内存。当然,也可以是分批换出程序到磁盘,若此后此进程不再被执行了(比如进入阻塞状态),就叫做挂起。

注:页表映射时,不仅可以映射内存,也可以映射磁盘的位置。

后记

        以上就是进程的入门介绍了,算是一些零碎的知识点,相信大家对程序有了新的认知,在面对后面即将需要进一步学习的进程控制打下良好的基础,特别是进程的pcb、进程状态以及地址空间部分,会影响着对进程整体的理解,非常重要,文章中有看不懂的可以私我哦,加油,拜拜!


你可能感兴趣的:(linux,运维,服务器,c语言,bash,vim,后端)