目录
1.创建进程
fork()函数
2.终止进程
exit()函数
3.孤儿进程
4.僵尸进程
5.进程回收
wait函数
waitpid函数
进程是正在执行程序的实例,是操作系统进行资源分配和调度的基本单位。在内核看来,进程是一个实体,内核需要在他们之间共享各种计算机资源。
1.创建进程
创建进程的方式:
系统初始化:启动操作系统时会创建若干个进程
用户请求创建:例如双击图标启动程序
系统调用创建:一个运行的进程可以发出系统调用创建新的进程帮助完成工作
在这里,我们使用的是第三个创建进程的方法。
fork()函数
函数描述:
创建进程,新创建的是当前进程的子进程。
函数原型:
pid_t fork(void);
函数返回值:
成功:
父进程:返回新创建的子进程ID
子进程:返回0
失败:
返回-1,子进程不会被创建
fork函数会创建一个子进程,父进程的内容会复制到子进程的进程空间中,包括父进程的数据段和栈堆段,并且和父进程共享代码段,所以成功后父子进程都会停留在了进程创建函数fork()上,因此,fork()函数在父子进程中都会返回,两个返回值是不同的。
父子进程间遵循读时共享写时复制的原则。现在Linux内核在fork()函数时往往在创建子进程时并不立即复制给父进程的数据段和栈堆段,而是当子进程修改这些数据内容时复制操作才会发生,内核才会给子进程分配进程空间,将父进程的内容复过来,然后继续后面的操作,这样的实现更加合理。对于那些只是为了复制自身完成的一些工作的进程来说,这样做的效率会更高,这也是现代操作系统的一个重要概念——“写时复制”的一个重要体现。
int pid=fork();
if(pid==0){printf("child pid=%d,ppid=%d\n",getpid(),getppid());
}
if(pid>0){printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
}
while(1);
getpid是获取当前进程的进程ID的函数,getppid是获得当前进程的父进程的进程ID。通过打印,可以看到创建了一个子进程。
父进程的局部变量、全局变量、堆区空间是不共享的,父子进程打印变量的地址是相同的(该地址是虚拟地址空间),但是他们指向的物理空间是不同的。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int global_var = 10;void func(int param) {char* str = (char*)malloc(10);int local_var = 20;int pid = fork();if (pid == 0) {// 子进程global_var++;local_var++;sprintf(str, "child");printf("子进程:global_var = %d, local_var = %d, str = %s\n", global_var, local_var, str);} else if (pid > 0) {// 父进程sleep(1);printf("父进程:global_var = %d, local_var = %d, str = %s\n", global_var, local_var, str);}free(str);
}int main() {func(5);return 0;
}
在上述代码中,由于子进程对全局变量、局部变量以及堆区的内容进行了写操作,就会触发“写时复制”的机制,会复制一个副本。所以子进程输出的结果就上面图中的结果,而父进程没有进行写操作,读取到的就是原始数据。而对于str,因为它在堆区上且在子进程修改时触发了写时复制,父进程这边的str
所指向内存空间实际上并未有效写入内容,其内容是不确定的。
父子进程间文件共享,执行fork子进程会获得父进程所有的文件描述符分副本,这些副本的创建类似于dup(),同一文件描述符在父子进程中对应的是相同的文件。
2.终止进程
进程终止的方式:
1.正常退出
1.从main函数返回
2.调用exit()或者_exit(),exit()是库函数,_exit()是系统调用,程序一般不直接调用_exit(),而是调用库函数exit().
2.异常退出
被信号终止
exit()函数
函数描述
结束进程
头文件:
#include<stdlib.h>
函数原型
void exit(int status);
函数参数:
进程的退出状态,0表示正常退出,非0值表示因为异常退出,保存在全局变量$?中,$?保存的是最近一次运行的进程的返回值,返回值有以下三种情况。
1.程序中的main函数运行结束,$>保存main函数的返回值
2.程序运行中调用exit()函数结束运行,$?中保存exit函数的参数
3.程序异常退出$?中保存异常出错的错误号
3.孤儿进程
父进程先于子进程结束,则称子进程为孤儿进程,变成孤儿进程后会有一个专门用于回收的init进程成为它的父进程,称init进程领养孤儿进程。
int pid=fork();
if(pid==0){while(1){printf("child pid=%d ppid=%d\n",getpid(),getppid());sleep(1); }
}
sleep(10);
printf("parent pid =%d\n",getpid());
4.僵尸进程
进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成了僵尸进程。
僵尸进程不能使用kill命令清除掉。因为kill命令只是用来终止进程的,而僵尸进程已经被终止。可以杀死它的父进程,让init进程变成它的父进程,init进程可以回收它。
5.进程回收
一个进程在终止时会关闭所有的文件描述符,释放在用户空间分配的内存,但是它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出的状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这个信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell是它的父进程,当它终止时shell调用wait或waitpid得到它的退出状态同时清除掉这个进程。
wait函数
函数描述:
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
1.阻塞带带子进程退出
2.回收子进程残留资源
3.获取子进程结束状态(退出原因)
头文件:
#include<sys/type.h>
#include<sys/wait.h>
函数原型
pid_t wait(int* status)
函数参数
status为传出参数,用于保存进程的退出状态
函数返回值:
成功:返回清理掉的子进程id
失败:返回-1
当进程终止时,操作系统的隐式回收机制会:
1.关闭掉所有文件的文件描述符
2.释放用户空间、分配的内存。内核的PCB仍然存在。其中保存该进程的退出状态。(正常终止->退出值;异常终止->终止信号)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {pid_t pid;int status;// 创建子进程pid = fork();if (pid == 0) {// 子进程printf("子进程正在运行...\n");// 模拟子进程的工作,这里简单地睡眠2秒sleep(2);printf("子进程结束\n");// 子进程正常退出,返回状态码为0return 0;} else if (pid > 0) {// 父进程printf("父进程正在等待子进程结束...\n");// 调用wait函数等待子进程结束pid_t wpid = wait(&status);if (wpid == -1) {// 处理错误情况,如没有子进程等perror("wait");return 1;}// 检查子进程的退出状态if (WIFEXITED(status)) {int exit_status = WEXITSTATUS(status);printf("子进程正常退出,退出状态码为 %d\n", exit_status);} else if (WIFSIGNALED(status)) {int term_sig = WTERMSIG(status);printf("子进程被信号 %d终止\n", term_sig);}printf("父进程继续执行...\n");} else {// 处理fork错误情况perror("fork");return 1;}return 0;
}
可以使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程的具体原因。宏函数可以分成下面三组:
1.WIFEXITED(status);//为真->进程正常结束WEXITSTATUS(status);//如上宏为真,获取机场南退出状态(exit的参数)
2.WIFIGNALED(status);//为真->进程异常终止WTERMSIG(status);//如上宏为真,得是进程指针的那个信号的编号
3.WIFSTOPPED(status);//为真->进程出于暂停状态WSTOPSIG(status);//如上宏为真,取得使进程暂停的那个信号的编号WIFCONTINUED(status);//如上宏为真,进程暂停后已经继续运行
waitpid函数
函数描述:
作用同wait,但是可以指定进程id为pid的进行清理,可以不阻塞。
头文件:
#include<sys/type.h>
#include<sys/wait.h>
函数原型:
pid_t waitpid(pid_t pid,int *status,int options);
函数参数:
参数pid:
pid>0:回收指定ID的子进程
pid=-1:回收任意子进程(相当于wait)
pid=0:回收和当前调用进程(父进程)一个组的任一子进程
pid<0:设置负的进程组id,回收指定进程组内的任意子进程
函数返回值:
成功:返回成功清理掉的子进程id
失败:返回-1
参数3为WNOTHANG,且子进程正在运行,返回0
注意:一次wait或者waitpid调用只能清理掉一个子进程,清理多个子进行应使用循环
int pid;
int wpid;
int i;
for(i=0;i<10;i++ ){pid==fork();if(pid==0){break; }if(i==2){wpid=pid;printf("i = 2 process id =%d\n",wpid); }
}
if(i==10){printf("parent wpid=%d\n",wpid);int ret=waitpid(wpid,NULL,0);printf("ret =%d\n",ret);while(1);
}else{return 0;
}
上述代码是清除掉第三个子进程的信息。