[Linux]初识进程

进程

什么是进程

一个运行起来的程序就是进程。程序的本质是一个文件,它存储在磁盘中,而进程是已经被加载到了内存的程序。
进程 = 内核数据结构 + 进程对应的代码。

如何管理进程

当有多个程序被加载到内存,被CPU执行的时候。操作系统为了管理(管理的本质逻辑逻辑都是:先描述,再组织。描述:是指将某些东西的共同属性拿出来形成一个结构体或和类,组织:将对象通过某种数据结构联系起来)这些进程,要为每一个进程创建对应的pcb(是一个struct task_struct{};结构体,包含了该进程的所有属性和对应代码的地址),描述完成之后,再把所有创建完的结构体对象组织起来。所以对进程的管理就变成了对数据结构的增删查改。

查看进程的方式

  1. ps ajx | head -1 && ps ajx | grep mytest

  2. ls /proc/

常见的进程调用

  • getpid():查看进程的pid

  • getppid():查看进程的ppid,也就是该进程父进程的pid

    每一次重新运行程序的时候,它的pid都会改变,而ppid却一直不变。这是因为命令行上启动的进程,一般它的父进程没有特殊情况的话都是bash,而bash是我们登录时系统就为我们分配,直到我们退出之前都不会改变。

  • fork():创建子进程

    程序执行在fork()函数之前只有一个执行流,在fork()函数之后会产生两个执行流,所以它才会有两个返回值,将子进程的pid返回给父进程,将0返回给子进程。

进程状态

在一个进程的生命周期中,并不是一直处于运行的状态,它受到多种因素的影响体现出不同的状态。

操作系统层面

  1. 运行态:当一个进程正在CPU上运行或者在CPU的运行队列中。
  2. 阻塞态:进程在执行过程中,由于等待某一事件(如 I/O 操作完成、等待某个信号量、等待另一个进程的消息等)的发生而暂时无法继续执行的状态。在这个状态下,进程会让出 CPU,暂停自身的执行,直到等待的事件满足后才有可能被重新调度执行。操作系统会为每个处于阻塞态的进程维护一个等待队列,将阻塞进程加入到对应设备的阻塞队列中。
  3. 挂起态:由于内存空间不够,操作系统会将某些进程的代码和数据暂时保存到磁盘上,以节省内存空间。

Linux层面

  • R 运行状态(running):进程正在使用CPU执行指令。但也有可能处在运行队列里等待被调度运行,这种也属于运行态。

  • S 睡眠状态(sleeping):也称为可中断睡眠,意味着进程等待事件完成。

    #include <stdio.h>    
        
    int main()    
    {    
        int flag = 0;    
        int a = 0;    
        while(1)    
        {    
            a = 1 + 1;    
            printf("a的值是:%d,flag:%d\n", a, flag);                                
            flag++;    
        }    
        return 0;    
    }
    

    从上述代码的执行结果可以发现,为什么程序任然在执行,但却显示的是睡眠状态?

    那是因为当程序进行printf操作的时候,由于printf会访问显示器,而显示器又属于外设,进程访问外设的时间相对于CPU来说是很慢的,因此当查看进程状态的时候,进程几乎都会处于睡眠状态。

  • T 停止状态(stopped):可以发送SIGSTOP来使进程暂停运行,发送SIGCONT信号可以使进程重新恢复运行。

    停止:

    继续运行:

  • D 磁盘休眠状态(Disk sleep):又称为不可中断睡眠。在该状态的进程,无法被操作系统杀掉,只能通过断电或者进程自己醒来来解决。

  • X 死亡状态(dead):在进程列表中通常不可见。进程已经完全结束,资源已经完全释放,仅仅作为一个概念存在。

  • t 跟踪状态(Trace):当进程正在被跟踪(例如程序在调试时)所处的状态。

  • Z 僵尸状态(Zombie):进程退出的时候不能立即释放所拥有的资源,而是要保存一段时间,让父进程或操作系统来读取。若父进程或操作系统没有回收子进程的资源,此时子进程就会变为僵尸状态。由于僵尸状态的进程还会保留一定的资源,因此若有过多的僵尸进程存在则可能会导致资源耗尽等问题。

  • 以下是一个僵尸进程的例子:

    #include <stdio.h>    
    #include <unistd.h>    
    #include <stdlib.h>    
        
    int main()    
    {    
        pid_t id = fork();    
        
        if (id == 0)    
        {    
            printf("子进程pid:%d, ppid:%d\n", getpid(), getppid());    
            sleep(5);    
            exit(1);    
        }    
        else    
        {    
            printf("父进程pid:%d, ppid:%d\n", getpid(), getppid());    
            sleep(60);
        }
        return 0;    
    }  
    

    运行截图

进程状态后面的”+“表示这个进程是否是前台进程。带加号的是前台进程,此时输入的命令不会被立即执行。并且ctrl + c可以终止它。而不带加号的是后台进程,可以正常的输入命令来进行交互。但是无法通过ctrl + c来终止它。

孤儿进程

孤儿进程是指它的父进程已经结束运行,而自己仍然在运行的进程。

当父进程结束运行时,它的所有子进程会被操作系统的init进程接管,init进程的进程id通常为1,它会负责完成这些孤儿进程的后续资源回收等清理工作。而且一般孤儿进程都是后台进程,需要kill指令结束进程。

进程优先级

由于系统中的资源是有限的,因此有了进程优先级这个概念。进程优先级是决定一个进程获得某种资源的先后顺序。

  • PRI:是priority的缩写,一个进程的PRI值代表了它可被执行的优先级程度。数值越小,优先级越高。(默认值是80)
  • NI:是nice的缩写,NI的范围是[-20,19]。一个进程的最终优先级的计算方式为:PRI = PRI(默认) + NI。

通过TOP命令可以改变NI的值,进而影响最终PRI。TOP命令使用方法是:top -> 输入r -> 输入进程pid -> 输入NI值

进程切换

由于CPU资源有限,一个进程在CPU上运行的时候并不是一直占有直到进程结束,而是在不断的进行进程的切换,从而实现多任务处理。

当一个进程执行完一个时间片之后,就会进行进程的切换,如此循环往复,就形成了进程间的轮转调度。

那么当一个进程的时间片执行完成之后,当它下次又要被执行的时候,CPU是如何知道从什么地方开始执行呢?
那是因为在CPU的内部有一套寄存器,这套寄存器被所有进程共享,这些寄存器保存了当前进程执行的一些数据,当进程被切换的时候,这些数据会被保存到进程的内核栈中,然后再将另一个进程内核栈中对应的寄存器数据恢复到CPU上,这样就可以让CPU继续从上次被切换的位置继续向下执行。

posted @   羡鱼OvO  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示