6.1-操作系统上的进程

2023-11-04

复习

  • 操作系统内核的启动:CPU Reset → Firmware → Boot loader → Kernel _start() → …

本次课回答的问题

  • Q1: 操作系统启动后到底做了什么?
  • Q2: 操作系统如何管理程序 (进程)?

本次课主要内容

  • 虚拟化:操作系统上的进程
  • 进程管理 API

一、操作系统启动后到底做了什么?

从系统启动到第一个进程

回顾 thread-os.c 的加载过程

  • CPU Reset → Firmware → Boot loader → Kernel _start()

操作系统会加载 “第一个程序”

  • RTFSC (latest Linux Kernel)
    • 如果没有指定启动选项 init=,按照 “默认列表” 尝试一遍
    • 从此以后,Linux Kernel 就进入后台,成为 “中断/异常处理程序”

程序:状态机

  • C 代码视角:语句
  • 汇编/机器代码视角:指令
  • 与操作系统交互的方式:syscall
ls /sbin/init -l
# lrwxrwxrwx 1 root root 20 7月  28  2022 /sbin/init -> /lib/systemd/systemd

pstree

定制最小的 Linux

没有存储设备,只有包含两个文件的 “initramfs”

$ tree .
.
├── bin
│   └── busybox (可以在我们的Linux里直接执行)
└── init

加上 vmlinuz (内核镜像) 就可以在 QEMU 里启动了


可以直接在文件系统中添加静态链接的二进制文件

mv index.html\?dl=1 l.zip && unzip l.zip
cd linux-minimal  
tree                                             
# .
# ├── initramfs
# │   ├── bin
# │   │   └── busybox
# │   └── init
# ├── Makefile
# └── vmlinuz

make
tree
# .
# ├── build
# │   └── initramfs.cpio.gz
# ├── initramfs
# │   ├── bin
# │   │   └── busybox
# │   └── init
# ├── Makefile
# └── vmlinuz

vim Makefile 
.PHONY: initramfs run clean

$(shell mkdir -p build)

initramfs:
        @cd initramfs && find . -print0 | cpio --null -ov --format=newc | gzip -9 \
          > ../build/initramfs.cpio.gz

run:
        @qemu-system-x86_64 \
          -nographic \
          -serial mon:stdio \
          -m 128 \
          -kernel vmlinuz \
          -initrd build/initramfs.cpio.gz \
          -append "console=ttyS0 quiet acpi=off"

clean:
        @rm -rf build
make run 
/bin/busybox ls
# bin   dev   init  root

/bin/busybox find /
# /
    # /.ash_history
    # /init
    # /bin
        # /bin/busybox
    # /root
    # /dev
        # /dev/console



变魔术时间到

BusyBox: The Swiss Army Knife of embedded Linux

img

c1="arch ash base64 cat chattr chgrp chmod chown conspy cp cpio cttyhack date dd df dmesg dnsdomainname dumpkmap echo ed egrep false fatattr fdflush fgrep fsync getopt grep gunzip gzip hostname hush ionice iostat ipcalc kbd_mode kill link linux32 linux64 ln login ls lsattr lzop makemime mkdir mknod mktemp more mount mountpoint mpstat mt mv netstat nice nuke pidof ping ping6 pipe_progress printenv ps pwd reformime resume rev rm rmdir rpm run-parts scriptreplay sed setarch setpriv setserial sh sleep stat stty su sync tar touch true umount uname usleep vi watch zcat"

c2="[ [[ awk basename bc beep blkdiscard bunzip2 bzcat bzip2 cal chpst chrt chvt cksum clear cmp comm crontab cryptpw cut dc deallocvt diff dirname dos2unix dpkg dpkg-deb du dumpleases eject env envdir envuidgid expand expr factor fallocate fgconsole find flock fold free ftpget ftpput fuser groups hd head hexdump hexedit hostid id install ipcrm ipcs killall last less logger logname lpq lpr lsof lspci lsscsi lsusb lzcat lzma man md5sum mesg microcom mkfifo mkpasswd nc nl nmeter nohup nproc nsenter nslookup od openvt passwd paste patch pgrep pkill pmap printf pscan"

