进程相关理解
进程是什么?

我们看的见的有 双击win下的软件快捷方式 Linux下./xxx.exe某可执行文件 就是启动进程
进程被操作系统 通过先描述再组织的方式 对描述进程的结构体进行 增删查改
而运行代码和数据是通过这个结构体的某一个指针 去找到这些代码和数据段从而进行调用
而这个结构体 被统一称为PCB(里面包含指向数据段和代码的指针)
这个结构体在Linux下叫 struct task_struct{}; 具体内容如下

标识符(PID)每个进程都是唯一的 通过循环计数的方式 给进程分配pid
Ls /proc 查看所有进程信息(pid展示)/1(可跟具体pid查看具体进程的信息)
状态:核心运行与等待状态 暂停与调试状态 进程终止状态 这些状态在PCB里通过 整形码来标识
优先级:底层同样是用整形数码来标识 不过与状态的有所区别(有权重相关的)
程序计数器:是上下文数据内部的 一个子模块(指向即将要运行的下一条指令)
内存指针:就是指向 关联地址
查看进程
ls /proc 查看所有进程目录

ls /proc/xx(具体数字(pid))查看某个进程的具体细节

具体某个进程中 比较重要的几个文件
1.cwd 当前工作路径
-
查看进程运行时所在的目录
-
程序读取
./config.json报错No such file,立刻ls -al /proc/<pid>/cwd看它到底在哪个目录下找文件,解决“相对路径”引发的问题
2.exe 可执行文件的路径位置
-
指向被执行的二进制文件
-
场景:线上更新了代码但忘记重启,或者怀疑跑了旧版本的进程,
ls -al /proc/<pid>/exe看一眼修改时间(mtime),就能确认是不是旧进程
3.fd/ 打开的文件描述符
-
一个目录,里面全是数字,代表该进程打开的所有文件、Socket、管道
-
场景:
-
排查“Too many open files”:
ls -l /proc/<pid>/fd | wc -l看看是不是超了ulimit -n。 -
排查“端口被占用”:
ls -l /proc/<pid>/fd看哪些是socket:[12345],配合netstat定位是哪个进程占用了端口(比lsof -i有时候更快) -
查看日志输出:如果程序日志不打印了,看
fd/1(标准输出)和fd/2(标准错误)指向哪里,是不是被重定向到了黑洞
-
4. maps 内存映射区域
-
显示进程的虚拟内存布局(代码段、数据段、堆、栈、共享库)
-
场景:
-
确认动态库加载:
cat /proc/<pid>/maps | grep .so看程序到底加载了哪个目录下的libstdc++.so,解决“编译时高版本,运行时低版本”引发的undefined symbol崩溃 -
定位内存碎片/泄露:配合
pmap -x <pid>,看heap区域的地址范围是否异常膨胀
-
5. status 进程状态 细节更多
-
包含进程状态、内存用量、线程数、PPID(父进程ID)
常用检查项(grep 出来):
grep -E "VmRSS|VmSize|Threads|State" /proc/<pid>/status
-
VmRSS:物理内存实际占用--常驻内存 -
Threads:当前线程数,防止线程爆炸
6. stack 当前内核态调用栈(解“D”状态死锁)
-
显示进程当前正在执行的内核系统调用堆栈
-
场景:进程处于
D(不可中断睡眠)状态,kill -9都杀不死,看cat /proc/<pid>/stack确认是不是卡在某个磁盘 I/O 或锁上了(如wait_on_page_read)
7. wchan 进程在等待什么内核函数
-
显示进程当前阻塞在哪一个内核函数上。
-
场景:用
top看到进程是S(睡眠)状态,但不知道在等什么,cat /proc/<pid>/wchan
返回 futex_wait_queue_me 大多是在等用户态锁(互斥量),返回 epoll_wait 说明在正常等待网络事件,是健康的空闲状态
创建进程
在C/C++代码中 利用fork()函数创建子进程--可以创建多个子进程
-
运⾏ man fork 认识fork---fork函数是一个系统调用的函数
-
fork有两个返回值
-
⽗⼦进程代码共享,数据各⾃开辟空间,私有⼀份(采⽤写时拷⻉)
-
fork之后一般用if语句分流 返回值两个 0是子进程 子进程pid是父进程(大于0)
fork为什么会有两个返回值?
-
父进程拿到子进程 PID:父进程需要知道孩子的 ID,以便后续调用
waitpid()回收子进程,或者用kill()发信号 -
子进程拿到 0:子进程拿到 0 就知道是刚创建的子进程,然后立刻调用
exec()族函数加载新程序,或者去执行子任务
两个返回值各种给⽗⼦如何返回?
-
普通函数:调用一次,返回一次,调用者和被调用者在同一个线程空间
-
系统调用:调用一次,但内核在返回前“分裂”成了两个执行流。内核分别向这两个执行流各自执行了一次“返回值写入寄存器”的操作

⼀个变量怎么能让 if 和 else if 同时成⽴?
-
在内核两份独立的进程中分别两份 pid_t 进入了不同分支 然后在用户端出现了 两个进程同时运行的表现形式
-
fork()执行后,内核将父进程的地址空间完整复制了一份 因此物理内存中存在着两份独立的pid变量 -
父进程的那份内存里存的是子进程 PID(大于0),所以父进程走进了
if分支 子进程的那份内存里存的是 0,所以子进程走进了else if分支 -
由于调度器让两个进程交替或并行运行,在宏观的用户视角下,就好像‘同一个变量’同时成立了两个分支一样
进程状态
下面这个图的 表现形式适用于所有操作系统的状态理解

自己学的是 Linux内核
一个进程可以有多个状态 在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 */
};
R:运行状态 进程正常运行
S:浅度睡眠(阻塞状态)可中断睡眠状态
D:深度睡眠 无法被中断睡眠 需要用kill -9 信号命令来杀死
T:暂停状态
t:追踪状态 如 在cgdb模式下调试某进程
X:死亡状态 程序运行结束的状态
Z:僵尸状态 程序任务完成后 但没有结束的状态 如 父子进程 子进程结束 但是父进程没有结束 此时子进程处于僵尸状态
调度队列:进程运行本质上是被调度了 而进程要被调度 就要进入到调度队列里 等待被调度
等待队列:被调度的队列 有时会遇到暂停的时候 比如cin,scanf等发挥作用时 此时就会把这个进程从调度队列里拿到等待队列里 等待从 硬件里获取数据(操作系统先知道) 也就是等待硬件就绪 就绪后又会被放到调度队列里去 等待调度
挂起队列:当进程调度也就是运行时 或 进程阻塞的时候 内存空间不足 此时不运行的或者是交换影响小的进程就会和存储空间的swap分区交换进程的代码和数据段 给swap分区 此时PCB(pcb一直在内存里)进入挂起队列(抽象类比出来的)---这里又分为 运行挂起/阻塞挂起
swap分区不能过大 否则会拖慢系统运行速度 swap的本质就是用时间换空间
调度、阻塞和挂起的本质就是 看PCB在谁提供的队列里
孤儿进程: 父进程结束 子进程还没结束 此时子进程会被bash领养 这个子进程就叫做孤儿进程
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)