一、进程相关概念
1、什么是进程?
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
掐头去尾就是进程是一个程序的一次运行活动。
进程与程序的区别是:进程是动态的,而程序是静态的。
输入命令:
ps -aux|grep 名字
可通过查找对应名字来查看进程相关信息。
使用top指令查看。直接在终端输入top。
2、什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似于身份证。
pid=0称为交换进程,作用:进程调度。
pid=1称为init进程, 作用:系统初始化。
编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符。
函数用法:
#include
#include
pid_t getpid(void);
pid_t getppid(void);
代码:
#include
#include
#include
int main()
{
pid_t pid;
pid=getpid();
printf("%dn",pid);
return 0;
}
3、什么叫父进程,什么叫子进程?
进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。
二、编程实现
查看man手册,输入命令:
man fork
fork函数用法:
pid_t fork(void);
fork函数调用成功,返回两次。返回为非负数时。函数返回值为子进程的ID号。
返回值
描述
代表当前进程为子进程
非负数
代表当前进程为父进程
fork会创建一个子进程,复制一份和父进程一样的代码,子进程和父进程会继续运行在fork后的内容。
子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父、子进程并不共享这些存储空间部分。父、子进程共享正文段。
代码:
int main()
{
pid_t pid;pid2
pid=getpid();
printf("before fork:pid=%dn",pid);
fork();
pid2=getpid();
if(pid==pid2)
{
printf("this is father processn");
}else{
printf("this is child process ,child:pid=%dn",getpid());
}
return 0;
}
运行结果:
在fork之后,父子进程都会运行,会走不同分支,通过getpid()来获取当前进程进行区分。
通过fork函数返回值来区分父进程和子进程运行,验证代码段父子进程是共享的,但父子进程中数据改变互不影响。同时验证fork返回为非负数时,函数返回值为子进程的ID号。代码如下:
int main()
{
pid_t pid;
int a=10;
printf("before fork:a=%dn",a);
pid=fork();
if(pid==0)
{
printf("this is child process,a=%d,child process:pid=%dn",a,getpid());
}
else{
a=a+10;
printf("this is parent process,a=%d,pid=%dn",a,pid);
}
return 0;
}
运行结果:
通过运行结果可知,在父进程改变a的值,子进程a的值不会改变,进程会互不干扰运行。
了解一下:
新的版本:写时copy,只有在子进程中对a的值作改变时,才copy a的地址。
旧的版本:全copy,子进程会把父进程所有代码段都copy。
三、进程实际应用场景
1、fork创建子进程的一般目的
一个父进程希望复制自己,使父子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
代码如下:
int main()
{
pid_t pid;
int a;
while(1)
{
printf("Please input a data:n");
scanf("%d",&a);
if(a==1)
{
pid=fork();
if(pid>0)
{
}
else if(pid==0)
{
while(1)
{
printf("do net request,pid=%dn",getpid());//子进程进行处理客户要求
sleep(3);
}
}
}else{
printf("do not requestn");
}
}
return 0;
}
三、vfork函数
1、vfork与fork区别
区别一:vfork直接使用父进程存储空间,不拷贝。
区别二:vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
如下:
不加exit(),保证了子进程先运行。加了exit(),子进程被exit()终止,父进程才能运行。
子进程修改某一变量的值,父进程某一变量的值也会同步改变。如代码中,在子进程中修改cnt值,在父进程中cnt值也随着改变,体现了区别一。
代码:
int main()
{
pid_t pid;
int cnt=0;
pid=vfork();
if(pid==0)
{
while(1)
{
cnt++;
printf("this is child process,cnt=%dn",cnt);
sleep(1);
if(cnt==3)
exit(0);
}
}
else{
while(1)
{
printf("this is parent process,cnt=%dn",cnt);
sleep(1);
}
}
return 0;
}
运行结果:
四、进程退出
正常退出:
1、进程调用exit(),标准C库;
2、进程调用_exit()或者_Exit(),属于系统调用。
异常退出:
1、调用abort;
2、当进程收到某些信号时,如Ctrl+C
不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等等。
exit函数用法:
void exit(int status);
之前exit(0)表示退出且状态值(status)为0,
The exit() function causes normal process termination and the least
significant byte of status (i.e., status & 0xFF) is returned to the
parent (see wait(2)).
利用exit函数导致正常退出,会给父进程返回一个状态值,这个状态值可通过wait/waitpid函数进行收集。
五、父进程等待子进程退出
1、收集子进程的退出状态
使用wait和waitpid函数,查看man手册,输入命令:
man wait
函数用法:
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数说明:
wstatus:是一个整型数指针。非空:子进程退出状态放在它所指向的地址中,空:不关心退出状态。
状态值(wstatus 整型数)必须用下面的宏进行进行解析。
函数返回值:
wait函数:成功返回为子进程的ID号,失败返回-1。
waitpid函数:成功时,返回其状态已更改的子进程ID;如果指定了 WNOHANG,并且存在 pid 指定的一个或多个子任,但尚未更改状态,则返回 0。出错时,返回 -1。
对于waitpid函数中pid(fork函数返回值)参数的作用解释如下:
pid=-1:等待任一子进程。就这一方面而言,waitpid与wait等效。
pid>0:等待其进程ID与pid相等的子进程。
pid=0:等待其组ID等于调用进程组ID的任一子进程。
pid
waitpid中options作用:
常量
说明
WCONTINUED
若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态。
WNOHANG
若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,则此时其返回0(状态值)。
WUNTRACED
若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告过,则返回其状态,WIFSTOPPED宏确定返回值是否对应于一个暂停子进程。
该参数没有作用,只有pid参数起作用。
两个函数说明:
wait和waitpid区别:
wait使调用则阻塞,waitpid有一个选项,可以使调用者不阻塞。
检查wait和waitpid所返回的终止状态的宏
宏
说明
WIFEXITED(wstatus)
若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(wstatus),取子进程传递给exit,_exit或_Exit参数的低8位
WIFSIGNALED(wstatus)
若为异常终止子进程返回的状态,则为真。对于这种情况,可执行WTERMSIG(wstatus),取使子进程终止的信号编号。另外,有些实现(非Single UNIX Specification)定义宏WCOREDUMP(wstatus),若已产生终止进程的core文件,则返回真。
WIFSTOPPED(wstatus)
若为当前暂停子进程的返回的状态,则为真。对于这种情况,可执行WSTOPSIG(wstatus),取使子进程暂停的信号编号。
WIFCONTINUSED(wstatus)
若在作业控制暂停后已经继续的子进程返回了状态,则为真。(POSIX.1的XSI扩展,仅用于waitpid。)
僵尸进程:子进程退出状态不被父进程收集,变成僵尸进程。
代码如下:
int main()
{
pid_t pid;
int cnt=0;
pid=vfork();
if(pid==0)
{
while(1)
{
cnt++;
printf("this is child process,cnt=%d,pid=%dn",cnt,getpid());
sleep(1);
if(cnt==3)
exit(0);
}
}
else{
while(1)
{
printf("this is parent process,cnt=%d,pid=%dn",cnt,getpid());
sleep(1);
}
}
return 0;
}
运行结果:
可以看出子进程pid=40726, 父进程pid=40725,
程序运行时,再打开一个终端,输入命令:
ps -aux|grep vfork
运行结果:
可以看出,S+表示正在运行的状态,子进程后有Z+表示是僵尸进程,退出状态没有被收集。
在父进程中用wait/waitpid函数等待收集子进程退出的状态值,子进程就不会变成僵尸进程。代码如下:
int main()
{
pid_t pid;
int cnt=0;
pid=fork();
int status;
if(pid==0)
{
while(1)
{
cnt++;
printf("this is child process,cnt=%d,pid=%dn",cnt,getpid());
sleep(1);
if(cnt==3)
exit(5);
}
}
else{
while(1)
{
wait(&status);
printf("child quit,child status=%d,%dn",WIFEXITED(status),WEXITSTATUS(status));
printf("this is parent process,cnt=%d,pid=%dn",cnt,getpid());
sleep(1);
}
}
return 0;
}
运行结果:
waitpid可以自己测试一下。
六、孤儿进程
孤儿进程:父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”。
linux避免系统存在过多的孤儿进程,init进程(字符界面)收留孤儿进程,变成孤儿进程的父进程。在图形界面即ubuntu桌面版,systemd收留孤儿进程。
代码如下:
int main()
{
pid_t pid;
int cnt=0;
pid=fork();
if(pid>0)
{
printf("this is father process,pid=%dn",getpid());
}
else if(pid==0){
while(1)
{
printf("this is child process,pid=%d,my father pid=%dn",getpid(),getppid());
cnt++;
if(cnt==10)
{
exit(0);
}
sleep(1);
}
}
return 0;
}
运行结果:
通过运行结果可知孤儿进程的父进程ID号不为1,这是为什么呢?
通过查询资料,linux系统中子进程只要有一个进程统一负责领养就可以了,在ubuntu22(桌面版)中,systemd负责领养。
用到的命令:
pidof:用于查找指定名称且运行的进程的ID号。
普通用法:pidof 进程名称
发现ID为13987,与上面领养的父进程ID号一样。
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: lzxmw777