c3="pstree pwdx readlink realpath renice reset resize rpm2cpio runsv runsvdir rx script seq setfattr setkeycodes setsid setuidgid sha1sum sha256sum sha3sum sha512sum showkey shred shuf smemcap softlimit sort split ssl_client strings sum sv svc svok tac tail taskset tcpsvd tee telnet test tftp time timeout top tr traceroute traceroute6 truncate ts tty ttysize udhcpc6 udpsvd unexpand uniq unix2dos unlink unlzma unshare unxz unzip uptime users uudecode uuencode vlock volname w wall wc wget which who whoami whois xargs xxd xz xzcat yes"

for cmd in $c1 $c2 $c3; do
  /bin/busybox ln -s /bin/busybox /bin/$cmd
done
mkdir -p /proc && mount -t proc  none /proc
mkdir -p /sys  && mount -t sysfs none /sys
export PS1='(linux) '

例子:NOILinux-lite

2021 年,CCF 以迅雷不及掩耳盗铃之势发布了 NOILinux 2.0

  • Ubuntu 20.04 Desktop (x86-64 only)
  • 真就不管那些 32-bit 的老爷机和老爷系统的死活了?

和刚才的 “最小” 系统但本质一样

  • 有更多设备 (磁盘、网卡等)
  • initramfs 里挂载了磁盘
  • 磁盘里安装了最少的编译运行环境 (g++, …) 和一个 Web 服务
  • switch_root (pivot_root 系统调用) 完成 “启动”

小结:应用程序视角的操作系统

Linux 操作系统启动流程

  • CPU Reset → Firmware → Loader → Kernel _start() → 第一个程序 /bin/init → 程序 (状态机) 执行 + 系统调用

操作系统为 (所有) 程序提供 API

  • 进程 (状态机) 管理
    • fork, execve, exit - 状态机的创建/改变/删除 ← 今天的主题
  • 存储 (地址空间) 管理
    • mmap - 虚拟地址空间管理
  • 文件 (数据对象) 管理
    • open, close, read, write - 文件访问管理
    • mkdir, link, unlink - 目录管理

二、fork()

操作系统:状态机的管理者

C 程序 = 状态机

  • 初始状态:main(argc, argv)
  • 程序可以直接在处理器上执行

虚拟化:操作系统在物理内存中保存多个状态机

  • 通过虚拟内存实现每次 “拿出来一个执行”
  • 中断后进入操作系统代码,“换一个执行”

状态机管理:创建状态机

如果要创建状态机,我们应该提供什么样的 API?

UNIX 的答案: fork

  • 做一份状态机完整的复制 (内存、寄存器现场)

int fork();

  • 立即复制状态机 (完整的内存)
  • 新创建进程返回 0
  • 执行 fork 的进程返回子进程的进程号

Fork Bomb

模拟状态机需要资源

  • 只要不停地创建进程,系统还是会挂掉的
  • Don’t try it (or try it in docker)
    • 你们交这个到 Online Judge 是不会挂的

代码解析: Fork Bomb

:(){:|:&};:   # 刚才的一行版本

:() {         # 格式化一下
  : | : &
}; :

fork() {      # bash: 允许冒号作为标识符……
  fork | fork &
}; fork

这次你们记住 Fork 了!

因为状态机是复制的,因此总能找到 “父子关系”

  • 因此有了进程树 (pstree)
systemd-+-accounts-daemon---2*[{accounts-daemon}]
        |-agetty
        |-atd
        |-automount---2*[{automount}]
        |-avahi-daemon---avahi-daemon
        |-cron
        |-dbus-daemon
        |-irqbalance---{irqbalance}
        |-lxcfs---7*[{lxcfs}]
        ...

理解 fork: 习题 (1)

试着拿出一张纸,写出以下程序的输出结果

  • fork-demo.c
    • 你心里可能立即想,运行一下不就完了吗?
    • 记得用 “状态机” 去理解它
#include <unistd.h>
#include <stdio.h>

int main() {
  pid_t pid1 = fork();
  pid_t pid2 = fork();
  pid_t pid3 = fork();
  printf("Hello World from (%d, %d, %d)\n", pid1, pid2, pid3);
}
gcc fork-demo.c && ./a.out                                   

