Skip to content

操作系统到底在管理什么

资源共享

  • 时分复用:多个用户轮流使用同一个资源
  • 空分复用:多个用户同时使用一个资源的一部分

发展

  • 手工操作
    • 人机矛盾大:输入输出时间长
  • 批处理:一次处理一批
    • 联机批处理
    • 脱机批处理:外围机完成输入输出的方式
  • 分时操作系统
    • 多道程序运行:
    • 微观上串行
    • 多作业同时进行

调度

  • 作业调度:从外存到内存
  • 进程调度:分配资源

分时

  • 多终端共享主机
  • 时间片轮转:及时处理
  • 先分配次要资源s

实时

及时响应操作指令

  • 实时控制
  • 实时信息处理

通用

  • 批处理
  • 分时
  • 实时 满足三个中的多个就是通用操作系统

基本特征

并发

  • 并发:多个任务同一时间进行
  • 并行:多个任务前后进行

共享

资源可供多个并发的进程共同使用

  • 互斥共享:一段时间只允许一个进程访问
  • 同时访问:一段时间允许同时访问

虚拟

物理上的实体变为若干逻辑上的对应物。 e.g. 分时技术、虚拟内存

作用与功能

作用

  • 接口
  • 虚拟机
  • 管理者

功能

管理

  1. 处理机管理
  • 进程控制:进程的创建、撤销
  • 进程同步:并发的进程协调
  • 进程通信:负责信息交换
  • 调度: 作业调度和进程调度
    • 作业调度:从后备作业队列中按照一定规则选择进入内存
    • 进程调度:为作业分配CPU资源
  1. 存储器管理
  • 内存分配
  • 内存保护:e.g. 上下界寄存器
  • 地址映射:虚地址映射到实地址
    • 逻辑地址: 相对地址、虚拟地址。用户编程时使用的地址
      • 地址空间:逻辑地址的集合
    • 物理地址:内存中的地址。实地址、绝对地址
      • 内存空间:物理地址集合
  • 内存扩充:请求调入或置换
  1. 设备管理
  • 设备分配:根据I/O请求分配和回收设备
  • 缓冲管理:对缓冲区管理。速度差异的处理缓冲减少人机矛盾
  • 设备驱动:设备启动、I/O、中断处理
  1. 设备独立性:设备无关性
  2. 文件管理
  • 文件储存空间管理
  • 目录管理

接口

互斥:资源 同步:依赖

命令接口
  • 脱机控制:系统控制
    • 脱机命令接口:用户将作业和操作说明一同提交。
  • 联机控制:用户交互式控制
  • 联机命令接口:e.g. 键盘命令 - 内部命令:常驻内存,使用频繁 - 外部命令:独立驻留硬盘

系统调用

  • 分类
    • 设备管理
    • 文件管理
    • 进程控制
    • 进程通信
    • 内存管理
  • 过程
    • 保存现场
    • 执行
    • 恢复现场 高级相对人而言,人看得懂的称为高级。所以用户态相交内核态更加高级。

环境和内核结构

  • 模块结构
  • 层次结构
  • 宏内核
  • 微内核

操作系统给编程人员的接口是 系统调用


  • 进程状态:运行时状态,操作系统还会保存一些只读的状态
    • 通过系统调用看到进程号
      • getpid
      • 父进程
    • 让ai生成一段程序,来获取当前的pid
c
int main() {
	pid_t pid = getpid();
	pid_t ppid = getppid();
	uid_t uid = getuid();
	gid_t gid = getgid();
	printf("Process ID: %d\n", pid);
	printf("Parent Process ID: %d\n", ppid);
	printf("User ID: %d\n", uid);
	printf("Group ID: %d\n", gid);
	return 0;
}
  • 递增的pid,共同的父进程
Pasted image 20250317203814
  • 共同的进程是shell
    • ps ax | grep 19433%%ps ax显示现在的运行的进程%%
    • 找到813是 813 pts/3 Ss 0:00 /bin/bash --init-file /home/sda/.vscode-server/bin/ddc367ed5c8936efe395cffeec279b04ffd7db78/out/vs/workbench/contrib/terminal/common/scripts/s
  • 公理
    • 机器永远是对的
    • 未测代码永远是错的
    • c语言测试框架
  • 进程管理api(创建和销毁)
    • windows:CreateProcess 类型+名称,类型变量定义更加不可读避免出错,匈牙利命名法
