pid_t getpid(void)
获取调用进程的pid
pid_t getppid(void)
获取调用进程的父进程pid
它们返回一个类型为pid_t的整数值,在Linux系统上pid_t被定义为int。
pid_t fork(void)
为调用进程创建子进程。
RetVal:在父进程中,该函数的返回值为子进程pid;在子进程中,该函数的返回值为0;对于创建子进程失败的情况,返回-1给父进程。
task_struct
,同时为它创建一份与父进程的进程地址空间和页表几乎相同的副本。可以说,在子进程刚创建出来时,父子进程最大的区别就是pid不同。
子进程创建成功后,被OS添加至任务队列等待调度。
父进程继续执行fork的剩余代码,通过pid判断自己并不是刚刚创建出来的子进程,因此为父进程返回子进程的pid。
子进程被调度后,从上次父进程创建完子进程之后的下一条指令继续执行。在返回时,通过pid判断自己是刚刚创建出来的子进程,因此返回0。
所谓进程退出,就是进行“资源清理和进程善后”。比如:关闭进程打开的文件描述符、向父进程发送本进程退出的信号、“清理”进程相关的数据结构。注:清理的本质就是将这些数据结构归还到操作系统管理的数据结构内存池。
当进程在正常执行完指令,或者执行时发生异常,都要进行退出。
void exit(int status)
在任意函数调用exit()都会终止整个进程。其中,status会以退出码的形式存储下来,等待父进程读取。
注:该函数会刷新进程相关的IO缓冲区到对应文件。
return n
main函数中的return语句会终止整个进程,return n相当于exit(n),返回值即为进程的退出码。
void _exit(int status)
与exit()基本相同,但是调用该函数退出时,进程相关IO缓冲区不会被刷新。
这些终止信号可以由用户自己发送,比如使用kill
命令;也可以由系统发送,比如程序遇到访问野指针、除零等问题时。
退出码是标识进程退出状态的数据,通过退出码可以知道进程是否正常执行,执行结果是否正确。
用户可以自定义退出码,也可以使用C库中提供的。
echo $?
查看最近一次命令的退出码对于需要建立子进程执行的命令(比如./xxx),命令退出码就是进程退出码;
而对于shell内建命令(比如echo、clear),命令退出码是由shell制定的,其中0表示成功结束。
所谓内建命令,就是内置到命令行解释器bash里的函数。
相对地,外部命令本质是外部程序代码,没有内置到命令行解释器中,因此需要bash创建子进程并进行程序替换去执行这些命令。
当进程由于某种原因终止时,内核并不会立即将它清除,而是让它停留在"Z"状态(Zombie),这种Z状态的进程被称为僵尸进程。
僵尸进程会一直占用系统资源,直到被父进程等待并回收,或者在父进程退出后由1号init进程领养并适时处理。
最好的避免僵尸进程的方式就是调用系统提供的等待方法等待子进程退出。
pid_t wait(int *status)
其中,status是一个输出型参数,它的低16位用来存储有效信息:
status & 0x7f
得到,或者通过宏函数:
WIFEXITED(status)
:如果进程是正常通过exit或return退出的,则返回真;
WEXITSTATUS(status)
:对于正常退出的进程,返回它的退出码;
(status >> 8) & 0xff
获得,或者通过宏函数:
WIFSIGNALED(status)
:如果进程是由于收到信号而退出的,则返回真;
WTERMSIG(status)
:对于收到信号退出的进程,返回信号编号。
pid_t waitpid(pid_t pid, int *status, int options)
waitpid是升级版的wait函数,它拥有更多的功能。
pid:如果pid>0,表示当前进程等待ID为pid的子进程;如果pid=-1,表示当前进程等待所有子进程中的任意一个。
status:与wait的status完全相同,是一个输出型参数;
options:传0时,表示阻塞等待退出的进程;传WUNTRACED时,表示阻塞等待退出的进程或者收到信号停止运行的进程;传WCONTINUED时,表示阻塞等待退出的进程或者收到信号继续运行的进程;任意一个选项与WNOHANG按位或都表示以非阻塞方式调用waitpid;
RetVal:如果对应options等待成功,则返回子进程pid;如果函数执行出错或父进程不存在没有退出的子进程,则返回-1;如果options有WNOHANG,即非阻塞等待,那么返回0表示本次没有等待到退出的进程。
status的第7位是core dump(核心转储)标志位。当进程因为异常而退出时,可以选择把进程的用户空间数据存储到硬盘上,产生一个名为core的文件,有助于之后的调试纠错。
如果当前操作系统允许发生核心转储,那么该标志位为1。
但是,云服务器一般默认不允许产生core文件,因为core文件可能包含用户密码等信息,不安全;而且core文件一般较大,如果进程重复发生崩溃,那么可能会产生大量的文件。
ulimit -a
:查看当前系统的资源限制设置情况,该命令可查看当前core文件是否能生成,以及能够生成的最大core文件大小
ulimit -c
:修改core文件的最大生成大小
进程的退出码和导致进程退出的信号保存在task_struct
中:
由于子进程退出时处于僵尸状态,资源没有被清理,所以task_struct
中的内容可以被父进程调用wait
函数读取到。
当进程因为等待某个事件发生而停止执行其它指令,这种状态称为“阻塞状态”。
阻塞状态下的进程如果放在“运行队列”,无疑是白白浪费时间片,因此操作系统维护了一个“等待队列”。
如果进程调用方法等待某个事件,发现它没有发生便立即返回继续向下执行其它指令,这种称为“非阻塞等待”。
非阻塞等待的优势就是可以在等待事件没有发生时继续完成其它工作。对应的,由于一次等待没有结果便返回,因此非阻塞等待往往需要“轮询”的方式频繁地进行等待方法的调用。
所谓进程替换,就是利用内存映射,将当前进程的代码和数据替换成另一个可执行文件的代码和数据。
int execve(const char *filename, char *const argv[], char *const envp[])
key=value
的形式,最后以NULL结尾。至此,目标可执行文件被完全加载到当前进程,execve在其中起到了加载器的作用。
根据进程替换的原理,execve并没有创建新的进程,而是在当前进程地址空间的基础上通过替换,使得新的程序在当前进程中运行。
因此execve通常与fork搭配使用:
父进程通过fork创建子进程,将子进程替换为另一个可执行文件(该可执行文件可以由任意语言编写而成)。
Linux在系统调用execve
的基础上又封装了一批进程替换的接口:
1、int execl(const char *path, const char *arg, ...);
2、int execlp(const char *file, const char *arg, ...);
3、int execle(const char *path, const char *arg, ..., char * const envp[]);
4、int execv(const char *path, char *const argv[]);
5、int execvp(const char *file, char *const argv[]);
6、int execvpe(const char *file, char *const argv[], char *const envp[]);
函数名中带’l’表示以可变参数列表的形式传命令行参数;
函数名中带’v’表示以数组的形式传命令行参数;
函数名中带’p’表示如果不指明文件路径,则利用环境变量PATH中的默认路径查找;
函数名中带’e’表示该接口允许传自定义的环境变量,如果没有’e’则默认传当前进程的环境变量。