建设银行集团网站,网站建设速成,显示网站目录,体育视频网站建设12 [虚拟化] 进程抽象#xff1b;fork#xff0c;execve#xff0c;exit
南京大学操作系统课蒋炎岩老师网络课程笔记。 视频#xff1a;https://www.bilibili.com/video/BV1N741177F5?p12 讲义#xff1a;http://jyywiki.cn/OS/2021/slides/8.slides#/ 本讲概述
回到“…12 [虚拟化] 进程抽象forkexecveexit
南京大学操作系统课蒋炎岩老师网络课程笔记。 视频https://www.bilibili.com/video/BV1N741177F5?p12 讲义http://jyywiki.cn/OS/2021/slides/8.slides#/ 本讲概述
回到“操作系统是管理程序运行的软件”
操作系统中的进程 程序 状态机 MR操作系统 多个状态机 进程管理API fork状态机的复制execve状态机的重置exit
再次强调一定要深入理解程序进程就是一个状态机。
操作系统中的进程
复习应用程序
应用程序 代码 数据文件 状态机
a.out, bash, ls ,grepgcc(cc1, as, collect2, ld)xedit, vscode
复习操作系统
操作系统是管理多个应用程序执行的软件。
应用视角操作系统就是一组系统调用硬件视角操作系统就是个状态机C程序
理解“最小”操作系统
如果硬件提供一些机制如虚拟存储来虚拟化内存M和寄存器R即MR使得各个“线程”不能访问其他“线程”、操作系统的内存就得到了虚拟化的“进程”仿佛独占CPU运行。注意这里是将运行在操作系统上的各个程序进程看做了是运行在操作系统这个大程序进程上的一些”线程“。
操作系统状态机的虚拟化
操作系统“模拟”了其中所有程序的状态机
这就是“虚拟化”程序仿佛自己独占CPU运行但它独占的只是CPU的一部分其他部分它“看不见”。
进程运行的程序。任意时刻进程都可以看做是状态机的状态。
操作系统在终端以后可以选择将进程状态机调度到CPU上运行。而进程执行系统调用会使用指令syscall等回到操作系统。
“操作系统是一个中断处理程序”
被动的中断硬件时钟、I/O设备、NMI主动的中断系统调用
操作系统运行的两种模式
用户态ring 3应用程序运行在用户态内核态ring 0操作系统运行在内核态
二者的切换如上面所述
中断、系统调用用户态 - 内核态操作系统调度内核态 - 用户态
就是这样的切换使得我们的应用程序在用户态实现了虚拟化同时操作系统仿佛就是一个中断处理程序。
操作系统课的三种调用 进程状态机管理 fork, execve, exit进程状态机的创建、改变和删除 存储地址空间管理 mmap对进程虚拟地址空间的一部分进行映射 brk虚拟地址空间管理 文件数据对象管理 open, close文件访问管理 read, write数据管理 mkdir, link, unling目录管理
fork() 状态机管理创建状态机
如果需要创建状态机我们需要什么样的API
UNIX的答案fork()
做一份状态机的完整的复制内存M寄存器现场R父进程返回子进程的PID子进程返回0
fork bomb
fork bomb代码解析
:(){:|:};: # 一行版本的fork bomb:(){ # 格式化一下: | :
};:fork(){ # 这其实在bash中定义了一个函数bash允许以冒号作为标识符fork | fork
}; fork
父子进程、进程树
因为状态机是复制的因此总能找到”父子关系“。
因此有了进程树pstree如下
systemd─┬─ModemManager───2*[{ModemManager}]├─NetworkManager─┬─dhclient│ └─2*[{NetworkManager}]├─accounts-daemon───2*[{accounts-daemon}]├─acpid├─avahi-daemon───avahi-daemon├─boltd───2*[{boltd}]├─colord───2*[{colord}]├─cron├─cups-browsed───2*[{cups-browsed}]├─cupsd───dbus├─dbus-daemon├─gdm3─┬─gdm-session-wor─┬─gdm-x-session─┬─Xorg───{Xorg}│ │ │ ├─gnome-session-b─┬─gnome-shell─┬─ibus-daemon─┬─ibus-dconf───3*[{ibus-dconf}]│ │ │ │ │ │ ├─ibus-engine-sim───2*[{ibus-engine-
...进程树的存在是我们用fork()复制创造子进程所得到的一个很自然的结果。
例程1
猜猜会打印出什么呢提示可以试着画一下状态机线程树
#include unistd.h
#include stdio.hint main(){pid_t pid1 fork();pid_t pid2 fork();pid_t pid3 fork();printf(Hello World from (%d, %d, %d)\n, pid1, pid2, pid3);
}例程2
猜猜会打印出什么呢
#include unistd.h
#include stdio.h#define N 2
int main(){for (int i0; iN; i){fork();printf(Hello\n);}
}还是可以逐步画一下程序的状态机
gcc test.c
./a.out输出结果应该是6。
有趣的是如果我们将输出重定向到管道再通过wc -l命令打印出行数这时会输出8这不禁令我们大为惊奇。
为什么会这样呢这其实是因为printf函数将内容直接输出到标准输出stdout时是直接输出的会按照我们的理解打印出6个Hello。但是如果要重定向到管道或文本文件等printf则会将要输出的内容先放置到缓冲区到最后一起打印在本例中由于我们fork()创建进程的时候会将全部的内存M和寄存器现场R复制一份导致每次fork()时缓冲区也被完整地复制最后我们每个进程的缓冲区有2个Hello最后共有四个进程故会有8个Hello。也可以重定向到文本文件中试一下确实是有8个Hello。这恰好进一步验证了我们所说的fork()是对整个MR的完整复制。
可以尝试理解一下N3时正常打印和重定向打印会有多少个Hello。笔者认为分别是 ∑i1N2i\sum_{i1}^N2^i∑i1N2i 和 2N×N2^N\times N2N×N。
机器永远是对的计算机系统的世界没有魔法一切都是按部就班地进行的
execve() 状态机管理替换状态机执行
只有fork新建还不够我们还需要能够执行别的程序。
UNIX的答案execve
execve(filename, argv, enpv)
执行名为filename的程序分别传入参数argv(v)和环境变量enpv(e)
这刚好对应了main函数的参数
int main(int argc, char** argv, char** enpv){// ...
}关于main函数的参数Linux中 C main函数参数argc和argv含义及用法
execve可以看作状态机的重置。
环境变量
环境变量即应用程序执行的环境。
使用env命令来查看 PATH可执行文件的搜索路径PWD当前路径HOMEhome目录DISPLAY图形输出PS1shell的提示符 export告诉shell在创建子进程时设置环境变量
PATH环境变量
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 fork-demo.c gcc: error trying to exec ‘as’: execvp: No such file or directory $ PATH/usr/bin/ gcc fork-demo.c 在gcc被execve时将环境变量PATH传给gcc它就会按照其顺序来搜索可执行文件的路径。
计算机系统里没有魔法机器永远是对的
exit() 状态机管理终止状态机
有了forkexecve我们可以自由地创建、执行程序状态机了还缺一个销毁状态机的函数
UNIX的答案exit
销毁当前状态机并允许有一个返回值子进程终止会通知父进程之后会讲
问题是一个进程状态机中有多个线程啊。
结束程序执行的三种方法
exit的几种写法它们是不同的
exit(0) - stdlib.h中声明的libc函数 它会调用atexit _exit(0) - glibc中的syscall wrapper 执行exit_group系统调用终止整个进程所有线程不会调用atexit syscall(SYS_exit, 0) 执行exit系统调用终止当前线程不会调用atexit
最起码要区分好库函数应用程序的一部分和系统调用。
可以用strace观察各种结束方式的执行。
Fork-Exec vs. Spawn
我们既然fork创建了一个子进程那我们绝大多情况下肯定是要execve执行这个进程的也就是说fork后面几乎一定会跟着execve那为什么不直接把它们合成一个系统调用 spawn(path, argv, enpv) 呢即spawn fork execve
实际上fork execve是一个非常优雅的实现因为要考虑到进程可以持有操作系统中的对象这使fork、execve、exit还要涉及到操作系统的对象的管理。
例如在上面用到过的管道技术中
./a.out | wc -l其中./a.out持有了操作系统中的对象——管道的写口而wc -l则持有了管道的读口从而能够将前者的输出作为后者的输入。而如果./a.out这个进程状态机需要fork一个子进程那么这个子进程就可以自然地复制拿到父进程的全部MR。这样对于操作系统中的对象——管道子进程就持有了其写口。
Take aways and Wrap-up
虚拟化
程序 状态机操作系统 状态机的管理者 用硬件物理状态机实现多个并发执行的虚拟状态机APIforkexecveexit
Ref
http://jyywiki.cn/OS/2021/slides/8.slides#/
https://www.bilibili.com/video/BV1N741177F5?p12