企业网站租服务器,做外贸那里发广告网站,中山有网站建设公司吗,深圳seo网络推广公司目录 进程创建fork再介绍写时拷贝 进程终止退出码退出方式 进程等待获取子进程statuswaitwaitpid 在前两篇进程概念中#xff0c;对进程进行了介绍#xff0c;进行了初步认识#xff0c;也认识到了与之相关联的进程地址空间#xff1b;本文则对进程的生命周期——创建… 目录 进程创建fork再介绍写时拷贝 进程终止退出码退出方式 进程等待获取子进程statuswaitwaitpid 在前两篇进程概念中对进程进行了介绍进行了初步认识也认识到了与之相关联的进程地址空间本文则对进程的生命周期——创建终止回收进行介绍加深对进程的学习。如果对进程相关概念不了解的可以参考——进程概念一进程概念二
进程创建
进程创建中fork函数是不可或缺的。
fork再介绍
在Linux中fork函数是非常重要的函数它从已存在进程中创建一个新进程。新进程为子进程而原进程为父进程
FORK(2) FORK(2)NAMEfork - create a child processSYNOPSIS#include sys/types.h#include unistd.hpid_t fork(void);fork为系统级调用类型为pid_t本质为int返回值自进程中返回0父进程返回子进程id出错返回-1
进程调用fork当控制转移到内核中的fork代码后内核做以下工作 分配新的内存块和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork返回开始调度器调度 当一个进程调用fork之后就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程看如下程序
#includestdio.h
#includestdlib.h
#includeunistd.h
#includesys/types.hint main()
{printf(before fork pid:%d\n,getpid());pid_t pid fork();printf(after fork pid:%d\n,getpid());
}这里看到了三行输出一行before两行after。进程35244先打印before消息然后它又打印after。另一个after 消息有35245打印的。注意到进程35245没有打印before为什么呢如下图所示 所以fork之前父进程独立执行fork之后父子两个执行流分别执行。
注意fork之后父子进程谁先执行完全由调度器决定。 fork函数返回值 子进程返回0父进程返回的是子进程的pid
写时拷贝
进程具有独立性父子进程理论上都具有独立的代码和数据但是实际中在不修改数据时父子进程看到的是同一份代码和数据只有在子进程修改数据时才会通过写时拷贝机制存放这份子进程独有的数据如下
int g_val100;//进程创建
int main()
{pid_t pidfork();if(pid0)//父进程{int num4;while(num--){printf(I am father pid: %d g_val:%d g_val_addr: %p\n,getpid(),g_val,g_val);sleep(1);}}else if(pid0)//子进程{int num3;while (num--){printf(I am child ppid: %d pid:%d g_val: %d g_val_addr: %p\n,getppid(),getpid(),g_val,g_val);sleep(1);if(num1){g_val200;printf(child g_val change to %d\n,g_val);}}}else{perror(fork\n);}return 0;
}可以看到当子进程对g_val进行修改后父子进程的g_val不再是同一个值这就是通过写时拷贝实现的。
不解地址为什么还是相同的原因请参考——进程地址空间
通常父子代码共享父子再不写入时数据也是共享的当任意一方试图写入便以写时拷贝的方式各自一份副本。具体见下图: fork常规用法 一个父进程希望复制自己使父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子进程来处理请求。一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数。
进程终止
事物有始有终一个进程既然被创建那么就会有终止的时候例如父进程创建子进程去执行相关任务但是父子进程是独立的父进程如何知道子进程有没有完成下派的任务呢这就需要进程退出码了。
退出码
进程退出场景
代码运行完毕结果正确代码运行完毕结果不正确代码异常终止
进程退出有以上三种状况而这三种状况中前两种的退出码是有意义的可参考的。代码异常终止一般是收到信号程序并没有安设想的方式退出退出码也就没有参考意义了。 正常退出 例如
int main()
{return 99;
}查看退出码
使用指令echo $?可以查看上一次程序的退出码 可以看到在运行程序后查看退出码确实是myproc的退出码但是再次查看时退出码却是0这是因为指令本身也是可执行程序这个退出码0是指令的退出码。 异常终止 异常终止一般是收到了信号如直接使用kill -9指令直接终止该进程
int main()
{while(1){printf(pid:%d\n,getpid());sleep(1);}return 99;
}可以看到该退出码并不是我们设想的所以异常退出的退出码是没有意义的。
而不同的退出码一般会被定义为不同的问题在C语言中可以使用strerror查看在C语言中退出码0代表成功这也是为什么在main函数中总是return 0的原因而1到133都对应着不同的问题。
int main()
{for(int i0;i150;i){printf(%d :%s\n,i,strerror(i));}return 0;
}退出方式 正常终止 从main返回调用exit_exit main返回 退出码是给父进程看的可以判断子进程是否成功运行在C/C中。main函数是程序的开始所以main函数返回会使用return告诉父进程OS子进程的完成情况。 exit返回 NAMEexit - cause normal process terminationSYNOPSIS#include stdlib.hvoid exit(int status);exit为库函数用于退出当前进程同时会将退出码设置。如
int main()
{exit(1);printf(can u see me?\n);return 0;
} 可以看到退出码确实为1而且并没有执行exit后面的代码所以exit的作用就是直接退出当前进程并设置退出码 _exit NAME_exit, _Exit - terminate the calling processSYNOPSIS#include unistd.hvoid _exit(int status);_exit为系统级调用实际上库函数exit就是对_exit的封装_exit的用法和exit一致那有什么区别呢看下面的例子 可以看到_exit的代码中u can see me这句话并没有打印出来。
实际上exit最后也会调用exit但在调用exit之前还做了其他工作
_exit 就只是单纯的退出程序而exit在退出之前还会做一些事比如冲刷缓冲区再调用_exit 所以推荐使用exit
进程等待
为什么要进行进程等待谁在等待
首先需要明确如果父进程不对子进程进行回收子进程结束后就会变成僵尸进程有可能导致内存泄漏。
进程等待必要性
之前讲过子进程退出父进程如果不管不顾就可能造成‘僵尸进程’的问题进而造成内存泄漏。另外进程一旦变成僵尸状态那就刀枪不入“杀人不眨眼”的kill -9 也无能为力因为谁也没有办法杀死一个已经死去的进程。最后父进程派给子进程的任务完成的如何我们需要知道。如子进程运行完成结果对还是不对或者是否正常退出。父进程通过进程等待的方式回收子进程资源获取子进程退出信息
所以当子进程创建出来后父进程需要进行等待目的是获取进程退出码次要和释放子进程的资源主要避免僵尸问题。
获取子进程status
系统提供的父进程等待函数有两个wait和waitpid后者可操作项更多比较常用 如何获取进程退出码的方式就是通过第二个参数status所以在介绍wait waitpid前先了解父进程是如何获取子进程退出码的。
wait和waitpid都有一个status参数该参数是一个输出型参数由操作系统填充。如果传递NULL表示不关心子进程的退出状态信息。 否则操作系统会根据该参数将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待可以当作位图来看待具体细节如下图只研究status低16比特位 只研究低16位比特位从0号开始到7号为信号部分8到15才是具体的进程退出码。位图结构 如何获取退出码呢虽然status是整型但使用时却是当作位图来使用的所以需要进行一些位操作而系统也已经提供了对应的宏解析status。
WEXITSTATUS(status);//查看进程退出码
WIFEXITED(status);//检测进程是否收到信号退出若正常退出则为真否则为假WEXITSTATUS就是一个宏
#define __WEXITSTATUS(status) (((status) 0xff00) 8)注意WIFEXITED仅用来判断是否收到信号而退出进程不解析收到几号信号若收到信号退出则为假正常退出为真。
WTERMSIG才是用来查看收到几号信号的。
WTERMSIG(status)宏定义
#define __WTERMSIG(status) ((status) 0x7f)wait
函数原型
#includesys/types.h
#includesys/wait.h
pid_t wait(int*status);返回值 成功返回被等待进程pid失败返回-1。 参数 输出型参数获取子进程退出状态若不关心则可以设置成为NULL
int main()
{//WNOHANG;//宏为1pid_t pidfork();if(pid0){// childint cnt 5;while (cnt--){printf(I am child pid:%d ppid:%d\n, getpid(), getppid());sleep(1);if (cnt 0){printf(child over\n);}}exit(99);}pid_t ridwait(NULL);printf(father wait success rid:%d\n,rid);//注意父子进程本应是各自执行自己的代码的但是wait会阻塞父进程直至回收才会执行后面的代码。return 0;
}注意使用wait回收子进程父进程会阻塞在wait处直到回收成功才继续执行后续代码。
waitpid
函数原型
#includesys/types.h
#includesys/wait.h
pid_ t waitpid(pid_t pid, int *status, int options);返回值
当正常返回的时候waitpid返回收集到的子进程的进程ID如果设置了选项WNOHANG而调用中waitpid发现没有已退出的子进程可收集则返回0如果调用中出错,则返回-1这时errno会被设置成相应的值以指示错误所在
参数 pid Pid-1等待任一个子进程。与wait等效。 Pid0等待其进程ID与pid相等的子进程。 status: WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出 WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码 WTERMSIG(status)提取子进程收到的信号。 options: 0将option设为0等待方式和wait一样为阻塞等待。 WNOHANG: 若pid指定的子进程没有结束则waitpid()函数返回0不予以等待。若正常结束则返回该子进 程的ID。
这也是另一种等待方式——非阻塞轮询
第三个参数options提供不同的等待方式一般为0阻塞等待或者WNOHANG非阻塞轮询——父进程可以执行自己的代码。 阻塞等待 waitpid(pid,status,0); int main()
{//WNOHANG;//宏为1pid_t pidfork();if(pid0){// childint cnt 3;while (cnt--){printf(I am child pid:%d ppid:%d\n, getpid(), getppid());sleep(1);if (cnt 0){printf(child over\n);}}exit(99);}// fatherint status0;pid_t ridwaitpid(pid,status,0);int cnt 5;while (cnt--){printf(I am father pid:%d\n, getpid());sleep(1);}if(rid0 WIFEXITED(status))//若为正常终止子进程返回的状态则为真。查看进程是否是正常退出,不能看出是几号信号{printf(wait success:%d exit_code:%d\n,rid,WEXITSTATUS(status));}else{printf(kill by sig:%d\n, WTERMSIG(status));//((status) 0x7f)}return 0;
}非阻塞轮询 waitpid(pid, status, WNOHANG) int main()
{pid_t pid fork();if (pid 0){// childint cnt 3;while (cnt){printf(I am child pid:%d ppid:%d\n, getpid(), getppid());sleep(1);cnt--;if (cnt 0){printf(child over\n);}}exit(1);}int status0;pid_t rid;while(1){rid waitpid(pid, status, WNOHANG);//非阻塞轮询if(rid0){printf(father do other things...\n);sleep(1);}else if (rid 0){if (WIFEXITED(status)){printf(wait success:%d exit_code:%d\n, rid, WEXITSTATUS(status));}else{printf(kill by sig:%d\n, WTERMSIG(status));//((status) 0x7f)}break;}else{printf(waitpid error...\n);break;}}printf(father over\n);//sleep(5);return 0;
}以非阻塞轮询的方式回收可以看到父进程每隔一段时间就去查看子进程是否退出若未退出则父进程先去忙自己的事情过一段时间再来查看直到子进程退出后读取子进程的退出信息。
除了以上正常方式退出当然也有可能收到信号而退出。如kill -9指令杀掉一个进程。 以上就是进程创建终止等待回收的相关问题。