进程创建与进程状态
前言
Vue框架:
Vue驾校-从项目学Vue-1
算法系列博客友链:
神机百炼
查看进程
法一:aux指令
-
查看单一进程指令:
ps aux | head -1 && ps aux | grep 进程名
-
进程PCB信息:
关注点:PID + STAT + COMMAND
法二:proc文件夹
-
process文件夹:
ls /proc/
-
查看所有进程:
-
以上所有蓝色数字就是PID,查看单一进程更多信息:
ls /proc/对应进程的pid //大致信息 ls /proc/对应进程的pid -al //详细信息
-
单一进程大致信息:
-
单一进程详细信息:
回顾权限:
-
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 文件名 用户 增删 权限
-
PS:粘滞位更改删除权限:
chmod +t 文件名 //设置粘滞位t防止同group删除文件
-
PS:权限掩码umask
假设二进制权限为110 110 110
权限掩码umask为 001 001 001
最终权限为:umask取反后 与 初始权限 ,即还是110 110 110
创建进程:
-
先回顾一下什么叫创建进程:
- 程序文件和 相关数据 ,从硬盘转移到内存
- 内存中创建一个PCB结构体,加入PCB双链表同时链接上对应程序+数据
- PCB等待CPU调度
运行程序创建进程:
-
初学进程时的概念就是程序运行起来就是进程
./文件名.文件后缀
系统调用接口创建进程:
fork()调用实例:
-
创建进程的系统调用接口
#include <unistd.h> pid_t fork(void); //fork()有两个返回值:一会输出看看 //为父进程返回子进程的pid //为子进程返回0
-
在代码中创建子进程,同时查看其pid & ppid:
-
gcc 编译+ ./运行:
-
结论:
-
想要创建子进程,父进程必须在运行
-
fork之前的代码,只有父进程执行
fork之后的代码,父子进程都在执行
-
fork之后的代码,父子进程谁先被执行取决于进程调度算法,先后顺序不定
-
所有进程由父进程创建,可以构成一颗多叉树
-
-
硬件层面:子进程共享父进程数据和代码
利用fork()双返回值实现伪并行:
-
代码:
-
结论:fork()有两次/两个返回值
第一次为父进程返回的是创建的子进程的pid第二次为子进程返回0
-
应用:
利用不同的返回值,让父子进程同时执行不同的代码,也就是if else可以同时执行了:
#include <unistd.h> #include <stdio.h> 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); } } }
-
关于fork的软硬件细节,在《进程控制》中详细展开
exit(1)退出进程
-
结束进程的系统调用接口:
#include <stdlib.h> void exit(int status); //status传递1即可
-
使用:
#include <stdio.h> #include <stdlib.h> #include <uinstd.h> 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; }
kill -9杀死进程
- kill -9 进程和exit(1)不同,kill的进程属于非自然死亡,不经过下文的进程状态z
进程状态
-
对于所有类型操作系统,宏观上的进程状态理解:
-
linux将不同的进程状态通过宏定义,数字化表示
且进程状态STAT信息存储在PCB/task_struct{}当中
R:
-
含义:R状态虽然写作running,但是其实是“就绪”,并非“运行中”
所以很多PCB进程块可以同时表示自己已经R,等待被CPU调度
-
调度队列:
- 当一个进程准备就绪时,其PCB加入调度队列,CPU不必遍历所有PCB双链表来寻找可以调度的进程
- 当一个进程执行完毕或暂时不准备执行时,其PCB更改STATE,且退出调度队列
-
调度算法:父进程创建子进程后,执行顺序由调度队列的调度算法决定
- FIFO:先来先调度
- 优先级:下一篇展开
- ……
S:
-
含义:进程在等待其他事件就绪,称为浅度睡眠,该进程随时可以被唤醒也可以被杀掉
-
出现情况:
- 主动休眠:sleep()
- 被动休眠:等待外设或其余数据准备就绪,才能开始执行,如printf()
-
典型情况:
-
查看进程状态:
D:
-
含义:磁盘休眠状态 / 深度睡眠deep sleep
区别于直接sleep,D状态的进程除非自己结束,谁也不能kill该进程
设立D状态的目的同样也是为了等待其他事件的就绪或者说发生
-
当PCB队列非常长的时,OS是可以kill掉S状态的进程的,但是不能kill掉D状态进程
T:
-
含义:暂停进程
-
测试用例:
-
查看此时进程状态:s
-
kill -l 查看Linux支持的所有信号集:
可以通过kill命令向目标进程发送上述64条命令(1~31条是重点) -
向进程发送STGSTOP信号:
kill -STGSTOP 28427
-
再看该进程的状态:T
-
与休眠的区别:
- S/D状态在等,等其他条件具备来唤醒该进程
- T状态单纯自己暂停一会,由STG信号来暂停和开启
-
结束暂停信号:开始继续运行
kill -STGCONT 28427
Z&X:
-
Z zombie僵尸状态:
进程退出后,OS还保留着他曾申请的资源,供其父进程/OS读取
X dead死亡状态:
进程退出后,OS读取完他曾申请的资源后(一定要有人读取完才能释放),释放这些曾用过的资源
-
为什么要有僵尸状态:
创建进程是为了解决问题,问题是否解决就看数据是否处理完成
进程结束后,数据拿给父进程/OS检查一下,才能知道进程的任务完成的好不好
-
典型的僵尸状态的应用:
#include<stdio.h> int main(){ //任务代码 return 0; }
main函数的调用关系:
- OS调用加载器,
- 加载器调用mainCRCStartup()函数,
- mainCRCStartup()函数调用main()函数
- 如果任务代码没有出问题,那么虽然main进程结束,但是return 的 0就会被mainCRCStartup()接收
-
退出码$:
命令行中,最近一次进程退出时return的数据称为退出码:
echo $? //$即退出码,此处打印11
-
进程退出的相关信息:
进程退出后,保留的相关消息有很多,不只$退出码,当然这些都保存在了PCB结构体属性中
-
查看/读取退出相关信息:
等到《进程控制》博客,再来展开
-
查看僵尸状态:
-
检测进程状态脚本:
while :; do ps aux | head -1 && ps aux | grep myproc | grep -v grep;echo "##################################"; sleep 1; done //##################################是自己设置的行分割符
-
创建进程:myproc.c
#include <stdio.h> #include <unistd.h> #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; }
-
查看进程状态:
-
孤儿进程:
-
僵尸进程:
子进程直接退出,进入Z状态,等待父进程读取其退出信息后,子进程正式死亡
-
孤儿进程:
父进程直接退出,父进程进入Z状态,等待父进程的父进程读取其信息,将其死亡
此时子进程还在运行,进入了**“孤儿进程状态”**,父进程已经退出,那么谁来回收子退出时的退出信息呢?
如果孤儿进程的僵尸资源没有人回收,那么造成了经典内存泄漏问题
-
孤儿进程会被OS领养,其退出时的僵尸资源被OS读取和释放,让孤儿僵尸进程彻底死亡
-
举例:父进程先退出:
#include <stdio.h> #include <unistd.h> #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; }
-
脚本查看父子进程状态:
while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; echo "################################" ; sleep 1; done;
-
查看孤儿进程状态:ppid==1
进程状态切换:
- 一图总结: