Linux---进程(概念,PCB,进程属性,标示符,fork) 什么叫进程程序和可执行程序是一回事包括指令的本质也是程序只要是文件就一定是存在磁盘上根据计算机体系结构磁盘属于外设一切程序要运行第一步肯定是要加载到内存里在我们的操作系统里可以同时运行多个程序每一个都要加载到内存这时候就会产生很多的进程操作系统要管理这些进程就需要先描述再组织获取这些进程的数据是第一步再用结构体去描述每一个进程的属性最后用数据结构将这些结构体组织起来值得一提的是我们还没有去启动进程时候的第一个软件就是操作系统它也在内存里。那到底什么是进程程序的代码和数据从磁盘加载到内存准备要运行操作系统为了管理这些进程就需要先有描述每一个进程的结构体结构体程序本身就称为进程所以进程 内核数据结构对象(内核里的一个结构体变量) 自己程序的代码和数据。知道了进程的概念那什么不是进程呢程序不是进程程序由代码和数据构成说白了就是01数据它的本质就是文件。我们知道程序运行要先从磁盘加载到内存那程序加载到内存之后的数据和代码是不是进程呢也不是。由上边得出的结论对于进程来讲对重要的其实是描述它的结构体里边包含了进程的属性和信息只有结构体加载到内存的程序才叫进程。PCBPCB就是描述进程的那个结构体的名称它是操作系统这个学科里描述进程结构体的统称具体到Linux里描述进程的结构体的名字叫struct task_struct里边一定包含了进程的所有属性直接或者间接通过这个结构体找到。OS管理进程就是在管理PCB平时在Linux里./运行程序使用指令(本质上就是一个可执行文件)windows里双击手机里打开APP这些都是在启动进程就这样理解只要是从外设加载到内存并且有相应的结构体去描述的过程都叫启动进程。进程属性下边列出的就是tast_struct里包含的各种进程的属性tast_struct是个结构体我不管它这里边的每个属性具体有多复杂我能确定一点这些属性本质上就是变量(不管是intfloat自定义类型)。标⽰符(pid): 描述本进程的唯⼀标⽰符⽤来区别其他进程。(就像学校里的学号)状态: 任务状态退出代码退出信号等。(就像播放音乐时候的开关状态)优先级: 相对于其他进程的优先级。(决定了获得CPU资源的先后就像食堂排队吃饭一样有优先级的本质就是资源少人多僧多粥少放到电脑里就是CPU资源少进程多)程序计数器: 程序中即将被执⾏的下⼀条指令的地址。(CPU内有很多的寄存器用来保存执行过程中的各种临时数据CPU执行代码是从上往下一行行执行的程序的代码很多CPU怎么知道代码执行到了哪一行呢CPU里还有一个寄存器叫EIP(PC指针)就是程序计数器)内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针。(简单一说当CPU选中某个进程的时候这个进程有其对应的PCB那光有PCB无法找到这个进程对应程序的代码和数据呀因此内存指针的作用就是帮助CPU去找到它们)上下⽂数据: 进程执⾏时处理器的寄存器中的数据[休学例⼦要加图CPU寄存器]。..........接下来重点说一下上下文数据首先来看一个问题一个进程执行代码会占有CPU那它会把自己的代码执行完才放弃CPU给下一个进程吗不会试想一下你现在在执行一个代码假如说是while(1)死循环要是真得等一个进程执行完才行那OS不就死机了吗当代计算机会给每一个进程分配一个时间片时间片执行完毕就自动出让CPU让另一个进程执行这叫基于时间片的轮转调度时间片你就可以理解为是CPU给它旗下的每一个进程分配的执行时间该进程的执行时间结束就会立马出让它所占据的CPU资源因此一个进程没有执行完就有可能会把CPU让出去。由于时间片的到达就会存在进程切换和调度的动作说白了就是进程现在退出了到时候又回到之前的位置继续执行那我当前这个进程再要回到原来的位置上继续执行它是怎么找到的呢答案就是保留上下文数据这个上下文数据就是进程对应的CPU内的寄存器的临时数据当进程随着时间片的结束退出CPU的时候CPU会保存它的上下文数据这个数据会随进程退出的时候一起带走等它再回来的时候就恢复上下文数据到相应的寄存器里就可以继续接着上一次退出的地方运行了至于上次执行到了哪条指令不是有程序计数器嘛上下文数据也包括了EIP里的数据。注意上下文数据是指的寄存器里的临时数据是数据不是寄存器本身。CPU硬件只有一套CPU内的寄存器硬件可以在不同的时间段保存不同进程的数据。再通俗一点就是寄存器是唯一的但数据可以是多份的对应不同的进程因此上下文数据是进程私有的数据随着时间片的结束上下文数据就保存在进程对应的PCB里(这对于老式内核来说是这样的所以才有上下文数据这个属性)。标示符一个进程如何获取自己的标示符获取自己的标识符就是在获取自己的task_struct结构体内部的属性值在我上一篇文章里说过人无法直接去操控操作系统里的数据结构这样不安全因此我现在想获取这个进程的标识符就必须通过相应的系统调用去帮我们完成这个系统调用就是getpid哪个进程在调用就是获取该进程自己的pid。只要有while(1)死循环进程就可以一直运行不退出但不代表没有死循环的就不是进程只要是运行可执行程序都是进程只不过运行完就退出了不好观察。(注意下边打错了应该是标示符)现在我们知道了上边的程序是一个进程标示符为15156怎么证明呢首先你必须得让程序一直在运行一直运行就代表这个进程一直存在然后有个ps axj指令可以查看系统中所有的进程。而且上文也说了指令本质就是可执行文件因此执行指令也是在启动进程所以由下图就可以知道code.exe确实是一个进程且PID为18630不知道大家有没有发现怎么同一个code.exe的进程PID不一样原因就是之前关掉过这个进程当重新启动这个进程的时候此进程非彼进程又会有新的PID然后这个PID的大小其实是递增的系统内部有个计数器(全局变量)每启动一个进程计数器就当进程分配完了计数器的值又会从0开始那为什么我看到的code.exe的进程的PID不是连续增大的是因为在我终止进程和启动进程的中间系统可能又启动了好几个其他的进程。(注意可以用 ; 把两条命令集然起来或者还可以用)除了进程自己的pid它还有一个ppid叫做父进程标示符在Linux系统中新的进程往往是通过父进程的方式创建出来的。如下图我们获取了所有进程并且通过ppid筛选出了当前进程的父进程发现就是bash命令行解释器也就是说我们执行的所有的指令都是bash的子进程并且shell外壳自己就是一个进程。通过ppid我们也可以知道进程结构是树形结构的每一个进程有对应的父进程也可以有子进程。如何创建进程(子进程)(通过代码的方式)创建子进程的本质就是OS内部多了一个子进程我们不可能直接在操作系统里创建一个task_struct吧用户不能直接和操作系统进行交互因此创建子进程一定是通过系统调用的方式fork就是来创建子进程的包括bash也是调用fork来创建子进程的。/proc在我们的根目录下有一个叫proc的目录里边包含了很多用数字命令的目录这些数字就是当前正在运行的进程的PID因此在/proc里就能实时看到当前正在运行的所有进程。每创建一个进程/proc目录下就会多一个以这个进程的PID命名的目录当然如果终止了一个进程那/proc里就会自动把以该进程PID命名的目录关闭。(Linux下一切皆文件)随便打开一个进程的文件夹就能在里边看到进程的所有属性ps ajx显示的进程属性内容也是从它的/proc/PID里筛选出来的一个进程有非常多的属性其中有exe表示的是进程在启动之前的那个可执行文件还有一个属性是cwdcwd就是当前进程的工作路径就是我们所谓的当前路径每一个进程在启动的时候都默认有自己的cwd那我们怎么修改它呢肯定还是无法直接修改PCB里的属性值那肯定是用系统调用chdir可以修改进程的工作路径它的参数就是你想修改的路径字符串。重谈fork上文已经知道了fork是创建子进程的由下边的现象可知fork之后代码是共享执行的就是如下所示嘛fork之后是一个死循环父进程打印一句之后子进程也打印一句。为什么父进程要创建子进程本质上是为了让子进程完成任务。fork是为了实现分流利用它的返回值查看man手册可以知道fork是有两个返回值的如果创建子进程成功就返回子进程的PID给父进程返回0给子进程。如果失败就返回-1给父进程并且设置一个错误值。所以我们利用fork的返回值就可以用来进行父子进程分流一份代码两个进程。小实践三个问题认知对于一个父进程来说有它自己的task_structtask_struct内部有指针指向可执行程序的代码和数据方便将来CPU调度的时候执行而我现在在代码里边用fork创建了一个子进程只要是进程我先不管怎么来的肯定是有它的PCB而实际上子进程的PCB是以父进程的为模板来创建的把父进程里的属性拷贝给子进程当然也有子进程自己的属性需要改改。父进程的代码和数据是从磁盘上加载到内存里加上PCB才形成一个进程的它是一个完整的进程而子进程的代码和数据哪儿来呢默认是没有的没有就没法运行但是子进程内部有各种指针默认是指向的就是父进程的代码和数据说白了就是在默认情况下fork之后的代码和数据一般都是父子共享的注意是一般因为根据后边的知识还有一些额外的点先不说。这也就说明了为什么fork之后的代码怎么父进程执行完子进程也执行。问题1为什么给子进程返回的是0给父进程返回的是子进程的PID父进程和子进程的比例是 1 : n 的。给子进程返回0是因为不存在父进程找不到的问题getppid就能找到了但是一个父进程有很多个子进程给父进程返回子进程的PID是为了标识特定的一个子进程未来控制特定的子进程。问题2fork()一个函数怎么返回了两次如果一个函数已经准备return了那么它的核心功能已经做完了同理fork函数准备返回之前它的核心功能(创建一个子进程并且创建完毕就要能被OS调度执行了)也已经做完了又由上文说的fork之后父子进程共享代码这句话说的就是子进程被创建之后的事return语句本身也是一句代码也就是说fork函数内部将子进程创建完毕之后return语句也是属于子进程被创建之后的代码自然也要父子共享被执行两次所以一个fork函数返回了两次。问题3为什么一个id可以同时接受两个fork的返回值?(代码见上文小实践那里的id)这里涉及到后边的知识先说结论后边会重谈的。源文件被编译链接之后变量名就变成一个地址了这个地址叫做虚拟内存地址在虚拟地址空间里最终会被转移到物理内存里而父子进程都有自己的虚拟地址空间fork返回的时候返回值id在它们的虚拟地址空间里都会存一份两个空间里id的地址是一样的如果发现父子进程之中有一个的id被修改了在物理内存里就会新开辟一块空间把修改的id的内容写进去(父子进程的虚拟地址空间不一样但是它们都关联同一个物理内存)这叫写时拷贝而return语句的本质就是在写入return了两次就写了两次物理内存里就有两块互不相关的空间存着两次返回的id值。(大概了解一下就行了)