Linux:进程退出/等待/替换,这些进程控制都学会了吗?

  在本篇博客中将深入学习总结进程控制,在学习这一部分时,主要分为了以下一个问题:

  • 进程创建,用户要创建新进程,如何创建呢?
  • 进程是如何退出进行终止的?
  • 进程等待,如何进行父子进程的交互?
  • 进程程序时如何进行替换的?

进程控制概念

进程控制是一种功能,它能完成一系列进程的相关操作,创建进程终止已完成进程,将发生异常而无法运行的进程置于阻塞状态,负责进程运行中的状态转换等工作。

进程拥有独立性,各有各的虚拟地址空间,映射各自数据存储,进程之间没有交叉关系,不会受到其他进程运行的影响,保证了进程稳定运行。

进程创建

  进程创建就是创建一个pcb,因为pcb在Linux下是一个task_struct结构体,这个结构体通过系统调用的接口实现创建。fork/vfork函数是进程创建时两个重要函数。

pid_t fork(void);

返回值:
对于父进程:返回子进程的pid
对于子进程:返回0

通过复制父进程创建子进程,父子进程间数据独有代码共享

pid_t vfork(void);

创建子进程,但是一个父进程使用vfork创建子进程之后,vfork的调用,并不会立即返回(通常称之为阻塞父进程),而是让子进程先运行,直到子进程退出,或者进行程序替换之后,父进程才能运行。

这里写了一个简单的vfork的调用过程做以演示:
在这里插入图片描述
  为了更清楚的看到子进程先于父进程进行运行的过程,这里对子进程进行了处理,让它停留3秒,看看子进程没有运行时父进程是否会先进行调用,下面就是代码演示的结果:
在这里插入图片描述
  这里我们可以观察到在子进程没有调用时,父进程不会先运行,但是这个过程好像发生了死循环一直在运行,这里并不是程序发生了死循环
说明:
1.vfork创建子进程时,父子进程共用了父进程的虚拟地址空间,使用了同一个栈,如果父子进程同时运行就会造成调用栈混乱,因此要让子进程先运行,直到子进程退出或职责程序替换后有了自己的程序地址空间(原有的地址空间中子进程的调用已经出栈了);
2.vfork创建的子进程如果发生了程序替换,就会开辟新的物理内存空间,建立新的内存映射关系,进程中的每个变量,各个数据使用的都是虚拟地址,用的时候分配一块物理内存;
3.vfork创建的子进程,不能再main函数中使用return进行退出,因为子进程使用return退出时释放了所有的资源,父进程运行的时候资源是错误的。

在创建进程时,我们使用到了一种写时拷贝技术:

写时拷贝技术:创建子进程后,与父进程映射访问同一跨物理内存,当物理内存中数据即将发生改变时,重新为子进程开辟物理内存,实现拷贝数据。写时拷贝技术是为了避免直接给子进程开辟空间,拷贝数据,而进程不使用,降低了进程创建效率,造成内存冗余数据

进程终止

进程终止:实现进程的退出

进程退出的场景

  1. 正常退出
  2. 异常退出

进程常见的退出方法

  • 正常终止(可以通过echo $? 查看进程退出码)

1.main函数中的return;(普通函数中的return只能退出函数,不能退出进程)
2.调用exit函数;(可以在程序的任意位置退出一个进程)
3.调用_exit:(可以在程序的任意位置退出一个进程)

  • 异常退出

ctrl + c,信号终止

_exit函数和exit函数

void exit(int status)

void _exit(int status)

status定义了进程的终止状态,父进程通过wait 进行获取值;
区别与联系:

  • _exit是系统调用接口,exit是库函数;
  • exit最后也会调用_exit,只不过会进行缓冲区的刷新;
  • _exit会直接释放资源,不会刷新缓冲区;

在这里插入图片描述
关于刷新缓冲区的解释说明:printf打印数据实质是将数据交给显示器进行显示,其效率低下,所以对这一过程进行了优化,将数据放入内存的缓冲区,积累成为一个大数据,并不是直接的将数据交给显示器,在程序退出刷新缓冲区时才将所有的数据交给显示器,\n的除了换行的作用,它还可以进行刷新缓冲区。

