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

操作系统进程管理笔记

1. 进程的基本概念

1.1 进程的定义

进程就是运行中的程序。程序本身是没有生命周期的,它只是存在磁盘上面的一些指令(也可能是一些静态数据)。是操作系统让这些字节运行起来,让程序发挥作用。

1.2 CPU的时分共享

操作系统通过让一个进程只运行一个时间片,然后切换到其他进程,提供了存在多个虚拟CPU的假象。这就是时分共享(time sharing)CPU技术,允许用户如愿运行多个并发进程。潜在的开销就是性能损失,因为如果CPU必须共享,每个进程的运行就会慢一点。

1.3 进程的机器状态

进程的机器状态包括:

  • 内存:进程可以访问的内存(称为地址空间,address space)
    • 指令存在内存中
    • 正在运行的程序读取和写入的数据也在内存中
  • 寄存器
    • 程序计数器(Program Counter,PC):告诉我们程序当前正在执行哪个指令
    • 栈指针(stack pointer)和帧指针(frame pointer):用于管理函数参数栈、局部变量和返回地址

寄存器结构图

2. 程序与进程的区别

  • 程序(Program):一个静态实体,通常是以可执行文件形式存储在磁盘上的指令和数据的集合
  • 进程(Process):程序的动态执行实例,运行时加载到内存中,拥有独立的地址空间、执行状态和系统资源

简单来说,程序是静态的"蓝图",而进程是程序被激活后的"活体"。

3. 操作系统启动并运行程序的过程

3.1 加载程序到内存

  1. 定位可执行文件:根据用户命令或系统调用找到磁盘上的可执行文件
  2. 读取文件头部
    • 入口点(Entry Point):程序开始执行的地址
    • 段信息:代码段、数据段等的地址和大小
  3. 从磁盘读取字节
    • 代码段:包含程序的指令,通常是只读的
    • 数据段:包含初始化的全局变量和静态变量
    • BSS段:包含未初始化的全局变量
  4. 分配虚拟地址空间:为进程分配独立的虚拟地址空间
  5. 按需加载(可选):延迟加载优化性能

3.2 分配堆栈和堆

  • 堆栈(Stack):存储函数调用信息、局部变量等
  • 堆(Heap):用于动态内存分配

3.3 设置执行环境

  • 寄存器初始化
  • 命令行参数和环境变量
  • 文件描述符

3.4 创建进程控制块(PCB)

记录进程元数据,包括:

  • 进程ID(PID)
  • 进程状态
  • 内存管理信息
  • 打开的文件描述符

3.5 调度执行

进程被加入就绪队列,等待CPU调度

4. 进程状态

进程的三种基本状态:

  • 运行(running):在处理器上运行,执行指令
  • 就绪(ready):准备好运行,但操作系统选择不在此时运行
  • 阻塞(blocked):执行了某种操作,直到发生其他事件时才会准备运行
    从就绪到运行意味着该进程已经被调度(scheduled)。从运行转移到就绪意味着该进程已经取消调度(descheduled)。一旦进程被阻塞(例如,通过发起 I/O 操作),OS 将保持进程的这种状态,直到发生某种事件(例如,I/O 完成)。此时,进程再次转入就绪状态(也可能立即再次运行,如果操作系统这样决定)。

image.png
寄存器上下文将保存其寄存器的内容。当一个进程停止时,它的寄存器将被保存到这个内存位置。==通过恢复这些寄存器(将它们的值放回实际的物理寄存器中)​,==操作系统可以恢复运行该进程。我们将在后面的章节中更多地了解这种技术,它被称为上下文切换(contextswitch)​。

5. 进程管理系统调用

5.1 fork系统调用

  • 创建一个新进程,作为调用进程的副本
  • 子进程复制父进程的地址空间、PCB等
  • 父进程返回子进程PID,子进程返回0
