一、进程概述
进程的定义
程序和进程的区别: 程序:是静态的,存放在磁盘上的可执行文件。
进程:是动态的,是运行在内存中的程序的执行实例。
程序是一些指令的有序集合,而进程是程序执行的过程,进程是程序的一次执行过程。 进程的状态是变化的,其包括进程的创建、调度和消亡。 只要程序运行,此时就是进程,程序每运行一次,就会创建一个进程。
在linux系统中,进程是管理事务的基本单元。 进程拥有自己独立的处理环境和系统资源(处理器、存储器、I/O设备、数据、程序)。
进程的状态及转换
进程整个生命周期可以简单划分为三种状态:
就绪态:进程已经具备执行的一切条件,正在等待分配CPU的处理时间。
执行态:该进程正在占用CPU运行。
等待态:进程因不具备某些执行条件而暂时无法继续执行的状态。
进程的调度进制
时间片轮转,上下文切换
多进程不是说一个进程执行完再执行另一个进程,而是交替执行的,一个进程执行一 段时间,然后下一个进程在执行一段时间,依次类推,所有进程执行完之后再回到第一个。
二、进程控制
进程号
每个进程都由一个进程号来标识,其类型为pid_t,进程号的范围:0~32767 进程号是由操作系统随机给当前进程分配的,不能自己控制 进程号总是唯一的,但进程号可以重用。
当一个进程终止后,其进程号就可以再次使用了。
在ubuntu中查看当前系统中所有的开启的进程
ps ajx
名字 | 含义 |
---|---|
PPID | 当前进程的父进程的进程号 |
PID | 当前进程的进程号 |
PGID | 当前进程所在的组的进程组ID |
COMMAND | 当前进程的名字 |
特殊的进程号:
在linux系统中进程号由0开始,进程号为0及1的进程由内核创建。
进程号为0的进程通常是调度进程,常被称为交换进程(swapper)。
进程号为1的进程通常是init进程,init进程是所有进程的祖先。
除调度进程外,在linux下面所有的进程都由进程init进程直接或者间接创建。
如何查看系统中有哪些进程
1.使用ps指令查看 在实际工作中,配合grep来查找程序中是否存在某一个进程。
2.Ps -aux查看完整的进程,查看某一个用ps -aux|grep init。
3.使用top指令查看,类似windows任务管理器。
进程的创建–fork函数
fork函数调用成功,返回两次,返回值为0代表当前进程是子进程,返回值非负数代表当前进程为父进程,调用失败返回-1。
使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。
地址空间
包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。子进程所独有的只有它的进程号,计时器等。
fork函数执行完毕后父子进程的空间示意图:
获得进程号的函数
getpid()、getppid()、getpgid()。
获取进程号对比区分父子进程
使用返回值不同区分父子进程
接下来研究fork的返回值fork函数调用成功,返回两次,返回值为0代表当前进程是子进程,返回值非负数代表当前进程为父进程,调用失败返回-1。
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
//fork函数的使用 子进程和父进程的判别 并且添加frok返回值
int main()
{
pid_t pid;
pid_t pid2;
pid_t retpid;
pid=getpid();
printf("befor fork:pid=%d\n",pid);
//pid_t fork(void); 返回值pid_t 使用后会打印两次同样的进程标识符 说明多创建了一次进程 进程是程序的一次运行活动。
retpid=fork(); //fork之前都是父进程
pid2=getpid();
printf("after fork:pid=%d\n",retpid);
if(pid==pid2)
{
printf("this is father print:retpid=%d\n",retpid); //fork的第一次返回值是子进程的pid号 第二次是0
}
else
{
printf("this is child print,retpid=%d,child pid= %d\n",retpid,getpid());
}
//printf("my pid is %d,current pro id: %d\n",pid,getpid()); 获得两次不一样的进程标识符
return 0;
}
fork返回值为0代表当前进程是子进程,返回值非负数代表当前进程为父进程 并且子进程把父进程的内容拷贝了一边如果子进程对数据修改则修改的是被复制的那份父进程的不会被修改。
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
//fork返回值为0代表当前进程是子进程,返回值非负数代表当前进程为父进程 并且子进程把父进程的内容拷贝了一边如果子进程对数据修改则修改的是被复制的那份父进程的不会被修改
int main()
{
pid_t pid;
int data=10;
printf("father:id=%d\n",getpid());
pid=fork(); //子进程从这里执行
if(pid>0)
{
printf("this is father print,pid=%d\n",getpid());
}
else if(pid==0)
{
printf("this is child print,child pid=%d\n",getpid());
data=data+100;
}
printf("data=%d\n",data);
}
进程的挂起
小练习
用条件语句创建子进程,父进程里什么都不做子进程一直打印,每次输入1就会得到一个新的进程也会多一个进程号被打印。
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h> //sleep
//子进程 用条件语句创建子进程父进程里什么都不做 子进程一直打印 每次输入1就会得到一个新的进程也会多一个进程号被打印
int main()
{
pid_t pid;
int data=10;
while(1)
{
printf("please input a data\n");
scanf("%d",&data);
if(data==1)
{
pid=fork();
if(pid>0)
{
}
else if(pid==0)
{
while(1)
{
printf("do net request,pid=%d\n",getpid());
sleep(3);
}
}
}
else
{
printf("wait ,do nothing\n");
}
}
return 0;
}
Vfork函数
fork和vfork函数的区别
vfork保证子进程先运行,在它调用exec或exit之后,父进程才可能被调度运行。 vfork和fork一样都创建一个子进程,但它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不访问该地址空间。 相反,在子进程中调用exec或exit之前,它在父进程的地址空间中运行,在exec之后子 进程会有自己的进程空间。
fork父子进程相互抢占
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
//fork函数回顾 父子争夺进程 与vfork的区别 子进程退出执行父进程 并且变量不考呗但共享变量跟随改变
//进程退出
int main()
{
pid_t pid;
int cnt=0;
pid=fork();
//pid=vfork();
if(pid>0)
{
while(1)
{
printf("this is father print,pid=%d\n",getpid());
sleep(1);
printf("cnt=%d\n",cnt);
}
}
else if(pid==0)
{
while(1)
{
printf("this is child print,pid=%d\n",getpid());
sleep(1);
cnt++;
if(cnt==3)
{
exit(0);
//_exit(0); _Exit(0);
}
}
}
return 0;
}
进程的终止
exit函数
** _exit函数**
exit和_exit函数的区别:
exit为库函数,而_exit为系统调用
exit会刷新缓冲区,但是_exit不会刷新缓冲区,一般会使用exit。
子进程在父进程之前运行
进程退出清理
进程的等待
wait函数
取出子进程的退出信息:
WIFEXITED(status) 如果子进程是正常终止的,取出的字段值非零。 WEXITSTATUS(status) 返回子进程的退出状态,退出状态保存在status变量的8~16位。 在用此宏前应先用宏WIFEXITED判断子进程是否正常退出,正常退出才可以使用此宏。
注意:此status是个wait的参数指向的整型变量。
当进程运行时使用ps -aux|grep newpro查看进程状态z+表示僵尸进程。
使用fork时在父子进程中子进程用exit退出后父进程没有等待 会产生僵尸进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
//使用fork时在父子进程中子进程用exit退出后父进程没有等待 会产生僵尸进程
int main()
{
pid_t pid;
int cnt=0;
pid=fork();
//pid=vfork();
if(pid>0)
{ //而且父亲先让孩子打印等待孩子结束
while(1)
{
printf("this is father print,pid=%d\n",getpid());
sleep(1);
printf("cnt=%d\n",cnt);
}
}
else if(pid==0)
{
while(1)
{
printf("this is child print,pid=%d\n",getpid());
sleep(1);
cnt++;
if(cnt==3)
{
exit(0);
//_exit(0); _Exit(0);
}
}
}
return 0;
}
加上wait之后结果防止子进程变成僵尸进程而且父进程等待子进程打印结束
子进程用exit退出后父进程等待不会产生僵尸进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
//使用fork时在父子进程中子进程用exit退出后父进程没有等待 会产生僵尸进程
int main()
{
pid_t pid;
int cnt=0;
pid=fork();
//pid=vfork();
if(pid>0)
{ //而且父亲先让孩子打印等待孩子结束
wait(NULL); //加上wait之后结果防止子进程变成僵尸进程 NULL不关心子进程的退出状态
while(1)
{
printf("this is father print,pid=%d\n",getpid());
sleep(1);
printf("cnt=%d\n",cnt);
}
}
else if(pid==0)
{
while(1)
{
printf("this is child print,pid=%d\n",getpid());
sleep(1);
cnt++;
if(cnt==3)
{
exit(0);
//_exit(0); _Exit(0);
}
}
}
return 0;
}
参数status
参数status是一个整型指针。如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其
中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的(一个进程也可以被其他进程
用信号结束,我们将在以后的文章中介绍),以及正常结束时的返回值,或被哪一个信号结束的等信息。由于
这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的
宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:
1,WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。(请注
意,虽然名字一样,这里的参数status并不同于wait唯一的参数--指向整数的指针status,而是那个指针所
指向的整数,切记不要搞混了。)
2, WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子
进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(statu
s)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
/*函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,
wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。*/
int main()
{
pid_t pid;
int cnt=0;
int status=10;
pid=fork();
//pid=vfork();
if(pid>0)
{
//pid_t wait(int *status);
wait(&status);
printf("child quit ,child status=%d\n",WEXITSTATUS(status)); // 对于正常退出情况可执行NEXITSTATUS(status)来查看子进程退出的状态码5
while(1)
{
printf("this is father print,pid=%d\n",getpid());
sleep(1);
printf("cnt=%d\n",cnt);
}
}
else if(pid==0)
{
while(1)
{
printf("this is child print,pid=%d\n",getpid());
sleep(1);
cnt++;
if(cnt==3)
{
exit(5);
//_exit(0); _Exit(0);
}
}
}
return 0;
}
waitpid函数
孤儿进程
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。