什么是进程
简单来说进程是程序的动态的概念,进程是当程序运行起来时就会存在进程
图中的a.out就是一个程序,而当其运行起来后,可以通过指令
ps -aux |grep a.out 来查看进程
可以看到进程的pid号,以及运行的状态等信息
父进程
指已创建一个或多个子进程的进程。在UNIX里,除了进程0以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。
子进程
指的是由另一进程(对应称之为父进程)所创建的进程。子进程继承了对应的父进程的大部分属性,如文件描述符。在Unix中,子进程通常为系统调用fork的产物。在此情况下,子进程一开始就是父进程的副本。
进程创建
上文提到fork,而进程创建里就需要依赖fork函数来创建,而创建进程有两种
创建进程方式一:fork()
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
由fork的返回值来判断是进入了父进程还是子进程,当返回值大于零时是进入了父进程,当返回值等于零时是进入了子进程,由以下例子可以看到父进程fork的返回值是其子进程的pid号,获取当前进程的pid号可以用getpid(),若想获取其父进程的pid号可以用getppid()
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
// pid_t getpid(void);
// pid_t getppid(void);
int main(){
pid_t pid,pid1;
pid1=getpid();
printf("创建进程前的进程号是:%d\n",pid1);
pid=fork();
pid_t pid2;
pid2=getpid();
if(pid>0){
printf("这是父进程pid号是:%d\n",pid2);
printf("父进程fork的返回值:%d\n",pid);
}
else{
printf("这是子进程pid号是:%d\n",pid2);
printf("其父进程%d\n",getppid());
printf("子进程fork的返回值:%d\n",pid);
}
return 0;
}
创建进程方式二:vfork()
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
这两种方法都可以用来创建子进程,两者的区别是这几种。
区别一:当用了fork来创建进程时,父子进程同时抢夺cpu的资源来运行,而当用了vfork来创建子进程时父进程必须等待子进程结束后,才能轮到父进程运行。
区别二:vfork创建的子进程是共享父进程的数据和储存,而fork创建的子进程,是写实拷贝,也就是只有当子进程中改变了数据段的时候,子进程会复制一份数据给自己,然后只改动自己的部分,父进程的数据不会随着子进程改变而改变。
这里可以举例说明。
①fork创建
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
// pid_t getpid(void);
// pid_t getppid(void);
int main(){
pid_t pid,pid1;
pid1=getpid();
int a=0;
pid=fork();
pid_t pid2;
pid2=getpid();
if(pid>0){
while(1){
a++;
printf("父进程%d\n",a);
sleep(1);
}
}
else{
while(a<=5){
a++;
printf("子进程%d\n",a);
sleep(1);
}
}
return 0;
}
可以看到父子进程在争夺cpu,而且子进程复制了一份数据
②vfork创建
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
// pid_t getpid(void);
// pid_t getppid(void);
int main(){
pid_t pid,pid1;
int a=0;
pid1=getpid();
pid=vfork();
pid_t pid2;
pid2=getpid();
if(pid>0){
while(1){
a++;
printf("父进程%d\n",a);
sleep(1);
}
}
else{
while(1){
a++;
printf("子进程%d\n",a);
sleep(1);
if(a>4){
exit(3);
}
}
}
return 0;
}
可以看到这里父进程在等待子进程结束后,才开始的父进程,且数据段是共享的
父进程等待子进程
我们可以知道用vfork()时父进程必须等待子进程退出后,才能执行,而用fork()也可以使父进程等待子进程退出,这里需要用到wait函数。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
#include <stdlib.h>
void exit(int status);
①wait
wait函数需要配合着exit来使用,二者的status就是进程退出的状态码,如果子进程退出后父进程没有用wait来收集子进程的状态码,那么子进程就会变成僵尸进程(z+),我们可以用进程指令来查看。
ps -aux |grep a.out
这里可以用一个例子来说明
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
pid_t pid,pid1;
pid1=getpid();
int a=0;
int states;
pid=fork();
pid_t pid2;
pid2=getpid();
if(pid>0){
// wait(NULL);//不关心子进程退出的状态
wait(&states);
printf("子进程状态码:%d\n",WEXITSTATUS(states));
printf("father%d\n",a);
}
else{
while(1){
a++;
printf("child%d\n",a);
sleep(1);
if(a>5){
exit(3);
}
}
}
return 0;
}
可以看到wait里的参数可以是子进程退出的状态码,也可以是NULL,而这时就是不关心子进程的退出的状态,但也会等待,只是不关心,而且子进程结束后也不会变成僵尸进程,而如果想要打印出状态码,还需要借助WEXITSTATUS(states)。
②waitpid
参数pid
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
参数wstatus
和wait一样根据关心状态与否来选择参数
参数options
这里如果打算不等待子进程退出还依然用waitpid的话,可以选择用WNOHANG,如果选择了WNOHANG,子进程还是会变成僵尸进程,若需要等待则把该值设为0,那么效果会等同于wait函数
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
pid_t pid,pid1;
pid1=getpid();
int a=0;
int states;
pid=fork();
pid_t pid2;
pid2=getpid();
if(pid>0){
// wait(NULL);//不关心子进程退出的状态
//` wait(&states);
waitpid(-1,&states,0);
printf("子进程状态码:%d\n",WEXITSTATUS(states));
while(1){
printf("father%d\n",a);
sleep(1);
}
}
else{
while(1){
a++;
printf("child%d\n",a);
sleep(1);
if(a>5){
exit(3);
}
}
}
return 0;
}
孤儿进程
孤儿进程顾名思义就是子进程还在运行,但是父进程以及提前结束了,而linux系统为了避免过多的孤儿进程而做了一些处理,当其父进程提前结束自己时,linux中的init进程会收留子进程,可以举个例子来测试一下。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
pid_t pid,pid1;
pid1=getpid();
printf("创建进程前的进程号是:%d\n",pid1);
int a=0;
pid=fork();
pid_t pid2;
pid2=getpid();
if(pid>0){
printf("这是父进程pid号是:%d\n",pid2);
printf("父进程fork的返回值:%d\n",pid);
printf("father:%d\n",a);
sleep(2);
}
else{
printf("这是子进程pid号是:%d\n",pid2);
printf("其父进程%d\n",getppid());
printf("子进程fork的返回值:%d\n",pid);
while(a<=5){
printf("其父进程%d\n",getppid());
a++;
printf("child%d\n",a);
sleep(1);
}
}
return 0;
}
从这里可以看到,子进程在父进程运行到一半时结束了,子进程的pid号也随之发生了改变