#include <stdio.h>      // 包含标准输入输出库,提供 printf() 等函数
#include <stdlib.h>     // 包含标准库,提供 exit() 函数等
#include <unistd.h>     // 包含 UNIX 标准函数声明,提供 fork()、getpid() 等int main(int argc, char *argv[])  // 程序入口,argc/argv 用于获取命令行参数{// 在创建子进程之前,先打印当前进程的 PID(进程标识符)printf("hello world (pid:%d)\n", (int)getpid());// 调用 fork(),创建一个新进程(子进程)int rc = fork();if (rc < 0) {            // fork 返回值小于 0,表示创建子进程失败fprintf(stderr, "fork failed\n");  // 向标准错误输出错误信息exit(1);            // 退出程序,并返回非零状态码表示异常} else if (rc == 0) {    // fork 返回值等于 0,表示当前是子进程// 子进程执行的代码路径printf("hello, I am child (pid:%d)\n", (int)getpid());} else {                 // fork 返回值大于 0,表示当前是父进程// rc 存储的是子进程的 PIDprintf("hello, I am parent of %d (pid:%d)\n",rc, (int)getpid());}return 0;  // 程序正常退出,返回值 0}

image.png
子进程并不是完全拷贝了父进程。具体来说,虽然它拥有自己的地址空间(即拥有自己的私有内存)​、寄存器、程序计数器等,但是它从fork()返回的值是不同的。父进程获得的返回值是新创建子进程的PID,而子进程获得的返回值是0。
系统显示父进程先执行,但是这是随机的,CPU调度程序(scheduler)决定了某个时刻哪个进程被执行

5.2 wait系统调用

  • 父进程阻塞直到子进程结束
  • 返回已结束子进程的PID
#include <stdio.h>      // 标准输入输出,提供 printf()
#include <stdlib.h>     // 标准库,提供 exit()
#include <unistd.h>     // POSIX API,提供 fork()、getpid()
#include <sys/wait.h>   // 等待子进程,提供 wait()int main(int argc, char *argv[])
{// 在 fork 之前,先打印当前进程(父进程)的 PIDprintf("hello world (pid:%d)\n", (int)getpid());// 创建一个新进程;父进程中 rc > 0,子进程中 rc == 0,失败时 rc < 0int rc = fork();if (rc < 0) {// fork 调用失败fprintf(stderr, "fork failed\n");exit(1);} else if (rc == 0) {// 子进程执行这里的代码printf("hello, I am child (pid:%d)\n", (int)getpid());} else {// 父进程执行这里的代码// wait(NULL) 阻塞直到任意子进程结束,返回值是已结束子进程的 PIDint wc = wait(NULL);printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",rc, wc, (int)getpid());}return 0;  // 正常退出
}

image.png

5.3 exec系统调用

  • 在当前进程中加载并执行新程序
  • 替换当前地址空间
  • 重置堆栈、堆和寄存器
#include <stdio.h>      // 标准输入输出,提供 printf()
#include <stdlib.h>     // 标准库,提供 exit()
#include <unistd.h>     // POSIX API,提供 fork()、execvp()、getpid()
#include <string.h>     // 字符串操作,提供 strdup()
#include <sys/wait.h>   // 等待子进程,提供 wait()int main(int argc, char *argv[])
{// 程序启动时打印当前进程(父进程)的 PIDprintf("hello world (pid:%d)\n", (int)getpid());// 创建子进程:父进程 rc>0,子进程 rc==0,失败时 rc<0int rc = fork();if (rc < 0) {// fork 失败,打印错误并退出fprintf(stderr, "fork failed\n");exit(1);}else if (rc == 0) {// 子进程执行此路径printf("hello, I am child (pid:%d)\n", (int)getpid());// 准备 execvp 的参数数组// myargs[0] 指定要运行的程序名 "wc"// myargs[1] 指定要处理的文件 "p3.c"// myargs[2] 置 NULL,标记参数数组结束char *myargs[3];myargs[0] = strdup("wc");      myargs[1] = strdup("p3.c");   myargs[2] = NULL;// 用 execvp 替换当前子进程映像,执行 word count 程序execvp(myargs[0], myargs);// 如果 execvp 返回,说明执行失败,才会走到这里perror("execvp failed");exit(1);}else {// 父进程执行此路径// wait(NULL) 阻塞直到任意子进程结束,返回已结束子进程的 PIDint wc = wait(NULL);// 打印父进程信息,rc 是子进程的 PID,wc 是 wait 返回的 PIDprintf("hello, I am parent of %d (wc:%d) (pid:%d)\n",rc, wc, (int)getpid());}return 0;  // 正常退出
}

