Linux---进程状态
在上篇中知道了1.一个程序被运行必须先加载到内存当中2. 一个进程包括PCB自己代码和数据(整体操作系统而言) task_struct代码和数据(具体的操作系统linux而言)3.通过先描述后组织 操作系统对进程的管理转换为了对一个链表的增删查改。在一个task_struct中包含了各种信息 而进程状态其实就是里面的一个信息 进程状态在task_struct中其实就是一个数字通过数字来代表它的状态。运行 阻塞 挂起状态进程需要通过CPU来调度但是系统里面会有很多的进程而CPU却只有几个或者一个所以进程的task_struct会通过runqueue来管理起来由其在requeue中的顺序来决定执行的顺序。运行状态只要task_struct属于调度队列当中该进程就属于运行状态。阻塞状态例如 一个c程序的进程 它要运行会进入调度队列中 当排到它的时候开始执行它的代码 在代码中遇到scanf的时候 此时需要等待我们硬件的输入(键盘)此时它就处于阻塞状态。那我们一直不输入呢?现象是它就一直在那里等直到我们输入了内容按下回车键 此时它才会继续运行。那么其在等待我们键盘输入的过程中 这个进程还在原来的调度队列中吗不在原来的调度队列中了 它需要一直等待键盘输入如果它一直在调度队列中不就影响到其他进程的运行效率了吗所以它会被放到wait_queue中 而wait_queue是管理硬件的结构体的一个成员变量中 等待键盘输入的过程中它就一直在这个wait_queue中 此时该进程的状态就是阻塞状态在调度队列中的进程会继续的运行 这样就不会影响到其他的进程了。而这个进程直到键盘输入成功了此时这个硬件的结构体对象通过里面的成员变量接收到这个信息了就会把wait_queue里面的这个进程重新放到调度队列中 就重新变回了运行状态。挂起状态在上面的阻塞提到了管理硬件的结构体中会有wait_queuewait_queue里面的进程处于阻塞的状态下.如果此时内存的空间严重的不足了操作系统需要进行一些处理就会把处于阻塞状态的这些进程的代码和数据放到磁盘中的swap交换分区当中而task_struct保留此时这些代码数据被放到交换分区中的进程的状态就叫做阻塞挂起当此时键盘输入成功后操作系统一定是优先知道的 就会把交换分区中的代码和数据重新加载到内存当中然后处于阻塞状态的进程重新回到调度队列中。而如果此时内存空间依然不足的时候 操作系统就会把调度队列中靠后的一些进程的代码和数据放到磁盘swap交换分区中 task_struct保留此时代码和数据被放到交换分区中的进程的状态就叫做就绪挂起由上面我知道进程状态的变化 表现之一 就是要在不同的队列中进行流动 本质都是数据结构的增删查改上面这些是对具体的linux中而言的 而下图是对所有操作系统而言的下面是对所有的操作系统都符合的一个版本但是具体的操作系统可能会有一些差别。理解一个进程同时属于多种数据结构在上面中一个进程既属于调度队列又能属于双向链表还能属于等待队列 他们之间是怎么同时存在并且能够转换的在之前我们实现双向链表的时候都是用下面这样的方式-------一个结构体里面存着变量及该结构体的两个指针 一个指向前一个节点 一个指向后一个节点。通过里面存的指针就能找到下一个节点进而访问里面的数据而在linux中用下面这种方式先用一个结构体list_head来包含了prev及next指针而在task_struct中保存着各种数据及刚刚的list_head此时通过list_head里面的next及prev就能找到下一个或者前一个tast_struct的list_head但是此时有一个问题----通过 next及prev我们只能访问到前后tast_struct的list_head 并不能访问task_struct里面的其他信息呀?首先我们知道一个结构体对象的地址和第一个成员变量的地址是相同的例如 task_truct a aa.x结论通过(task_struct)(next-(((task_struct)0)-links))就可以由某一个list_head对象来找到当前所属的task_struct的第一个位置了过程分析(task_struct)0 以0为task_struct类型的第一个地址((task_struct)0)-links list_head在task_struct中的偏移量(task_struct)(next-(((task_struct)0)-links)) 由某个list_head对象next 来找到当前所属的task_struct的第一个位置也可以由c中offsetof这个宏来计算结构体的偏移量在stddef.h头文件中所以我们只要知道一个task_struct的head_struct对象就可以找到该task_struct结构体对象的所有信息这个问题是不用担心的。基于此就可以在task_struc写类似于head_struct的结构体这样一个task_struc就可以同时属于多种数据结构 也就可以理解了上面一个PCB为什么既能输入调度队列又能属于双向链表还可以在等待队列当中。linux进程状态 实验如下linux中各种状态在里面存的是数字打印时候转换为相应字符串就可以了。R运行状态如下 我们写了一个简单的程序后把它运行然后通过while :; do ps ajx |head -1 ps ajx | grep code; sleep 1; done(每一秒查一次code的进程) 查看它的进程发现一直处于S(阻塞状态)的状态下这是为什么呢因为代码里面有printf实际上运行时只有一小段时间是在执行代码 大部分时间处于等待IO的过程中所以我们观察到的大部分都处于S的状态下。而在把printf给去掉后 就可以发现这个进程处于R状态(运行状态)了R后面的是什么意思呢如下在让这个程序运行的时候再后面加一个再次观察该进程状态就会发现它处于R的状态加号消失了。所以 在后面带一个可以让进程在后台进行进程的状态后面带就说明当前的进程在前台运行----会阻塞我们继续在命令行进行输入而没有就说明该进程在后台运行。杀掉进程如果我们要杀掉这个进程可以用 kill -9 pid 的方式如下在执行 ./code 后一行显示出了该进程的pid或者我们可以通过查看进程pid 然后通过这个pid来把这个进程给杀掉。s(阻塞状态--浅睡眠)如下在代码写一个scnaf函数程序运行时候由上面理论知识我们知道进程就会处于阻塞状态 查看的时候发现果然处于S状态。T(阻塞状态---暂停状态深度睡眠)如下 在编译时候再后面加 -g让该程序处于debug下然后通过gdb调试 打了断点后通过r运行到断点处后发现----当前进程就处于t状态(追踪状态)。程序运行之后通过ctrlz后把该程序暂停后再次查看就会发现此时处于T状态了为什么要暂停?不直接结束进程当处于该状态通常是发生了意外的问题系统给终止了然后交给用户由用户自己决定接下来的处理。S和T都属于阻塞状态为什么要有T呢s(浅度睡眠)是可中断休眠此时可以通过kill来杀掉他而D(深度睡眠)是不可中断休眠不能被kill杀掉例如下面这样的场景此时有一个进程这个进程任务是向磁盘当中写10mb的数据而这个进程在通知磁盘之后不能直接结束因为它要接收磁盘是否把这10mb的数据写入成功了但是此时内存里面空间已经严重不足了操作系统看到这个进程一直在这里等着没什么事干就把他给杀掉了那此时磁盘如果写入数据失败了就不能把写入失败的信息告诉进程进而传达给用户。所以此时这10mb的资源泄漏了 并且用户自己还不知道但是这也不是操作系统、磁盘、进程任何人的问题 操作系统不这样处理可能造成更严重后果它并不知道这个进程还很重要所以此时有了D的状态用来标识类似于这个进程这样的情况处于D(深度睡眠)的情况下,不能通过kill把它杀掉 只能在它完成后自己结束。一点信号拓展ctrlz其实是通过信号把进程暂停了 通过kill -l可以查看所有信号之前的kill -9杀死进程就是用的9号信号通过kill -19 可以达到和ctrlz同样的作用 kill -18 可以让暂停的进程继续运行x(死亡状态) z(僵尸状态)一个进程在结束之后就进入了x状态 所以我们也观察不到。重点在于僵尸状态。z僵尸状态存在的意义fork可以创建子进程而父进程创建子进程一定是为了让子进程去完成什么任务而这个任务完成的结果也要通过这个子进程来获取(结束之后把信息存在PCB当中)而子进程在结束之后不会再被调度了其代码和数据没有意义了就会把它的代码和数据释放掉而PCB内容不会释放掉此时这个子进程的状态就是僵尸z状态。模拟验证z(僵尸状态)如下代码子进程在五秒之后就会结束 而父进程会一直进行下去。通过观察进程状态可以发现在子进程结束之前其是S状态而在子进程结束之后其就变成了Z状态 并且在该行最后还有default(失效的无用的)父进程是通过子进程的这个PCB来获取信息并且回收子进程输出信息的僵尸状态的那么如果父进程一直不管不回收不获取子进程的输出信息那么这个Z状态的PCB就会一直存在就会造成了内存泄漏的问题 解决方式是通过wait waitpid的方式在之后学习中会了解到补充两点1.内存泄漏内存泄漏问题如malloc或者new向堆区申请了空间但是一直没有被释放但是内存泄漏问题在程序结束后会自动解决会自动释放堆区空间所以内存泄漏真正的危害是对那些一直需要运行不退出的进程(常驻内存的进程)而言的例如 我们平常使用的各种软件大部分都是这样的2.内核结构申请在之前的数据结构中我们知道创建一个对象需要先动态开空间然后对对其进行初始化而动态开空间也是有消耗的 所以对于那些已经开好空间不再需要使用的对象可以不释放而是把它存储在某数据结构的缓存中在之后需要创建新的对象的时候可以不再动态开空间了而是直接把刚刚数据结构缓存中的一个拿过来进行初始化替换这样就节省了开空间的消耗。孤儿进程僵尸进程是子进程先结束了父进程没结束子进程的PCB还会保留就处于僵尸状态了而孤儿进程是父进程先结束了而子进程没有结束。如下 可以看到父进程结束之后子进程的父进程变成了一个pid为1的进程被该进程领养了该一号进程其实可以看做操作系统为什么要领养假设如果不领养的话子进程结束后还没有父进程把它回收它就会进入僵尸状态PCB里面的资源不会被释放 直到有一个进程给它处理了 而这个处理的人就只能是父进程和系统 而父进程已经结束了 所以只能由系统来管理。 所以如果不被领养的话就会造成僵尸进程的危害。并且我们可以发现刚刚的程序不能在命令行通过ctrlc来终止因为变成孤儿进程后该进程就变成了后台进程ctrlc不能直接取终止后台进程此时我们可以用kill -9来把它杀掉。