这里写目录标题
- Read with Questions.
- fork
- wait
- exec
- Why? Motivating The API
- Process Control And Users
- Useful Tools
- Summary
- Homework
Read with Questions.
- 如何创建和控制进程?OS提供哪些接口?
- 如何设计这些接口?
fork
作用:创建新的进程。
过程:调用fork创建的子进程是一个副本,不会从main()开始。
子进程不是完全一致的copy,它拥有自己的地址空间、PID、寄存器、PC等,返回给fork()的值是不同的。
不确定性并发:子进程和父进程的进行顺序是不确定的,在多道程序设计中这一点很重要。由CPU调度程序决定进程的运行顺序。
wait
作用:延迟执行。
过程:有时父进程需要等待子进程完成后才执行。可以使用wait来让进程的输出具有确定性。
(注意,有时候wait会在子进程退出之前返回,具体参考xv6手册)
(linux中,有六种exec的变体:execl, execlp, execle, execv, execvp, execvpe)
exec
作用:运行与调用程序不同的程序。
过程:给定可执行文件的名称和一些参数,将从该可执行文件中加载代码(和静态数据),并用它覆盖当前代码段(和当前静态数据);程序的堆和堆栈以及内存空间的其他部分将重新初始化,然后操作系统运行该程序,传入参数作为该进程的argv。因此,exec并不是创建一个新的进程,它是将现有进程转换成另一个不同的运行程序。
成功的exec调用不会返回。
Why? Motivating The API
为什么要为创建新进程建造这样奇怪的应用程序接口?
事实证明,fork和exec分离是在建立UNIX shell中的必要部分,因为它允许shell在调用fork之后、调用exec之前运行代码。代码可以改变即将运行的程序的运行环境,从而可以轻松构建各种有趣的功能。比如输入输出重定向,管道等,不用在程序开始运行前改变任何东西。
shell:shell是一个用户程序,向用户显示提示,然后等待输入内容。
用户在其中键入命令(可执行程序名称以及所需参数)。
shell找出可执行文件在文件系统中的位置。
调用fork 创建一个子进程来运行该命令。
调用某些exec变体来运行该命令
调用wait 等待完成该命令。
子进程完成时,shell从wait返回并再次打印提示,准备下一条命令。
(shell有很多类型,比如tcsh, bash, zsh)
重定向,UNIX开始的文件描述符是0,标准输出流STDOUT FILENO是第一个可用的,因此在调用open()被赋值,后续由子进程写入标准输出文件符。
UNIX的管道系统以类似的方式实现,使用pipe()系统调用函数。
一个进程的输出连接到内核管道,另一个进程的输入连接到统一管道。因此,一个过程的输出可以无缝地用作下一个过程的输入,并且可以将冗长的命令链串在一起。
Process Control And Users
UNIX系统中其他用于和进程进行交互的接口:kill(),发送信号到进程。包括暂停、撕掉的指令和其他有用的命令。
eg. 快捷键
ctrl-c 发送SIGINT(中断)
ctrl-z 发送SIGTSTP(停止)
系统进行通信时,进程使用signal()调用来捕获各种信号,将特定信号发送给进程时,进程将暂停其正常执行并响应该信号,运行特定的代码段。
用户现代操作系统的概念,用户登陆获取对系统资源的访问权限,智能控制自己的流程;操作系统将资源分配给每个用户(及其进程),以实现总体系统目标。
Useful Tools
ps:查看正在运行的进程
(更多参阅man pages)
top:展示系统进程和CPU、资源占用
kill&killall
superuser(亦称为root)
系统通常需要一个能够管理系统的用户,而不是像大多数用户那样受到限制。这样的用户应该能够杀死任意进程(例如,如果它以某种方式滥用系统),即使该进程不是由该用户启动的。这样的用户还应该能够运行强大的命令,比如shutdown(毫不奇怪,shutdown会关闭系统)。
Summary
Key Process API Terms
name | content |
---|---|
PID | process name |
fork | create a new process |
wait | a parent waits for its child to complete execution |
exec | a child to break free from its similarity to its parent and execute an entirely new program |
UNIX shell | use fork(), wait(), exec() to launch user commands |
signals | cause jobs to stop, continue, terminate |
users | controll processes, only control their own processes |
superuser | control all processes |
Homework
模拟作业
- 运行./fork.py -s 10,预测每一步进程树的样子。使用-c来检查答案。
- 模拟器提供fork_percentage,由-f标志控制。值越高,下一个操作越可能是一个fork。通过大量操作将派生百分比从0.1更改为0.9,随着百分比的变化,最终的进程树是什么样子?
- 使用-t标志切换输出。给定一组进程树,说出采取了哪些行动?
- 一个子进程退出后,它在进程树中的孩子有什么变化?创建一个特例:./fork.py -A a+b, b+c, c+d, c+e, c-. 这个例子中a创建了b,b创建c,c创建d和e,然后c退出。进程树会是什么样子? 如果用-R标志呢?了解有关独自处理孤立过程的更多信息。
- -F标志,跳过中间步骤,仅要求填写最终的果成熟。运行./fork.py -F并尝试是否可以通过查看生成的一系列操作来写下最后的树。
- 同时使用-t和-F,显示了最终的流程树,随后填写已发生的操作。通过查看树可以确定发生的确切操作吗?什么情况下能指出,什么情况下不能?
编程作业
-
写一个程序,调用fork,在调用fork前,让主程序接受一个变量x,并设置它的值为100。子进程中这个变量的值是多少?如果子进程和父进程都改变了x的值,变量会怎样?
子进程中变量值一开始和父进程相同,都是100。
父子进程中对x的值的改变是独立的,不会影响其他进程内x的值。 -
写一个程序,打开文件(使用open()),调用fork创建新进程。子进程和父进程都可以访问open返回的文件描述符吗?他们并行(同时)写入文件时会发生什么?
-
写一个程序,调用fork。子进程输出“hello”,父进程输出“goodbye”。确保子进程总是先输出,要求不在父进程中使用wait
最初的想法是通过pipe管道,让子进程给父进程一个输入后,父进程再printf,但是实际操作过程中发现没办法靠这个读入实现。
然后是一个比较暴力的方法,让父进程sleep(1),强行阻塞到子进程进行结束。
最后比较合适的方法是使用vfork(),直接保证子进程先运行,直到子进程遇到exit后父进程才开始运行。 -
写一个程序,调用fork,然后调用一些exec的形式来运行程序/bin/ls。尝试所有Linux上exec的变体,为什么同样的基本调用会有这么多的变体?
-
写一个程序,在父进程中使用wait来等待子进程结束,wait返回什么?如果你在子进程中使用wait会发生什么?
子进程没有子进程,直接返回-1,父进程返回的是子进程的pid。 -
对前面的程序稍作修改,使用waitpid而不是wait,什么时候waitpid有用?
-
写一个程序,创建子进程,然后在子进程中关闭标准输出流(STDOUT_FILENO),如果子进程在关闭描述符后调用printf()来打印一些输出会发生什么?
关闭标准输出流后子进程不会产生输出。 -
写一个程序,创建两个子进程,然后用一个子进程的标准输出连接到另一个子进程的标准输入。使用pipe()来完成这个程序。