0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深度剖析Linux中进程控制(上)

jf_78858299 来源:一口Linux 作者:土豆居士 2023-05-12 10:49 次阅读

一、进程创建

fork函数初识

Linux中,fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

返回值:

在子进程中返回0,父进程中返回子进程的PID,子进程创建失败返回-1。

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

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

fork之后,父子进程代码共享。例如:

图片

运行结果如下:

图片

这里可以看到,Before只输出了一次,而After输出了两次。其中,Before是由父进程打印的,而调用fork函数之后打印的两个After,则分别由父进程和子进程两个进程执行。也就是说,fork之前父进程独立执行,而fork之后父子两个执行流分别执行。

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

fork函数返回值

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

一个父进程可以创建多个子进程,而一个子进程只能有一个父进程。因此,对于子进程来说,父进程是不需要被标识的;而对于父进程来说,子进程是需要被标识的,因为父进程创建子进程的目的是让其执行任务的,父进程只有知道了子进程的PID才能很好的对该子进程指派任务。

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

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

图片

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

写时拷贝

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

图片

这种在需要进行数据修改时再进行拷贝的技术,称为写时拷贝技术。

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

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

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

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

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

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

fork常规用法

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

fork调用失败的原因

fork函数创建子进程也可能会失败,有以下两种情况:

  1. 系统中有太多的进程,内存空间不足,子进程创建失败。
  2. 实际用户的进程数超过了限制,子进程创建失败。

二、进程终止

进程退出场景

进程退出只有三种情况:

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

进程退出码

我们都知道main函数是代码的入口,但实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,例如在VS2013当中main函数就是被一个名为__tmainCRTStartup的函数所调用,而__tmainCRTStartup函数又是通过加载器被操作系统所调用的,也就是说main函数是间接性被操作系统所调用的。

既然main函数是间接性被操作系统所调用的,那么当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回,我们一般以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,这就是为什么我们都在main函数的最后返回0的原因。

当我们的代码运行起来就变成了进程,当进程结束后main函数的返回值实际上就是该进程的进程退出码,我们可以使用echo $?命令查看最近一次进程退出的退出码信息。

例如,对于下面这个简单的代码:

图片

代码运行结束后,我们可以查看该进程的进程退出码。

[cl@VM-0-15-centos procTermination]$ echo $?

图片

这时便可以确定main函数是顺利执行完毕了。

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

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

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

图片

运行代码后我们就可以看到各个错误码所对应的错误信息:

图片

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

可以看到,这些命令成功执行后,其退出码也是0。

图片

但是命令执行错误后,其退出码就是非0的数字,该数字具体代表某一错误信息。

图片

注意: 退出码都有对应的字符串含义,帮助用户确认执行失败的原因,而这些退出码具体代表什么含义是人为规定的,不同环境下相同的退出码的字符串含义可能不同。

进程正常退出

return退出

在main函数中使用return退出进程是我们常用的方法。

例如,在main函数最后使用return退出进程。

图片

运行结果:

图片

exit函数

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

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

例如,以下代码中exit终止进程前会将缓冲区当中的数据输出。

图片

运行结果:

图片

_exit函数

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

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

图片

运行结果:

图片

return、exit和_exit之间的区别与联系

return、exit和_exit之间的区别

只有在main函数当中的return才能起到退出进程的作用,子函数当中return不能退出进程,而exit函数和_exit函数在代码中的任何地方使用都可以起到退出进程的作用。

使用exit函数退出进程前,exit函数会执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再终止进程,而_exit函数会直接终止进程,不会做任何收尾工作。

图片

return、exit和_exit之间的联系

执行return num等同于执行exit(num),因为调用main函数运行结束后,会将main函数的返回值当做exit的参数来调用exit函数。

图片

使用exit函数退出进程前,exit函数会先执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再调用_exit函数终止进程。

