再次理解进程
进程:内核的相关管理数据结构(task_struct(进程控制块PCB),mm_struct(地址空间),页表) + 代码和数据
那么如何理解进程具有独立性?
我们之前已经学习过进程控制块啊,地址空间啊,页表啊,他们不都是随着进程的创建而被创建,所以每个进程都有独立的一份这三个结构,那代码是共享的,数据是经过写时拷贝实现进程之间的独立的。
fork函数的返回
fork子进程返回0
fork父进程返回是子进程的pid
为什么父进程返回的是子进程的PID,给子进程返回的是0!!!
首先我们为了父进程方便对子进程进行标识,进而进行管理。
退出码?退出信号?
我们在写c/c++main函数的时候我们是不是往往都会写一个return 0来结束程序的运行!
为什么?
我们在命令行中使用echo $?内键命令
会发现打印出我们刚刚通过./运行的程序的main函数的返回值,我们把return后面设置为什么就会为我们返回什么!
我们知道,内键命令打印的都是bash内部的变量数据。
所以父进程bash获取到的是,最近一个子进程退出的退出码
告诉关心方(父进程),我任务完成的怎么样了!
实际上0代表成功,非0代表失败。
这是程序的退出码
1、2、3……不同的非零值,一方面表示失败,一方面表示失败的原因,
它们都有对应的错误描述,可以以字符串的形式打印出来!
父进程bash为什么要得到子进程的退出码呢?
这是要知道子进程的退出情况(成功,失败,失败的原因是什么?)
这些不都是为了让我们用户看到吗,为我们用户负责!!!
退出码不仅可以使用默认的,也可以进行自定义!
进程的终止
1.进程的终止在做什么?
释放曾经的代码和数据所占用的空间
释放内核数据结构(task_struct)-->我们知道子进程结束,父进程一直在跑但什么事情都不做,子进程进入Z(僵尸态),此时就是task_struct仍然存在的原因,这样就会造成内存泄露的问题。
那么进程的终止不就是回收资源,防止内存泄露吗!
2.进程终止的三种情况?
a.代码跑完,结果正确
可以通过进程的退出码决定!!!
b.代码跑完,结果不正确
同样可以通过进程的退出码决定!!!
c.代码执行时,出现了异常,提前退出了
两个原因,一个系统检测到错误,异常退出,一个我们人为自定义!0退出码
异常退出?(本质是因为进程收到了OS发给进程的信号!)
vs编程运行的时候,崩溃了 --- 操作系统发现你的进程做了不该做的事情,OS杀了进程,一旦出现异常,退出码没有意义了!!!
我为什么出现了异常?原因是?进程出异常,本质是因为进程收到了OS发给进程的信号!
我们可以写一段野指针的操作代码来观察现象
我们发现运行时是出现了段错误,OS提前终止进程。
当我们启动一个进程也可以用kill -11 pid来终止一个进程,其实这就是我们手动发给进程一个信号,让进程异常终止,报的也就是上面的段错误,所以进程出异常,本质是因为进程收到了OS发给进程的信号!
我们可以看进程退出的时候,退出信号是多少,就可以判断我的进程为什么异常了!!!
总结:
所以我们面对程序出现错误,两步判断就可
- 先确认是否是异常
- 不是异常就一定是代码跑完了,看退出码就行。
最后,我们说衡量一个进程退出,我们只需要两个数字退出码,退出信号!
这些都是要让父进程bash知道。
其实退出码,退出信号就是在我们的task_struct源代码中的两个个变量
inr exit_code,int exit_signal
3.如何终止?
a.main函数return表示进程终止(非main函数return,函数结束)
b.代码调用exit函数(注意:我们代码的任意位置调用exit,都表示进程退出)
c._exit() --- system call 系统调用接口
exit与_exit都可以让进程退出,那么它俩有什么区别呢?
exit vs _exit:exit会在进程退出的时候,充刷缓冲区,_exit不会,其实这是因为exit是c语言的库函数,它的底层还会调用_exit这个系统调用接口,来操作操作系统,所以充刷缓冲区应该在exit函数用户层的操作。
注意:目前我们说的缓冲区不是内核缓冲区!!!
4.进程等待?
是什么?
结论:任何子进程,在退出的情况下,一般必须要被父进程进行等待。进程在退出的时候,如果父进程不管不顾,退出进程,状态Z(僵尸态),造成内存泄漏。
为什么?
1.父进程通过等待,解决子进程退出的僵尸问题,,回收系统资源(一定要考虑的)
2.获取子进程的退出信息,知道子进程是因为什么原因退出的(可选的功能)
怎么办?
父进程可以通过调用wait/waitpid函数来做到上面两个事情
pid_t wait(int* status)
如果子进程没有退出,父进程其实一直在进行阻塞等待
参数部分,等待父进程中,等任意一个子进程退出。
返回值,等待成功时,子进程的pid。
进程阻塞等待?
子进程本身就是软件,父进程本质是在等待某种软件条件就绪,你如何理解阻塞等待子进程呢?
status是什么呢?
这里就要讲讲waitpid了
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。
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
- wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能简单的当作整形来看待,可以当作位图来看待。
所以status其实是个输出型参数,我们学过scanf函数,我们从键盘输入数据输出到一个变量中,status就类似把进程退出码与进程退出信号输出到status中,以便父进程拿到子进程的退出信息。
那status是如何存储进程退出码与进程退出信号两个数据呢?
其实status是使用了32位比特位来存储,用status的后7位来存储终止信号,第8位存储core dump(后面讲),再往后8位存储的是进程退出信息,再往后尚未使用。
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void ChildRun()
{
int *p = NULL;
int cnt = 5;
while(1)
{
printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
sleep(1);
cnt--;
*p = 100;
}
}
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);
// father
//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", status, (status>>8)&0xFF, status & 0x7F);
}
运行结果:
这个就是因为段错误,出现异常,进程异常退出!
进程的非阻塞式等待?
我们已经知道如果子进程没有退出,而父进程在进行执行waitpid进行等待,阻塞等待,
那么这其实就是进程阻塞了,waitpid在等待某种条件发生(子进程退出) --- 》父进程其他事情什么都没干。
waitpid函数的第三个参数就是可以控制为是否阻塞等待,传入0为阻塞等待,传一个宏WNOHANG代表非阻塞等待。
非阻塞等待其实就是代表在你等待
子进程退出时仍可以进行自己要干的事情,边等待边完成任务,---》允许父进程可以做其他事情。
代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
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(1);
}
else {
int status = 0;
pid_t ret = 0;
do
{
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
if (ret == 0) {
printf("child is running\n");
}
sleep(1);
} while (ret == 0);
if (WIFEXITED(status) && ret == pid) {
printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
}
else {
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
执行结果!