Linux进程控制——Linux进程终止

简介: Linux进程控制——Linux进程终止

前言:前面了解完前面的Linux进程基础概念后,我们算是解决了Linux进程中的一大麻烦,现在我们准备更深入的了解Linux进程——Linux进程控制!

我们主要介绍的Linux进程控制内容包括:进程终止,进程等待与替换,进程程序替换!



本篇主要内容:

重识进程创建

进程退出场景

错误码和退出码


1. 重识进程创建

1.1 fork函数

在我们之前提到过,创建进程使用的是fork函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>

pid_t fork(void);

// 返回值:自进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

fork函数返回值:

  • 子进程返回0,
  • 父进程返回的是子进程的pid

这些我们在之前都提到过,我们复习一下,今天我们重点要讲的是fork的写时拷贝


1.2 写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。

当父进程形成子进程之后,子进程写入,发生写时拷贝,重新申请空间,进行拷贝,修改页表(OS)

但是,我们怎么知道发生了写时拷贝呢?写时拷贝的内容都是由操作系统来完成的

  • 其实父进程创建子进程的时候首先将自己的读写权限,改成只读,然后再创建子进程,此时是操作系统在做,用户并不知道,而且用户可能会对某一数据进行写入,这时页表转换就会出现问题,操作系统就会介入,就触发了我们重新申请内存拷贝内容的策略机制

1.3 fork创建进程与进程终止

接下来我们看一下这段代码:

  #include<stdio.h>
  #include<unistd.h>
  #include<stdlib.h>
  
  #define N 10
  
  typedef void (*callback_t)(); // 函数指针
   
  void Worker()
  {
     int cnt = 10;
     while(cnt)
     {
         printf("i am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(),     cnt);
         sleep(1);
         cnt--;
     }
  }
 
  void createSubProcess(int n, callback_t cb)
  {
      
      int i = 0;
      for(i = 0; i < n; i++)
      {
          sleep(1);
          pid_t id = fork();
          if(id == 0)
          {
              printf("create child process success: %d\n", i);
              // child
              cb();                                                                      
              exit(0);
          }
      }
  }
  int main()
  {
      createSubProcess(N, Worker);
  
      // 只有父进程走到这里 
      sleep(100);
  
      return 0;
  }

而代码中的exit函数可以让进程终止

这段代码涵盖了fork创建进程与进程终止,让我们来执行以下

image.png

fork创建进程与进程的终止

下面将让我们真正来了解进程终止!


2. 进程终止

进程退出场景:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

前面二者都是正常退出,而最后则是异常退出


2.1 main的退出码

我们之前写代码中,main函数只能return 0吗?答案是肯定不是!

在多进程环境中,我们创建子进程的目的就是协助父进程办事,但是父进程怎么知道子进程把事情办得怎么样?所以父进程要知道子进程办的怎么样,就有了退出码,而main函数的返回值,就是进程的退出码!

返回 0 就表示成功,其他数字就表示进程失败的原因,每个不同的数字代表不同的原因!

我们可以通过函数来直接查看每个数字代表的意义:strerror

它可以返回描述错误码的字符串

#include<stdio.h>
#include<string.h>

int main()
{
  for(int i = 0; i < 200; i++)
  {
    printf("%d: %s\n", i, strerror(i));
  }
  return 0;
}

退出码 0 正好对应的是成功!

当我们134位置处时,发现已经没有错误信息了。

注意:错误码我们可以自己自定义!


main函数的退出码是可以被父进程获取的,用来判断子进程的运行结果

我们可以直接用指令查看进程的退出码:

指令:echo $?

int main()
{
  return 31;
}

我们在执行完,这段代码后,再用指令查看退出码

我们可以发现指令:echo $?返回的是上一个进程的错误码


2.2 C语言的错误码

错误码和退出码:

  • 错误码通常是衡量一个库函数或者是一个系统调用一个函数的调用情况
  • 退出码通常是一个进程退出的时候,他的退出结果
  • 二者都是当失败的时候,用来衡量函数、进程出错是的出错详细原因

我们用代码来测试一下:

#include<stdio.h>
#include<errno.h>

int main()
 {
  // errno 可以返回最近一次错误的错误码
    int ret = 0;
    printf("before: %d\n", errno);
    FILE *fp = fopen("csdn.txt", "r");
    if(fp == NULL)
    {
       printf("after: %d, error string: %s\n", errno, strerror(errno));                                                                                                               
       ret = errno;
    }
    return ret;
 }

一旦出现错误,错误码errno就会被系统自动赋值

可以通过错误码来判断代码运行的情况


2.3 进程异常终止

当我们的代码出异常了,也就是进程异常终止了,退出码还有意义吗?

// 当我们在运行这样的代码时

int a = 100;
a /= 0;
......
int *p = NULL;
*p = 100;
......

第一种情况: Floating point exception

第二种情况: Segmentation fault

当然不止这两个情况,但是它们都会让程序进程异常终止!

其实一旦程序出现了异常,操作系统就是通过信号的方式来杀掉这个进程!

而我们的前面两种情况正好对应了kill -8kill -11

我们来试试,通过使用信号能不能还原之前这几种错误情况

while(1)
{
  printf("i am a process, pid: %d\n", getpid());
  sleep(1);
}

image.png

通过信号终止进程

进程出异常本质是收到了对应的信号,因此一个进程是否出异常,我们只要看有没有收到信号即可


3. 总结拓展

拓展:exit与_exit的区别

在之前讲到正常退出时,我们有3种情况。

正常终止

  1. 从main返回
  2. 调用exit
  3. _exit

而exit与_exit两个有什么区别和联系呢?

首先他们二者都可以让进程终止,并且使用方法也一样,那他们到底有什么区别呢?我们用代码来一探究竟!

//代码一:
int main()
{
  printf("Hello");
  exit(0); 
}

......
//代码二:
int main()
{
  printf("Hello");
    _exit(0); 
}

代码一:

代码二:

为什么会出现这种情况呢?

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

printf打印如果不使用\n换行的话,数据会被存储到缓冲区里,,exit函数会帮助我们
刷新缓冲区的数据,然而_exit函数不会。因为exit函数在调用exit之前将所有缓存数据都写入了,所以在终止进程时,会将数据打印在屏幕上!

综上:

  • 1.exit 是库函数,exit是系统调用
  • 2.exit终止进程的时候,会自动刷新缓冲区。 exit终止进程的时候,不会自动刷新缓冲区

总结

进程终止相对于其他的进程控制来说,是很好理解的。我们主要介绍了进程终止,在明白错误码和退出码之后,相信能理解父进程是怎么得到子进程信息的。让我们期待下一节——进程等待与替换!

谢谢大家支持本篇到这里就结束了


目录
相关文章
|
16天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
43 1
|
5天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
45 13
|
12天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
19天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
1月前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
137 4
linux进程管理万字详解!!!
|
25天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
77 8
|
1月前
|
网络协议 Linux 虚拟化
如何在 Linux 系统中查看进程的详细信息?
如何在 Linux 系统中查看进程的详细信息?
68 1
|
1月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
71 4