当前位置: 首页 > news >正文

嵌入式 - Linux软件编程:进程

目录

一、进程:程序的动态执行实体

1. 进程与程序的本质区别

2. 进程的内存空间布局

3. 虚拟地址与物理地址的映射

4. 多进程的调度机制

5. 进程相关的命令

1. top 命令

2. ps -ef 命令

3. ps -aux 命令

4. 后台执行进程

5. jobs 命令

6. fg 命令

7. nice/renice 命令

8. kill/killall 命令

6. 进程的状态

3. 进程相关函数接口

1. fork 函数

2. getpid 和 getppid 函数

3. exit 与 _exit 函数

4. 进程回收:wait / waitpid

(1) wait 函数

(2) waitpid 函数(扩展)

4. 进程消亡

1. 孤儿进程

2. 僵尸进程

二、线程:进程内的轻量级执行单元

1. 线程与进程的核心区别

2. 线程的创建与终止

3. 线程同步机制

三、总结


在操作系统的世界里,进程与线程是实现多任务处理的核心机制。无论是服务器后台的并发处理,还是嵌入式设备的任务调度,都离不开对进程和线程的深入理解。本文将基于 Linux 系统,详细解析进程的本质、生命周期及相关操作,并补充线程的核心知识,帮助开发者构建清晰的多任务编程认知。

一、进程:程序的动态执行实体

1. 进程与程序的本质区别

进程是程序的动态执行过程,包含创建、调度、消亡的完整生命周期。

        《进程像一个教室有不同的学生上课,每个座位号可以对应不同的学生》

而程序则是存放在外存中的一段静态数据集合,是进程运行的 "蓝图"。

打个比方,程序就像乐谱,进程则是按照乐谱进行的演奏过程 —— 同一乐谱可以被多个乐队同时演奏(多进程运行同一程序),每个演奏过程都是独立的进程。

2. 进程的内存空间布局

当进程运行时,操作系统会为其分配 0-4G 的虚拟内存空间,分为用户空间内核空间(内核空间不允许用户直接访问)。

  • 进程空间:用户空间 + 内核空间(不允许用户访问)
  • 用户空间:文本段(文本区) + 数据段(数据区) + 系统数据段(堆区、栈区)

用户空间又细分为以下几个部分:

  1. 文本段:存放程序的代码和指令,是只读区域,确保代码不会被意外修改。
  2. 数据段:包含字符串常量、已初始化的全局 / 静态变量、未初始化的全局 / 静态变量。
    • 其特点是
    • 编译时开辟空间,
    • 程序结束时回收,
    • 且未初始化的变量会被默认设为 0 值。
  3. 系统数据段:包括堆区和栈区
    • 堆区
      • 由开发者通过malloc手动申请,
      • free释放,
      • 需注意避免内存泄露。
    • 栈区:自动存放局部变量函数运行状态
      • 变量超出作用域后自动回收,
      • 执行到变量定义开辟内存空间
      • 未初始化的变量值为随机值。

3. 虚拟地址与物理地址的映射