# Hello World from (586, 587, 588)
# Hello World from (586, 0, 589)
# Hello World from (0, 590, 591)
# Hello World from (0, 590, 0)
# Hello World from (586, 0, 0)
# Hello World from (586, 587, 0)
# Hello World from (0, 0, 592)
# Hello World from (0, 0, 0)

理解 fork: 习题 (2)

问以下程序的输出结果

  • 一个更好的版本: fork -printf.c
    • 用状态机的视角再试一次
    • 试一试:./a.out v.s. ./a.out | cat
for (int i = 0; i < 2; i++) {
  fork();
  printf("Hello\n");
}

计算机系统里没有魔法。机器永远是对的。

理解 fork: 习题 (3)

多线程程序的某个线程执行 fork(),应该发生什么?

  • 这是个很有趣的问题:创造 fork 时创始人并没有考虑线程

我们可能作出以下设计:

  • 仅有执行 fork 的线程被复制,其他线程 “卡死”
  • 仅有执行 fork 的线程被复制,其他线程退出
  • 所有的线程都被复制并继续执行
    • 这三种设计分别会带来什么问题?

三、execve()

状态机管理:替换状态机

光有 fork 还不够,怎么 “执行别的程序”?

UNIX 的答案: execve

  • 将当前运行的状态机重置成成另一个程序的初始状态
int execve(const char *filename, char * const argv, char * const envp);
  • 执行名为 filename 的程序
  • 允许对新状态机设置参数 argv (v) 和环境变量 envp (e)
#include <unistd.h>
#include <stdio.h>

int main() {
  char * const argv[] = {
    "/bin/bash", "-c", "env", NULL,
  };
  char * const envp[] = {
    "HELLO=WORLD", NULL,
  };
  execve(argv[0], argv, envp);
  printf("Hello, World!\n");
}
bash -c env

gcc execve-demo.c && ./a.out 

# PWD=/home/xiaoxiong/Operating-system
# HELLO=WORLD
# SHLVL=0
# _=/usr/bin/env

env | grep DISPLAY
# DISPLAY=:0

env | grep PATH   

环境变量

“应用程序执行的环境”

  • 使用 env 命令查看
    • PATH: 可执行文件搜索路径
    • PWD: 当前路径
    • HOME: home 目录
    • DISPLAY: 图形输出
    • PS1: shell 的提示符
  • export: 告诉 shell 在创建子进程时设置环境变量
    • 小技巧:export ARCH=x86_64-qemuexport ARCH=native
    • 上学期的 AM_HOME 终于破案了

环境变量:PATH

可执行文件搜索路径

  • 还记得 gcc 的 strace 结果吗?
