Linux进程控制

目录

 进程终止

一、进程退出场景

二、进程常见退出方法

进程等待

为什么有进程等待

什么是进程等待

怎么做到进程等待

wait

waitpid

进程程序替换

现象:单进程版--见见猪跑

原理

指令 

向子进程传递环境变量

拓展

多程序并发

 用户程序的调用

脚本的调用 test.sh

python的调用 test.py


 进程终止


 
在 Linux 编程的世界里,进程的生命周期管理是极为关键的内容,而进程终止作为其中一个重要环节,有着丰富的细节和不同的实现方式。今天,我们就深入探讨 Linux 进程终止的相关知识,带你全面了解进程退出场景、常见退出方法以及背后的原理 。
 

一、进程退出场景
 


(一)代码运行完毕,结果正确(不关心)
 
(二)代码运行完毕,结果不正确
 
虽然代码完整地执行了,但由于算法逻辑瑕疵、数据处理失误等原因,导致最终结果与预期不符。例如,在一个排序程序中,因排序算法实现有误,输出的序列并非有序的,进程虽然运行结束,但结果不正确,这提示我们需要去排查程序的逻辑错误。
 
(三)代码异常终止
 

这种情况就比较糟糕了,进程在运行过程中遇到了意外状况,无法继续正常执行而被迫终止。常见的如访问了非法内存地址( segmentation fault )、除零错误( floating point exception )等,系统会发送相应信号,让进程异常退出,以此来保护系统的稳定性和安全性 。此时不关心退出码,更关心为什么抛异常(进程收到对应信号)

进程是否正确可以通过return返回值进行判断,

返回值表示含义如下


 


二、进程常见退出方法
 


(一)正常终止
 
正常终止的进程,我们可以通过在终端执行  echo $?  命令来查看进程退出码,退出码能反映进程的退出状态。主要有以下几种方式:
 
1. 从 main 返回
 
在 C 语言程序中, main  函数是程序的入口,当  main  函数执行到  return  语句时,进程会正常终止。 return  的值会作为进程的退出码,父进程可以通过相关机制获取这个值,以此了解子进程的运行结果。例如:
 

#include <stdio.h>
int main() {
    printf("This is a simple program.\n");
    return 0; // 进程正常终止,退出码为 0
}
 
 


这里  return 0  表示程序正常运行结束,若  return  其他非零值,一般可用来表示程序运行中出现了某种自定义的“异常”情况,方便父进程做相应处理。
 
2. 调用 exit
 
 exit  函数是一个常用的进程正常终止函数,其声明为:

 
#include <stdlib.h> 
void exit(int status);

(注意:头文件是  <stdlib.h>  ,不是文中提到的  <unistd.h>  ,文中此处可能笔误 )
 exit  函数在使进程终止前,会做一系列清理工作:
3. _exit
 
 _exit  函数的声明为:
 

#include <unistd.h>
void _exit(int status);


 
 
参数  status  定义了进程的终止状态,父进程通过  wait  系列函数来获取该值。需要注意的是,虽然  status  是  int  类型,但仅有低 8 位可以被父进程所用。所以当  _exit(-1)  时,在终端执行  echo $?  会发现返回值是 255(因为 -1 的补码在低 8 位表示为 255 )。 _exit  函数相对比较“直接”,它不会像  exit  那样做复杂的清理工作,只是简单地终止进程,关闭进程相关的资源,把状态返回给父进程。
 
我们来看一个对比  exit  和  _exit  行为的实例:


示例 1:使用 exit 的情况
 

#include <stdio.h>
#include <stdlib.h>
int main() {
    printf("dlihmr"); 
    exit(0); 
}


这里  printf("hello");  的输出内容,因为  exit  会刷新缓冲区,所以 “hello” 会被输出到终端。
 
示例 2:使用 _exit 的情况
 

#include <stdio.h>
#include <unistd.h>
int main() {
    printf("dlihmr"); 
    _exit(0); 
}


 
 由于  _exit  不会刷新缓冲区, printf  输出的 “hello” 还在缓冲区中,没来得及输出到终端,进程就终止了,所以看不到输出内容 。缓冲区一定不在内核,而是在用户空间。
 echo $?是返回上级最近进程的退出码

errno的使用

errno  是记录系统最后一次错误代码的机制 ,它本质是一个 int  类型的值,在 <errno.h>  头文件中定义 。当 Linux C 等环境下的 API 函数发生异常时,通常会将 errno  变量(使用时需包含 errno.h  头文件 )赋予一个整数值,不同数值对应不同错误含义,借助它能推测程序出错原因,是调试程序的重要手段。比如调用 open  函数打开文件失败,可通过查看 errno  知晓是文件不存在、权限不足等具体问题 。


(二)异常退出


 
最常见的就是我们在终端按下  ctrl + c  ,这会向进程发送  SIGINT  信号(中断信号 ),进程接收到这个信号后会异常终止。当然,系统还有其他多种信号可以让进程异常退出,比如  SIGSEGV (段错误信号 )、 SIGFPE (浮点异常信号 )等,当进程触发相应的错误条件时,系统会发送这些信号,促使进程异常退出,以此来告知用户或父进程,程序运行出现了严重问题 。
 结合kill演示

给予对应的kill退出指令,进程会按要求结束返回错误码
三、return 退出补充说明
 
 return  是在函数中常用的退出方式,在  main  函数中,执行  return n  等同于执行  exit(n)  。因为调用  main  函数的运行时函数(可以简单理解为系统启动  main  执行的相关底层逻辑 )会将  main  的返回值当做  exit  的参数,进而按照  exit  的流程去终止进程。例如:
 

