进程是什么?

我们看的见的有 双击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领养 这个子进程就叫做孤儿进程

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