[Linux]进程控制详解
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)
{
// child
int cnt = 5;
while (cnt)
{
printf("I am child,pid: %d,ppid: %d\n", getpid(), getppid());
cnt--;
sleep(1);
}
printf("马上变僵尸\n");
exit(0);
}
sleep(10);
// father
pid_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)
{
// child
int cnt = 5;
while (cnt)
{
printf("I am child,pid: %d,ppid: %d\n", getpid(), getppid());
cnt--;
sleep(1);
}
sleep(12);
exit(111);
}
// father
int 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;
}
当子进程还在运行的时候,父进程不是傻傻的等,等待的时候可以去完成别的工作。