【Linux】进程状态

发布于:2025-07-26 ⋅ 阅读:(16) ⋅ 点赞:(0)

前言:

        上文我们讲到了关于进程的基本概念【Linux】初见,进程概念-CSDN博客

        本文我们来讲讲关于进程的状态

操作系统进程状态

操作系统中进程的概念大致分为三种:运行、阻塞、挂起

 运行(runing)

        如上图,只要在调度队列中的进程,其状态就是运行状态

阻塞

        当执行一个进程时,需要等待某种设备或资源就绪才可以继续执行时。就会因其需要等待而进入阻塞状态。比如需要执行scanf,就要等待键盘发送数据给计算机。

        如图,假设当前进程需要执行scanf,等待键盘的数据。操作系统就会将当前这个进程从运行队列中拿出,并链接至对应硬件的等待队列中(wait queue)当对应硬件有响应时,操作系统会第一时间得知,并再次将对应的进程链接回运行队列

        等待队列中的进程状态就是阻塞状态,阻塞状态下的进程不能被执行。

挂起

       挂起状态是一个极端情况下会触发的状态 。当内存空间不足时,操作系统为了正常的运行,就会将阻塞状态的进程的代码和数据唤入到磁盘中(具体为磁盘的swap交换分区。当需要执行该进程时,再将代码和数据从swap交换分区中唤出,并链接至运行队列。

        极端情况下,操作系统甚至可能把运行队列末端的进程进行挂起

        如果一个进程的代码和数据被唤入swap交换分区,这个进程的状态就是挂起状态

        进程状态的变化,其表现之一,就是在不同队列中流动,本质就是对不同的数据结构进行增删改查。

理解内核中的链表

        【Linux】初见,进程概念-CSDN博客在上文我们讲到了linux中stack_struct的组织方式是:双链表。而上面我们又讲到了CPU的调用是通过调度队列实现的。

        这就很矛盾了,在我们之前的学习中一个元素只可能属于一个数据结构。难道这里是不符合常理的吗??

        是的,这里的元素即属于双链表,又属于队列。可以同时属于多个数据结构

        内核中的实现方式与我们之前见到的有显著区别。 内核中将前后指针单独作为一个元素放在结构体中。那么问题就来了,内核中是如何实现找到其他成员变量的呢?

        1.找到links变量,在结构体中的偏移量   2.links的地址减去links的偏移量,就得到了结构体的起始地址。

prev/list - &((struct task_struct*)0) -> links

         &((struct task_struct*)0) -> links 的作用就是获得links的偏移量。 运算符 -> 的底层运行原理是:基地址 + 偏移量 = 目标地址,而偏移量是编译器在编译过程中自动推导的(既编译过程时就确定了每个成员的偏移量,&((struct task_struct*)0) -> links 其实也是宏 offsetof 的实现原理
        这里我们让0作为了基地址,0 + 偏移量 = 目标地址。于是我们通过 -> 运算符得到的目标地址就是偏移量

        前面的就好理解了,通过这个我们就可以得到结构体的起始地址。得到起始地址,那么访问任意一个元素就没有任何困难了

推导

        再上面的基础上,我们再添加多个links,就可以实现一个元素同时连接至多个数据结构。不论是双链表、队列、单链表等等等等,都可以实现

Linux进程状态

        上面讲到的操作系统进程状态,是笼统的大概念。下面我们来讲讲具体的操作系统下进程的状态

static const char *const task_state_array[] = {
 "R (running)", /*0 */
 "S (sleeping)", /*1 */
 "D (disk sleep)", /*2 */
 "T (stopped)", /*4 */
 "t (tracing stop)", /*8 */
 "X (dead)", /*16 */
 "Z (zombie)", /*32 */
};

        如上述,linux中的进程共有7种。在task_struct中用整型来表示进程的状态

运行状态

         R(running)与上述无异,既在运行队列中的进程即为运行状态

阻塞状态

        S(sleeping)D(disk sleep)

        S:可中断休眠,浅睡眠可以被信号唤醒,当等待的事件发生(键盘输入)时也会被唤醒

        D:不可中断休眠,深度睡眠通常用于等待磁盘I/O操作完成。等待I/O操作的过程中,进程等待磁盘的响应结果,以反馈给用户,所以不希望被打断。只有当I/O操纵完成时,进程才会被唤醒

停止状态  

        T(stopped):可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的 进程可以通过发送SIGCONT信号让进程继续运行。

        也可以通过调试打断点,来实现停止状态。

僵尸状态(重要)

        Z(zombie),是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程的退出代码时就会进入僵尸状态

        僵尸进程会一直在进程列表中保持退出状态,直到父进程读取带退出代码。

僵尸进程的危害

  1. 进程的退出状态必须被维护下去,因为子进程的执行结果必须要告知父进程才行。如果父进程一直没来读取子进程的退出代码,那么子进程就会一直处于僵尸状态。
  2. 进程的退出状态,也是属于进程的基本信息,所以会保存在task_struct中。也就是说Z状态一直不退出,task_struct就一直维护
  3. 父进程不回收子进程,就导致内存资源的浪费既内存泄漏。因为进程的task_struct是一个结构体对象,其本身就要占用内存。

孤儿进程

        在一组父子进程中,如果父进程比子进程先退出怎么办?父进程退出不用但是,因为父进程一定有它的父进程。但子进程这下就没有父进程了,如果后面子进程也要退出,没有父进程回收那岂不是出问题了?

        所以,对于父进程先退出的子进程,操作系统会让1号进程“领养”子进程。而这个被领养的子进程就叫作孤儿进程

        并且孤儿进程会自动转化为后台进程(后台进程不能使用指令:ctrl + c 杀掉)

补充:内核结构的申请

        一个操作系统中,有源源不断的进程被创建、被销毁。我们都知道一个进程包括:PCB + 代码和数据。PCB是一个结构体对象,而如果系统在创建进程时就申请空间、退出进程时就销毁PCB,这会极大拖慢运行速度

        所以,当一个进程退出时,操作系统并不会直接销毁其PCB。而是采用PCB复用机制,将空闲的PCB其链接至一个链表中。当系统需要创建进程时,就像链表中的空闲PCB取出,再初始化。即可重新使用


网站公告

今日签到

点亮在社区的每一天
去签到