转自:https://www.cnblogs.com/vamei/archive/2012/09/20/2694466.htmlhtml
做者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!shell
计算机实际上能够作的事情实质上很是简单,好比计算两个数的和,再好比在内存中寻找到某个地址等等。这些最基础的计算机动做被称为指令(instruction)。所谓的程序(program),就是这样一系列指令的所构成的集合。经过程序,咱们可让计算机完成复杂的操做。程序大多数时候被存储为可执行的文件。这样一个可执行文件就像是一个菜谱,计算机能够按照菜谱做出可口的饭菜。
apache
那么,程序和进程(process)的区别又是什么呢?bash
进程是程序的一个具体实现。只有食谱没什么用,咱们总要按照食谱的指点真正一步步实行,才能作出菜肴。进程是执行程序的过程,相似于按照食谱,真正去作菜的过程。同一个程序能够执行屡次,每次均可以在内存中开辟独立的空间来装载,从而产生多个进程。不一样的进程还能够拥有各自独立的IO接口。函数
操做系统的一个重要功能就是为进程提供方便,好比说为进程分配内存空间,管理进程的相关信息等等,就好像是为咱们准备好了一个精美的厨房。spa
首先,咱们可使用$ps命令来查询正在运行的进程,好比$ps -eo pid,comm,cmd,下图为执行结果:操作系统
(-e表示列出所有进程,-o pid,comm,cmd表示咱们须要PID,COMMAND,CMD信息)firefox
每一行表明了一个进程。每一行又分为三列。第一列PID(process IDentity)是一个整数,每个进程都有一个惟一的PID来表明本身的身份,进程也能够根据PID来识别其余的进程。第二列COMMAND是这个进程的简称。第三列CMD是进程所对应的程序以及运行时所带的参数。线程
(第三列有一些由中括号[]括起来的。它们是内核的一部分功能,被打扮成进程的样子以方便操做系统管理。咱们没必要考虑它们。)设计
咱们看第一行,PID为1,名字为init。这个进程是执行/bin/init这一文件(程序)生成的。当Linux启动的时候,init是系统建立的第一个进程,这一进程会一直存在,直到咱们关闭计算机。这一进程有特殊的重要性,咱们会不断提到它。
实际上,当计算机开机的时候,内核(kernel)只创建了一个init进程。Linux内核并不提供直接创建新进程的系统调用。剩下的全部进程都是init进程经过fork机制创建的。新的进程要经过老的进程复制自身获得,这就是fork。fork是一个系统调用。进程存活于内存中。每一个进程都在内存中分配有属于本身的一片空间 (address space)。当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。
老进程成为新进程的父进程(parent process),而相应的,新进程就是老的进程的子进程(child process)。一个进程除了有一个PID以外,还会有一个PPID(parent PID)来存储的父进程PID。若是咱们循着PPID不断向上追溯的话,总会发现其源头是init进程。因此说,全部的进程也构成一个以init为根的树状结构。
以下,咱们查询当前shell下的进程:
root@vamei:~# ps -o pid,ppid,cmd PID PPID CMD 16935 3101 sudo -i 16939 16935 -bash 23774 16939 ps -o pid,ppid,cmd
咱们能够看到,第二个进程bash是第一个进程sudo的子进程,而第三个进程ps是第二个进程的子进程。
还能够用$pstree命令来显示整个进程树:
init─┬─NetworkManager─┬─dhclient │ └─2*[{NetworkManager}] ├─accounts-daemon───{accounts-daemon} ├─acpid ├─apache2─┬─apache2 │ └─2*[apache2───26*[{apache2}]] ├─at-spi-bus-laun───2*[{at-spi-bus-laun}] ├─atd ├─avahi-daemon───avahi-daemon ├─bluetoothd ├─colord───2*[{colord}] ├─console-kit-dae───64*[{console-kit-dae}] ├─cron ├─cupsd───2*[dbus] ├─2*[dbus-daemon] ├─dbus-launch ├─dconf-service───2*[{dconf-service}] ├─dropbox───15*[{dropbox}] ├─firefox───27*[{firefox}] ├─gconfd-2 ├─geoclue-master ├─6*[getty] ├─gnome-keyring-d───7*[{gnome-keyring-d}] ├─gnome-terminal─┬─bash │ ├─bash───pstree │ ├─gnome-pty-helpe │ ├─sh───R───{R} │ └─3*[{gnome-terminal}]
fork一般做为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。实际上,子进程总能够查询本身的PPID来知道本身的父进程是谁,这样,一对父进程和子进程就能够随时查询对方。
一般在调用fork函数以后,程序会设计一个if选择结构。当PID等于0时,说明该进程为子进程,那么让它执行某些指令,好比说使用exec库函数(library function)读取另外一个程序文件,并在当前的进程空间执行 (这其实是咱们使用fork的一大目的: 为某一程序建立进程);而当PID为一个正整数时,说明为父进程,则执行另一些指令。由此,就能够在子进程创建以后,让它执行与父进程不一样的功能。
当子进程终结时,它会通知父进程,并清空本身所占据的内存,并在内核里留下本身的退出信息(exit code,若是顺利运行,为0;若是有错误或异常情况,为>0的整数)。在这个信息里,会解释该进程为何退出。父进程在得知子进程终结时,有责任对该子进程使用wait系统调用。这个wait函数能从内核中取出子进程的退出信息,并清空该信息在内核中所占据的空间。可是,若是父进程早于子进程终结,子进程就会成为一个孤儿(orphand)进程。孤儿进程会被过继给init进程,init进程也就成了该进程的父进程。init进程负责该子进程终结时调用wait函数。
固然,一个糟糕的程序也彻底可能形成子进程的退出信息滞留在内核中的情况(父进程不对子进程调用wait函数),这样的状况下,子进程成为僵尸(zombie)进程。当大量僵尸进程积累时,内存空间会被挤占。
尽管在UNIX中,进程与线程是有联系但不一样的两个东西,但在Linux中,线程只是一种特殊的进程。多个线程之间能够共享内存空间和IO接口。因此,进程是Linux程序的惟一的实现方式。
程序,进程,PID,内存空间
子进程,父进程,PPID,fork, wait