何谓进程?进程就是一个正在运行着的程序实例。
在Linux中,每一个进程都有一个进程号(Process ID)来标示身份。
在shell中你可以通过ps命令来查看。
[liyong@localhost temp]$ ps PID TTY TIME CMD 17197 pts/8 00:00:00 bash 31141 pts/8 00:00:00 ps |
关于ps的详细用法参考man手册。
PID下面的数字就是进程号,比如正在执行的bash的进程号是17197,ps的进程号是31141。
进程号是一个16位的数字,也就是说,一个系统中顶多只能由2^16-1个=65535进程。
你在程序中可以通过下面函数来获得进程号。
pid_t getpid()
pid_t pid=getpid(); printf(“%d/n”,(int)pid); |
每一个进程都有其父进程。你在shell下直接运行的进程,其父进程就是bash。系统中只有一个进程没有父进程,就是init。它的进程号永远为1。所有进程都是init进程的子孙进程。
你可以通过下面的函数来获得其父进程号。
pid_t getppid(); |
进程可以通过自身的停止执行来结束,也可以通过kill命令来杀死。
[liyong@localhost ~]$ ps PID TTY TIME CMD 17197 pts/8 00:00:00 bash 31213 pts/8 00:00:00 find 31220 pts/8 00:00:00 ps [liyong@localhost ~]$ kill 31213 [liyong@localhost ~]$ ps PID TTY TIME CMD 17197 pts/8 00:00:00 bash 31221 pts/8 00:00:00 ps |
kill其实是向进程发送一个SIGTERM信号。
下面谈一下如何创建一个进程。
创建一个进程由两种常用的方法:system和fork/exec family。
system方法比较低效,通常不用。在这里重点说明fork/exec family用法。
使用fork来创建一个新的进程,其实是将进程自身复制成一个新的进程。记住,是复制,不是共享。你可以通过fork函数的返回值来区分该进程是父进程还是子进程。对于父进程,fork返回了子进程的pid。对于子进程而言,fork返回0。
下面是一个简单的fork程序。
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h>
int main() { int i=0; pid_t child_pid=fork(); if(child_pid==0) {//child process for(i=0;i<100;i++) { printf("%d:%d/n",getpid(),i); fflush(stdout); } exit(0); } else { for(i=0;i<100;i++) { printf("%d:%d/n",getpid(),i); fflush(stdout); } exit(0); } } |
exec是一系列函数族。它和fork不同的是,他请求运行的程序将代替原先正在运行的程序。该函数执行完后,原先运行的程序将被终止,请求运行的程序开始运行,而进程号不变。
exec有一系列寒暑,他们区分如下:
l 如果名字里含有p,比如execvp,execlp,那么表示他们运行的程序地址就在当前目录下,而如果没有,则需要写出程序的绝对地址。
l 如果名字里有v,则表示传递给即将运行的程序的参数是字符串,而如果是l,则表示参数格式采用的是c语言里的变参数。
如果传递的参数是字符串,需要注意的是该字符串内容和在命令行下执行程序时传递的参数一样,也就是说字符串从argv[0]到argv[argc-1]。且argv[0]就是该程序的名字。另外,该字符串必须以NULL来结尾。
参考下面一个程序。
#include <stdio.h> #include <unistd.h> #include <sys/types.h>
int main() { char * argu_list[]={"ls","/","-l",NULL}; int status=0; pid_t child_pid=fork(); if(child_pid==0) { //child process execvp("ls",argu_list); fprintf(stderr,"exec ls error/n"); exit(1); } else { //parent process wait(&status); if(WIFEXITED(status)) printf("the child process exit normally./n"); else printf("the child process exit abnormally./n"); printf("done with the main program./n"); exit(0); } } |
在这里,我们先调用fork复制了一个子进程,然后再调用execvp新运行了一个程序。
在这个程序离我们还调用了wait函数。该函数有什么作用了?
在一些情况下,我们的父进程可能比子进程结束的要早。如果父进程提前结束了,子进程就变成了僵尸进程。我们需要父进程来清理子进程结束后的一些环境。在这个时候调用wait,父进程将阻塞在wait地方,等待子进程结束。
wait函数的参数,在这里我们输入的是status,将返回子进程结束的返回值,就是return的值。如果不需要,可以设为NULL。
注意,如果有多个子进程,那么当只要有一个进程结束的时候,wait函数就会返回。
如果你在父进程中忘记调用了wait,那么子进程将变成僵尸进程。由于init进程是所有进程的祖宗进程,init的一个职责就是不断查询系统,来获得哪些僵尸进程。它会代替父进程来清理子进程的环境。