赞
踩
在C\C++中,mian()函数一般情况return 0,而这个“0”就是我们将要说的进程退出码,它表示进程退出的信息,让父进程来进行读取,从而使在僵尸状态(Z)的子进程被父进程读取退出信息,使其进入死亡状态(X)。
在Linux命令行中可以使用echo $?表示在bash中,最近一次执行完毕时,对应进程的退出码。
[swb@VM-8-12-centos test]$ echo $?
0
第二次第三次echo的时候输出的是前一次echo这个进程的退出码。实际上每个命令执行都是一个进程,都会有一个退出码,这个退出码就是main函数的返回值,每个进程都有一个main函数
不同的错误码对应不同的错误信息,当然我们也可以自己设计一种错误码和错误信息的映射。
在C\C++的库文件中存在strerror函数可以打印出退出码对应不同的错误原因。
1 #include <stdio.h>
2 #include <string.h>
3
4 int main(){
5
6 int i = 0;
7 for(;i < 10; i++){
8 printf("%d : %s\n",i,strerror(i));
9 }
10
11 return 0;
12 }
[swb@test temp_test]$ ./test
0 : Success
1 : Operation not permitted
2 : No such file or directory
3 : No such process
4 : Interrupted system call
5 : Input/output error
6 : No such device or address
7 : Argument list too long
8 : Exec format error
9 : Bad file descriptor
正常终止(可以通过 echo $? 查看进程退出码):
#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值(status有很多宏值,比如EXIT_SUCCESS)
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。
终止进程中内核会做什么操作?
子进程从僵尸状态(Z)到死亡状态(X),会释放子进程的代码和数据,但操作系统可能并不会释放该进程的内核数据结构。(注: 内核数据缓冲池——slab分派器的作用)
异常退出:
ctrl + c,信号终止
父进程执行fork之后,会创建子进程,子进程一般都是要帮助父进程完成某种任务,此时父进程需要通过wait/waitpid等待子进程退出。
等待任意退出的一个子进程
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。
最常用的父进程回收子进程资源,让子进程从Z状态到X状态。
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
返回值:
>0:等待子进程成功,返回子进程pid。
=0:等待失败。
参数:
pid:>0:指定等待哪一个子进程;-1:等待任意进程(和wait函数一样)
status:输出型参数(通过调用该函数,从函数内部拿出来特定的数据),其中的函数内部指的是从子进程的task_struct中拿出子进程退出的退出码,给父进程。
options:0:阻塞等待;非0:非阻塞等待。
子进程的退出有两种情况
通过判断是否收到终止信号(0-6,低7位)来判断进程是否是正常终止,如果是正常终止(低7位全是0),再去看退出状态(8-15,8个比特位)。
位运算获取子进程的退出状态(退出码)和终止信号
如果要获取8-15这8位退出状态(退出码),可以让status右移8位,然后和000…000 1111 1111((status>>8)&0xFF)与一下(因为status是32位的,右移8位后,把前面的24位置0);
如果要获取0-6这7位终止信号,可以让status&0x7F(只需要获取低7位就行);
如果正常退出,退出信号都是0;如果异常退出,退出信号就不是0,此时前面的退出状态也就没有意义。
C语言中宏定义获取子进程的退出状态(退出码)和判断是否收到终止信号
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
在linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1。
进程调用fork(),当控制转移到内核中的fork代码后,内核将做:
fork()函数执行之前父进程独立执行,fork()函数之后,父子进程分别执行(父子共享所有代码)。
子进程执行的后续代码并不等于共享的所有代码,子进程只能从fork()函数之后开始执行(注:与CPU中的程序计数器也就是eip或pc指针有关)。
fork()函数之后,操作系统做了什么?
因为进程具有独立性,所以在fork()之后,创建子进程的内核数据结构(struct task_struct、struct mm_struct和页表),以及继承了父进程的代码,用写时拷贝的方式将父进程的数据进行共享或独立。
fork()写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
父进程和子进程的虚拟地址空间以及页表都是各自有一份的,但是对应数据虚拟地址映射到的物理地址是同一个。
默认所有的数据都是只读的,如果有子进程想要写某个数据(页表项100),会发生写时拷贝,就会发生缺页中断,先拷贝一份数据到新的物理地址空间,然后修改子进程页表的映射关系,让子进程对应数据的虚拟地址映射到新的物理地址,然后中断结束,让子进程来修改数据。
fork常规用法
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。