#include <stdio.h>
int main() {
    printf("Program is ending...\n");
    return 2; // 等同于 exit(2),进程终止,退出码为 2
}


 这样的设计让我们在编写简单程序时,使用  return  就能方便地实现进程的正常终止,并且传递退出码 。
 
四、总结
 
进程终止在 Linux 进程管理中占据重要地位,不同的退出场景对应着程序不同的运行结果状态。而  return (在  main  中 )、 exit 、 _exit  等不同的退出方法,有着各自的特点和适用场景。 _exit  简单直接,适合对清理要求不高、追求快速终止的情况; exit  因为具备丰富的清理机制,在一般的应用程序中,当需要妥善处理资源释放、保证数据完整性时,更为常用; return  则在  main  函数中提供了简洁的退出方式,和  exit  有着紧密的联系 。
 
 

进程等待

为什么有进程等待

僵尸进程无法杀死,需要进程等待来消灭他,进而解决内存泄漏问题--必须解决的

我们要通过进程等待,获得子进程退出情况--知道我们给子进程的任务,他完成的怎么样--不必要

什么是进程等待

通过调用wait/waitpid,来进行堆子进程进行状态检测与回收功能!


怎么做到进程等待

代码:

原理:

 我们发现,子进程在运行结束后成为僵尸进程,状态Z。

父进程通过调用wait/waitpid,来进行僵尸进程的回收问题。

wait

 

运行结果

我们发现5s后子进程变为僵尸进程,当再过5s父进程结束wait等待回收子进程pid,子进程成功释放。

因此,进程等待是必须的(回收僵尸进程,避免内存泄漏)

wait是等待任意子进程

父进程执行for循环,子进程内部创建

回收操作 

子进程运行结束变为僵尸进程之后成功回收 

如果子进程一直不死,那么父进程也不死,默认wait时,调用这个系统调用时不返回,默认叫做阻塞状态。(类似于scanf等待输入操作)

waitpid

wait的功能waitpid子集 

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
 当正常返回的时候waitpid返回收集到的子进程的进程ID;
 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
 pid:
 Pid=-1,等待任一个子进程。与wait等效。
 Pid>0.等待其进程ID与pid相等的子进程。
 status:
 WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)


 WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
 options:
 WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。(见下文第三个参数

第一个参数:等谁

第二个参数:获取进程退出结果(输出型参数

操作系统会把id进程推出信息拷贝到status。上文exit(1)

 

但是status结果是256 

子进程退出场景有三种,父进程期望获得子进程什么信息?

1.子女进程代码是否异常

2.没有异常,结果对吗?exitcode,不对为什么?通过退出码表示

statu返回256原因:

status36位,前8位表示进程是否出异常,收到什么样的信号,第8位cure dump

信号表,从1开始,若1-7位是不是0,说明 代码是否跑完

8-16表示退出状态,及退出码。1+0000 0000==256。通过statu反应代码是否异常。

父进程想获取子进程信息为什么需要系统调用,直接传全局变量不好吗??(进程独立性)。

位操作查看(区分256和1)

代码

结果 

想让waitpid错误返回示例(父进程等待的子进程不是自己的) 

第三个参数 (设置等待方式)

--0:阻塞:子进程退出不满足条件,父进程等待(阻塞,不被调用)

非阻塞轮询。(父进程等待过程中可以做别的事,不会一直扽等子进程)

演示:

 

进程程序替换

现象:单进程版--见见猪跑

        现象:自己的程序可以把系统程序封装并运行

原理

进程替换后没有产生了新进程 

观察发现进程运行至回收后pid并没有发生变化

另外子进程发生程序替换后,子进程后续代码不再运行 

原因就是进程替换后原有代码已经被覆盖,爱不再存在

linux中形成的可执行程序,有是有格式的,可执行程序就在磁盘对应的表头,类似于字符串加载时之传输首字符地址

指令 

系统调用,下方6个接口都会调用他

库函数 

execl(list)参数传输形式近似链表 

execlp(PATH,函数自己会在默认的PATH环境变量中查找)不用自己提供路径

execv(vector)

验证命令行参数

test.c调用下方命令行参数

mycode.cpp下方命令行参数

代码编译结果

验证环境变量 

上述代码没传环境变量,但test.c却得到了

环境变量在替换过程中不会被覆盖

execvp(vector) 

 execl类似于加载器,将资源从磁盘加载到内存

execle   execvpe(指定环境变量下运行env通过下面拓展理解

向子进程传递环境变量

方式一(向父进程导入环境变量,子进程自然新增环境变量)

方式二(putenv单独向子进程加环境变量

父进程不受影响

方式三

系统默认传参(environ新增)

他类似于父亲留给儿子的便条

传消息(告诉子进程环境变量的存放路径)

改行为(让子进程以特定语言运行)

灵活适配(父进程按需给子进程定制变量)

子进程启动时会“继承”父进程的环境变量,这样不用修改子进程代码,就能让它按父进程的需求工作。

声明

 依然成功获取到环境变量

用户传参自定义环境变量(覆盖环境变量,非追加)

覆盖环境变量

execle 

拓展

execl 可执行系统命令也可执行用户命令

c++后缀.cpp .cc .cxx

多程序并发

 连续执行

 用户程序的调用

          路径              执行方式

脚本的调用 test.sh

bash test.sh调用,./test(不行) 

.复杂化

 bash test.sh同样可以调用

 调用使用

结果

python的调用 test.py

 python3  test.py 调用

  调用使用

 

 结果

 程序和脚本能够跨语言执行原因在于他们运行起来都是进程,只要他变成进程,都可以互相调用

评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值