赞
踩
虽然通过标题,我们就轻易的知道了这三个函数的作用,可是,你真的了解这几个函数码?下面让我们来看看这三个函数到底有什么!
首先,我们来看一看fork()的函数声明:
#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1
可以看到十分简单,既然如此,我们便来理解一下fork()之后的过程:
1.内核分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表当中
4.fork返回,开始调度器调度
看完了过程,脑海中是否有了一些大致的过程?可有了过程,是否又有了一些新的疑惑?既然子进程继承了父进程的代码,犹如又运行了一份代码,那子进程到底是从头开始,还是从fork()处开始呢?如果从fork()开始,那一分被“截断”了的代码又是如何保证正常运行的呢?
先回答第一问答案:fork()处开始有运行,下面看代码结果
- #include<stdio.h>
- #include<unistd.h>
- #include<stdlib.h>
- int main()
- {
- pid_t pid;
- printf("马上开始创建另一个进程\n");
- pid=fork();
- if(pid==-1)
- {
- perror("fork()");
- exit(1);
- }
- printf("我是进程,我的pid是:%d,我的ppid是:%d,我的fork返回值是:%d\n",
- getpid(),getppid(),pid);
- return 0;
- }
结果:
可以看到,只有三行,说明了子进程是从fork()开始执行的,不过需要注意的是, fork()之后,谁先执行,完全是由调度器决定的!
回答第二问答案:父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。如图:
这幅图便很好的解释了第二问。既然数据共享,那么就算代码发生了“截断”,子进程也能访问到“原来”的数据了。
这个函数一看,是不是有些熟悉,没错,这个函数出现在了上面当fork()返回值为-1的情况下,至于用法,我们先看声明:
#include <stdlib.h>
void exit(int status);
看完了声明,各位再等一等,我还想介绍一个函数, 名叫_exit(),这个函数的功能也是退出进程,不过真的是仅仅退出进程,缓冲区的什么的,全都不管了。各位可能会好奇我介绍这个函数干嘛呢。原因一,各位不要觉得这个函数和exit()差不多长得一样,就当相同功能的函数用了,最重要的原因二在于,_exit()为exit()的基础函数,exit()是_exit()的封装函数,一个是爹,一个是儿子。
那么既然exit()是_exit()的封装函数,具体是如何封装的呢?
1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit
至此,一个exit()函数才真正的展开了,再回到开头的使用方法,其实用法很简单,就是在参数列表,就是括号里输入你想输入的错误码即可退出进程并返回错误码了。
不过这听起来这使用方法和return好像没啥区别,但其实执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
对于进程等待,各位可能有些不了解应用场景,觉得必要性不大,但放在压轴的函数,怎么可能必要性不大!下面便是应用的场景:
1.当创建的子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
2.进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经退出的进程。
3.最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
看到这么多条,就算不仔细看,怎么说也知道必要性了吧!等待进程就像老板等待员工提交工作情况一样,老板要是不管不顾的话,严重性不必我来说了吧!
而对于等待进程,其实有着两个函数,一个是wait(),一个是waitpid()。我都来介绍一下,不过重点放在waitpid()。注意,两个是同级函数,是”兄弟“。
wait() 函数
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功等待到结束进程返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
waitpid()函数
#include<sys/types.h>
#include<sys/wait.h>pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则 返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
看到这么多的文字,是不是觉得很复杂,我们来提炼一下功能:
1.如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源, 获得子进程退出信息。
2.如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
3.如果不存在该子进程,则立即出错返回。
总体功能如上,如果你想实现更多样化的的功能,则可以详细看看定义。下面我们来看一下代码使用。
- #include <sys/wait.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- int main( void )
-
- {
- pid_t pid;
- if ( (pid=fork()) == -1 )
- perror("fork"),exit(1);
- if ( pid == 0 ){
- sleep(20);
- exit(10);
- }
- else {
- int st;
- int ret = wait(&st);
- if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
- printf("child exit code:%d\n", (st>>8)&0XFF);//*注意点,稍后讲
- }
- else if( ret > 0 ) { // 异常退出
- printf("sig code : %d\n", st&0X7F );//*注意点
- }
- }
- }
- int main()
- {
- pid_t pid;
- pid = fork();
- if(pid < 0){
- printf("%s fork error\n",__FUNCTION__);
- return 1;
- }
- else if( pid == 0 ){ //child
- printf("child is run, pid is : %d\n",getpid());
- sleep(5);
- exit(257);
- }
- else{
- int status = 0;
- pid_t ret = waitpid(-1, &status, 0);//*注意点,阻塞式等待,等待5S
- printf("this is test for wait\n");
- if( WIFEXITED(status) && ret == pid ){
- printf("wait child 5s success, child return codeis:%d.\n",
- WEXITSTATUS(status));//宏函数的使用方法
- }
- else{
- printf("wait child failed, return.\n");
- return 1;
- }
- }
- return 0;
- }
仔细看代码,会发现我注释了两个注意点,一个是关于参数status,一个是waipid的第三个参数。
1.参数status
先来讲一讲参数status的使用方式”(st>>8)&0XFF、st&0X7F“,为什么这么奇怪?这是因为stasus作为一个输出型参数,为了携带更多的信息,使用了位图结构,如下图:
位图结构便是使用类型里特定的比特位存储信息, 在参数status中我们只用关心低16位。所以可以看到代码中使用了这种奇怪的方式获取进程退出信息。如果觉得不好用,可以将等待函数换为waitpid(),使用WIFEXITED、WEXITSTATUS这两个宏函数获取退出信息。当然,要是不关心退出信息,定义中也告诉我们了,将status置为NULL指针即可。
2.waitpid()第三个参数
代码中可以看见,第三个参数为0,特意注释了阻塞式等待,即为父进程到了waitpid()这儿就等着子进程运行完了获取结果了,再往下运行。请问,你觉得这合适吗?哪里还有老板一直等员工的!所以waitpid()第三个参数就顺应非阻塞式等待而生,这是wait()所不能及的,使用如下:
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
至于设置 WNOHANG之后发生的事,可以在上文的”定义“中看到,我想要说的是,waitpid()返回0之后,继续运行到子进程死亡时,有两种情况,1.父未死、2.父已经死了,子进程变成孤儿进程。
那么子进程的状态就接收不到了吗?答案是当然不会,第2种情况发生后,init进程(一号进程)会来收养子进程,确保子进程一直有个“爹”。
所以既然一直有爸爸,当子进程死后,就会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。
那么至此,waitpid()函数等待进程的过程已经讲解完毕。至于使用时wait()和waitpid的选择,我倾向于waitpid()。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。