第八章 进程和程序:编写命令解释器sh
1. 进程=运行中的程序
程序是存储在文件中的机器指令的序列。执行程序是指把机器指令的序列载入到内存然后让处理器逐条执行。一个进程是程序运行时的内存空间和设置。
2. 通过命令ps来学习进程
1) 进程存在于用户空间,用户空间是存放运行的程序和他们的数据的一部分内存空间。
ps命令会列出用户空间中当前所有的进程。ps命令的参数及输出各部分的意思。
2)ps中列出的系统进程中有大部分并不与具体的终端相连,它们是系统启动的,而并非是用户通过终端启动。
3. 进程管理和文件管理的 对比学习:磁盘中的多个文件,既具有内容也有属性;而内存中的进程也需要内核为之分配空间并记录分配情况、属性。
4. 内存和程序
1) 进程的概念虽然抽象,但进程却代表着一些非常实际的实体:内存中的一些字节。
2) 内存的三种模式:内存在系统框图中的内核框和进程框;像数组一样描述的内存;物理硬件内存芯片
3) 建立一个进程的过程同在磁盘创建文件的过程相似:文件的内容存储自磁盘不同的快中,进程的数据也被存储在内存中零散的块中;文件创建需要存储文件存储块的记录列表,进程同样需要维护类似的分配信息。所以在框图中用小方框描述进程是一种抽象。
5. shell:进程控制和程序控制的一个工具
Shell的三大功能:运行程序;管理输入和输出;可编程
6. shell是如何运行程序的:
Shell运行的时间轴(默认用户已经登录系统,处在命令提示符下):
-->用户输入程序
-->系统新建进程
-->查找程序文件,载入内存
-->运行程序直到程序退出
7. 理解shell的运行过程后编写shell程序的工作主要集中在以下三个方面:
1) 一个程序如何运行另外一个程序:程序调用execvp
a) exec1.c中execvp函数的使用;
b) 程序中第二条打印丢失的原因(新的程序替换了原来的程序和数据);
c) execvp函数就像换脑;
d) 编写带输入提示的shell程序psh1.c(逐个输入shell命令和参数,但同样出现了“换脑”后无法继续执行其他命令的情况)
2) 如何建立一个新的进程:程序调用fork
fork函数会新建一个进程,复制当前进程到新建进程,fork返回时父进程和子进程都从fork后运行。
a) forkdemo1.c:使用fork创建一个进程
b) forkdemo2.c:多次fork验证fork工作机制
c) forkdemo3.c:在fork函数返回后更具返回值区分父进程和子进程
3) 父进程如何等待子进程退出:进程调用wait等待子进程退出
a) wait()做两件事:暂停调用它的进程直到子进程结束时通知,取得子进程结束时传递给exit()的值(通信)
b) waitdemo1.c:wait阻塞调用它的进程直到子进程结束;wait返回结束进程的PID(这使得父进程知道是哪个子进程结束了,从而进行不同的后续操作)
c) waitdemo2.c:通信。
-->进程以三种方式之一结束(正行完成任务,程序调用exit(0)或者调用return 0; 进程可能失败,exit传递一个非零值;进程被一个信号杀死)
-->wait函数的参数完成子进程退出状态的传递:如果子进程调用exit函数退出,则将exit的返回值存储在该整型变量中;如果是被信号杀死,则将信号存储在给变量中。这个整型变量由三部分组成:8个bit记录退出值,7个bit记录信号序号,1个bit指明发生错误并产生了内核映像。
-->wait参数中的整型变量由 sys/wait.h中的宏来检测
4) 小结:shell如何运行程序
shell用fork建立新进程;
用exec在新进程中运行用户指定的程序;
调用wait等待新进程的结束,wait系统调用从内核获取退出状态和信号序号,以告知子进程是如何结束的。
8. 实现一个shell:psh2.c
1) 问题:ctrl+c不仅杀死了新建进程而且杀死了psh2进程本身,这是因为键盘信号发送给所有连接的进程
2) 希望用户能够在一行输入所需要运行的程序名及参数
9. 思考:用进程编程
1) 函数和进程之间的相似性
execvp/exit就像call/return:call和return是结构化编程的基础。函数调用所用到的堆栈几乎是没有限制的,一个被调用的程序还可以调用其他的程序。一个通过fork/exec调用起来的程序可以通过fork/exec调用其他的程序。exec传递的参数必须是字符串,这也正好是支持跨平台所需的。
2) 全局变量和fork/exec
普通的全局变量会破坏封装原则,导致出让人意料的副作用和难以维护的代码。Unix提供了环境变量的方式来实现全局变量,环境变量是一些传递给进程的字符串变量合集,不会有副作用。
10. exit和exec的其他细节
1) 进程死亡:exit和_exit
exit是fork的逆操作;exit刷新所有的流,调用由atexit和on_exit注册的函数,执行当前系统定义的其他与exit有关的操作,然后调用_exit函数。
_exit函数是一个内核操作,主要处理所有分配给这个进程的内存,关闭这个进程打开的文件,释放所有内核用于管理和维护该进程的数据结构。具体包括:
-->关闭所有文件描述符和目录描述符
-->将进程的PPID设置为init进程的PID
-->如果父进程调用wait或waitpid来等待子进程结束,则通知父进程
-->向父进程发送SIGCHLD(其实父进程没有调用wait,内核也会发送该信号,该信号的默认处理是忽略)。
已经死亡但是没有给exit赋值的进程成为僵尸进程(zombie)。有些系统下ps命令中可以看到这些进程并被标记为defunc。
2) exec家族
分清各个家族成员的不同之处。
11. 小结
1) unix同过将可执行代码载入到进程并执行它来运行一个程序。进程是运行一个程序所需要的内存空间和其他资源的集合。
2) 进程有自己的一套属性,如同磁盘上的文件一样。内存的三个维度的表示。
3) fork完成了什么?exec在新进程中执行程序。wait实现父进程对子进程的等待。
----------------------------------------------------------------------------------------
本文链接http://blog.csdn.net/yongchurui/article/details/27379601
2014.05.28