[pid 28369] execve("/usr/local/sbin/as", ["as", "--64", ...
[pid 28369] execve("/usr/local/bin/as", ["as", "--64", ...
[pid 28369] execve("/usr/sbin/as", ["as", "--64", ...
[pid 28369] execve("/usr/bin/as", ["as", "--64", ...
  • 这个搜索顺序恰好是 PATH 里指定的顺序
$ PATH="" /usr/bin/gcc a.c
gcc: error trying to exec 'as': execvp: No such file or directory
$ PATH="/usr/bin/" gcc a.c

计算机系统里没有魔法。机器永远是对的。

四、_exit()

状态机管理:终止状态机

有了 fork, execve 我们就能自由执行任何程序了,最后只缺一个销毁状态机的函数!

UNIX 的答案: _exit

  • 立即摧毁状态机

​ void _exit(int status)

  • 销毁当前状态机,并允许有一个返回值
  • 子进程终止会通知父进程 (后续课程解释)

这个简单……

  • 但问题来了:多线程程序怎么办?

结束程序执行的三种方法

exit 的几种写法 (它们是不同)

  • exit(0) - stdlib.h 中声明的 libc 函数

    • 会调用 atexit
  • _exit(0) - glibc 的 syscall wrapper

    • 执行 “exit_group” 系统调用终止整个进程 (所有线程)
      • 细心的同学已经在 strace 中发现了
    • 不会调用 atexit
  •   syscall(SYS_exit, 0)
    
    • 执行 “exit” 系统调用终止当前线程
    • 不会调用 atexit

不妨试一试

结束当前进程执行的四种方式

  • return, exit, _exit, syscall
  • exit-demo.c
    • 用 strace 观察程序的执行
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/syscall.h>

void func() {
  printf("Goodbye, Cruel OS World!\n");
}

int main(int argc, char *argv[]) {
  atexit(func);

  if (argc < 2) return EXIT_FAILURE;

  if (strcmp(argv[1], "exit") == 0)
    exit(0);
  if (strcmp(argv[1], "_exit") == 0)
    _exit(0);
  if (strcmp(argv[1], "__exit") == 0)
    syscall(SYS_exit, 0);
}
gcc exit-demo.c && ./a.out 
# Goodbye, Cruel OS World!

./a.out exit
# Goodbye, Cruel OS World!

gcc exit-demo.c -static && strace ./a.out
# execve("./a.out", ["./a.out"], 0x7ffca586f590 /* 60 vars */) = 0
# brk(NULL)                               = 0x2318000
# brk(0x23191c0)                          = 0x23191c0
# arch_prctl(ARCH_SET_FS, 0x2318880)      = 0
# uname({sysname="Linux", nodename="xiaoxiong-PC", ...}) = 0
# readlink("/proc/self/exe", "/home/xiaoxiong/Operating-system"..., 4096) = 38
# brk(0x233a1c0)                          = 0x233a1c0
# brk(0x233b000)                          = 0x233b000
# fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0x2), ...}) = 0
# write(1, "Goodbye, Cruel OS World!\n", 25Goodbye, Cruel OS World!
# ) = 25
# exit_group(1)                           = ?
# +++ exited with 1 +++

总结

本次课回答的问题

  • Q1: 操作系统启动后到底做了什么?
  • Q2: 操作系统如何管理程序 (进程)?

Take-away messages

  • 对 “操作系统” 的完整理解
    • CPU Reset → Firmware → Loader → Kernel _start() → 执行第一个程序 /bin/init → 中断/异常处理程序
    • 一个最小的 Linux 系统的例子
  • 进程管理 API
    • fork, execve, exit: 状态机的复制、重置、销毁
    • 理论上就可以实现 “各种功能” 了!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

6.1-操作系统上的进程 的相关文章

随机推荐

  • spark学习9:sparkStreaming

    1 sparkStreaming是什么 sparkStreaming 其实是对RDD进行微批量处理 核心还是对RDD的操作 只不过spark是线程级的应用 实现秒级的运算是可以的 所以sparkStreaming并不是真正意义上的流处理 最
  • QT 发布打包 缺少依赖dll问题解决

    QT 在开发环境中测试时 开发环境会自动连接需要的依赖dll库 在产品发布时 需要将自己的exe和依赖的dll库 整体打包分发 避免其他设备缺少依赖的dll QT windeployqt exe 就是为了解决产品发布 打包问题的 有很多博客
  • python xlwt生成excel,列数大于256,分表

    usr bin python coding utf8 import xlwt def write data to excel print 开始生成 wbk xlwt Workbook sheets sheet wbk add sheet S
  • SGX技术的分析与研究 学习笔记

    SGX技术的分析与研究 学习笔记 SGX技术的分析与研究 学习笔记 1 SGX架构概述 2 SGX关键技术 2 1 Enclave安全容器 2 2 Enclave保护机制 2 2 1 内存访问语义 2 2 2 地址映射保护 2 2 3 En
  • FFmpeg入门详解之92:Live555学习之(一)-------Live555的基本介绍

    Live555学习之 一 Live555的基本介绍 前一阵子 因为项目需要 研究了一下Live555开源框架 研究的不是很深入 基本上把Live555当做API用了一下 但是毕竟也是本人看的第一个开源框架 在此记录总结一下 Live555是
  • ios sdk 穿山甲_iOS 穿山甲广告 SDK 的使用

    信息流广告 自定义使用时 相关类的使用 BUAdSlot 广告位类 加载广告时需要设置的广告位描述信息 传入广告的主要请求 ID 广告类型 位置等 BUMaterialMeta 广告数据的载体类 访问可以获取所有的广告属性 主要属性名 数据
  • LevelDB源码阅读-key

    levelDB中的key 前言 在levelDB中有五种不同的key 在正式分析memtable之前我们先介绍一下这5中不同的key user key ParsedInternalKey InternalKey LookupKey Memt
  • 7.4 初等矩阵和可逆性

    初等矩阵和可逆性 初等矩阵 对单位矩阵 进行 一次 初等变换 得到 初等矩阵一定是可逆的 gt 因为初等变换是可逆的 所以初等矩阵是可逆的 对于一般矩阵 如果可逆的话 怎样得到逆矩阵呢 根据之前的分析 gt 如果矩阵A可逆的话 即存在一系列
  • java输入只有一行_即三个整数_中间用空格隔开_蓝桥杯 算法训练 排序

    问题描述 编写一个程序 读入一组整数 这组整数是按照从小到大的顺序排列的 它们的个数N也是由用户输入的 最多不会超过20 然后程序将对这个数组进行统计 把出现次数最多的那个数组元素值打印出来 如果有两个元素值出现的次数相同 即并列第一 问题
  • 注解@Autowired是如何实现的

    1 Autowired注解用法 2 Autowired注解的作用到底是什么 Autowired这个注解我们经常在使用 现在 我想问的是 它的作用到底是什么呢 首先 我们从所属范围来看 事实上这个注解是属于spring的容器配置的一个注解 与
  • MySQL高级篇_第10章_索引优化与查询优化

    都有哪些维度可以进行数据库调优 简言之 索引失效 没有充分利用到索引 索引建立 关联查询太多JOIN 设计缺陷或不得已的需求 SQL优化 服务器调优及各个参数设置 缓冲 线程数等 调整my cnf 数据过多 分库分表 关于数据库调优的知识点
  • 如何看服务器请求信息,如何看服务器请求信息

    如何看服务器请求信息 内容精选 换一换 请求URI由如下部分组成 URI scheme Endpoint resource path query string 例如您需要获取华北 北京四区域设备管理服务的accessToken 查询到的En
  • LambdaQueryWrapper中常用方法

    LambdaQueryWrapper中常用方法 LambdaQueryWrapper实例1 2 链式查询 3 排序查询以及between and查询 4 修改
  • 微信记账小程序

    第1章 绪论 1 1 研究背景与意义 随着网络时代的到来 互联网的优势和普及时刻影响并改变着人们的生活方式 在信息技术迅速发展的今天 计算机技术已经遍及全球 使社会发生了巨大的变革 为了不受时间和地点的限制 智能手机用户可以通过移动网络访问
  • 量化投资学习-39:股市里看人生

    1 股市 1 1 股市就是一个舞台 股市就是一个大舞台 在这个舞台上 每天上演着无数的人生悲喜故事 股市上的每只股票背后都是一种人的人生故事 有大起大落 有平淡无奇 有蓄势待发 有气势如虹 有高楼起 有楼塌了 起起伏伏 跌跌宕宕才是规律 1
  • CGI环境变量

    CGI环境变量 所有的CGI程序都接收以下的环境变量 这些变量在CGI程序中发挥了重要的作用 变量名 描述 CONTENT TYPE 这个环境变量的值指示所传递来的信息的MIME类型 目前 环境变量CONTENT TYPE一般都是 appl
  • [Python Scrapy爬虫] 二.翻页爬取农产品信息并保存本地

    前面 Python爬虫之Selenium Phantomjs CasperJS 介绍了很多Selenium基于自动测试的Python爬虫程序 主要利用它的xpath语句 通过分析网页DOM树结构进行爬取内容 同时可以结合Phantomjs模
  • DNS基本指南,域名解析,A记录,MX,CNAME,URL Redirect(301),URL frame,ns,TXT Record

    DNS 基本指南 DNS DNS 是 Domain Name System 域名系统 的缩写 此系统用于管理和识别域名 DNS 的最基本功能是为域的一个或多个 IP 地址提供名称 例如 可以将域名 wolf example com转换为 1
  • 面试python常见问题_Python干货Python常见面试问题

    Python干货Python常见面试问题 人工智能的火热进一步带动了求职市场上对Python程序员需求量不断增加 对于很多Python程序员而言 面试过程能不能更好的发挥自己展示自己 最终也会影响到能不能拿到心仪的薪酬 能不能通过面试 为此
  • 6.1-操作系统上的进程

    复习 操作系统内核的启动 CPU Reset Firmware Boot loader Kernel start 本次课回答的问题 Q1 操作系统启动后到底做了什么 Q2 操作系统如何管理程序 进程 本次课主要内容 虚拟化 操作系统上的进程