多个进程空间在操作系统中存储时,空间是独立的(物理地址是独立的

多个进程在操作系统中共用同一个虚拟内存空间(虚拟地址是共享的)

  • 虚拟地址:用户可见的地址,代表程序可寻址的范围,所有进程共享相同的虚拟地址空间。
  • 物理地址:内存硬件实际存储数据的地址,每个进程的物理地址是独立的。
  • MMU(内存映射单元):负责将虚拟地址转换为物理地址,实现进程对内存的安全访问。

4. 多进程的调度机制

多进程能 "同时" 运行,本质是宏观并行、微观串行

  • 宏观上,一个 CPU 似乎在同时处理多个进程;
  • 微观上,CPU 通过调度算法在进程间高速切换,实现多任务并发。

常见的调度算法包括:

  • 先来先执行:按进程到达顺序调度;
  • 高优先级调度:优先级高的进程优先执行;
  • 时间片轮转:每个进程分配固定时间片,超时后切换到下一个进程。

5. 进程相关的命令

1. top 命令
  • 示例top
  • 功能:实时查看进程的 CPU、内存占用等信息,动态刷新。
  • 字段说明
    • PID:进程唯一 ID
    • USER:进程所属用户
    • PR NI:优先级(PR 实时优先级,NI nice 值)
    • VIRT:虚拟内存占用(含交换区)
    • RES:物理内存占用(常驻内存)
    • SHR:共享内存占用
    • S:进程状态(R/S/D 等)
    • %CPU:CPU 使用率
    • %MEM:内存使用率
    • TIME+:累计运行时间
    • COMMAND:进程命令
  • 退出:按 q 键
2. ps -ef 命令
  • 示例ps -ef
  • 功能:查看某一时刻的全量进程信息(系统级视角)。
  • 字段说明
    • UID:进程所属用户 ID
    • PID:进程 ID
    • PPID:父进程 ID
    • C:CPU 利用率(简化值)
    • STIME:启动时间
    • TTY:关联终端(? 表示无终端)
    • TIME:累计 CPU 时间
    • CMD:进程命令
  • 过滤用法ps -ef | grep 进程名(筛选目标进程)查找与进程名对应的进程信息
3. ps -aux 命令
  • 示例ps -aux
  • 功能:查看某一时刻的全量进程信息(用户级视角,含资源细节)。
  • 字段说明
    • USER:进程所属用户
    • PID:进程 ID
    • %CPU:CPU 使用率
    • %MEM:内存使用率
    • VSZ:虚拟内存大小(KB)
    • RSS:常驻内存大小(KB)
    • TTY:关联终端
    • STAT:进程状态(如 R/S/Z 等)
    • START:启动时间
    • TIME:累计 CPU 时间
    • COMMAND:进程命令
4. 后台执行进程
  • 示例./a.out &
  • 功能:让进程在后台运行(& 符号标记后台任务)
5. jobs 命令
  • 示例jobs
  • 功能:查看当前终端的后台进程列表
6. fg 命令
  • 示例fg %1%1 为 jobs 查看的任务编号)
  • 功能:将后台进程调至前台运行
7. nice/renice 命令
  • 示例
    • 启动时设优先级:nice -n 10 ./a.out(优先级 10
    • 修改已有进程:renice -n 5 -p 1234(PID 1234 优先级改为 5
  • 功能:设置 / 修改进程优先级
  • 注意
    • 优先级范围:-20 ~ 20(值越小,优先级越高,需权限)
8. kill/killall 命令
  • 示例
    • 杀进程:kill -9 1234-9 为强制终止信号 SIGKILL
    • 杀同名进程:killall -TERM nginx-TERM 为终止信号)
  • 功能:通过信号终止进程

6. 进程的状态

进程状态标识含义描述
就绪态 / 运行态R进程在 CPU 调度队列中,随时可执行(或正在执行)
可唤醒等待态S等待资源(如 IO),资源就绪后可被唤醒;等待中可被信号打断
不可唤醒等待态D等待资源(如磁盘 IO),资源就绪后可被唤醒;等待中不可被信号打断(强阻塞)
停止态T进程被暂停(如 Ctrl+Z),需手动恢复
僵尸态Z进程已终止,但父进程未回收其 PCB,残留退出状态等信息
结束态X进程已终止,且内核已回收所有资源,无实际实体

3. 进程相关函数接口

1. fork 函数
  • 原型pid_t fork(void);
  • 功能:创建子进程,父进程复制自身给子进程(写时复制)。
  • 返回值
    • 父进程:返回 子进程 PID>0)。
    • 子进程:返回 0
    • 出错:返回 -1
  • 核心特性
    • 子进程拷贝父进程的代码段、数据段、系统数据(初始共享,修改时拷贝)。
    • 父子进程空间独立,变量修改互不影响。

注意:

子进程拷贝父进程文本段、数据段、系统数据段

父进程与子进程空间独立,同一份代码中的变量和数据都会在父子进程中各有一份,父子进程

修改自己空间的数据不会影响对方的空间

进程的PID不一样

fork的返回值不一样,父进程中返回子进程的PID,子进程中返回0

PID:一定是 > 0

#include "../head.h"int main(void)
{pid_t pid;pid = fork();if(-1 == pid){perror("fail to fork");return -1;}if(0 == pid){printf("====子进程=====\n");printf("我是子进程\n");printf("PID:%d PPID:%d\n",getpid(),getppid());printf("====end=====\n\n");}if(pid > 0) //进程的pid一定是个 大于0 的数{printf("=====父进程====\n");printf("我是父进程\n");printf("自己PID: %d 子进程pid: %d\n",getpid(),pid);printf("====end=====\n\n");}printf("hello world\n\n");// while(1)// {// }return 0;
}

 

#include "../head.h"int main(void)
{pid_t pid;pid_t xpid;pid = fork();if(-1 == pid){perror("fail to fork");return -1;}if(0 == pid){printf("1子进程\n");printf("PID: %d PPID: %d\n",getpid(),getppid());printf("======end=======\n");}else if(pid > 0){printf("=====1======\n\n");//(文本段)printf("how are you"); //会一起在 2子进程里打印//因为stdout 是标准io 遇到\n缓存//子进程 拷贝文本段,数据段,系统数据段//父子进程 "空间独立" ,同一份的代码和数据会在父子里面给有一份//父子进程修改自己空间的数据不会互相影响xpid = fork(); //从创建的时候开始 子进程2 就开始执行以下的程序printf("======2=====\n\n");if(-1 == xpid){perror("fail to fork");return -1;}if(0 == xpid){printf("2子进程\n");printf("PID: %d PPID: %d\n",getpid(),getppid());printf("======end=======\n");}       else if(xpid > 0){printf("父进程\n");printf("PID: %d 1子PID: %d ",getpid(),pid);printf("2子PID: %d\n",xpid);printf("======end=======\n"); }while(1) {//不让父进程结束,防止2子进程成为孤儿进程}}return 0;
}

  • 子进程拷贝父进程的代码段、数据段、系统数据(初始共享,修改时拷贝)
#include "../head.h"int main(void)
{pid_t pid;printf("hello world");pid = fork();if (-1 == pid){perror("fail to fork");return -1;}if (0 == pid){printf("thank you!\n");}else if (pid > 0){printf("how are you\n");}return 0;
}
  • 父子进程空间独立,变量修改互不影响。
#include "../head.h"int main(void)
{pid_t pid;int *pnum = NULL;pnum = malloc(4);if (NULL == pnum){perror("fail to malloc");return -1;}pid = fork();if (-1 == pid){perror("fail to fork");return -1;}if (0 == pid){*pnum = 100;printf("pnum = %p, *pnum = %d\n", pnum, *pnum);}else if (pid > 0){sleep(1);printf("pnum = %p, *pnum = %d\n", pnum, *pnum);}return 0;
}
2. getpid 和 getppid 函数
  • getpid
    • 原型:pid_t getpid(void);
    • 功能:获取当前进程的 PID。
  • getppid
    • 原型:pid_t getppid(void);
    • 功能:获取当前进程父进程的 PID。
3. exit 与 _exit 函数
  • exit
    • 原型:void exit(int status);
    • 功能:终止进程,刷新缓冲区(如标准输出缓存)后退出。
  • _exit
    • 原型:void _exit(int status);
    • 功能:终止进程,不刷新缓冲区,直接退出。
  • 差异
    • 主函数中 return 等价于 exit(触发缓冲区刷新)。
    • 非主函数中,exit 终止整个进程,return 仅终止当前函数。
#include "../head.h"int main(void)
{pid_t pid;pid = fork();if (-1 == pid){perror("fail to fork");return -1;}if (0 == pid){while (1){printf("PID:%d PPID:%d\n", getpid(), getppid());sleep(1);}}else if (pid > 0){printf("父进程即将退出!\n");printf("父进程即将退出!"); //没有 \n 会停滞在缓存区_exit(0); //不会刷新缓存区所以不会打印}return 0;
}
  •  stdout 是标准IO 会缓存

4. 进程回收:wait / waitpid
(1) wait 函数
  • 原型pid_t wait(int *wstatus);
  • 功能阻塞等待任意子进程终止,回收其资源。
  • 参数wstatus 存储子进程退出状态(可通过 WEXITSTATUS 等宏解析)。
  • 返回值
    • 成功:返回已回收子进程的 PID
    • 失败:返回 -1(如无子进程)。
#include "../head.h"int main(void)
{pid_t pid;pid_t ret;pid = fork();if (-1 == pid){perror("fail to fork");return -1;}if (0 == pid){sleep(5);printf("子进程即将退出!\n");exit(0);}else if (pid > 0){ret = wait(NULL);printf("回收到子进程(PID:%d)空间\n", ret);while (1){}}return 0;
}
(2) waitpid 函数(扩展)
  • 原型pid_t waitpid(pid_t pid, int *wstatus, int options);
  • 功能:指定回收目标子进程(更灵活,支持非阻塞)。
  • 参数
    • pid:目标 PID(-1 表示任意子进程,同 wait)。
    • wstatus:存储退出状态。
    • options:如 WNOHANG(非阻塞模式,子进程未结束时返回 0)。
  • 返回值
    • 成功回收:返回子进程 PID。
    • 非阻塞且子进程未结束:返回 0
    • 失败:返回 -1

4. 进程消亡

1. 孤儿进程
  • 产生:父进程先终止,子进程被 init 进程(系统初始化进程)收养。
  • 特性init 会负责回收孤儿进程的资源,因此不会产生僵尸进程。
2. 僵尸进程
  • 特性:是每个进程结束必然会经历的阶段
  • 产生原因
    • 子进程结束后,父进程没有回收子进程空间,导致进程执行结束但资源(如 PCB )依然被占用的状态,称为僵尸进程
  • 如何避免产生僵尸进程?
    • 让父进程先结束:子进程成为孤儿进程,被 init 进程收养;子进程再结束时,init 进程会回收其空间。
    • 父进程主动回收:子进程结束后,父进程调用 wait/waitpid 回收子进程空间。
#include "../head.h"int main(void)
{pid_t pid;pid_t ret;pid = fork();if (-1 == pid){perror("fail to fork");return -1;}if (0 == pid){sleep(5);printf("子进程即将退出!\n");exit(0);}else if (pid > 0){ret = wait(NULL);printf("回收到子进程(PID:%d)空间\n", ret);while (1){}}return 0;
}

二、线程:进程内的轻量级执行单元

进程是资源分配的基本单位,而线程是CPU 调度的基本单位。一个进程可以包含多个线程,线程共享进程的内存空间(文本段、数据段、堆区等),但拥有独立的栈区和寄存器。

1. 线程与进程的核心区别

维度进程线程
资源分配独立地址空间,资源独占共享进程资源
切换开销大(需切换地址空间)小(仅切换栈和寄存器)
通信方式需 IPC(如管道、信号量)直接访问共享内存
独立性高(一个崩溃不影响其他)低(共享资源,相互影响)

2. 线程的创建与终止

  • 创建线程:通过pthread_create()函数,需指定线程执行的函数、参数等。
  • 终止线程pthread_exit()主动结束线程,pthread_cancel()可取消其他线程。
  • 等待线程pthread_join()阻塞等待线程结束并回收资源,类似进程的wait()

3. 线程同步机制

由于线程共享资源,需通过同步机制避免数据竞争:

  • 互斥锁(pthread_mutex_t:确保同一时间只有一个线程访问临界资源(如全局变量)。
  • 条件变量(pthread_cond_t:让线程等待特定条件满足后再执行(如 "队列非空" 时消费数据)。
  • 信号量:控制同时访问资源的线程数量(如限制 5 个线程同时读写文件)。

三、总结

进程是操作系统资源分配的基本单位,拥有独立的内存空间和完整的生命周期;线程是轻量级的执行单元,共享进程资源,适合高并发场景。理解进程的内存布局、调度机制和状态流转,掌握线程的同步方法,是编写高效 Linux 多任务程序的基础。

http://www.dtcms.com/a/331592.html

相关文章:

  • PIDGenRc函数中lpstrRpc的由来和InitializePidVariables函数的关系
  • 什么是期权ETF分仓的意思呢?
  • 安全加固4(K8S最小化微服务安全)
  • java-JVM详解
  • 如何安装 scikit-learn Python 库
  • Azure微软云内网接入问题
  • 大规模调用淘宝商品详情 API 的分布式请求调度实践
  • ant design vue pro 1.7.8 自定义渲染菜单,多页签,keep-alive 详细教程 vue2.x版
  • day33-LNMP
  • PostgreSQL——视图
  • 六十五、【Linux数据库】MySQL表结构 、 MySQL键值
  • 重学JS-003 --- JavaScript算法与数据结构(三)JavaScript 基础调试方法
  • Codeforces 1042 Div3(ABCDEFG)
  • 【科研日常】使用tensorflow实现自注意力机制和交叉注意力机制
  • Java中Record的应用
  • Flink Stream API 源码走读 - socketTextStream
  • Spark Shuffle机制原理
  • STM32HAL 快速入门(七):GPIO 输入之光敏传感器控制蜂鸣器
  • 深入理解管道(下):括号命令 `()`、`-ExpandProperty` 与 AD/CSV 实战
  • Java 大视界 -- Java 大数据在智能家居能耗监测与节能优化中的应用探索(396)
  • 【漏洞复现】WinRAR 目录穿越漏洞(CVE-2025-8088)
  • JavaScript 解构赋值语法详解
  • iOS Sqlite3
  • Playwright初学指南 (3):深入解析交互操作
  • 【完整源码+数据集+部署教程】肾脏病变实例分割系统源码和数据集:改进yolo11-CARAFE
  • 基于机器学习的文本情感极性分析系统设计与实现
  • 华为宣布云晰柔光屏技术迎来重大升级
  • 生产环境sudo配置详细指南
  • 机器学习学习总结
  • 如何选择适合工业场景的物联网网关?