引用《linux程序设计》的一句话:进程和信号构成了Linux操作环境的基础部分,控制着Linux和其他所有类UNIX计算机系统执行的几乎所有动作。(哇,这么高端, 其实算来,windows下,貌似也是进程,不过信号有没有这么重要的作用就不知道了,不过windows下应该是消息)
一、什么是进程
说到进程,其实在操作系统这门课就讲过,也就是正在执行的程序(当然,这定义太简单了)。UNIX标准将进程定义为:一个其中运行着一个或多个线程的地址空间和这些线程所需要的系统资源。目前,先将进程看做正在运行的程序。
需要说的是,Linux高效性的一个地方在于,系统会在进程间共享程序代码和系统函数库,所以在任一时刻,内存中都只有代码的一份副本!
二、进程的管理
如果有2个用户neil和rick,他们同时运行grep在不同文件中查找不同字符串,则使用的进程如下图所示:
进程的关键要素,PID这个直接调用getpid()就可以得到,取值2到32768(1呢?给init进程了啊)
正常情况下,进程不能对用来存放代码的内存区域进行写操作,即代码是以只读方式加载到内存的。虽然不能写,不过可以被多个进程安全的共享。可以共享的不知是用户函数,系统函数也可以,共享的思想,类似于windows下的.dll机制。不过不是所有东西都可以共享,进程的变量就不行。(其实说到共享,想到在unix环境下,进程通信貌似是很重要的部分呢么)
另外,进程还有自己的栈空间,用于保存函数中的局部变量和控制函数的调用与返回。进程还有自己的环境空间,包含专门为这个进程建立的环境变量(其实对于这个不是很理解)
三、启动进程以及linux的启动
启动进程,我们可以用system函数(其实以前看过还可以fork什么的,后面上)。system原型为:
#include <stdlib.h> int system(const char *string);以前经常用system("cls")和pause什么的,system的作用就是运行以字符串参数的形式传递给它的命令并等待该命令的完成。
linux的启动过程呢,可以参考http://www.ibm.com/developerworks/cn/linux/kernel/startup/,不过这个略高深了点。单从进程的启动过程来讲,启动过程就是,首先启动init进程(pid=1)可以把它看做整个系统的进程管理器,或者看做祖先进程。然后init来处理系统的初始化,(主要是rc.sysinit)处理完后,回到init,启动系统服务和设置文件。下面终于到我们熟悉的终端和x-window界面了。(可参考:http://www.cnblogs.com/scnutiger/archive/2009/09/30/1576795.html)
四、exec(),fork(),wait()僵尸进程
突然想起,在kismet的源代码中见过exec()和fork(),不过对这俩印象不是很深刻,下面来学习一下吧。
1、 exec():替换进程映像。exec家族函数:
#include <unistd.h> char **environ; int execl(const char *path, const char *arg0, ... ,(char *)0); int execlp(const char *file, const char *arg0, ...,(char *)0); int execle(const char *path, const char *arg, ...,(char *)0,char *const envp[]) ; int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);这些函数可以分为2大类。execl、execlp和execle的参数个数可变,参数以一个空指针结束;execv和execvp的第二个参数是一个字符串数组(和main的命令行参数一样)。不管哪种情况,新程序启动时会把argv数组中的参数传递给main函数。
p结尾的函数是通过搜索PATH环境变量来查找新程序的可执行文件的路径,如可执行文件不在PATH定义路径中,我们就需要把包括目录在内的使用绝对路径的文件名作为参数传递。
environ为全局变量,可用来把一个值传递到新的程序环境中。此外,函数execle和execve可以通过envp传递字符串数组作为新程序的环境变量。
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { printf("Running ps with execlp \n"); execlp("ps","ps","ax",0); printf("Done .\n"); exit(0); }运行以上程序,有正常的ps输出,却没有done的输出,另外,ps的输出中,没有exec进程(本程序可执行名)的任何信息。
关于fork(),http://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940154.html有提到,不过个人更喜欢http://blog.csdn.net/lingdxuyan/article/details/4993883,毕竟,fork出来的进程,说父子程序,其实也可以说兄弟程序。fork过程示意图:
fork()函数原型:
#include <sys/types.h> #include <unistd.h> pid_t fork(void);关于fork,需要注意的几个点就是:
a.fork是对当前父进程的复制,包括代码段,数据段,堆栈段。说是复制,其实这里用共享更合适,如http://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940154.html所讲,fork对于父子进程的划分,只是逻辑上,不是物理上,只有数据改变时,才物理上进行划分。不过不精确,因为共享的,显然只能是代码段,数据必然是进程所独有的,堆栈也只能的记录fork那个时刻的堆栈情况。所以虽然子进程继承了父进程的大部分资源,不过只是父进程在fork那个时刻的数据,在fork以后,数据段就分开了,要进行共享,只能进程通信实现。
b.父子进程的pid,对子进程是0,对父进程在而是一个正整数。
示范程序如下:
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; char *message; int n; printf("fork program starting ...\n"); pid = fork(); switch (pid) { case -1: perror("fork failed!"); exit(1); case 0: message = "This is the child "; n = 5; break; default: message = "This is the parent" ; n = 3 ; break; } for ( ; n > 0 ; i-- ) { puts(message); sleep(1); } exit(0); }这个程序以2个进程的形式存在,子进程被创建并输出5次,父进程只输出3次。父进程在打印完它的全部消息后接受,因此我们能看到在输出内容中混杂了一个shell提示符(从这还是能看出是父子关系啊)结果如下:
3、wait():等待一个进程
上一个示例,父进程在子进程结束之前结束了,不过子进程还在继续运行,得到的输出就有点乱了,我们可以通过在父进程中调用wait函数让父进程来等待子进程的结束。 wait()函数原型如下:
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc);wait调用的作用,暂停父进程直到它的子进程结束为止,调用返回子进程的pid,通常是已经结束的子进程的pid。对上面fork的例子,我们可以加上如下一段来等待子进程的完成。
if (pid != 0) { int stat_val; pid_t child_pid; child_pid = wait(&stat_val); printf("Child has finished : PID = %d\n",child_pid); if (WIFEXITED(stat_val)) printf("Child exited with code %d \n",WEXITSTATUS(stat_val) ); else printf("Child terminated abnormally \n"); }运行结果如下:
在上面的代码中,父进程用wait调用将自己的执行挂起,直到子进程的状态信息出现为止(在子进程调用exit时发生)。
4、僵尸进程
用fork产生进程时,还有一个问题,子进程终止时,它与父进程之间的关联还会保持,直到父进程也正常终止或父进程调用wait才结束。因此,进程表中代表子进程的表项不会立即释放,虽然子进程已不再运行,但它仍存在于系统中,因为它的退出码还需要保存起来,以备父进程今后的wait调用,这时候,它就成为了一个僵尸zombie进程。
对上面的fork示例,我们兑换父子进程的n值,就可以看到僵尸进程了。(在子进程结束后,父进程结束之前调用ps,我们会看到<defunct>或者<zombie>)lz的f9看到的是 如下情况:
如果此时父进程异常终止,子进程自动把PID为1的进程(即init)作为自己的父进程。这样,如果异常终止的过多,init接管的就越多,僵尸进程一直保留在进程表中,直到被init发现并释放。表越大,过程就越慢,所以应该尽量避免僵尸进程产生。
僵尸进程的回收,可以用wait来实现,具体可以参考:http://linux.chinaitlab.com/c/831518.html
另外http://blog.csdn.net/lingdxuyan/article/details/4996471,里还有exit和_exit(),看到这个就想起了cd和cd ~,su和su -的区别(虽然在f9上还是有区别的,不过在ubuntu10.10上看不出任何差别啊,可以看做系统更人性化了吧)
目前就是这样了,对于进程什么的,还是压力巨大的,对window下的线程倒是试过,以后有时间了,慢慢试试linux下的进程通信什么的。
菜鸟goes on~~~