【Linux】进程(一)
进程一文章目录进程一一、 进程1、程序与进程2、 task_structPCB3、进程状态二、 进程信息查看1、 PID和PPID2、查看进程的底层方式/proc 文件系统1 ls /proc2 ls /proc/PID3、 top/htop、ps1 top 命令动态查看进程2 htop3 ps 命令显示当前进程信息4 fork创建进程三、 代码实操1、 单进程无限循环打印自身PID2、 进程阻塞演示scanf输入阻塞3、fork()创建子进程四、 CPU如何实现同时运行多个进程1、 时间片轮转2、 上下文切换3、 fork 创建的父子进程也是靠这个原理同时运行吗一、 进程1、程序与进程我们写的.c源代码文件编译后生成可执行程序程序目前还只是文件还只是静态的代码和数据的集合进程通俗的说就是我们在终端敲./process时正在运行的程序就是进程。它会占用CPU、内存、执行代码有自己的生命周期但是从内核的角度来看进程内核数据结构task_struct自己的程序代码和数据2、 task_structPCBPCB进程控制块Process Control Block管理描述组织内核要想管理进程首先就得知道进程的基本信息。PCB专门给每个进程单独创建信息记录表在Linux下PCB的具体实现为task_struct 结构体。task_struct是Linux内核为每个进程创建的核心结构体包含进程的所有属性内核通过双向链表组织所有进程的task_struct实现进程的调度、查找、创建与删除核心内容有标示符PID/PPID进程的“身份证号”唯一区分每个进程进程状态进程当前的“工作状态”比如运行、阻塞、暂停、僵尸优先级进程“抢占CPU的优先级”优先级高的先使用CPU程序计数器PC进程“下一条要执行的指令地址”相当于看书时的书签上下文数据保存寄存器数据切换现场用内存指针告诉操作系统进程的代码和数据存在内存的哪个位置I/O 状态信息记录进程打开文件、设备占用情况记账信息记录进程占用了多少CPU、内存资源这里这些名词并没有解释很清楚接下来的博客会慢慢重点拆解的大家就有个印象就好了所有运行在系统里的进程都以task_struct双链表的形式存在内核里3、进程状态进程在运行过程中会切换不同状态用ps aux命令可查看STAT列每种状态对应具体场景不用死记硬背R运行态要么正在占用CPU运行要么在等待CPU调度排队等“工位”S可中断睡眠等待某个事件完成比如等待用户输入可被信号唤醒比如CtrlC终止D不可中断睡眠等待磁盘I/O比如读写文件不响应任何信号避免数据丢失T停止态进程被暂停比如CtrlZ需特定信号才能恢复运行Z僵尸态进程已退出但“人事档案”PCB未被回收会占用少量内存大家有个印象就好了以后会慢慢讲解清楚二、 进程信息查看1、 PID和PPIDPID( Process ID进程ID系统为每个进程分配的唯一整数相当于“个人身份证号”getpid()获取当前进程的PIDPPIDParent Process ID父进程ID创建当前进程的进程的ID相当于“父亲的身份证号”getppid()获取当前进程的父进程PID注意使用这两个系统调用时需引入头文件sys/types.h和unistd.h否则会编译报错2、查看进程的底层方式/proc 文件系统/proc是Linux系统中的虚拟文件系统Linux下所有进程的详细信息都以文件和文件夹的形式都暴露在/proc目录下1 ls /procls /proc 可以查看系统中所有进程对应的目录每个以数字命名的文件夹就是对应PID的进程目录图片中的1就是PID1的进程目录也就是系统里的第一个进程2 ls /proc/PIDls /proc/PID可以查看指定PID的进程详细信息图片中前三行报错是因为我现在是普通用户没有权限访问这些敏感文件所以系统拒绝了我的请求。用sudo提权就能看了这些文件/文件夹都是PCB的信息比如cwd是当前工作目录exe是进程对应的可执行文件路径task是进程内的所有线程信息/proc目录下的文件均为虚拟文件不占用实际磁盘空间仅在内存中存在进程终止后对应的/proc/PID目录会自动删除3、 top/htop、ps1 top 命令动态查看进程功能实时动态显示系统中所有进程的运行状态默认按CPU占用率排序可实时刷新启动top终端敲top进入动态监控界面控制键P按CPU占用率从高到低排序默认M按内存占用率从高到低排序k终止指定进程输入进程PID按回车确认再按y确认终止q退出top界面第三行是CPU使用率id为空闲率id越高系统越空闲第四、五行内存/交换分区使用下方表格是进程列表PID为进程号%CPU、%MEM为资源占用2 htophtop 是 top 的升级版彩色界面、支持鼠标操作更直观易用先安装再实践我是CentOS系统直接敲sudo yum install htop -y就安装成功了安装成功后直接敲htop进入监控界面可通过鼠标滚轮滚动进程列表按 F6 按CPU/内存排序操作更便捷退出同样按 q 键,其他操作与top基本一致3 ps 命令显示当前进程信息ps aux查看系统中所有进程的详细信息a显示所有用户进程u显示用户信息x显示后台进程ps aux | grep myprocess过滤查找指定进程比如查找myprocess进程我们后面再代码演示4 fork创建进程查看进程后我们进一步学习如何创建新进程。Linux中创建进程的核心系统调用是fork()其功能是复制当前进程同时生成一个新的子进程后续会通过完整代码实操这里先做基础认知:fork()调用一次返回两次操作系统会同时让父进程和子进程都执行这一句父进程返回子进程PID子进程返回0)创建子进程后父子进程会同时运行父子运用同一份代码但是各自的数据是独立的你改你的我改我的互不影响三、 代码实操1、 单进程无限循环打印自身PID创建一个单进程无限循环打印自身PID观察进程运行状态#includestdio.h#includesys/types.h// getpid()所需头文件#includeunistd.h// sleep()所需头文件intmain(){while(1)// 无限循环进程持续运行{pid_tidgetpid();// 获取当前进程PIDprintf(我是一个进程: pid: %d\n,id);// 打印PIDsleep(1);// 暂停1秒避免打印过快}}myprocess原本只是静态的程序./myprocess运行它程序被加载到内存变成了动态的进程我们可以看到每一行都有唯一的pid:4663,在进程运行期间这个PID固定不变同一时间系统里不会有第二个进程的PID是4663这就是进程的“身份证号”按ctrlc就能中止运行了4.现在我们再开一个终端执行同一个./myprocess这时第二个终端里的PID会是一个完全不同的数字5402这说明同一个程序每次运行会生成一个新的进程拥有独立的PID2、 进程阻塞演示scanf输入阻塞演示进程的S可中断睡眠状态进程等待用户输入时会暂停运行直至输入完成后恢复执行 ./myprocess2 后终端光标一直闪烁没有任何输出这是因为进程执行到 scanf(“%d”, a); 时主动放弃了 CPU进入了阻塞状态Linux 中称为 S 可中断睡眠正在等待用户输入数据打开第二个终端查看所有和 myprocess2 相关的进程可以看到当前进程状态为S前台阻塞睡眠S表示进程在可中断睡眠状态表示它是一个前台进程现在的终端被它占用了不能输入其他指令输入3并按下回车后scanf接收到了用户输入进程从阻塞状态被唤醒恢复为运行态继续执行后续代码3、fork()创建子进程通过fork()系统调用创建子进程父子进程各自无限循环持续打印自身PID和父进程PID直观看到多进程运行效果父进程先打印一次自身信息然后休眠 3 秒再调用 fork() 创建子进程。fork() 会复制当前进程创建一个几乎一模一样的子进程fork() 调用一次返回两次父进程返回子进程 PID子进程返回 0通过 if/else 分支让父子进程进入不同的无限循环分别打印各自的信息父子进程是两个独立进程各自在自己的地址空间运行互不干扰第一行输出是父进程在 sleep(3) 之前打印的此时 ppid 是终端进程的 PID 2195后续父进程输出中 ppid 变成 0说明父进程的父进程终端已经退出了此时父进程被系统 1 号进程收养在某些系统上会显示为 0本质上父进程成为了孤儿进程我们后面再详细解释32565 是子进程的 PID它的父进程 PID 正好是父进程的 PID 32557完美验证了 fork() 创建的父子关系父子进程各自循环执行 printf说明两个进程同时运行操作系统在调度它们ps -ef 验证父进程的 PID 32557子进程的 PPID 正好等于它和打印的结果完全对应fork() 为什么会有两个返回值因为调用 fork() 后操作系统把当前进程复制了一份此时存在两个进程父进程和子进程两个进程都会执行fork()这一行所以父进程返回一次子进程返回一次看起来就像 “调用一次返回两次”两个返回值如何分配给父子进程父进程fork() 返回新创建的子进程的 PID正整数子进程fork() 返回 0如果创建失败如进程数超限fork() 返回 -1为什么同一个变量 id 能让 if 和 else 同时 “成立”并不是变量同时等于 0 和不等于 0而是两个进程在各自的地址空间里运行fork之后变成两个独立进程它们各自有各自的变量id互不干扰父进程的 id 变量值是子进程的 pid非 0所以进入 else 分支。子进程的 id 变量值是 0所以进入 if 分支它们是两个独立进程里的两个同名变量互不干扰因此会出现 “同时执行两个分支” 的效果四、 CPU如何实现同时运行多个进程CPU 同一时刻一个核心只能跑一个进程。我们看到的软件同时运行、父子进程一起打印、后台挂一堆程序都不是真正并行是高速切换模拟出来的并发效果1、 时间片轮转操作系统会公平管理所有进程给每一个进程分配一小段极短的时间这就叫时间片时间片一般只有几毫秒短到人完全感知不到。 一个进程拿到时间片就会占用 CPU 执行代码时间片一旦耗尽就会立刻暂停换下一个进程上 CPU。所有进程排队轮流使用 CPU雨露均沾不管是你自己写的 fork 父子进程还是浏览器、终端、输入法、系统后台服务全部都要遵守规则排队抢时间片轮流上 CPU 运行2、 上下文切换进程被强行暂停时它算到一半的数据、下一行要执行的代码不能丢。解决方案就是上下文切换。当前进程时间片用完内核立刻把它 CPU 寄存器里所有数据、运行位置、当前状态全部保存到该进程的 task_structPCB 进程档案里。暂时把这个进程 “挂起”放一边排队调取下一个待运行进程的 task_struct把它之前保存的寄存器数据、运行状态重新写回 CPUCPU 接着这个进程上次停下的位置继续往下执行这个保存现场 切换进程 恢复现场 的完整过程就叫做 进程上下文切换打个比方把CPU核心想象成只有一张的办公桌桌子只有一张同一时间只能一个人办公所有人所有进程排队每人只允许用桌 3 秒钟时间一到立刻收走这个人的本子、草稿、写到哪一页全部记好。换下一个人坐上来拿出自己的资料继续写切换速度超级快一秒钟来回换人几十上百次肉眼看起来所有人好像一直在同时办公实际上轮流插队、高速换人。3、 fork 创建的父子进程也是靠这个原理同时运行吗就是这个原理我们调用 fork() 后系统多出一个子进程此时系统里就有父进程、子进程 两个独立进程操作系统一样给它们各自分配时间片父跑一会儿、切走子跑一会儿、切走来回高速切换所以在终端上父打印一行、子打印一行、交替输出父子进程不是真的一起在 CPU 上跑是轮流抢 CPU 时间片 频繁上下文切换并发和并行并行有多颗 CPU / 多核同一时刻真正同时跑多个进程并发日常使用单个核心靠时间片高速切换宏观同时、微观串行有机会我们再来详细谈谈