1.创建进程
进程调用fork,当控制转移到内核中的fork代码后,内核做:
● 分配新的内存块和内核数据结构给子进程
● 将父进程部分数据结构内容拷贝至子进程
● 添加子进程到系统进程列表当中
● fork返回,开始调度器调度
这个前面提到过,就不多说了
2.写时拷贝
引言:当程序变成二进制后,所有的变量名都会变成地址。
fork之后代码共享,就是子进程被创建时,会以父进程为模板,子进程和父进程默认拥有一份相同的代码和数据,当然页表中存储的地址也是一样的。
两个问题:
创建子进程时,为什么不直接把父进程的数据给一份?
因为操作系统按需分配,节省空间。
为什么要拷贝?开新空间就好了啊
这里的写:包括了增删查改,不是全被都被覆盖。可能只修改一部分的数据
上图中页表部分括号里有个只读权限,接下来我们分析一下页表
(i)页表
页表不仅仅有虚拟地址和物理地址,物理地址还包括了权限
举例:字符常量无法被修改,就是我们在写入时,由虚拟地址到物理地址的转化中,权限中没有写入权限。
写代码时const的意义:把运行时产生的错误提前到编译时(防御性编程)
3.终止进程
进程退出情况
我们写代码时,最后都会return 0,main函数的返回值,就是进程的退出码
一般0表示成功,非0表示失败。
在进程执行结束之后,如果进程失败了,我们最想知道的是他的错误原因
echo $?
这个可以查看进程的退出码
当然错误码我们是无法理解的,所以就有错误码转化为错误描述
strerror()
我们可以看看linux中的错误码
int main()
{for(int i=0;i<200;i++){printf("%d: %s\n",i,strerror(i));}return 0;
}
函数退出情况
除了进程有退出,函数也是有退出的。
函数也是有返回值的,函数的返回值就是错误码,这个和进程的一样
总结
总结:
任何进程的最终执行情况我们用两个数字表示==(异常)信号编号+进程退出码
如何让进程退出?
1.main函数return
2.exit(退出码)——终止进程,status(进程退出码)
这个进程退出码status会在进程等待中详细的说
4.进程等待
我们提到的父进程如果不等待子进程,就会使子进程变成僵尸进程,会引发内存泄漏
所以父进程通过wait的方式去回收子进程的资源,这个是必须要做的事情。
wait会默认进行阻塞等待,等待任意一个子进程;
返回值大于0,等待成功,等待子进程的pid;返回值小于0,等待失败
int main()
{pid_t id = fork();if (id == 0){// childint cnt = 5;while (cnt){printf("I am child,pid: %d,ppid: %d\n", getpid(), getppid());cnt--;sleep(1);}printf("马上变僵尸\n");exit(0);}sleep(10);// fatherpid_t rid = wait(NULL);if (rid > 0){printf("wait success child_id:%d\n", rid);}return 0;
}
wait比较简单,我们来看看waitpid()
status:局部使用的位图形式
status的8到15位时退出码,0到7时退出信号。
提取status的信号:status&0x7F
这样就把他的前7位的数字取出来了;
提取status的退出码:(status>>8)&0xFF
这样就把他的次低8位的数字取出来了.
当然这种方式太麻烦了,我们有专门的宏来完成这件事
if (WIFEXITED(status)){printf("wait success,rid: %d,status: %d,exit code: %d\n",rid,status,WEXITSTATUS(status));}
非阻塞等待才是重点
这里当options设位0,就是阻塞等待,设为WNOHANG就是非阻塞等待。
非阻塞:当在等待子进程退出的时候,父进程还可以去做别的事情
waitpid的返回值:
● 大于0:系统调用成功
● 等于0:系统调用成功,但子进程还没退出
● 小于0:调用失败
我们用代码来看看非阻塞等待的优势
const static int NUM=3;
typedef void(*func_t)();func_t task[NUM];void printfNAME()
{printf("this is print name\n");
}
void printfNPC()
{printf("this is print npc\n");
}
void printfAGE()
{printf("this is print age\n");
}void InitLog()
{task[0]=printfNAME;task[1]=printfNPC;task[2]=printfAGE;task[3]=NULL;
}
void Exe()
{for(int i=0;task[i];i++){task[i]();}
}
int main()
{InitLog();pid_t id = fork();if (id == 0){// childint cnt = 5;while (cnt){printf("I am child,pid: %d,ppid: %d\n", getpid(), getppid());cnt--;sleep(1);}sleep(12);exit(111);}// fatherint status=0;pid_t rid = waitpid(id,&status,WNOHANG);while(1){if(rid>0){printf("wait sucess,rid: %d,status: %d,exit code: %d\n",rid,status,WEXITSTATUS(status));break;}else if(rid==0){printf("child is running,do other thing\n");//开始任务printf("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");Exe();}else{perror("waitpid");break;}sleep(1);}return 0;
}
当子进程还在运行的时候,父进程不是傻傻的等,等待的时候可以去完成别的工作。