一、进程创建
1.1 fork函数介绍
在命令行下我们可以通过 ./ + exe文件 来创建一个进程,通过fork函数,我们可以通过代码的形式从一个进程中创建一个进程,新进程为子进程,原进程为父进程,子进程在创建时,会与父进程共享下面的代码与数据,当数据被修改时,会采用写实拷贝的方式保证进程间的独立性。
//头文件
#include<unistd.h>
//函数
pid_t fork()
//返回值
#子进程中返回0
#父进程返回子进程id
#出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
fork函数常规用法:
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请 求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
fork函数失败原因:
- 系统中有太多的进程
- 实际用户的进程数超过了限制
1.2 使用举例
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
//父进程
printf("I am parent process ppid:%d pid:%d\n",getppid(),getpid());
//创建子进程
pid_t id=fork();
//根据id的值分流,使父子进程执行不同的代码
if(id == -1)
{
exit(-1);
}
else if(id == 0)
{
//子进程
printf("I am child process ppid:%d pid:%d\n",getppid(),getpid());
}
else
{
//父进程
printf("I am parent process ppid:%d pid:%d\n",getppid(),getpid());
}
return 0;
}
二、进程终止
2.1 进程退出场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码没有运行完,发现异常,提前终止
2.2 错误码与信号码
错误码:
- 在main函数中,我们通常会return 0,return -1等等,那这些数字有什么意义吗?
//strerror可以查看错误码信息
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int i=0;
for(i=0;i<255;i++)
{
printf("%d:%s\n",i,strerror(i));
}
return 0;
}
可以看到,不同的数字拥有不同的含义,0表示成功,非0表示错误,当程序执行完后,操作系统会检测到错误,并将错误信息打印出来,反馈给用户,这样就可以通过查看错误码来确定程序终止的情况,例如:
ps:操作系统中存在一个变量,名字就叫 ?,它保存了最近一次进程的错误码,可以通过
echo $? 查看
信号码:
- 模拟野指针异常:
int main()
{
int* p=NULL;
*p=1;
retrun 0;
}
- 通过kill -11 给进程发信号
通过上述两个例子可以看出,异常终止也是因为OS给进程发了信号,让进程终止的
可以通过kill -l 查看所有信号
总结:
确认进程终止情况的方法:
1.先判断是否是异常导致
2.是异常通过查看信号码确定异常信息
3.不是异常直接看错误码确定错误信息即可
三、进程等待
3.1 进程等待的必要性
- 子进程退出后,若父进程不进行管理,就会造成僵尸进程,会导致内存泄露问题
- 父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
3.2 进程等待方法
3.2.1 wait
#include<sys/types.h>
#include<sys/wait.h>
//等待任一个子进程
pid_t wait(int*status);
#返回值:
成功返回被等待进程pid,失败返回-1。
#参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void ChildRun()
{
int cnt = 5;
while(cnt)
{
printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt );
sleep(1);
cnt--;
}
}
int main()
{
printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());
pid_t id = fork();
if(id == 0)
{
// child
ChildRun();
printf("child quit ...\n");
exit(0);
}
// fahter
pid_t rid = wait(NULL);
if(rid>0)
{
printf("wait success pid:%d\n",rid);
}
return 0;
}
3.2.2 waitpid
//头文件:
#include<sys/type.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非零,提取子进程退出码。(查看进程的退出码)
ps:WIFEXITED 与 WEXITSTATUS是两个宏
options:
WNOHANG(非阻塞等待): 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
- pid = 0 ,子进程还没有退出,需要下一次再检测
- pid > 0 , 等待成功了,子进程退出,并且父进程回收成功
- pid < 0 , 等待失败
3.3 获取子进程status
父进程要获取子进程的退出信息最重要的就是错误码与信号码,而进程PCB中会存在两个整形变量,用于保存错误码与信号码信息,操作系统可以根据wait与waitpid中status ,将子进程的信息传递给父进程
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
- 0-7位表示子进程的信号码(可让 status & 0x7F获取)
- 8-15位表示子进程的错误码 (可让 (status >>8) & 0xFF获取 )
通过位运算获取错误码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void ChildRun()
{
int cnt = 5;
while(cnt)
{
printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt );
sleep(1);
cnt--;
}
}
int main()
{
printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());
pid_t id = fork();
if(id == 0)
{
// child
ChildRun();
printf("child quit ...\n");
exit(123);
}
sleep(7);
// fahter
//pid_t rid = wait(NULL);
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
printf("wait success, rid: %d\n", rid);
}
else
{
printf("wait failed !\n");
}
sleep(3);
printf("father quit, status: %d, child quit code : %d, child quit signal: %d\n", statu s, (status>>8)&0xFF, status & 0x7F);
}
通过宏获取错误码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void ChildRun()
{
int cnt = 5;
while(cnt)
{
printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
sleep(1);
cnt--;
}
}
int main()
{
printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());
pid_t id = fork();
if(id == 0)
{
// child
ChildRun();
printf("child quit ...\n");
exit(123);
}
sleep(7);
// fahter
//pid_t rid = wait(NULL);
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
//判断子进程是否是因为异常退出
if(WIFEXITED(status))
{
printf("wait success, child exit code:%d\n", WEXITSTATUS(status));//获取子进程退出码:123
}
}
else
{
printf("child process quit unnormal!\n");
}
}
else
{
printf("wait failed !\n");
}
sleep(3);
printf("father quit, status: %d, child quit code : %d, child quit signal: %d\n", status, (status>>8)&0xFF, status & 0x7F);
}
3.4 非阻塞等待
上述例子都是子进程正常退出的情况,属于阻塞等待,若子进程陷入死循环永远不退出,那么父进程就会一直待在waitpid函数体中,等待子进程退出,这段时间父进程什么事情也不能做,若给waitpid函数的options参数传入WNOHANG的话,此时等待就会变成非阻塞等待,父进程在等待子进程退出期间也可以做相关操作
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void DoanotherThing()
{
printf("I am doing anotherthing\n");
}
void ChildRun()
{
int cnt = 5;
while(cnt)
{
printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
sleep(1);
cnt--;
}
}
int main()
{
printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());
pid_t id = fork();
if(id == 0)
{
// child
ChildRun();
printf("child quit ...\n");
exit(123);
}
// fahter
while(1)
{
int status = 0;
pid_t rid = waitpid(id, &status, WNOHANG);
if(rid==0)
{
usleep(10000);
printf("child is running ,check next time!\n");
DoanotherThing();
}
else if(rid > 0)
{
//判断子进程是否是因为异常退出
if(WIFEXITED(status))
{
printf("wait success, child exit code:%d\n", WEXITSTATUS(status));//获取子进程退出码:123
}
else
{
printf("child process quit unnormal!\n");
}
break;
}
else
{
printf("wait failed !\n");
break;
}
}
}