理解Unix进程

最近读了一本介绍Unix进程的小册子,虽然讲的是ruby的东西,但是对进程的概念,fork,父子进程,进程通信,信号量,IPC以及僵尸进程,守护进程概念讲的蛮有意思的,书名就叫《理解Unix进程》。

让我们先从一个 有趣的面试题说起,记得之前见到过一个面试题,如下:

如上所示,要求if里面写入一条语句,打印出true和false,也就是说要求if 和 else都执行,如果你从来没有接触过fork的话,很难想象出if,else竟然都可以执行?其实这只是假象,程序表面上if,else都执行了,其实是创建了2个进程,父进程执行了if,而子进程执行了else而已。

Unix最大的优点是,对很多东西进行了抽象,或者说标准化,比如对程序进行了抽象,叫做进程,java里说万事万物皆对象,那么对于Unix而言,就是万事万物皆文件了,除了对进程进行了抽象,还有对文件,对网络,对外部设备等等都抽象为资源,即文件,进程有进程ID即pid,文件也有唯一标示,叫文件描述符,有了文件描述符,就能对抽象的东西进行操作,比如读取,写入等,如果你关心资源描述符的话,你会发现,它其实就是一个ID,但是始终是从3开始的,为什么?因为系统已经为我们抽象出了3个资源,他们分别是0,1,2,分别代表着标准输入,标准输出,标准错误。

进程皆有父,Unix里的每个进程都有一个父进程,通过ppid标示,最开始的进程叫init进程,pid是多少?你猜,老大当然是1,它的父进程是谁?这你都敢问?你说上帝的上帝是谁?宇宙的宇宙是神马?不过如果你非要知道的话,它的ppid是0.

现在回到我们一开始的fork系统调用上来,fork简直是Unix最伟大的发明,没有之一,它简单又高效,实现了一生二,二生四,四生万物的最简单法则,如果没有fork,那么你神马都得不到。

每一个进程都可以通过fork来产生子进程,同时无私的将自己的内存和代码复制给了子进程,注意是复制,不是共享,也就是说,从fork内核调用之后,子进程将和父进程一起运行下去,父进程占用了100M内存,子进程也会有一份自己的100M内存,如果我fork一万次,怎么办?现代操作系统用很聪明的方法,解决了内存复用和fork开销的问题,那就是COW,即copy on write,读我们可以共享,但是孩子,如果你一旦写,那就要写你自己那份,这即解决了fork时的系统代价问题,又解决了资源占用和并发问题,就像java里的并发集合里,也有一个copyonwrite的Array一样,有一些设计思想是相通的,语言只是武器,就像屠龙刀和柳树枝,高手只用柳树枝一样可以杀人。

进程间如何进行通信呢?如果我只是fork出子进程,而不能通信和管理,那岂不是乱了辈分,所以父进程可以通过系统调用,来和子进程通信,比如wait等待,等待子进程结束后返回的状态码,或者使用管道进行通信。我们shell里,经常使用的ls -a|grep xxx就是进程间管道通信最好的例子。

Unix还有一个比较有意思的机制就是信号,进程可以等待信号,就像我们发短息一样,而不用for循环一直浪费cpu去轮训一个状态,说到信号,不得不说的是一个我们经常使用的命令,kill 而kill的时候,我们经常会这样kill,即kill -9 这个9代表什么呢??比如说我们有时候,想知道程序的核心转储,即栈追踪,我们java里杀掉jvm往往会这样,kill -3,而3又代表什么呢?

原来,系统内置了很多信号,比如Stop,Core,Term,而3就代表着Core的系统信息,也可以使用Ctrl-/来发出,9代表着Term,即立即结束,虽然进程可以截获信号,并选择忽略,但是唯有Stop和Term是不能截获和忽略的,这也是为什么kill -9才是老大.

对了,还有一个系统调用,exec没有说,这个就留给能坚持读到这的你去了解下它和fork的区别和配合吧。

发表评论

电子邮件地址不会被公开。 必填项已用*标注


6 + = 十四

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">