进程异常退出

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

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

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

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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206738
  • PID
    PID
    +关注

    关注

    35

    文章

    1446

    浏览量

    83814
  • 函数
    +关注

    关注

    3

    文章

    3882

    浏览量

    61310
  • 数据结构
    +关注

    关注

    3

    文章

    564

    浏览量

    39900
  • Fork
    +关注

    关注

    0

    文章

    14

    浏览量

    3235
收藏 人收藏

    评论

    相关推荐

    Linux中进程和线程的深度对比

    关于进程和线程,在 Linux 中是一对儿很核心的概念。但是进程和线程到底有啥联系,又有啥区别,很多人还都没有搞清楚。
    发表于 10-14 16:47 1053次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>中进程</b>和线程的<b class='flag-5'>深度</b>对比

    Linux进程是如何创建出来的?

    Linux 中,进程是我们非常熟悉的东东了,哪怕是只写过一天代码的人也都用过它。但是你确定它不是你最熟悉的陌生人?我们今天通过深度剖析进程
    发表于 11-15 09:27 439次阅读

    c语言深度剖析

    c语言深度剖析
    发表于 04-02 09:12

    linux内核深度剖析,另附有光盘资料

    linux内核深度剖析,对于想学linux内核的人来说,绝对值得一看,另附有光盘资料。
    发表于 01-15 21:25

    C语言深度剖析

    C语言深度剖析
    发表于 08-25 09:08

    C语言深度剖析

    C语言深度剖析[完整版].pdfC语言深度剖析[完整版].pdf (919.58 KB )
    发表于 03-19 05:11

    Linux系统中进程如何查看及控制

    Linux系统中进程的查看及控制
    发表于 06-09 08:34

    Linux系统中的进程控制该怎样去实现呢

    Linux系统编程、网络编程》第5章 进程控制 2008年毕业于沈阳航空航...
    发表于 12-23 07:55

    Linux源码分析系列的进程

    概述:相关概念,进程在整个内核中的功能位置,源代码中进程相关的文件源代码中进程相关的文件。 Linux核心是多任务的,运行的程序称作进程 (
    发表于 11-03 22:26 32次下载

    Linux进程控制编程

    7.2 Linux进程控制编程 1.fork() 在Linux中创建一个新进程的惟一方法是使用fork()函数。fork()函数是Linux
    发表于 10-18 14:16 0次下载

    Linux守护进程详解

    分享到:标签:进程控制 Linux 守护进程进程 7.3 Linux守护进程 7.3.1 守
    发表于 10-18 14:24 0次下载
    <b class='flag-5'>Linux</b>守护<b class='flag-5'>进程</b>详解

    基于Linux进程管理的详细剖析

    上一篇,我们讲到了Linux内核开发和应用程序开发,今天我们来讲讲Linux重点部分Linux进程管理。
    的头像 发表于 01-26 11:24 3480次阅读
    基于<b class='flag-5'>Linux</b><b class='flag-5'>进程</b>管理的详细<b class='flag-5'>剖析</b>

    Linux0.11-进程控制块数据结构

    嵌入式Linux中文站收集整理Linux0.11版本内核学习笔记,本文分析了Linux进程控制模块的数据结构。
    发表于 05-15 15:22 850次阅读

    深度剖析Linux中进程控制(下)

    Linux中,fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程
    的头像 发表于 05-12 10:49 324次阅读
    <b class='flag-5'>深度</b><b class='flag-5'>剖析</b><b class='flag-5'>Linux</b><b class='flag-5'>中进程控制</b>(下)

    LINUX中进程的最大理论数计算

    LINUX中进程的最大理论数计算 每个进程都要在全局段描述表GDT中占据两个表项 每个进程的局部段描述表LDT都作为一个独立的段而存在,在全局段描述表GDT中要有一个表项指向这个段的起
    的头像 发表于 11-09 09:17 290次阅读
    <b class='flag-5'>LINUX</b><b class='flag-5'>中进程</b>的最大理论数计算