【Linux】--进程概念 一、冯诺依曼体系结构我们常见的计算机如笔记本不常见的计算机如服务器大部分都遵循冯诺依曼体系结构目前我们熟知的计算机都是由一个一个的硬件组件组成的如下输入单元包括键盘、鼠标、扫描仪、写板等等中央处理器(CPU)含有运算器和控制器输出单元显示器打印机等冯诺依曼体系结构的特点就是从输入到输出数据都必须经过存储器(存储器指的是内存)那么为啥要设计内存这个东西呢首先我们应该都有听过一个木桶效应那么如果是我们的外设和CPU之间直接进行连接那么整个计算的过程的速度就是由最慢的那个设备决定了然后我们的外设可能是以ms为单位的时间而我们的CPU是以us为单位的这两个时间上就差了很大一个量级了。所以在CPU和我们的外设之间又引入了内存。但是又有个疑问了就是引入了内存但是其最慢的设备不还是外设嘛那么按照木桶效应来说那么速度不还是由外设决定嘛。所以还有操作系统这个角色其对于外设的数据会局部性加载就是比如我们要查看一个文件的10MB的内容但是实际上我们操作系统在管理的时候会加载100MB的内容到我们的内存中这是因为局部性原理我们查看完这10MB的内容后大概率是会去查看另外的90MB的内容。所以后续对于计算机的运行速度是由内存决定的。总结如下不考虑缓存这里的CPU能且只对内存进行读写不能访问外设(输入或输出设备)外设(输入或输出设备)要输入或者输出数据也只能写入内存或者从内存中读取所以所有设备只能和内存打交道。所以也就理解了前面提到的所有的程序都要先加载到内存中才能被CPU获取到。还有一个问题就是那么我们直接将寄存器进行布局布局多一点也可以达到很大的存储空间 那么为啥不这样做呢这是因为我们的计算机是给人使用的从商业角度上价格不能太过于昂贵这样才能被更多的人使用才能构建出我们现在这么庞大的互联网所以冯诺依曼为我们的计算机提高了性价比而且对于计算机的运行速度也没有造成太大的影响。首先我们先来固定一个说法我们平时一直说的输入输出到底是从谁的角度来看的因为对于数据的输入来说从键盘角度我们又是输出对于内存来说是输入对于显示器来说获取到数据是输入将数据打印在屏幕上是输出 。在此我们统一口径是站在内存的角度。或者是站在加载到内存的程序的角度。所以我们在C语言和C代码程序中都会先包含stdio.h和iosteam头文件。我们从外设将数据加载到内存是一个拷贝的过程从内存加载到CPU也是一个拷贝的过程CPU将计算好的数据传递给内存也是一个拷贝的过程从内存将数据输出给外设也是一个拷贝的过程。所以计算机的效率问题是由设备的拷贝效率决定的。下面我们通过一个qq给某个人发送消息来理解整个数据流动的过程首先qq之间进行信息的传递实际上就是两个主机之间进行通信那么就是两个冯诺依曼体系的机器进行通信。从发送方其从键盘输入信息然后拷贝到内存然后CPU获取到内存中的数据进行加密处理然后又拷贝到内存然后就其输出到我们的输出设备网卡和显示器然后通过网络传输到接收方的主机然后接收方的输入设备网卡获取到数据将其输入给内存然后给CPU进行解密计算然后又通过内存输出给显示器。二、操作系统1、概念任何计算机都包含一个基本的程序集合其称为操作系统。操作系统包括内核(进程管理、内存管理、文件管理、驱动管理)其他程序函数库shell程序等简单来说操作系统是一个进行管理计算机软件硬件资源的比如管理底层的驱动程序的硬件设备。那么为啥要操作系统来进行管理呢我们要操作那个硬件其无法自己完成么?这是因为大部分硬件我们用户是无法直接去进行操作的所以通过操作系统来帮助我们对硬件进行使用。操作系统可以给我们提供一个更加接近人类的操作逻辑使我们使用计算机的时候得到一个稳定可靠、方便、安全的操作环境。2、进一步理解操作系统操作系统就好比我们学校的辅导员这样我们的学校就是我们的硬件那么学生就是用户那么我们要使用学校的各个服务那么就需要通过辅导员呀宿管阿姨的帮助呀.还有好比如银行其有前台、窗口、钱库等硬件。然后还有大堂经理、业务员等那么我们第一次去存款那么我们对于这个硬件的操作肯定不熟悉那么就需要工作人员的帮忙。然后我们取钱不可能直接进入到钱库拿吧也需要经过业务员的操作。所以我们的操作系统其给了我们用户一个系统调用接口允许我们通过接口间接和计算机的软件硬件交互。3、系统调用操作系统对外会提供一些接口给上层开发进行使用这部分由操作系统提供的接口就称为系统调用然后操作系统提供的系统调用接口其对于用户的要求其实挺高的所以后续有的开发者对于部分系统调用的接口进行了封装然后这些被封装的系统调用形成一个一个的库。那么我们的用户要如何去使用系统调用呢我们日常使用电脑的时候其实并没有感觉到这些我们只需要在鼠标键盘上操作即可。我们这个操作就是对操作系统发起操作然后操作系统根据我们的操作然后去进行系统调用的转换。三、进程理解课本的概念程序的一个执行实例正在执行的程序等内核观点担当分配系统资源(CPU实体内存的实体)下面我们来进行讲解我们先来了解一下操作系统是如何进行管理的还是前面下学校例子我们很多同学见过校长可能就是新生开学典礼和毕业典礼上会见到所以其对于学生的管理如下首先找到对应的部门然后由这个部分作为执行者去找学生然后完成对应的工作。就比如校长需要开除这个学年绩点不及格的最后五个学生那么不需要去当面找学生只需要在学生的管理系统中找绩点最低的五个学生然后和辅导员说让辅导员去执行这个开除的操作即可。所以其角色如下操作系统-校长底层驱动-辅导员底层硬件-学生操作系统管理的不是每个硬件的具体数据而是其属性。就比如我们的校长其管理的是我们在学校是各种属性成绩呀奖学金评选呀保研等。我们的操作系统是可以同时运行很多程序的那么每一个程序都需要加载到内存中吧那么我们的内存中就一定会存在很多的进程那么这些进程也需要被操作系统进行管理。就好比我们的学校我们要对学生进行管理那么我们可以将学生的一些数据使用一个学生管理系统进行记录保存管理那么我们要先对学生这个进行描述然后将学生的信息进行组织。所以我们的操作系统对于进程先描述一个进程在每个执行任务被加载到内存中的时候操作系统在内存中会申请一个空间这个空间就是操作系统中用来描述该进程的结构体。所以未来我们要是有一百个进程那么在我们的内存中就会有一百个结构体对象描述我们的进程的。所以对于磁盘中的文件其不是进程对于加载到内存中的程序其也不是进程。进程操作系统内核结构体对象进程的代码和数据描述进程PCBPCB是在任何操作系统中都使用的叫法。进程信息被放在一个叫做进程控制块的数据结构中可以理解为进程属性的集合在Linux操作系统下PCB叫task_struct。这个结构体中包含了进程的所有属性进程的所有属性都可以通过这个结构体直接或者间接的获取到。我们在Windows中双击一个应用程序在Linux中输入指令在手机上点击APP其本质都是启动一个进程。所以总的来说进程就是进程内核数据结构PCB自己的程序的代码和数据所以操作系统对于进程的管理就变成了对于PCB的管理。PCB中一般就存储以下的信息标识符pid描述本进程的唯一标识符用来区别其他进程。就好比我们在学校中每一个学生都会有学号在社会上我们都有身份证号这都是在不同场景下唯一标识我们身份的东西。状态任务状态、退出代码、退出信号等等在我们的PCB中也会有标识其状态的标识符这个我们后续会进行详细讲解优先级相对于其他进程的优先级相当于我们生活中的排队谁在前那么谁就先拥有这个资源那么我们的进程就是谁的优先级高那么谁就可以先得到CPU的资源。程序计数器程序中即将被执行的下一条指令的地址在我们的CPU中会存在很多的寄存器这些寄存器中会存放着进程执行过程中的各自临时数据这是因为我们的代码不是一次就执行完的。所以在我们的CPU中还有一个寄存器EIP其就是告诉我们的CPU下一次要执行的指令。内存指针包括程序代码和进程相关数据的指针还有其他进程共享的内存块指针上下文数据进程执行的时候处理器寄存器中的数据一个进程并不是执行完毕才会让出CPU资源的当代计算机都会给每一个进程分配时间片当时间片执行完毕那么就会自动出让CPU让别的进程使用。所以就会有一个进程并没有执行完毕然后CPU就被别的进程使用了所以这样就会产生进程切换和进程调度的工作。这是基于时间片的轮转调度后面我们在对于信号的讲解中会进行详细讲解这里我们就做一个了解即可。我们的CPU中寄存器硬件中有一套但是其可以在不同的时间段保存不同进程的数据所以实质上上下文数据是属于进程的数据。是属于进程私有的I/O状态信息包括显示的I/O请求分配给进程I/O设备和被进程使用的文件列表记账信息可能包括处理器时间总和使用的时钟总数和、时间限制、记帐号等四、查看进程首先我们要注意的是我们要查看一个进程那么我们要先保证其在我们的内存中运行才行。指令ps命令ps查看进程是属于静态查看其有如下选项a 显示所有用户的进程u显示用户信息x显示没有控制终端的进程-e显示所有进程-f显示完整的进程的信息top命令按q退出P表示按照CPU的使用率进行排序M表示按照内存的使用率排序基本使用如下ps -f |head -1;ps aux| grew -w 具体的程序的名字ps -p PID1、标识符标识符就类似我们的身份证学号等可以表示我们的身份的东西其表示了每一个进程在task_struct结构体中存放了两个成员变量PID和PPIDPID进程的唯一标识PPID 进程的父进程的PID那么我们要在这个进程中获取自己的pid、其实就是获取进程自己的task_tstruct结构体中的属性值但是我们是无法自己获取到这个值的所以操作系统必须给我们提供一个系统来给我们获取pid。我们可以通过头文件sys/types.h中的两个库函数来查看父子进程的PIDgetpid();getppid();如下如果我们将其打开然后终止在打开然后再运行我们会发现我们的pid的数字是一直增大的其就是线性增大的到一个阈值后会回旋。再Linux系统中新的进程往往是通过父进程创建的。可以看到其是线性增大的。我们再看看其父进程我们可以发现我们的父进程的pid是没有发生变化的那么这就说明了我们的父进程是没有发生变化的。那么我们可以通过ps来查看父进程到底是谁在我们的Linux系统中从你开机开始就会存在一个进程bash其就是我们的命令行解释器所以我们在Linux中执行的每一个指令还有自己写的程序都是通过其进行创建的子进程进行执行的。2、查看进程的信息进程的信息我们可以通过/proc来查看。proc其就是我们Linux中的一个磁盘文件其会保存着我们当前的进程的信息。可以看到上面的数字就是我们当前机器所有的进程。那么我们可以通过/proc/进程pid查看到进程的详细信息。可以看到上面的话有一个信息cwd其就是我们的当前进程的工作路径。所以这也是为啥我们之前使用fopen函数对一个文件进行打开创建如果我们不去指定路径那么其就会在当前路径下进行创建。所以我们对于当前路径要有一个新的认识当前路径就是当前进程的工作路径。3、fork函数前面我们知道了如何进行查看当前进程的pid和父进行的pid那么我们是否可以通过代码的方式创建进程呢创建进程的本质就是OS中多了一个子进程那么就多了一个task_struct结构体和进程的代码和数据。所以我们的操作系统需要给我们提供系统调用来创建系统调用。fork函数是一个系统调用其作用是在当前的进程下创建一个子进程其需要包含头文件unistd.h这个函数的返回值为一个pid_t变量pid_t实际上是一个整型变量如果pid_t0那么就说明进程创建成功下面我们来看看fork函数是如何进行进程的创建的。其是由当前正在运行的进程调用这个正在运行的进程就是后面创建的进程的父进程。然后子进程PCB结构成员的参数基本都是按照父进程然后生成唯一PID的标识符。然后子进程和父进程共享代码数据则是由子进程在访问的时候直接可以访问父进程然后子进程要对数据进行修改的话那么其会在内存中申请一块和父进程一样的空间然后对其数据进行修改。可以看到我们使用了fork函数后确实是出现了两个pid。但是我们疑惑的是fork会有两个返回值然后呢给子进程返回的是0给父进程返回的是子进程的pid。然后一个函数怎么可以返回两次呢一个变量这么会接收两次返回值呢下面我们一一进行解答1、为啥给子进程返回的是0给父进程返回的是子进程的pid?首先我们知道在日常使用中父进程和子进程的比例是1n的那么我们的父进程也是需要对其进行管理的所以创建好一个子进程要将其的信息给我们的父进程一份。2、fork函数其为啥能够返回两次首先我们思考一个问题我们的函数在要进行return的时候其工作是否已经完成了答案是完成了就和下班一样完成了工作才能下班那么也就是说在返回之前我们的子进程就已经创建好了那么我们的子进程此时也会运行在fork函数那么子进程也就也执行了这个return。所以子进程执行返回一次父进行执行一次。3、为啥一个id变量可以接收两个返回值首先我们知道在创建子进程那么就要在内存为其创建一个task_struct结构体然后子进程其不是在我们的磁盘中存在的那么其代码和数据是靠父进程给的所以其会先和父进程共享一个代码和数据但是此时实际上子进程在内存中还是没有单独的内存空间的。其实在进程和物理空间之间还会有一个虚拟地址空间对于虚拟地址空间我们后续会进行详细讲解。所以我们的子进程创建出来后其是先有一个虚拟地址空间只有当父子进程中有一个进程要对数据和代码进行修改那么就会触发写时拷贝然后其在物理内存上就是独立的了所以父子进程再id上实际上是不同的两个内存空间。