【Linux】进程控制深度了解(上)

简介: 【Linux】进程控制深度了解(上)

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等

> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:熟练掌握Linux下的进程控制

> 毒鸡汤:在等待的日子里,刻苦读书,谦卑做人,养得深根,日后才能枝叶茂盛。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕  



🌟前言

       最早的时候我们学习了进程的状态,进程优先级和进程切换,当时不把进程控制加在里面,这里我们单独把它拉出来讲解,学习完本章对进程的板块算是熟练掌握了,咱们话不多说,直接进入今天的主题:【Linux】进程控制深度了解。



⭐主体

我们从以下学习【Linux】进程控制深度了解🥰🥰。



🌙进程创建

💫初识fork函数

  • fork函数的作用:是从已存在进程中创建一个新进程,新进程为子进程,而原进程为父进程。
  • fork函数的返回值:在子进程中返回 0 ,父进程中返回子进程pid,子进程创建失败返回 1 。


#include <unistd.h>
pid_t id = fork(void);


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

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


举个栗子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
 
int main(void)
{
  pid_t pid;
  printf("Before: pid is %d\n", getpid());
  if ((pid = fork()) == -1)
  {
    perror("fork()");
    exit(1);
  }
  printf("After:pid is %d, fork return %d\n", getpid(), pid);
  sleep(1);
  return 0;
}


运行结果:



可以看到fork前只输出了一次,fork之后输出了两次。


过程分析:

  • Before是由父进程打印的。
  • 调用fork函数之后打印的两个After,则分别由父进程和子进程两个进程执行。
  • fork之前父进程独立执行,而fork之后父子两个执行流分别执行。


注意: fork之后,父进程和子进程谁先执行完全由调度器决定。


💫fork函数返回值

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


fork函数为什么给子进程返回0,给父进程返回的是子进程的PID?


       一个父进程可以创建多个子进程,而一个子进程只能有一个父进程,因此对于子进程来说父进程是不需要被标识的,而对于父进程来说,子进程是需要标识的,因为父进程创建子进程的目的是让其指向对于的任务,只有直到了各个子进程的PID,才能更有效率的工作。


为什么fork函数有两个返回值?


       父进程调用fork函数后,为了创建子进程,fork函数内部会进行一系列复杂的操作,包括创建子进程PCB,虚拟地址空间,创建子进程对应的页表等等。子进程创建完毕后,操作系统还需要将子进程的进程控制块添加到系统进程列表中。



       在fork函数内部执行return语句之前,子进程就已经创建完毕了,那么之后的return语句不仅父进程需要执行,子进程也同样需要执行,这就是fork函数有两个返回值的原因。


💫写时拷贝

  • 当子进程刚刚被创建时,子进程和父进程的数据和代码是共享的。
  • 此时父子进程的代码和数据通过页表映射到物理内存的同一块空间。
  • 当父进程或子进程需要修改数据时,才将父进程的数据在内存当中拷贝一份,再进行修改。



1、为什么数据要进行写时拷贝?

多进程运行需要独享各种资源,多进程运行期间是互不干扰,不能让子进程的修改影响到父进程。


2、为什么不在创建子进程的时候就进行数据的拷贝?

子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间。


3、代码会不会进行写时拷贝?

90%的情况下是不会的,但这并不代表代码不能进行写时拷贝,例如在进行进程替换的时候,则需要进行代码的写时拷贝。


问题1. 地址空间不隔离

所有程序都直接访问物理内存,程序使用的物理空间不是相互隔离的。万一进程越界进行非法操作,这样是非常不安全的。


问题2. 内存使用效率低

没有有效的内存管理机制,通常执行一个程序时,监控程序需要将其整个程序装入内存然后开始执行。


问题3. 程序运行的地址不确定

因为每次需要装入运行时,我们都需要给他分配一块足够大的物理空间,而这个物理空间是不确定的。


💫fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。


💫fork调用失败原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制


🌙进程终止

💫进程退出场景

  • 代码运行完毕,结果正确。
  • 代码运行完毕,结果不正确。
  • 代码异常终止(进程崩溃)。


💫进程退出码

  • 我们都知道main函数是代码的入口,但实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,例如在VS2013当中main函数就是被一个名为__tmainCRTStartup的函数所调用,而__tmainCRTStartup函数又是通过加载器被操作系统所调用的,也就是说main函数是间接性被操作系统所调用的。
  • 既然main函数是间接性被操作系统所调用的,那么当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回,我们一般以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,这就是为什么我们都在main函数的最后返回0的原因。
  • 当我们的代码运行起来就变成了进程,当进程结束后main函数的返回值实际上就是该进程的进程退出码,我们可以使用echo $?命令查看最近一次进程退出的退出码信息。


举个栗子1:

#include <stdio.h>
 
int main()
{
  printf("hello linux\n");
  return 0;
}


使用指令查看进程退出码1:

echo $?



过程分析1:

为什么以0表示代码执行成功,以非0表示代码执行错误?


因为代码执行成功只有一种情况,成功了就是成功了,而代码执行错误却有多种原因,例如内存空间不足、非法访问以及栈溢出等等,我们就可以用这些非0的数字分别表示代码执行错误的原因。


C语言当中的strerror函数可以通过错误码,获取该错误码在C语言当中对应的错误信息:


举个栗子2:

#include <stdio.h>
#include <string.h>
 
int main()
{
  int i = 0;
  for(i = 0;i < 100;i++)
  {
    printf("%d:%s\n",i,strerror(i));
  }
  return 0;
}


运行结果2:



过程分析2:

       实际上Linux中的ls、pwd等命令都是可执行程序,使用这些命令后我们也可以查看其对应的退出码。可以看到,这些命令成功执行后,其退出码也是0。



💫进程常见退出方法

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

  • 从main返回,return退出
  • 调用exit
  • _exit


异常退出:

  • ctrl+c,信号终止


1.return

return是一种常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数,会将main的返回值当做 exit的参数。



2.exit

使用exit函数退出进程也是我们常用的方法,exit函数可以在代码中的任何地方退出进程,并且exit函数在退出进程前会做一系列工作:

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


举个栗子:

#include <stdio.h>
#include <stdlib.h>
 
void show()
{
  printf("hello linux\n");
  exit(1);
}
 
int main()
{
  show();
  return 0;
}


运行结果:



结果分析:

exit终止进程前会将缓冲区当中的数据输出。


3._exit函数

使用_exit函数退出进程的方法我们并不经常使用,_exit函数也可以在代码中的任何地方退出进程,但是_exit函数会直接终止进程,并不会在退出进程前会做任何收尾工作。

举个栗子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
void show()
{
  printf("hello linux");
  _exit(2);
}
 
int main()
{
  show();
  return 0;
}


运行结果:



结果分析:

使用_exit终止进程,则缓冲区当中的数据将不会被输出。



💫进程异常退出


情况一:向进程发生信号导致进程异常退出。

例如,在进程运行过程中向进程发生kill -9信号使得进程异常退出,或是使用Ctrl+C使得进程异常退出等。


情况二:代码错误导致进程运行时异常退出。

例如,代码当中存在野指针问题使得进程运行时异常退出,或是出现除0的情况使得进程运行时异常退出等。



【Linux】进程控制深度了解(下)     https://developer.aliyun.com/article/1565492

目录
相关文章
|
1月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
34 0
|
3月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
633 2
|
3月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
66 2
|
13天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
43 4
linux进程管理万字详解!!!
|
4天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
39 8
|
12天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
45 4
|
13天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
15天前
|
消息中间件 存储 Linux
|
21天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
22 1
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
20 1