• Linux-3-进程创建与进程状态


    前言

    Vue框架:Vue驾校-从项目学Vue-1
    算法系列博客友链:神机百炼

    查看进程

    法一:aux指令

    • 查看单一进程指令:

      ps aux | head -1 && ps aux | grep 进程名
      
      • 1
    • 进程PCB信息:
      进程信息
      关注点:PID + STAT + COMMAND

    法二:proc文件夹

    • process文件夹:

      ls /proc/
      
      • 1
    • 查看所有进程:
      所有进程

    • 以上所有蓝色数字就是PID,查看单一进程更多信息:

      ls /proc/对应进程的pid 				    //大致信息
      ls /proc/对应进程的pid -al				//详细信息
      
      • 1
      • 2
    • 单一进程大致信息:
      proc进程大致信息

    • 单一进程详细信息:
      proc进程详细信息

    回顾权限:

    • PS:linux文件类型

      符号含义作用
      -普通文件
      d目录文件
      c字符设备文件外设驱动
      b块设备文件外设驱动
      l符号链接文件快捷方式—指向另一文件/文件夹
      s套接字文件
      p管道文件
    • PS:权限:

      linux文件的用户分三类:owner编写者 + group同组员 + others陌生人

      linux文件的权限有三种:r读取 , w写入 , x运行 , -无权限

      三位二进制权限:rw-为110 ,r–为100

      对于d文件夹:ls为r ,touch为w ,cd为x

    • PS:用户权限更改:

      chmod u/g/o  +/-  rwx 文件名
      	  用户    增删 权限
      
      • 1
      • 2
    • PS:粘滞位更改删除权限:

      chmod +t 文件名				//设置粘滞位t防止同group删除文件
      
      • 1
    • PS:权限掩码umask

      假设二进制权限为110 110 110

      权限掩码umask为 001 001 001

      最终权限为:umask取反后 与 初始权限 ,即还是110 110 110

    创建进程:

    • 先回顾一下什么叫创建进程:

      1. 程序文件和 相关数据 ,从硬盘转移到内存
      2. 内存中创建一个PCB结构体,加入PCB双链表同时链接上对应程序+数据
      3. PCB等待CPU调度

    运行程序创建进程:

    • 初学进程时的概念就是程序运行起来就是进程

      ./文件名.文件后缀
      
      • 1

    系统调用接口创建进程:

    fork()调用实例:

    • 创建进程的系统调用接口

      #include 
      	pid_t fork(void);
      //fork()有两个返回值:一会输出看看
      //为父进程返回子进程的pid
      //为子进程返回0
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 在代码中创建子进程,同时查看其pid & ppid:
      fork创建进程

    • gcc 编译+ ./运行:
      查看fork进程ppd

    • 结论:

      1. 想要创建子进程,父进程必须在运行

      2. fork之前的代码,只有父进程执行

        fork之后的代码,父子进程都在执行

      3. fork之后的代码,父子进程谁先被执行取决于进程调度算法,先后顺序不定

      4. 所有进程由父进程创建,可以构成一颗多叉树

    • 硬件层面:子进程共享父进程数据和代码
      子进程共享父进程数据和代码

    利用fork()双返回值实现伪并行:

    • 代码:
      fork()代码

    • 结论:fork()有两次/两个返回值
      结论
      第一次为父进程返回的是创建的子进程的pid

      第二次为子进程返回0

    • 应用:

      利用不同的返回值,让父子进程同时执行不同的代码,也就是if else可以同时执行了:

      #include 
      #include 
      int main(){
      	printf("I am running...\n");
          pid_t id = fork();
          if(id == 0){				//子进程任务
      		while(1){
      			printf("child process\n");
                  sleep(1);
              }
          }else if(id > 0){			//父进程任务
      		while(1){
      			printf("parent process\n");
                  sleep(2);
              }
          }else{						//fork调用失败时返回负数
      		while(1){
      			printf("failed process\n");
                  sleep(1);
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 关于fork的软硬件细节,在《进程控制》中详细展开

    exit(1)退出进程

    • 结束进程的系统调用接口:

      #include 
      void exit(int status);					//status传递1即可
      
      • 1
      • 2
    • 使用:

      #include 
      #include 
      #include 
      int main(){
          pid_t id = fork();
          if(id == 0){
              for(int i=0; i<5; i++)
      			printf("这是子进程\n");
              printf("退出子进程\n");
              exit(1);						//进程退出
          }else if(id > 0){
      		while(1){
      			printf("这是父进程\n");
              }
          }
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

    kill -9杀死进程

    • kill -9 进程和exit(1)不同,kill的进程属于非自然死亡,不经过下文的进程状态z

    进程状态

    • 对于所有类型操作系统,宏观上的进程状态理解:
      宏观进程状态

    • linux将不同的进程状态通过宏定义,数字化表示

      且进程状态STAT信息存储在PCB/task_struct{}当中

      linux进程状态

    R:

    • 含义:R状态虽然写作running,但是其实是“就绪”,并非“运行中”

      ​ 所以很多PCB进程块可以同时表示自己已经R,等待被CPU调度

    • 调度队列:

      1. 当一个进程准备就绪时,其PCB加入调度队列,CPU不必遍历所有PCB双链表来寻找可以调度的进程
      2. 当一个进程执行完毕或暂时不准备执行时,其PCB更改STATE,且退出调度队列

    调度队列

    • 调度算法:父进程创建子进程后,执行顺序由调度队列的调度算法决定

      1. FIFO:先来先调度
      2. 优先级:下一篇展开
      3. ……

    S:

    • 含义:进程在等待其他事件就绪,称为浅度睡眠该进程随时可以被唤醒也可以被杀掉

    • 出现情况:

      1. 主动休眠:sleep()
      2. 被动休眠:等待外设或其余数据准备就绪,才能开始执行,如printf()
    • 典型情况:
      sleep()主动休眠

    • 查看进程状态:
      S状态

    D:

    • 含义:磁盘休眠状态 / 深度睡眠deep sleep

      区别于直接sleep,D状态的进程除非自己结束,谁也不能kill该进程

      设立D状态的目的同样也是为了等待其他事件的就绪或者说发生

    • 当PCB队列非常长的时,OS是可以kill掉S状态的进程的,但是不能kill掉D状态进程

    T:

    • 含义:暂停进程

    • 测试用例:
      测试用例

    • 查看此时进程状态:s
      s

    • kill -l 查看Linux支持的所有信号集:
      kill -l信号集可以通过kill命令向目标进程发送上述64条命令(1~31条是重点)

    • 向进程发送STGSTOP信号:

      kill -STGSTOP 28427
      
      • 1
    • 再看该进程的状态:T
      T暂停状态

    • 与休眠的区别:

      1. S/D状态在等,等其他条件具备来唤醒该进程
      2. T状态单纯自己暂停一会,由STG信号来暂停和开启
    • 结束暂停信号:开始继续运行

      kill -STGCONT 28427
      
      • 1

    Z&X:

    • Z zombie僵尸状态:

      进程退出后,OS还保留着他曾申请的资源,供其父进程/OS读取

      X dead死亡状态:

      进程退出后,OS读取完他曾申请的资源后(一定要有人读取完才能释放),释放这些曾用过的资源

    • 为什么要有僵尸状态:

      创建进程是为了解决问题,问题是否解决就看数据是否处理完成

      进程结束后,数据拿给父进程/OS检查一下,才能知道进程的任务完成的好不好

    • 典型的僵尸状态的应用:

      #include
      int main(){
          //任务代码
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      main函数的调用关系:

      1. OS调用加载器,
      2. 加载器调用mainCRCStartup()函数,
      3. mainCRCStartup()函数调用main()函数
      4. 如果任务代码没有出问题,那么虽然main进程结束,但是return 的 0就会被mainCRCStartup()接收
    • 退出码$:

      命令行中,最近一次进程退出时return的数据称为退出码:
      $接收return的退出码

    echo $?                         //$即退出码,此处打印11
    
    • 1
    • 进程退出的相关信息:

      进程退出后,保留的相关消息有很多,不只$退出码,当然这些都保存在了PCB结构体属性中

    • 查看/读取退出相关信息:

      等到《进程控制》博客,再来展开

    • 查看僵尸状态:

      1. 检测进程状态脚本:

        while :; do ps aux | head -1 && ps aux | grep myproc | grep -v grep;echo "##################################"; sleep 1; done
        
        //##################################是自己设置的行分割符
        
        • 1
        • 2
        • 3
      2. 创建进程:myproc.c

        #include 
        #include 
        #inclued <stdlib.h>
        int main(){
            pid_t id = fork();
            if(id == 0){
        		for(int i=0; i<5; i++){
        			printf("I am child process. pid:%d ppid:%d %d\n", getpid(), getppid(), i);
                    sleep(1);
                }
                printf("child quit\n");
                exit(1);
            }else if(id > 0){
        		while(1){
        			printf("I am father. pid:%d ppid:%d\n", getpid(), getppid());
                	sleep(1);
                }
            }else {
        		printf("failed child process\n");
            }
        	return 0;
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
      3. 查看进程状态:
        僵尸进程状态查询

    孤儿进程:

    • 僵尸进程:

      子进程直接退出,进入Z状态,等待父进程读取其退出信息后,子进程正式死亡

    • 孤儿进程:

      父进程直接退出,父进程进入Z状态,等待父进程的父进程读取其信息,将其死亡

      此时子进程还在运行,进入了**“孤儿进程状态”**,父进程已经退出,那么谁来回收子退出时的退出信息呢?

      如果孤儿进程的僵尸资源没有人回收,那么造成了经典内存泄漏问题

    • 孤儿进程会被OS领养,其退出时的僵尸资源被OS读取和释放,让孤儿僵尸进程彻底死亡

    • 举例:父进程先退出:

      #include 
      #include 
      #inclued <stdlib.h>
      int main(){
          pid_t id = fork();
          if(id == 0){
      		while(1){
      			printf("I am child. pid:%d ppid:%d\n", getpid(), getppid());
              	sleep(1);
              }
          }else if(id > 0){
              for(int i=0; i<5; i++){
      			printf("I am father. pid:%d ppid:%d %d\n", getpid(), getppid(), i);
                  sleep(1);
              }
              printf("father quit\n");
              exit(1);
          }else {
      		printf("failed child process\n");
          }
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 脚本查看父子进程状态:

      while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; echo "################################" ; sleep 1; done;
      
      • 1
    • 查看孤儿进程状态:ppid==1
      孤儿进程的父进程是OS,ppid=1

    进程状态切换:

    • 一图总结:
      进程状态切换
  • 相关阅读:
    【昇腾310】【mindspore 安装后测试报错】ImportError: libacl_tdt_channel.so
    【Java刷题系列】03构造类
    【Python】对中文排序
    Android持久化技术,好内存不如烂存储
    记 cisco ucs b200 m3 部署esxi 6.7
    SQL 如何提取多级分类目录
    C++ 算法竞赛、02 周赛篇 | AcWing 第2场周赛
    二手车交易管理系统
    低代码如何改变IT行业小作坊生产模式
    Docker在Centos7下的安装
  • 原文地址:https://blog.csdn.net/buptsd/article/details/126039603