目录
一、进程
1、定义
进程是一种抽象的概念,从来没有统一的标准定义。
从不同的角度,进程可以有不同的定义,比较典型的定义有:
①进程是程序的一次执行过程。
②进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
③进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
简单来说,进程就是程序的一次执行过程,程序是静态的,它作为系统中的一种资源是永远存在的。而进程是动态的,它是动态的产生,变化和消亡的,拥有其自己的生命周期。
举例子理解:同时挂三个 QQ 号,它们就对应三个 QQ 进程,退出一个就会杀死一个对应的进程。但是,就算你把这三个 QQ 全都退出了,QQ 这个程序死亡了吗?显然没有死亡,程序还是存储在存储器中的,除非你把这个程序从存储体中给删除了(这一操作就相当于我们卸载手机里的软件)。
进程不仅包含正在运行的程序实体,它还包括这个运行的程序中占据的所有系统资源,比如说 CPU、内存、网络资源等。一些人会把进程理解为它就是一个运行的实体,而忽略掉了进程所占据的资源。比如说,同样一个程序,同一时刻被两次运行了,那么他们就是两个独立的进程,这两个进程所占用的资源是相互独立的。
引入进程实体的概念后,我们可以把传统操作系统中的进程定义为:“进程是程序实体的运行过程,是系统进行资源配和调度的一个独立单位。”
总之,进程是操作系统中用于管理程序执行和资源分配的基本单位。
2、特性
①动态性
进程的动态性体现在其从创建到终止的整个生命周期中。这个过程可以概括为:
动态产生:当程序被加载到内存中并开始执行时,一个进程就被创建了。这个过程通常包括分配内存、初始化数据段、设置文件描述符等步骤。
执行程序:进程的核心任务是执行程序中的指令。在执行过程中,进程会占用CPU时间、内存和其他系统资源。
数据:进程在执行过程中需要处理的数据也是其重要组成部分。这些数据可能来自程序本身、用户输入、文件系统或其他进程。进程通过内存地址空间来访问这些数据,并确保数据的完整性和安全性。
动态消亡:当程序执行完毕或遇到错误时,进程会进入终止状态。在终止过程中,进程会释放所占用的资源,如内存、文件描述符等,并通知操作系统该进程已经结束。
②并发性
1)并发性与并行性的区别
并发性和并行性是既相似又有区别的两个概念。并行性是指两个或多个事件在同一时刻发生,这通常发生在多处理器或多核系统中。而并发性则是指两个或多个事件在同一时间间隔内发生,这可以在单处理器或多处理器系统中实现。在单处理器系统中,并发性是通过时间片轮转技术来实现的,即操作系统将CPU时间分割成多个时间片,每个时间片分配给一个进程执行,当时间片用完时,操作系统会切换到下一个进程,从而实现多个进程的交替执行
2)进程在并发执行中的表现
在并发执行中,进程具有以下特点:
a.宏观上同时执行:由于CPU在高速切换,用户或系统很难感知到进程的交替执行,因此在宏观上给人一种多个进程同时执行的错觉。
b.微观上交替运行:在微观上,由于CPU同一时间只能执行一个进程对应的代码,因此多个进程是交替运行的。
c.时间片:时间片是操作系统调度技术的一种,用于在多个进程间分配CPU时间。每个进程在执行时会获得一个时间片,当时间片用完时,操作系统会切换到下一个进程。
补充:linux系统里面有自己的调度器,它会决定让某个进程执行,也可以进行切换,让其他的进程执行(系统中断)。
③独立性
指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。凡未建立PCB的程序,都不能作为一个独立的单位参与运行
进程是系统进程资源分配和调度的最小单位。创建完PCB后,进程都具有唯一的标识--PID,用非负整数表示进程的ID,这个ID就是进程的标识符。作用:就如同身份证具有唯一性。
进程A和进程B,空间独立,互不干扰.
④异步性
由于进程的相互制约,使得进程按各自独立的、不可预知的速度向前推进。异步性会导致执行结果的不可再现性,为此在操作系统中必须配置相应的进程同步机制。
什么是同步? 硬件上->有CLK时钟线的话,就是同步;系统里面一般认为,有约束就是同步.例如:串口通信—没有约束—异步 IIC—同步半双工
什么是异步? 没有约束的就是异步
⑤结构性
进程的结构,主要有这三部分组成:进程控制块(PCB)、数据段、程序段。
进程控制块:
每个进程有且仅有一个进程控制块(PCB),或称作进程描述符,它是进程存在的唯一标识,是操作系统用来记录和刻画进程状态及环境信息的数据结构,也是操作系统掌握进程的唯一资料结构和管理进程的主要依据。创建进程和撤销进程等都是指对 PCB 的操作,当进程被创建时,操作系统为其创建 PCB,当进程结束时,会回收其 PCB。
进程控制块 PCB 一般包含如下四类信息:
进程描述信息:用来让操作系统区分各个进程,当进程被创建时,操作系统会为该进程分配一个唯一的、不重复的 “身份证号”— PID(ProcessID,进程 ID)。另外,进程描述信息还包含进程所属的用户 ID(UID)
进程控制和管理信息:记录进程的运行情况。比如 CPU 的使用时间、磁盘使用情况、网络流量使用情况等。
资源分配清单:记录给进程分配了哪些资源。比如分配了多少内存、正在使用哪些 I/O 设备、正在使用哪些文件等。
CPU 相关信息:进程在让出 CPU 时,必须保存该进程在 CPU 中的各种信息,比如各种寄存器的值。用于实现进程切换,确保这个进程再次运行的时候恢复 CPU 现场,从断点处继续执行。这就是所谓的保存现场信息。
数据段:
一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果,即进程运行过程中的各种数据(比如程序中定义的变量)。
程序段:
程序段就是能被进程调度时程序调度到CPU执行的程序代码段(指令序列)。注意,程序可被多个进程共享,即多个进程可以运行同一个程序。例如:同时挂三个 QQ 号,会对应三个 QQ 进程,它们的 PCB、数据段各不相同,但程序段的内容都是相同的(都是运行着相同的 QQ 程序)
3、进程和程序的区别
①程序以文件的形式保存在内存中(占用存储器的内存—不会消失)。而进程只能能够运行于系统内存(运行内存—系统关机之后都会消失)中。
②进程是一个动态产生到动态消亡的过程,程序只是一个静态的概念。
③进程和程序之间的对应关系,未必是一一对应的,一个程序(例如:main()—fork()创建父子进程)可以对应多个进程。
4、进程的状态
在这一整个过程中,进程所有的状态如下:
(一)新建状态:进程正在被创建,创建好后会转到就绪状态。创建进程通常需要先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息,然后由系统为该进程分配运行时所必须的资源,最后把该进程转入就绪状态。
(二)就绪状态:进程已经分配到了必要的内存、文件、设备等资源;程序和数据已经加载到内存中,等待CPU的调度(分配CPU时间片);系统中处于就绪状态的进程可能有多个,通常将他们排成一个队列,成为就绪队列。
(三)运行状态:当进程已经获得CPU,并且其程序正在执行时,该进程处于执行状态
(四)阻塞状态:又称等待状态。当进程因为等待某个事件(如I/O操作、资源申请、信号量等)而无法继续执行时,该进程处于阻塞状态;进程的状态信息会被保存在PCB(进程控制块)中,以便在事件完成后恢复执行.
(五)终止状态:进程正从系统中消失,可能是进程正常结束或其他原因中断退出运行。进程需要结束运行时,系统首先必须置该进程为结束状态,然后再进一步处理资源释放和回收等工作。
进程中就绪状态、执行状态、阻塞状态是如何进行切换的?
就绪状态 → 执行状态:当操作系统调度程序选择了一个就绪进程并将其分配到CPU上时。
执行状态 → 就绪状态:当进程的时间片用完、被更高优先级的进程抢占CPU、或主动让出CPU时。
执行状态 → 阻塞状态:当进程等待某个事件(如I/O操作)而无法继续执行时。例子:进程可能需要从磁盘读取数据或向磁盘写入数据。由于磁盘I/O操作通常比CPU操作慢得多,进程必须等待I/O操作完成才能继续执行。在这种情况下,进程会从执行状态转变为阻塞状态,直到I/O操作完成并且数据准备就绪。
阻塞状态 → 就绪状态:当等待的事件完成时,进程被唤醒并重新放入就绪队列中。
二、进程相关名词
1、父子进程
大多数进程都不是凭空产生的,往往需要另一个进程的产生的,此时这个两个进程就属于父子进程,一般是在一个进程中使用fork()函数产生父子进程的。其中父子进程,最顶层父进程称为祖先进程—对应的PID号=1。
注意:子进程运行结束后,需要父进程收回子进程的空间,目的防止僵尸进程的产生
2、祖先进程
祖先进程是系统引导时创建的第一个用户级进程,它负责初始化系统环境,并作为后续所有进程的祖先。
特点:
系统中第一个被创建的用户级进程(PID=1)。
通常是由系统内核在引导过程中创建的,而不是由其他进程创建的。
负责系统的初始化工作,如挂载文件系统、启动系统服务等。
功能:
系统初始化:祖先进程在系统启动时负责初始化系统环境,包括挂载文件系统、设置系统变量、启动必要的服务等。
进程管理:它作为系统中所有进程的祖先,负责管理和协调这些进程的运行。当某个进程结束时,如果它的父进程已经不存在,那么该进程会成为孤儿进程,由祖先进程来收养并处理其资源回收工作。
服务启动:祖先进程通常会启动系统所需的各种服务,如网络服务、打印服务等,以确保系统的正常运行。
系统关闭:在系统关闭时,祖先进程会负责关闭系统服务、卸载文件系统等工作,确保系统的安全退出。
3、守护进程
守护进程又可以称为精灵进程(后台进程),运行在后台的一种特殊进程。并独立于控制终端并且周期性的执行某个任务
4、僵尸进程
僵尸进程是指已经完成执行但仍在进程表中保留其信息的进程。这种状态通常发生在子进程结束后,父进程尚未读取其退出状态,导致子进程的信息仍保留在系统中。
特点:
僵尸进程不再执行任何代码,也不占用CPU或内存资源(除了进程表中的一个记录)。
僵尸进程不会主动结束,需要父进程进行清理才能释放资源。
产生原因:
当一个子进程完成其任务并调用exit()系统调用时,它会向操作系统报告自己的退出状态。如果父进程没有调用wait()或waitpid()等系统调用来获取子进程的退出状态,那么子进程就会变成僵尸进程。
解决方法:
①父进程应定期调用wait()或waitpid()来处理其子进程,确保获取其退出状态并释放资源。
②如果父进程无法处理子进程(例如,父进程是一个循环且不会结束),可以通过重启父进程来清理其所有子进程(包括僵尸进程)。
③在某些情况下,可以手动杀死父进程,使子进程成为孤儿进程,然后由init进程接管并处理。
5、孤儿进程
在子进程结束之前,父进程已经结束,此时子进程将称为孤儿进程,直到祖先进程“收养”,子进程运行结束之后,有祖先进程将空间收回。
三、进程控制相关函数
1、获取进程ID
getpid
函数功能:
获取当前进程的PID号
头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getpid(void);
函数参数:无
函数返回值:
类型:无符号的整数
返回的就是当前进程的PID号
getppid
函数功能:
获取当前进程父进程的PID号
头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getppid(void);
函数参数:无
函数返回值:
类型:无符号的整数
返回的就是当前进程的父进程PID号
PID和PPID之间的区别:
标识对象不同:PID标识的是当前进程本身,而PPID标识的是创建当前进程的父进程。
用途不同:PID主要用于进程管理和调度,而PPID主要用于构建进程之间的层级关系和追踪进程来源。
数值不同:在大多数情况下,一个进程的PID和它的父进程的PPID是不同的(除非是在进程创建时发生的特殊情况,但这种情况非常罕见)
示例1:如何获取对应进程的PID号,输出当前进程父进程的PID号
现象:
代码:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
//先输出一下,当前进程的PID号
pid_t pid,ppid;
pid=getpid();
printf("pid=%d\r\n",pid);
//再输出一下,当前进程父进程PID号
ppid=getppid();
printf("ppid=%d\r\n",ppid);
return 0;
}
示例2:如何获取对应进程的PID号,输出当前进程父进程的PID号,并且杀死对应的进程
现象:
代码:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
//先输出一下,当前进程的PID号
pid_t pid,ppid;
pid=getpid();
printf("pid=%d\r\n",pid);
//再输出一下,当前进程父进程PID号
ppid=getppid();
printf("ppid=%d\r\n",ppid);
while(1);
return 0;
}
2、运行进程
system
函数功能:
接收一个命令行指令或者可执行程序,然后执行
头文件:
#include <stdlib.h>
函数原型:
int system(const char *string);
函数参数:
const char *string:表示待执行的命令或者可执行程序
函数返回值:
成功 返回0
失败 返回-1
示例:利用system()函数,验证一下它的现象。
现象:
代码:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
//先输出一下,当前进程的PID号
pid_t pid,ppid;
pid=getpid();
printf("pid=%d\r\n",pid);
//再输出一下,当前进程父进程PID号
ppid=getppid();
printf("ppid=%d\r\n",ppid);
//调用system()函数
int res = system("ls -l");
printf("res=%d",res);
return 0;
}
3、替换进程
execl
函数功能:
用参数指定的进程替换当前的进程,并且执行完替换进程后,原来的进程不再继续执行
头文件:
#include <unistd.h>
函数原型:
int execl(const char *path,char *arg....)
函数参数:
const char *path:表示所需要替换执行程序的路径,一般情况当前路径下,也要加上可执行程序的名字
char *arg:表示执行当前程序的路径argv[0],还需要加上NULL
函数返回值:
成功 不会返回,也就说,execl()之后的代码不会执行。
失败 返回-1
示例:用参数指定的进程替换当前的进程 ,并且PID号不发生变化的
现象:
代码:
进程A
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
//输出一下当前进程的pid号,ppid号
printf("pid=%d\r\n",getpid());
printf("ppid=%d\r\n",getppid());
//调用一下对应的替换进程
execl(argv[1],argv[0],NULL);
//如果execl函数执行成功,后面的代码是无法执行的
printf("hello\r\n");
return 0;
}
进程B
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
//先输出一下,当前进程的PID号
pid_t pid,ppid;
pid=getpid();
printf("pid=%d\r\n",pid);
//再输出一下,当前进程父进程PID号
ppid=getppid();
printf("ppid=%d\r\n",ppid);
printf("123\r\n");
return 0;
}
4、创建进程
fork
函数功能:
用于从当前进程(称为父进程)创建一个新的进程(称为子进程)。调用 fork() 后,会有两个几乎完全相同的进程,唯一的区别在于它们的返回值和进程 ID(PID)。
头文件:
#include <unistd.h>
函数原型:
pid_t fork(void);
函数参数:无
函数返回值:
成功 在父进程中,返回新创建的子进程的PID
在子进程中,返回0
失败 返回-1
注意:先父后子的执行顺序
示例1:利用一下对应的fork()函数,观察一下对应的现象
现象:
代码:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
int i = 0;
i++;
printf("parent i:%d\r\n",i);
//创建子进程
fork();
printf("i:%d\r\n",i);
printf("hello\r\n");
while(1);
return 0;
}
示例2:利用fork函数,实现父子进程完成不同的功能
现象:
代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int res;
int i = 0;
i++;
printf("parent i:%d\r\n",i);
//创建子进程
res = fork();
if(res == 0)//子进程
{
printf("son i:%d\r\n",i);
}
else//父进程
{
sleep(1);
printf("parent:hello\r\n");
}
return 0;
}
示例3:利用父子进程实现交替报数 ---父进程—报2 子进程—报1 利用sleep(1)
思路:1:父进程--负责报偶数 2:子进程--负责报奇数
现象:
代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int res;
//创建子进程
res = fork();
if(res == 0)//子进程
{
int i = 1;
while(1)
{
printf("son i:%d\r\n",i);
i+=2;
sleep(2);
}
}
else//父进程
{
int i = 2;
while(1)
{
sleep(1);
printf("parent i:%d\r\n",i);
i+=2;
sleep(1);
}
}
return 0;
}
改进对应的报数,采用的方法:判断语句---if(i%2 ==0) else ---sleep(1)
现象一样
代码:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int ret;
int i=1;
//利用fork函数,创建对应的子进程
ret=fork();
if(ret == 0)
{
//子进程事情
while(1)
{
if((i%2) != 1)
{
printf("child i=%d\r\n",i);
sleep(1);
}else
{
sleep(1);
}
//将对应的值加1
i++;
}
}
else
{
//父进程事情
while(1)
{
if((i%2) == 1)
{
printf("parent i=%d\r\n",i);
sleep(1);
}
else
{
sleep(1);
}
//将对应的值加1
i++;
}
}
return 0;
}
示例4:利用三个进程实现交替报数
分析思路:主函数里面—fork()
在创建对应的子进程的时候,需要将它们放到对应的判断语句里面(放到父进程里面或者放到子进程里面)
多进程的框架(方案一):
int main
{
//创建子进程
ret=fork();
if(ret == 0)
{
//子进程里面再创建子进程
ret1=fork();
if(ret1 == 0)
{
//孙子进程做的事情
}
else
{
//子进程做的事情
}
}
else
{
//父进程完成的事情
}
}
现象:
代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int res;
//创建子进程
res = fork();
if(res == 0)//子进程
{
//创建子进程
res = fork();
if(res == 0)//孙进程
{
int i = 3;
while(1)
{
sleep(2);
printf("sun i:%d\r\n",i);
i+=3;
sleep(1);
}
}
else//子进程
{
int i = 2;
while(1)
{
sleep(1);
printf("son i:%d\r\n",i);
i+=3;
sleep(2);
}
}
}
else
{
int i = 1;
while(1)
{
printf("parent i:%d\r\n",i);
i+=3;
sleep(3);
}
}
return 0;
}
多进程的框架(方案二):
int main
{
//创建子进程
ret=fork();
if(ret == 0)
{
//子进程1—完成的事情
}
else
{
//父进程完成的事情
res=fork();
if(ret2 == 0)
{
//子进程2—完成的事情
}
else
{
//父进程完成的事情
}
}
}
现象:
代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int res,ret;
//创建子进程
res = fork();
if(res == 0)//子进程1
{
int i = 2;
while(1)
{
sleep(1);
printf("son1 i:%d\r\n",i);
i+=3;
sleep(2);
}
}
else
{
//创建子进程
ret = fork();
if(ret == 0)//子进程2
{
int i = 3;
while(1)
{
sleep(2);
printf("son2 i:%d\r\n",i);
i+=3;
sleep(1);
}
}
else//父进程
{
int i = 1;
while(1)
{
printf("parent i:%d\r\n",i);
i+=3;
sleep(3);
}
}
}
return 0;
}
vfork
功能:
创建对应的子进程,直到子进程运行结束后才执行父进程
头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t vfork(void);
函数参数:无
函数返回值:
在父进程中,返回新创建的子进程的PID
在子进程中,返回0
失败,则返回-1
注意:新进程和父进程共享相同的地址空间
示例1:利用vfork,输出子进程的PID和PPID再加入变量,也输出父进程的PID和PPID再加入一个变量
现象:
代码:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int ret;
//输出PID号
printf("parent pid=%d\r\n",getpid());
ret = vfork();
if(ret == 0)
{
sleep(1);
//输出PID号
printf("child pid=%d\r\n",getpid());
exit(0);
}
else
{
printf("hello\r\n");
}
}
fork和 vfork的异同
①:共同点,都可以创建一个子进程,并具有相同的返回值。
②:不同点,fork()函数,父子进程的运行顺序可以调整,而vfork()函数的执行顺序不能调整,必须等待子进程运行结束。
③:不同点,fork()执行前,父子进程空间独立,而vfork执行前父子进程空间是公用的。
示例2:利用vfork函数,验证一下空间是否是公用的。
现象:
代码:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int ret;
int i = 1;
//输出PID号
printf("parent pid=%d\r\n",getpid());
ret = vfork();
if(ret == 0)
{
i++;
sleep(1);
//输出PID号
printf("child pid=%d\r\n",getpid());
printf("child i=%d\r\n",i);
i++;
exit(0);
}
else
{
printf("hello\r\n");
printf("parent i=%d\r\n",i);
}
}
5、摧毁进程
exit
功能:
正常结束进程,并把参数返回给父进程,会将进程缓冲区的内容全部写回并关闭文件
头文件:
#include <stdlib.h>
函数原型:
void exit(int status);
函数参数:
int status:用于返回数据给父进程,一般填写为0(不传递参数的时候)
函数返回值:无
_exit
功能:
正常终止目前正在执行的进程,但是不会将缓存区的内容写回
头文件:
#include <unistd.h>
函数原型:
void _exit(int status);
函数参数:
int status:用于返回数据给父进程,一般填写为0(不传递参数的时候)
函数返回值:无
示例:利用exit函数,观察对应的现象。
现象:
代码:
#include <stdlib.h>
int main(int argc,char *argv[])
{
printf("hello");
sleep(1);
exit(0);
return 0;
}
exit()和_exit()区别
exit和_exit函数,都可以结束对应的进程
exit函数,可以将对应缓存里面的内容写回
_exit函数,不能将对应缓存里面的内容写回
6、等待进程
wait
功能:
父进程为了等待对应的子进程运行结束
头文件:
#include <sys/types.h>
#include <sys/wait.h>
函数原型:
pid_t wait(int *status);
函数参数:
int *status:就是子进程退出的时候,传递过来的数据->NULL
函数返回值:
成功 对应退出子进程的PID号
失败 -1
补充:
WIFEXITED(status) --->主要是为了判断对应的子进程,是否正常退出(正常退出返回1)
WEXITSTATUS(status) --->主要是为了获取到子进程退出的时候,传递过来的参数
示例1:利用wait函数,等待对应的子进程运行结束
现象:
代码:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int ret,sta;
int i = 1;
//输出PID号
printf("parent pid=%d\r\n",getpid());
ret = fork();
if(ret == 0)
{
i++;
sleep(1);
//输出PID号
printf("child pid=%d\r\n",getpid());
printf("child i=%d\r\n",i);
i++;
exit(0);
}
else
{
//等待子进程运行完
pid_t PID = wait(NULL);
printf("PID :%d\r\n",PID);
printf("parent i=%d\r\n",i);
}
示例2:利用exit函数参数的传递,传递给对应的父进程,输出观察是否一致。
现象:
代码:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int ret,sta;
int i = 1;
//输出PID号
printf("parent pid=%d\r\n",getpid());
ret = fork();
if(ret == 0)//子进程
{
i++;
sleep(1);
//输出PID号
printf("child pid=%d\r\n",getpid());
printf("child i=%d\r\n",i);
i++;
exit(123);
}
else//父进程
{
//等待子进程运行完,并且接收子进程返回的PID
pid_t PID = wait(&sta);
printf("PID :%d\r\n",PID);
printf("parent i=%d\r\n",i);
//验证exti退出是否成功
int p = WIFEXITED(sta);
if(p == 1)
printf("EXIT success\r\n");
//验证exti返回的参数
int val = WEXITSTATUS(sta);
printf("val=%d\r\n",val);
//退出函数
exit(0);
}
waitpid
功能:
等待对应的子进程结束---可以指定对应的PID号
头文件:
#include <sys/types.h>
#include <sys/wait.h>
函数原型:
pid_t waitpid(pid_t pid,int *status,int options);
函数参数:
pid_t pid:指定等待的进程的PID号—子进程的PID号
如果pid = -1 等待任何子进程结束 相当于wait()
如果pid > 0 对应等待相应子进程结束-->在父进程中,返回新创建的子进程的PID
int *status:就是子进程退出的时候,传递过来的数据
int options:表示选项参数,一般设置为0
函数返回值:
成功 对应退出子进程的PID号
失败 -1
示例1:调用一下waitpid函数,观察一下对应的现象
现象:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int ret,sta;
int i = 1;
//输出PID号
printf("parent pid=%d\r\n",getpid());
ret = fork();
if(ret == 0)//子进程
{
i++;
sleep(1);
//输出PID号
printf("child pid=%d\r\n",getpid());
printf("child i=%d\r\n",i);
i++;
exit(123);
}
else//父进程
{
//等待子进程运行完,并且接收子进程返回的PID
pid_t PID = waitpid(-1,&sta,0);
printf("PID :%d\r\n",PID);
printf("parent i=%d\r\n",i);
//验证exti退出是否成功
int p = WIFEXITED(sta);
if(p == 1)
printf("EXIT success\r\n");
//验证exti返回的参数
int val = WEXITSTATUS(sta);
printf("val=%d\r\n",val);
//退出函数
exit(0);
}
}
示例2:改进之后,waitpid函数,观察指定进程退出的参数信息
现象:
代码:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int ret,sta;
int i = 1;
//输出PID号
printf("parent pid=%d\r\n",getpid());
ret = fork();
if(ret == 0)//子进程
{
i++;
sleep(1);
//输出PID号
printf("child pid=%d\r\n",getpid());
printf("child i=%d\r\n",i);
i++;
exit(123);
}
else//父进程
{
//在父进程中,返回新创建的子进程的PID
printf("childpid=%d\r\n",ret);
//等待子进程运行完,并且接收子进程返回的PID
pid_t PID = waitpid(ret,&sta,0);
printf("PID :%d\r\n",PID);
printf("parent i=%d\r\n",i);
//验证exti退出是否成功
int p = WIFEXITED(sta);
if(p == 1)
printf("EXIT success\r\n");
//验证exti返回的参数
int val = WEXITSTATUS(sta);
printf("val=%d\r\n",val);
//退出函数
exit(0);
}
}
wait()和waitpid()的区别
相同点:都可以等待对应的子进程运行结束,可以获取对应的子进程退出的时候参数信息
不同点:waitpid函数可以等待指定子进程结束。