Linux进程控制
来源:互联网 发布:土耳其软糖淘宝 编辑:程序博客网 时间:2024/06/10 02:15
Linux进程控制
1.基本知识:
(1)fork系统调用:创建一个新进程。即完成创建子进程,也返回一个值。
<0,创建失败;=0,子进程执行中;〉0,主进程进行中。
(2)getid:获得一个进程的id。
(3)lockf:在进程同步控制中为进程加锁。
(4)wait(等待子进程中断或结束): 进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
(5)不像fork那么难理解,从exit的名字就能看出,这个系统调用是用来终止一个进程的。无论在程序中的什么位置,只要执行到exit系统调用,进程就会停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。
2.简单操作:
(2) 多次连续反复运行这个程序,观察屏幕显示结果的顺序,直至出现不一样的情况为止。
fork.c:
# include<stdio.h># include<sys/types.h># include<unistd.h>int main(){ int pid1,pid2 printf(“I am father!\n”); if ((pid1 = fork())<0) { printf(“Child1 fail create!\n”); return 1; } else if (pid1 == 0) { printf(“I am son!\n”); return; } if ((pid2 = fork())<0) { printf(“Child2 fail create!\n”); return 1; } else if (pid2 == 0) { printf(“I am daughter!\n”); return; }}
(3) 编写一C语言程序(程序名为fork.c),使用系统调用fork( )创建一个子进程,然后在子进程中再创建子子进程。当程序运行时,系统中有一个父进程、一个子进程和一个子子进程在并发执行。父亲进程执行时屏幕显示“I am father”,儿子进程执行时屏幕显示“I am son”,孙子进程执行时屏幕显示“c”。
(4) 多次连续反复运行这个程序,观察屏幕显示结果的顺序,直至出现不一样的情况为止。
fork.c:
# include<stdio.h># include<sys/types.h># include<unistd.h>int main(){ int pid1,pid2 printf(“I am father!\n”); if ((pid1 = fork())<0) { printf(“Child1 fail create!\n”); return 1; } else if (pid1 == 0) { if ((pid2 = fork())<0) { printf(“c fail create!\n”); return 1; } else if (pid2 == 0) { printf(“c\n”); return; } else { printf(“I am son!\n”); return; } }}
(5) 修改程序,在父、子进程中分别使用wait、exit、lockf等系统调用“实现”其同步推进,多次反复运行改进后的程序,观察并记录运行结果。
Linux进程控制wait()函数解析
wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则 wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status可以设成NULL。子进程的结束状态值请参考waitpid()。返回值如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。
wait(等待子进程中断或结束),相关函数waitpid,fork。
范例:
#include<stdlib.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>int main(){ pid_t pid; int status,i; if(fork()= =0){ printf(“This is the child process .pid =%d\n”,getpid()); exit(5); } else{ sleep(1); printf(“This is the parent process ,wait for child...\n”; pid=wait(&status); i=WEXITSTATUS(status); printf(“child’s pid =%d .exit status=%d\n”,pid,i); }}(6) Linux进程控制 exit()
实例:
/* exit_test1.c */#include<stdlib.h>main(){printf("this process will exit!\n");exit(0);printf("never be displayed!\n");}编译后运行:
$gcc exit_test1.c -o exit_test1$./exit_test1this process will exit!我们可以看到,程序并没有打印后面的"never be displayed!\n",因为在此之前,在执行到exit(0)时,进程就已经终止了。
exit 系统调用带有一个整数类型的参数status,我们可以利用这个参数传递进程结束时的状态,比如说,该进程是正常结束的,还是出现某种意外而结束的,一般来说,0表示没有意外的正常结束;其他的数值表示出现了错误,进程非正常结束。
(7) Linux进程控制 lockf()利用系统调用lockf(fd,mode,size),对指定区域(有size指示)进行加锁或解锁,以实现进程的同步或互斥。其中,fd是文件描述字;mode是锁定方式,mode=1表示加锁,mode=0表示解锁;size是指定文件fd的指定区域,用0表示从当前位置到文件结尾(注:有些Linux系统是locking(fd,mode,size))。
范例:
#include<stdio.h>#include<sys/types.h>#include<unistd.h>int main(void){int pid1,pid2;lockf(1,1,0);printf(“Parent process:a\n”);if((pid1=fork())<0){printf(“child1 fail create\n”);return 1;}else if(pid1= =0){lockf(1,1,0);printf(“This is child1(pid=%d) process:b\n”,getpid());lockf(1,0,0);return;}if((pid2=fork())<0){printf(“child2 fail create\n”);return 1;}else if(pid2= =0){lockf(1,1,0);printf(“This is child2(pid=%d) process:c\n”,getpid());lockf(1,0,0);return;}}(8) wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,pid = wait(NULL)。返回值如果执行成功wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
实例A:
/* wait1.c */#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <stdlib.h>main(){pid_t pc,pr;pc=fork();if(pc<0) /* 如果出错 */printf("error ocurred!\n");else if(pc==0){/* 如果是子进程 */ printf("This is child process with pid of %d\n",getpid());sleep(10);/* 睡眠10秒钟 */}else{/* 如果是父进程 */pr=wait(NULL);/* 在这里等待 */printf("I catched a child process with pid of %d\n"),pr);}exit(0);}
编译并运行:
$ cc wait1.c -o wait1$ ./wait1This is child process with pid of 1608I catched a child process with pid of 1608
可以明显注意到,在第2行结果打印出来前有10 秒钟的等待时间,这就是我们设定的让子进程睡眠的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进程捕捉到。其实这里我们不管设定子进程睡眠的时间有多长,父进程都会一直等待下去,读者如果有兴趣的话,可以试着自己修改一下这个数值,看看会出现怎样的结果。
(9) 参数status如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的(一个进程也可以被其他进程用信号结束,我们将在以后的文章中介绍),以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:
1) WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数--指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了)
2) WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
实例B:/* wait2.c */#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>main(){int status;pid_t pc,pr;pc=fork();if(pc<0)/* 如果出错 */printf("error ocurred!\n");else if(pc==0){/* 子进程 */printf("This is child process with pid of %d.\n",getpid());exit(3);/* 子进程返回3 */}else{/* 父进程 */pr=wait(&status);if(WIFEXITED(status)){/* 如果WIFEXITED返回非零值 */printf("the child process %d exit normally.\n",pr);printf("the return code is %d.\n",WEXITSTATUS(status));}else/* 如果WIFEXITED返回零 */printf("the child process %d exit abnormally.\n",pr);}}编译并运行:
$ cc wait2.c -o wait2$ ./wait2This is child process with pid of 1838.the child process 1838 exit normally.the return code is 3.父进程准确捕捉到了子进程的返回值3,并把它打印了出来。
当然,处理进程退出状态的宏并不止这两个,但它们当中的绝大部分在平时的编程中很少用到,就也不在这里浪费篇幅介绍了,有兴趣的读者可以自己参阅Linux man pages去了解它们的用法。
实例C:
#include<stdlib.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>int main(){ pid_t pid; int status,i; if(fork()= =0) { printf(“This is the child process .pid =%d\n”, getpid()); exit(5); } else{ sleep(1); printf(“This is the parent process ,wait for child...\n”); pid = wait(&status); i = WEXITSTATUS(status); printf(“child’s pid =%d. exit status=%d\n”,pid,i); }}
执行结果:
This is the child process.pid=1902This is the parent process .wait for child...child’s pid =1902,exit status =5(10) waitpid(等待子进程中断或结束)
从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:
1) pid :从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。 pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
2) option:
参数option 可以为0 或下面的OR 组合:
WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等待。WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。子进程的结束状态返回后存于status,底下有几个宏可判别结束情况:
WIFEXITED(status)如果子进程正常结束则为非0 值。
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真。
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。
返回值如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno 中。
options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:ret=waitpid(-1,NULL,WNOHANG| WUNTRACED);如果我们不想使用它们,也可以把options设为0,如:ret=waitpid(-1,NULL,0);如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。
而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了,有兴趣的读者可以自行查阅相关材料。
看到这里,聪明的读者可能已经看出端倪了--wait不就是经过包装的waitpid吗?
没错,查看<内核源码目录>/include/unistd.h文件349-352行就会发现以下程序段:
static inline pid_t wait(int * wait_stat){return waitpid(-1,wait_stat,0);}实例:
/* waitpid.c */#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>main(){pid_t pc, pr;pc=fork();if(pc<0)/* 如果fork出错 */printf("Error occured on forking.\n");else if(pc==0){/* 如果是子进程 */sleep(10);/* 睡眠10秒 */exit(0);}/* 如果是父进程 */do{pr=waitpid(pc, NULL, WNOHANG);/* 使用了WNOHANG参数,waitpid不会在这里等待 */if(pr==0){/* 如果没有收集到子进程 */printf("No child exited\n");sleep(1);}}while(pr==0);/* 没有收集到子进程,就回去继续尝试 */if(pr==pc)printf("successfully get child %d\n", pr);elseprintf("some error occured\n");}
编译并运行:
$ cc waitpid.c -o waitpid$ ./waitpidNo child exitedNo child exitedNo child exitedNo child exitedNo child exitedNo child exitedNo child exitedNo child exitedNo child exitedNo child exitedsuccessfully get child 1890
父进程经过10次失败的尝试之后,终于收集到了退出的子进程。
因为这只是一个例子程序,不便写得太复杂,所以我们就让父进程和子进程分别睡眠了10秒钟和1秒钟,代表它们分别作了10秒钟和1秒钟的工作。父子进程都有工作要做,父进程利用工作的简短间歇查看子进程的是否退出,如退出就收集它。
(11) exit()函数实例:
/* exit_test1.c */#include<stdlib.h>main(){printf("this process will exit!\n");exit(0);printf("never be displayed!\n");}编译后运行:
$gcc exit_test1.c -o exit_test1$./exit_test1this process will exit!我们可以看到,程序并没有打印后面的"never be displayed!\n",因为在此之前,在执行到exit(0)时,进程就已经终止了。
exit 系统调用带有一个整数类型的参数status,我们可以利用这个参数传递进程结束时的状态,比如说,该进程是正常结束的,还是出现某种意外而结束的,一般来说,0表示没有意外的正常结束;其他的数值表示出现了错误,进程非正常结束。理论上exit可以返回小于256的任何整数。返回的不同数值主要是给调用者作不同处理的。单独的进程是返回给操作系统的。如果是多进程,是返回给父进程的。父进程里面调用waitpid()等函数得到子进程退出的状态,以便作不同处理。根据相应的返回值来让调用者作出相应的处理。总的说来,exit()就是当前进程把控制权返回给调用该程序的程序,括号里的是返回值,告诉调用程序该程序的运行状态。我们在实际编程时,可以用wait系统调用接收子进程的返回值,从而针对不同的情况进行不同的处理。
(12) exit和_exit
作为系统调用而言,exit和_exit是一对孪生兄弟。
这时随便一个懂得C语言并且头脑清醒的人都会说,exit和_exit没有任何区别,但我们还要讲一下这两者之间的区别,这种区别主要体现在它们在函数库中的定义。_exit在Linux函数库中的原型是:
#include<unistd.h> void _exit(int status);和exit比较一下,exit()函数定义在stdlib.h中,而_exit()定义在unistd.h中,从名字上看,stdlib.h似乎比unistd.h高级一点,那么,它们之间到底有什么区别呢?让我们先来看流程图,通过下图,我们会对这两个系统调用的执行过程产生一个较为直观的认识。
从图中可以看出,_exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。
exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的“清理I/O缓冲”一项。
在Linux 的标准函数库中,有一套称作“高级I/O”的函数,我们熟知的printf()、fopen()、fread()、fwrite()都在此列,它们也被称作“缓冲I/O(buffered I/O)”,其特征是对应每一个打开的文件,在内存中都有一片缓冲区,每次读文件时,会多读出若干条记录,这样下次读文件时就可以直接从内存的缓冲区中读取,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足了一定的条件(达到一定数量,或遇到特定字符,如换行符\n和文件结束符EOF),再将缓冲区中的内容一次性写入文件,这样就大大增加了文件读写的速度,但也为我们编程带来了一点点麻烦。如果有一些数据,我们认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时我们用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失,反之,如果想保证数据的完整性,就一定要使用exit()函数。
实例A:
/* exit2.c */#include<stdlib.h>main(){printf("output begin\n");printf("content in buffer");exit(0);}
编译并运行:
$gcc exit2.c -o exit2$./exit2output begincontent in buffer实例B:
/* _exit1.c */#include<unistd.h>main(){printf("output begin\n");printf("content in buffer");_exit(0);}
编译并运行:
$gcc _exit1.c -o _exit1$./_exit1output begin
(13) return函数与exit函数的总结
通常情况:exit(0)表示程序正常,exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件。用Error lookup可以查看。
exit()结束当前进程/当前程序/,在整个程序中,只要调用exit就结束(当前进程或者在main时候为整个程序)
return()是当前函数返回,当然如果是在主函数main, 自然也就结束当前进程了,如果不是,那就是退回上一层调用。在多个进程时.如果有时要检测上进程是否正常退出的.就要用到上个进程的返回值,依次类推。
1) 进程的开始:
C程序是从main函数开始执行, 原型如下:
int main(int argc, char *argv[]);
通常main的返回值是int型, 正确返回0.
如果main的返回值为void或者无, 某些编译器会给出警告, 此时main的返回值通常是0。
关于main的命令行参数不做过多解释, 以下面的程序展示一下:
#include <stdio.h>int main(int argc, char *argv[]){ int i; for (i = 0; i < argc; i++) printf("argv[%d]: %s\n", i, argv[i]); return 0;}
2) 进程终止:
C程序的终止分为两种: 正常终止和异常终止。
正常终止分为: return, exit, _exit, _Exit, pthreade_exit。
异常中指分为: abort, SIGNAL, 线程响应取消。
主要说一下正常终止的前4种, 即exit系列函数.
#include <stdlib.h> /* ISO C */void exit(int status);void _Exit(int status);#include <unistd.h> /* POSIX */void _exit(int status);以上3个函数的区别是:
exit()(或return 0)会调用终止处理程序和用户空间的标准I/O清理程序(如fclose),_exit和_Exit不调用而直接由内核接管进行清理。因此,在main函数中exit(0)等价于return 0.
3) atexit终止处理程序:
ISO C规定, 一个进程最对可登记32个终止处理函数, 这些函数由exit按登记相反的顺序自动调用. 如果同一函数登记多次, 也会被调用多次.
原型如下:
#include <stdlib.h>int atexit(void (*func)(void));其中参数是一个函数指针,指向终止处理函数,该函数无参无返回值。atexit函数本身成功调用后返回0。
以下面的程序为例:
#include <stdlib.h>static void myexit1(){ printf("first exit handler\n");}static void myexit2(){ printf("second exit handler\n");}int main(){ if (atexit(my_exit2) != 0) printf("can't register my_exit2\n"); if (atexit(my_exit1) != 0) printf("can't register my_exit1\n"); if (atexit(my_exit1) != 0) printf("can't register my_exit1\n"); printf("main is done\n"); return 0;}运行结果:
$ ./a.outmain is donefirst exit handlerfirst exit handlersecond exit handler
注意上面的结果,可以发现这些函数由exit按登记相反的顺序自动调用(先myexit1后myexit2)。如果同一函数登记多次,也会被调用多次(如这里的myexit1)。而这些处理函数都是在程序推出的时候利用atexit函数调用了这些处理函数。但是如果用_exit()退出 程序,则它不关闭任何文件,不清除任何缓冲器、也不调用任何终止函数!
(14) return函数exit函数区别
1) exit用于在程序运行的过程中随时结束程序,exit的参数是返回给OS的。main函数结束时也会隐式地调用 exit函数。exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流、关闭所有打开的流并且 关闭通过标准I/O函数tmpfile()创建的临时文件。exit是结束一个进程,它将删除进程使用的内存空间,同时把错误信息返回父进程,而 return是返回函数值并退出函数
2) return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
3) exit函数是退出应用程序,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息。
4) 和机器和操作系统有关一般是 0 为正常退出 非0 为非正常退出
5) void exit(int status);
6) atexit() 函数的参数是一个函数指针,函数指针指向一个没有参数也没有返回值的函数。atexit()的函数原型是:int atexit (void (*)(void));在一个程序中最多可以用atexit()注册32个处理函数,这些处理函数的调用顺序与其 注册的顺序相反,也即最先注册的最后调 用,最后注册的最先调用。
一般程序执行到 main() 的结束就完成了, 如果想在程序结束时做一些事情, 可以尝试着用这个函数。
example:
#include #include void f1(void){ printf("exit f1\n");}void f2(void){ printf("exit f2\n");}int main(){ atexit(f1); atexit(f2); printf("exit main\n"); return 0;}
- linux进程及进程控制
- linux进程及进程控制
- linux进程及进程控制
- linux进程及进程控制
- LINUX进程控制
- Linux 进程控制
- linux进程控制
- linux进程控制
- Linux的进程控制
- linux进程控制
- LINUX进程控制 笔记
- linux进程控制
- linux 进程控制说明
- linux 进程控制入门
- Linux进程控制
- Linux进程控制
- LInux进程控制
- Linux进程控制
- 文件 File
- @1224工作日志
- Servlet之监听器
- 欢迎使用CSDN-markdown编辑器
- C#序列化匿名对象为XML
- Linux进程控制
- Creating and Using a Temporary File
- 一起talk C栗子吧(第八十二回:C语言实例--简单圣诞树)
- Android SDK开发 -- TitleBar重构 (代理模式的使用)
- /etc/ld.so.conf文件
- Hdu 2037 之解题报告
- Ubuntu下为Firefox安装Adobe Flash Player
- Exception 'yii\db\Exception' with message 'SQLSTATE[HY000] [2002] No such file or directory’错误的解决方法
- 通过jquery实现页面的动画效果