前缀类型示例说明
bBOOL / BYTEbIsActive表示布尔变量
ccharcLetter单个字符
dwDWORD (32位整数)dwProcessId双字(32位无符号整数)
hHANDLEhFile句柄(如窗口句柄)
lLONGlHeight长整型
lpLONG POINTERlpBuffer指针变量
nintnCount一般整数
ppointerpData指针
szString (zero-terminated)szFileName\0 结尾的字符串
wWORD (16位整数)wVersion16位无符号整数

fork()

- 立即复制状态机
- 寄存器&每一个字节的内存,所有状态的拷贝。
- 原状态机返回新建进程的pid,副本返回0(子进程返回0)在子进程中,
- `pid == 0` 只是 `fork()` 的返回值,它并不代表进程的实际 PID。  
- 子进程的实际 PID 由操作系统分配,通常是大于 **1** 的唯一正整数。
- `man 2 fork`
  • 进程树
    • A(121)->B(122)->C(123)->D(124)
    • 如果B、C退出了,那么D的ppid是多少?
    • 123?不正确,会被复用
    • 121,树形的删除会接上,往上提会发错人
    c
            if (WIFSIGNALED(status)) {
                int sig = WTERMSIG(status);
                switch (sig) {
                    case SIGALRM: msg = pcol("Timeout", 33); break;
                    case SIGABRT: msg = pcol("Assertion fail", 35); break;
                    case SIGSEGV: msg = pcol("Segmentation fault", 36); break;
                    default: msg = pcol(strsignal(sig), 31);
                }
    • 实际上子进程退出时要通知父进程,通过SIGCHILD
    • 真实的父进程可能会waitpid()等待子进程结束
      • 也可能没有调用,父进程先退出
      • 树形往上接上不对,不是真实的父进程
ddb0c19e29a5ee534b2c2b2c25cf287
mermaid
%%{init: {'theme': 'default', 'themeVariables': { 'primaryColor': '#fff', 'fontSize': '12px'}}}%%
%%{init: {'flowchart': {'htmlLabels': false, 'curve': 'linear'}, 'width': '5%', 'height': '600px'}}%%

graph TD

1788 --> 1793

1788 --> 1794

1793 --> 1795

1793 --> 1797

1794 --> 1796

1794 --> 1798

1801 --> 1808

1795 --> 1802

1795 --> 1799

1802 --> 1807

1799 --> 1806

1797 --> 1801

1802 --> 1812

1799 --> 1811

1800 --> 1809

1796 --> 1800

1801 --> 1813

1803 --> 1814

1797 --> 1804

1800 --> 1816

1804 --> 1815

1796 --> 1803

1798 --> 1805

1804 --> 1820

1810 --> 1819

1805 --> 1818

1803 --> 1817

1805 --> 1821

1798 --> 1810

1810 --> 1822
mermaid
%%{init: {'theme': 'default', 'themeVariables': { 'primaryColor': '#fff', 'fontSize': '12px'}}}%%
%%{init: {'flowchart': {'htmlLabels': false, 'curve': 'linear'}, 'width': '100%', 'height': '600px'}}%%
graph TD

1788 --> 1794

1 --> 1795

1 --> 1797

1800 --> 1816

1 --> 1822

1 --> 1800

1 --> 1802

1 --> 1803

1 --> 1809

1 --> 1806

1 --> 1819

1 --> 1817

1 --> 1818

1 --> 1804

1 --> 1808

1 --> 1814
@Galaxy pstree % ps ax  
PID TT STAT TIME COMMAND  
1 ?? Ss 0:23.87 /sbin/launchd
  • 一号进程
  • fork bomb核裂变进程分裂,系统爆炸
  • 考虑下面程序
c
#include <stdio.h>
#include <unistd.h>
int main() {
    for (int i = 0; i < 2; i++) {
        fork();
        printf("Hello\n");
    }
    return 0;
}

print 打印的时候如果是管道,会等待再打印。如果是终端,会立即打印。实际行为是输出到buffer

python
        sys_fork()
        heap.buf += '⭐️'  # Actual behavior of printf()

fork之前是把全局变量输入到buffer里面,fork的时候缓冲区同步复制,里面的东西的地址是一样的,所以输出的时候只能输出一个。

mermaid
%% 直接运行(行缓冲)与管道运行(全缓冲)对比图

%%{init: {'theme': 'default', 'themeVariables': { 'primaryColor': '#fff', 'fontSize': '12px'}}}%%
%%{init: {'flowchart': {'htmlLabels': false, 'curve': 'linear'}, 'width': '100%', 'height': '600px'}}%%

flowchart TD
    subgraph 直接运行到终端[行缓冲模式]
        direction TB
        P0_初始["P0 (初始进程)\nBuffer: ∅"]
        
        %% 第一次循环 i=0
        P0_fork0[P0 fork]
        P1_创建["P1 (子进程)\nBuffer: ∅"]
        P0_print0["P0: printf('Hello\\n')\nBuffer刷新 → Hello"]
        P1_print0["P1: printf('Hello\\n')\nBuffer刷新 → Hello"]
        
        %% 第二次循环 i=1
        P0_fork1["P0 fork → P2"]
        P1_fork1["P1 fork → P3"]
        P0_print1["P0: printf('Hello\\n')\nBuffer刷新 → Hello"]
        P2_print1["P2: printf('Hello\\n')\nBuffer刷新 → Hello"]
        P1_print1["P1: printf('Hello\\n')\nBuffer刷新 → Hello"]
        P3_print1["P3: printf('Hello\\n')\nBuffer刷新 → Hello"]
        
        P0_初始 --> P0_fork0
        P0_fork0 --> P1_创建
        P0_fork0 --> P0_print0
        P1_创建 --> P1_print0
        P0_print0 --> P0_fork1
        P1_print0 --> P1_fork1
        P0_fork1 --> P0_print1
        P0_fork1 --> P2_print1
        P1_fork1 --> P1_print1
        P1_fork1 --> P3_print1
    end

    subgraph 管道运行[全缓冲模式]
        direction TB
        Q0_初始["Q0 (初始进程)\nBuffer: ∅"]
        
        %% 第一次循环 i=0
        Q0_fork0[Q0 fork]
        Q1_创建["Q1 (子进程)\nBuffer: 'Hello\\n'"]
        Q0_print0["Q0: printf('Hello\\n')\nBuffer: 'Hello\\n'"]
        Q1_复制["Q1继承Buffer: 'Hello\\n'"]
        
        %% 第二次循环 i=1
        Q0_fork1["Q0 fork → Q2"]
        Q1_fork1["Q1 fork → Q3"]
        Q0_print1["Q0: printf('Hello\\n')\nBuffer: 'Hello\\nHello\\n'"]
        Q2_复制["Q2继承Buffer: 'Hello\\nHello\\n'"]
        Q1_print1["Q1: printf('Hello\\n')\nBuffer: 'Hello\\nHello\\n'"]
        Q3_复制["Q3继承Buffer: 'Hello\\nHello\\n'"]
        
        %% 程序结束时刷新缓冲区
        Q0_flush["Q0退出 → 刷新Buffer\n输出2次"]
        Q1_flush["Q1退出 → 刷新Buffer\n输出2次"]
        Q2_flush["Q2退出 → 刷新Buffer\n输出2次"]
        Q3_flush["Q3退出 → 刷新Buffer\n输出2次"]
        
        Q0_初始 --> Q0_fork0
        Q0_fork0 --> Q0_print0
        Q0_fork0 --> Q1_创建
        Q1_创建 --> Q1_复制
        Q0_print0 --> Q0_fork1
        Q1_复制 --> Q1_fork1
        Q0_fork1 --> Q0_print1
        Q0_fork1 --> Q2_复制
        Q1_fork1 --> Q1_print1
        Q1_fork1 --> Q3_复制
        Q0_print1 --> Q0_flush
        Q2_复制 --> Q2_flush
        Q1_print1 --> Q1_flush
        Q3_复制 --> Q3_flush
    end

execve()

  • fork()只是复制当前程序,不能启动其他程序
  • 复位状态机: execve唯一能够“执行程序”的系统调用
    • 因此也是strace第一个系统调用
    • int execve(const char *filename, char * const argv[], char * const envp[]);
    • 将当前进程重置成一个可执行文件描述状态机的初始状态
    • execve之后的所有代码都不会被执行了
    • 操作系统维护的状态不变:进程号、目录、打开的文件……
      • 内维护的指
复位(Reset)​保留(Preserve)​
代码、数据、堆栈PID、PPID
信号处理器文件描述符(非 close-on-exec)
环境变量(除非显式指定新的)工作目录和根目录
寄存器状态(重置为新程序入口)用户/组 ID 和权限
内存映射和共享库资源限制(如 ulimit
  • execve必须用绝对路径
  • 环境变量

上次更新于: