小实验记录
阅读过程中发现了一些好玩的实操部分,正好提供了部分源码供下载就试试看。图一乐。
我的 ubuntu20.04 只有双核,多核实验具有局限性。另外虚拟机的实验结果和实机系统有一些微小的差异,不过我觉得可以忽略不计。
系统调用
strace 命令
以 hello.c 为例:
1 |
|
strace 命令可以追踪查看程序使用了哪些系统调用:
1 | strace -o hello.log ./hello |
每一行对应一个系统调用,大部分都是在main函数之前或之后执行的程序的开始和终止处理发起的(os提供的功能)。
无论使用何种语言编写的程序,都必须通过系统调用向内核发起请求,这一点可以用 python 进行试验:
1 | giantbranch@ubuntu:~/Desktop/【实验程序】linux-in-practice-master/02-syscall-and |
输出内容比上一次 strace 多得多(应该是python解析器的调用),但是有关 main 函数的主要系统调用还是那么几行。
另外,在 strace 命令后加上 -T 选项能够以很高的精度捕捉到每一种系统调用所消耗的时间, -tt 选项能够显示每一种内核处理发生的时刻。
sar 命令
需要安装 sysstat 软件包:如何在 Ubuntu 20.04 LTS 上安装 SysStat-云东方 (yundongfang.com)
用于获取进程分别在用户模式与内核模式下运行的时间比例。我们指定采集信息的周期与次数(分别为一次):

我的 ubuntu20.4 虚拟机设置了双核心,因此这里的 CPU 核心索引为 0-1。
%user 和 %nice 字段相加为进程在用户模式下运行的时间比例。
%system 则是在内核模式下执行系统调用等处理所占的时间比例。
在这里我没有运行任何程序,CPU 处于完全的空闲状态。
sar 命令还可以监测进程在各模式下的运行时间:
写一个无限循环 loop.c 进行测试
1 | int main(void) |
编译后运行,再用 sar 监测:

可以猜出进程始终通过 CPU-0 运行,且一直在用户模式下运行。单纯的 for 循环不涉及系统调用 0_0
kill 当前进程。
改写一下循环,加入一个函数再进行测试:
1 |
|

很明显修改后的程序在运行过程中内核处理的时间大大增加。
ldd 命令
可以查看程序所依赖的库
1 | teriteri@ubuntu:~/Desktop/【实验程序】linux-in-practice-master/02-syscall-and-no |
可见 python3 本身的实现同样依赖于 libc。
在 OS 层面上,C语言发挥着巨大的作用。
进程管理
fork()
调用 fork() 函数,基于发起调用的进程(父进程),创建一个新的进程(子进程)。
调用流程大概可以这么概括:
- 为子进程申请内存空间,把父进程的内存复制到新的内存空间。
- 父进程与子进程分裂为两个进程,分别执行不同代码。因为 fork() 函数返回了不同的值给父进程与子进程。
通过 fork.c 进行实验:
1 |
|

两个进程都得到了各自的结果。
execve()
用于启动一个进程,流程大概如下:
- 读取可执行文件,并读取创建进程的内存映像所需信息(ELF文件内容)
- 用新进程的数据覆盖当前进程的内存
- 从最初的命令开始运行新的进程
以 fork-and-exec.c 为例:
1 |
|
编译运行:

新进程取代了原来 fork() 生成的子进程。
进程调度器
单核心多进程实验
同时运行一个或多个单纯消耗 CPU 计算量的进程,记录
1.不同时间点运行的进程是哪个
2.每个进程的运行进度
源码 sched.c:
1 |
|
使用 OS 提供的 taskset 命令可以使程序在指定的逻辑 CPU 上运行,便于观察实验结果。同时将每次输出保存在另外的文件中。
1 | taskset -c 0 ./sched 参数1 参数2 参数3 >输出文件名.txt |

可以自己用 matplotlib 为每组数据分别做两张点阵图,一张是在单核 CPU 上运行的进程(x:开始运行后经过的时间,y:进程 ID),一张表示各进程的进度(x:开始运行后经过的时间,y:进度)。
单核单进程


单核双进程


单核四进程


一些总结
- 当只有一核 CPU 工作时,不管同时运行多少个进程,在任意一个时间点上只能有一个进程运行
- 当一核 CPU 需要运行多个进程时,它们将按轮询调度的方式循环运行,所有进程按顺序逐个运行,不断循环直至所有进程结束
- 每个进程被分配到的时间片的长度大致上是相等的
- 全部进程运行结束消耗的时间随着进程数量的增加而等比例地增加
图形实现
1 | import os |
进程状态查看
进程一般有四种状态:
- 运行态
- 就绪态 ——> 具备运行条件,等待 CPU 分配时间
- 睡眠态 ——> 不准备运行,除非发生某些时间,不占用 CPU 时间
- 僵死状态 ——> 运行结束,等待父进程回收
ps ax 查看进程状态。

STAT一栏一般有三种状态:
首字母 | 状态 |
---|---|
R | 运行态或就绪态 |
S 或 D | 睡眠态 |
Z | 僵死态 |
S 可通过接收信号回到运行态,D 指 S 以外的情况(主要出现于等待外部存储器地访问时)
一般处于睡眠态的进程所等待的事件有几种:
- 被要求等待指定的时间(比如 sleep 函数)
- 等待用户通过键盘或鼠标等设备输入
- 等待 SDD 或 HDD 等外部存储器的读写结束
- 等待网络的数据收发结束
1 | teriteri@ubuntu:~/Desktop$ ps ax | wc -l |
也可以直接查看进程总数。