要点说明

  • strdup():复制字符串并返回指向新内存的指针,用于给 execvp 准备参数。
  • execvp():用指定程序替换当前进程映像,不返回成功;如果失败,会返回 -1,此时应打印错误并退出。
  • wait(NULL):父进程阻塞直到子进程结束,避免子进程成为僵尸。

image.png

image.png

6. 安全机制:地址空间布局随机化(ASLR)

6.1 ASLR的定义

ASLR是一种安全技术,通过随机化进程的内存地址布局,防止攻击者利用已知的内存地址执行恶意代码。

6.2 工作原理

随机化内存布局的关键区域:

  • 堆栈(Stack)
  • 堆(Heap)
  • 可执行代码(Text Segment)
  • 动态链接库(Shared Libraries)

6.3 优缺点

优点

  • 提升安全性
  • 兼容性强

缺点

  • 非绝对防御
  • 轻微性能开销

7. 内存分配:堆和栈

7.1 栈(Stack)

保存内容

  • 局部变量
  • 函数参数
  • 返回地址
  • 栈帧

特点

  • 先进后出(LIFO)
  • 速度快
  • 大小有限

7.2 堆(Heap)

保存内容

  • 动态分配的对象
  • 全局数据(部分情况)

特点

  • 手动管理
  • 灵活性高
  • 速度较慢
  • 可能产生碎片

7.3 堆和栈的区别对比

特性栈(Stack)堆(Heap)
分配方式自动分配和释放手动分配和释放
存储内容局部变量、函数参数动态分配的数据
生命周期随函数调用结束而销毁在手动释放前一直存在
大小限制容量较小容量较大
速度操作更快操作较慢

相关文章:

  • 使用 VMware 安装一台 Linux 系统之Centos
  • 访问者模式:分离数据结构与操作的设计模式
  • 一种Spark程序运行指标的采集与任务诊断实现方式
  • win11什么都不动之后一段时间黑屏桌面无法显示,但鼠标仍可移动,得要熄屏之后才能进入的四种解决方法
  • MCP‌和LangGraph‌结合
  • Python 函数与模块
  • 开关量扫描处理(消抖)
  • Linux平台实现低延迟的RTSP、RTMP播放
  • Java技术体系的主要产品线详解
  • 专家系统的基本概念解析——基于《人工智能原理与方法》的深度拓展
  • 车载客流记录仪简介
  • 2025 年免费 Word 转 PDF 转换器有哪些?
  • 数据结构——二叉树,堆
  • 【硬核干货】SonarQube安全功能
  • 【动手学深度学习】ResNet残差网络
  • ragflow部署以及api调用整理
  • 创新项目实训开发日志4
  • 第十七届山东省职业院校技能大赛 中职组网络建设与运维赛项
  • 一天学完Servlet!!!(万字总结)
  • 第五章:Framework/Tooling Abstraction
  • 因商标近似李小龙形象被裁定无效,真功夫起诉国家知产局,法院判了
  • 潘功胜在美谈关税:吁全球经济勿滑向“高摩擦、低信任”轨道
  • 嫦娥五号月球样品将借给这些国家机构
  • 央媒关注脑瘫女骑手:7年跑出7.3万多份单,努力撑起生活
  • 央行副行长:上海国际金融中心建设是我国参与国际金融竞争的核心载体
  • 《哪吒2》再次延映至五月底,春节档影片仍有竞争力