进程等待

概念
父进程等待子进程退出,获取退出子进程返回值;释放退出子进程的所有资源,避免出现僵尸进程。

int wait(int *status);

函数功能:处理退出的子进程,如果调用这个接口时没有子进程已经退出,则会使父进程阻塞等待,直到子进程退出.
返回值:成功返回等待进程,失败返回-1;
status:输出型参数,用于获取退出子进程的返回值,不需要可以将其设置为NULL

阻塞:为了完成一个功能,我们发起一个调用,但是若当前不具备完成功能的条件,则调用等待;
非阻塞:为了完成一个功能,我们发起一个调用,但是若当前不具备完成功能的条件,则调用立即报错返回。

int waitpid(int pid, int status, int option;

函数功能:也是处理退出的子进程,waitpid可以等待指定的子进程,也可以等待任意一个子进程。
pid:pid=-1,等待任意一个子进程;pid>0,等待进程ID与pid相等的子进程;
status:用于获取子进程退出的返回值,其中有一个较为特殊:
WNOHANG若pid指定的子进程没有结束,则waitpid()函数返回0,不进行等待;若正常结束,则返回子进程的ID;
返回值
①当正常返回时waitpid返回收集到的子进程的进程Id;
②当设置了WNOHANG,而调用waitpid发现没有已退出的子进程可以收集时,则返回0;
③当调用出错时,则返回-1;这是errno会被设置为相应的值指出错误所在;

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息;
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。
    与wait的不同之处
  • wait等待的是任意一个子进程的退出,waitpid还可以等待指定的进程;
  • wait是一个阻塞接口,如果没有子进程退出就会一直等待,waitpid可以默认阻塞,也可以设置为非阻塞;

获取子进程status
  该参数是一个输出型参数,由操作系统进行填充,如果传递NULL,表示不需要进程状态的退出信息,否则操作系统会根据该参数将子进程的信息反馈给父进程,在之后我们学习信号时会仔细讲解这写反馈的信息都代表了什么。
core dump是核心转储文件,用于在进程崩溃时存储记录当前位置发生的错误信息。
在这里插入图片描述

进程替换

概念
将当前进程的数据段和代码段进行调换,替换成为其他程序的数据段和代码段,并且更新堆栈信息。
  fork创建一个子进程,父子进程代码共享,数据独有,它们所干的事情都是相同的,但是通常的情况下,我们创建一个子进程的目的是让它运行调度一个新的程序,完成其他的任务,这个时候我们就需要用到程序替换。

exec函数族

   int execl(const char *path, const char *arg, ...);
   int execlp(const  char  *file,  const  char  *arg,...);
   int execle(const char *path, const char *arg, ..., char * const envp[]);
   int execv(const char *path, char *const argv[]);
   int execvp(const char *file, char *const argv[]);
   int execvpe(const char *file, char *const argv[],char *const envp[]);

path:新的程序路径名称,通过这个将新的程序加载到内存中,让当前的pcb调度程序的运行;
arg/…:表示程序的运行参数,逐个赋予,最终以NULL结尾

函数功能

  • 这些函数如果调用成功则加载新程序从启动代码开始执行,不在返回;
  • 调用出错则会返回-1,调用成功没有返回值;

下面写了一小段代码做以exec函数的演示:
在这里插入图片描述
给一个进程替换一个新的要调度运行的程序,并且因为这个进程调度的程序已经被替换,因此当运行完毕新的程序后就会退出;原来的程序在程序替换以后的代码都不会被运行到(替换后相当于已经没有当前的代码了,只有新的程序)
命名理解

l:可变参数列表;
v:以字符指针数组的方式进行传递参数;
p:有p自动搜索环境变量PATH;
e:表示有自己维护的环境变量。

l和v的区别:在于程序运行参数的赋予方式不同,v是字符串指针数组,l逐个通过参数进行赋予;
有无p的区别:新的程序文件是否需要带路径;
有无e的区别:在于是否需要重新定义新的环境变量或者是使用原有环境变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值