进程管理进程:运行起来的程序,再内存中的程序程序的本质就是再磁盘上保存的文件我未启动进程前,操作系统是启动的第一个软件,你的操作系统可以同时运行多个程序,这些程序都被加载到内存中,多个程序肯定是要被操作系统管理起来的那么操作系统要如何管理??先描述,在组织1.描述进程的属性2.再用合适的数据结构将进程管理起来eg:1.如何证明你是这个学校的学生首先你的人应该再学校里面然后学校的学生数据里面应该有你这个人的信息2.你未来如何完成找工作这个事情首先要完成自己简历的制作-------描述然后再将自己的简历完成投递的过程----------组织进程内核数据结构代码和数据eg:在磁盘中的code.c,code.exe都不是进程,只是文件,将code.exe加载到内存也不是,只有将其加载到内存,并且操作系统将这个内存里面的信息属性用结构体储存起来,再用链表将其进行连接才是进程.操作系统 pcb -linux task_struct操作系统中内核如何区分、调度、管理成千上万的进程答案就是task_struct。 在 Linux 内核里每一个进程 / 轻量级线程都会对应一个task_struct结构体它就是教材里常说的PCB进程控制块保存了进程从创建、运行、阻塞到退出全生命周期的所有描述信息。一、task_struct 整体模块总览task_struct承载的全部信息标示符进程唯一身份标识状态进程运行状态、退出信息优先级CPU 调度权重程序计数器下一条待执行指令地址内存指针虚拟地址空间、共享内存映射上下文数据CPU 寄存器现场上下文切换核心I/O 状态信息打开文件、占用设备、IO 请求记账信息CPU、内存、IO 资源统计其他辅助扩展信息1. 标示符 —— 进程的身份证号通俗解释用来唯一区分电脑里所有进程就像每个人的身份证没有两个进程 ID 完全相同。PID进程编号ps、kill、top命令全靠这个编号找到对应程序父进程 ID记录是谁启动了这个进程比如终端启动程序终端就是父进程用户 ID记录是谁运行的这个程序用来控制权限普通用户不能修改系统文件程序名字简单的进程名执行ps命令就能看到。eg:就像公安局靠身份证号区分所有人内核靠 PID 区分所有进程执行kill 1234就是告诉内核干掉编号 1234 的程序。2. 状态 —— 进程当前在干嘛、退出结果通俗解释记录程序现在是正在运行、在休眠等待、还是已经卡死结束同时保存程序退出时的返回码。运行状态标记就绪 / 运行等着 CPU 调度、正在跑代码可中断睡眠等文件 / 网络资源能被信号唤醒不可中断睡眠读取硬盘阻塞强制唤醒会崩溃暂停被手动暂停比如CtrlZ僵尸程序已经跑完但父程序还没回收它的记录退出码、退出信号记录程序是正常结束还是崩溃、被强制杀死。eg:好比医院病历记录病人状态正常就诊、等待检查、病危、出院退出码就是出院小结。3. 优先级 —— 进程抢 CPU 的权重通俗解释CPU 一次只能跑一个程序优先级决定谁优先占用 CPU、谁分得更少运行时间。普通进程静态优先级nice命令调整数值越小越优先实时进程优先级音视频、工业控制这类程序专用优先级远高于普通软件动态优先级内核自动调整长时间占 CPU 的程序会降低优先级避免卡死其他软件。eg:医院急诊绿色通道实时进程 急诊病人普通低 nice 进程 重症普通病人后台挂机软件 普通排队病人。4. 程序计数器 —— 程序暂停后从哪一行继续跑通俗解释保存程序下一行要执行的代码地址是切换进程时的「书签」。用处CPU 运行代码时靠这个计数器记住执行到哪一行当 CPU 要切去运行别的程序就把这个地址存进进程档案。等再次轮到它运行直接跳回这个地址继续执行不会从头重跑。eg:看书看到一半合上书签记录读到第几页下次打开直接从书签位置读。5. 内存指针 —— 进程自己的全部内存账本通俗解释指向这个程序所有占用的内存区分哪些是自己独有的、哪些是和别的程序共用的。存储内容:私有内存代码、全局变量、程序运行时申请的堆、栈共享内存系统库、多个程序共用的文件映射页表虚拟内存和真实物理内存的映射关系。eg:租房台账记录专属单间私有内存、公共客厅共享库房东内核靠台账分配空间。6. 上下文数据 ——CPU 临时运算草稿切换核心通俗解释CPU 运行程序时所有临时计算数据都放在寄存器里切换进程前把所有寄存器数据存进档案下次运行再恢复这堆数据就是上下文。如何进行的CPU 正在运行浏览器寄存器存着网页计算的临时数字时间片到了内核把所有寄存器里的数据全部存进浏览器的task_struct把微信的上下文数据加载进 CPU 寄存器开始运行微信下次轮到浏览器把之前保存的寄存器数据放回 CPU继续之前的计算。eg:计算器算到一半切去算账把当前数字、临时草稿全部记下来回来直接接着算。7. I/O 状态信息 —— 进程打开的所有文件、占用硬件通俗解释记录这个程序打开了哪些文件、网络连接、鼠标键盘、磁盘设备。存储内容:文件描述符表标准输入输出、日志文件、管道、Socket 网络连接IO 等待队列程序读取文件 / 网络没数据时挂在等待队列硬件占用标记标记进程正在使用的显卡、磁盘、外设。eg:饭店点餐记录记录顾客进程打开的所有菜品文件、占用的餐具硬件没上菜时排队等待。8. 记账信息 —— 进程资源使用账单通俗解释全程统计这个程序占用了多少 CPU 时间、多少内存、读写多少磁盘用来监控和限制资源。存储内容:用户态、内核态 CPU 总耗时内存使用峰值、磁盘读写总字节资源上限最多能开多少文件、最大能占用多少内存。eg:手机流量账单记录 APP 消耗流量、后台耗电系统可以限制后台 APP 流量 / 内存占用。二.进程标识符标识符就是pid (process ID),每个正在运行的程序进程在系统中都有一个唯一的数字编我们不能直接获得pid,linux开放的系统调用接口,getpid可以获得pid的值man getpid父进程标识符:ppid(parent process ID) 创建进程的父进程 通过getppid获得写一个程序在屏幕上显示出我们发现创建这个进程的父进程是bash子进程的pid在调用时会发生变化,但是父进程不会发生变化,因为他是由bash创建出来的终端 (PID 14630) 是父进程。你的程序 (PID 22796, 22818...) 是子进程。每次启动程序子进程换新的 PID但父进程终端保持不变fork一、进程的创建方式1. 日常直观创建进程的操作Linux/macOS 环境终端输入./可执行文件名运行程序、Shell 执行命令、脚本启动程序底层都会触发进程创建逻辑Windows 环境双击 exe 程序、开始菜单打开软件、cmd/powershell 执行程序Windows 原生没有 fork使用CreateProcess系列 API 创建进程系统后台服务、程序内部自启动子进程也属于进程创建场景。2. 底层本质操作系统视角操作系统内核中进程的核心描述载体是task_struct结构体Linux它是进程唯一身份证存储全部进程信息PID、内存空间、文件描述符、调度优先级、寄存器上下文、信号掩码、用户组权限等。 创建一个进程内核必须完成两件核心工作分配、初始化全新的task_struct进程控制块分配独立的地址空间加载 / 复制代码段、数据段、堆、栈、共享库等资源让进程拥有独立运行环境。应用层代码没有权限直接在内核新建task_struct、分配内核资源所有进程创建都必须依赖操作系统提供的系统调用用户态无法绕过内核直接构造进程。fork就是创建一个子进程.fork会创建出一个新的进程,这个进程以原来的父进程为模板复制出来一份接下来两个进程都会执行接下来的代码我们来看下面这个代码会运行什么结果初始只有父进程执行第一条 printf输出第一行fork() 复制出子进程此时存在两个独立进程父子都会执行第二条 printf父进程 PID 不变PPID 还是 Shell打出第二行子进程新 PID16821父 PID 是 16820打出第三行sleep(1) 延迟退出保证打印完整。procproc就是进程文件夹,在里面我们可以看到所有的进程文件ls /procls /proc/进程的pid -l可以查看文件的所有属性exe就是磁盘级运行程序,就是这个可执行程序是在磁盘的位置cwd(current working directory)当前工作路径通过fopen理解那为什么没有这个文件,就会在当前路径下新建???通过cwd,获得当前进程的工作路径,而在fopen里面没有给他工作路径,系统默认就选择当前工作路径,如何验证??改变工作路径,看看文件是否在新的工作路径下面出现此时就在家目录下面看到了demo.txt我们在回到fork我们发现fork又两个返回值子进程返回值是0父进程返回值是子进程pid对fork的返回值条件判断可以实现分流对于第一个问题给子进程返回0,主要是判断子进程是否创建成功,给父进程返回子进程的pid,是为了更好的管理子进程,比如使用wait()/waitpid()回收僵尸子进程避免资源泄漏通过kill()向指定子进程发送信号监控子进程运行状态、调度业务逻辑。对于第二个问题对于我们之前对于进程的理解我们可以知道进程内核数据结构代码,数据fork创建一个新的进程是以父进程为原型,很多内容都是父进程的,当然代码,数据是从磁盘拷贝到内存,如果再拷贝一次是很消耗资源的,,一般情况是代码和数据是相同的,但是要是要对数据进行修改,两个进程就会相互影响,我们应该要保持进程的独立性,数据和代码就要单独再拷贝出来一份内核完成子进程创建后两条独立执行流分别从fork内核调用处返回用户态父进程执行流fork 返回子进程 PID继续向下执行原有代码子进程执行流从 fork 返回位置开始运行返回 0执行完全相同的代码逻辑。对于第三个问题有了对于第二个问题的理解,实际是不是id接受了两个不同的值,其实是,有两个id执行 fork 之后父子是两个隔离的进程拥有两套互不干扰的地址空间fork 调用前父进程栈上创建局部变量pid_t idfork 复制进程资源时子进程会完整复制父进程的栈内存生成一份属于子进程自己的id副本两次返回赋值发生在两块不同物理内存父进程内存栈id 子进程PID子进程内存栈id 0两个变量只是源码名字都叫id底层内存地址完全隔离不存在 “一个变量同时存两个值”。