【Linux】进程控制

news2025/7/20 23:57:09

目录

  • 🌈前言
  • 🌸1、进程创建
    • 🍡1.1、概念
    • 🍢1.2、fork()之后执行顺序
    • 🍧1.3、fork()返回值
    • 🍨1.4、写时拷贝
    • 🍩1.5、fork创建失败问题
  • 🍁2、进程终止
    • 🍲2.1、概念
    • 🍱2.2、常见进程终止方法
    • 🍰2.3、终止后,内核做了什么?
  • 🍂3、进程等待
    • 🍰3.1、为什么需要进程等待
    • 🍱3.2、进程等待方法
    • 🍲3.3、获取子进程status
    • 🍳3.4、options参数

🌈前言

本篇文章进行操作系统中进程控制的学习!!!


🌸1、进程创建

🍡1.1、概念

fork函数初识:

  • 在Linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程

  • 新进程为子进程,而原进程为父进程

#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程的pid,出错(创建子进程失败)返回-1

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

  • 分配新的内存块和内核数据结构给子进程(已知task_struct、mm_struct和页表)

  • 将父进程部分数据结构内容拷贝至子进程

  • 添加子进程到系统进程列表(运行队列)当中

  • fork返回时,开始调度器调度

进程 = 内核的进程数据结构 + 进程的代码和数据

  • 创建子进程的内核数据结构(struct task_struct + struct mm_struct + 页表)+ 代码继承父进程,数据通过写时拷贝,来实现父子进程之间的共享和独立!!!
[lyh_sky@localhost lesson14]$ ls
process2  process2.c
[lyh_sky@localhost lesson14]$ cat process2.c 
#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("我是一个进程: %d\n", getpid());
    fork();
    printf("我依旧是一个进程: %d\n", getpid());
    return 0;
}
[lyh_sky@localhost lesson14]$ ./process2 
我是一个进程: 8968         // 父进程
我依旧是一个进程: 8968		 // 父进程
我依旧是一个进程: 8969	     // 子进程

🍢1.2、fork()之后执行顺序

fork之后子进程是从哪里开始执行的呢?

  • fork之前父进程独立执行,fork之后就有两个进程了,父子两个执行流分别执行

  • fork之后,父子进程共享所有的代码,而不是拷贝fork之后的代码!!!

  • 但是子进程执行的后续代码不等于共享的所有代码,只不过子进程只能从这里开始执行

  • CPU中有一个EIP寄存器:它是一个程序计数器,作用是保存当前正在执行代码的下一条代码

  • EIP寄存器会拷贝给子进程,子进程便从EIP所指向的代码处开始执行!!!

这里子进程没有执行Before这条打印,因为EIP寄存器的原因!!!

[lyh_sky@localhost lesson14]$ ls
process2  process2.c
[lyh_sky@localhost lesson14]$ cat process2.c 
#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("Before PID: %d\n", getpid());
    fork();
    printf("After PID: %d\n", getpid());
    return 0;
}
[lyh_sky@localhost lesson14]$ ./process2 
Before PID: 9751
After PID: 9751
After PID: 9752

在这里插入图片描述


🍧1.3、fork()返回值

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

通过if else分流的方式来分别执行父子进程代码,各自完成自己的工作!!!

#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    pid_t id = fork();

    if (id == 0)
    {
        while (1)
        {
            cout << "我是子进程, 我的pid: " << getpid()
              << ", 我的父进程是: " << getppid() << endl;
            sleep(3);
        }
    }
    else
    {
        while (1)
        {
            cout << "我是父进程, 我的pid: " << getpid()
              << ", 我的父进程是: " << getppid() << endl;           
            sleep(3);
        }
    }
    return 0;
}

在这里插入图片描述


🍨1.4、写时拷贝

当fork一个子进程后:

  • 通常,父子代码共享,父子再不写入时,数据也是共享的

  • 当任意一方试图写入,便以写时拷贝的方式各自一份副本(独立性)

写时拷贝本身是由OS中内存管理模块管理的!!!

在这里插入图片描述

为什么要有写时拷贝???

  • 写时拷贝:如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用都是透明的(transparently)

  • 提高效率和节省内存

创建子进程的时候,直接把数据分开不行吗???

  • 【浪费内存资源】父进程的数据,子进程不一定使用,即便使用,也不一定全部进行写入

  • 【效率低】如果fork的时候,就无脑的拷贝数据给子进程,会增加fork的成本(内存和时间)

采用写时拷贝的好处:

  • 只会拷贝父子进程中其中一个数据修改的,变相的,就是拷贝数据的最小成本

  • 写时拷贝采用延时拷贝策略,只有当你真正使用的时候,才进行拷贝

  • 你想要,但是你不立马使用该空间,那先不给你,就意味着这段空间可以给别人使用,变相的提高内存的使用效率!!!


🍩1.5、fork创建失败问题

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

验证:系统有太多进程导致内存资源不足创建失败!!!

[lyh_sky@localhost lesson14]$ ls
test  test.c
[lyh_sky@localhost lesson14]$ cat test.c 
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int count = 0;

int main()
{
    while (1)
    {
        pid_t id = fork();
        if (id < 0)
        {
            printf("创建子进程失败!!!\n");
            break;
        }
        else if (id == 0)
        {
            printf("我是一个子进程: %d\n", getpid());
            // 让这个进程存在30秒后结束它
            sleep(30);
            // 结束该进程,错误码返回0
            exit(0);
        }
        ++count;
    }
    printf("一共创建了%d个子进程\n", count);
    return 0;
}

在这里插入图片描述


🍁2、进程终止

🍲2.1、概念

关于进程终止的认识:

  1. C/C++中main函数的return 0,是给谁return的呢?
  • 很多人说,是给操作系统返回的0,答案是不准确的

  • 进程会维护一个退出码,表征进程退出的信息,让父进程进行读取

  • bash进程运行一个子进程时,子进程执行结束,会返回一个退出码给bash进程

echo $?:打印bash进程最近执行完毕的子进程的退出码,第二次会变回0

[lyh_sky@localhost lesson14]$ ls
test  test.c
[lyh_sky@localhost lesson14]$ cat test.c 
#include <stdio.h>

int main()
{
    return 20;
}
// 在bash进程中运行一个子进程
[lyh_sky@localhost lesson14]$ ./test 
[lyh_sky@localhost lesson14]$ echo $?
20
[lyh_sky@localhost lesson14]$ echo $?
0
  1. 为什么return 0,可以return其他值吗?

常见的进程退出方式:

  1. 代码已经全部执行,结果是正确的

  2. 代码已经全部执行,结果是错误的

  3. 代码没有全部执行,程序出现异常

  • 进程代码跑完,结果是否正确,返回0代表是成功的,非0代表是失败的

  • 我们需要找到失败的原因,所以需要不同的错误码来标识不同的失败原因

  • 一般来说失败时,非0值可以自定义,错误退出码 可以对应不同的错误原因,方便定位问题

C语言中的错误码

[lyh_sky@localhost lesson14]$ ls
errno  errno_code.c
[lyh_sky@localhost lesson14]$ cat errno_code.c 
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main()
{
	// 错误码没有0,只有1~124个错误码
    for (int i = 1; i <= 124; ++i)
    {
        printf("Erron code %d: %s\n", i, strerror(i));
    }
    return 0;
}
[lyh_sky@localhost lesson14]$ ./errno

在这里插入图片描述


🍱2.2、常见进程终止方法

关于进程终止的常见做法:

  1. main函数return,非main返回不行,非main函数return代表该函数已经执行完毕

  2. void exit(int status),调用exit

  3. void _exit(int status),调用_exit

  • 参数:status 定义了进程的终止状态,父进程通过wait来获取该值

  • 说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255

exit 和 _exit的区别是什么??

  • exit终止进程,刷新缓冲区!!!

  • _exit直接终止进程,不会有任何的刷新操作!!!

exit函数执行过程:

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

在这里插入图片描述

exit

[lyh_sky@localhost lesson14]$ cat test.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("这是一个测试代码!!!");
    exit(0);
    return 0;
}
[lyh_sky@localhost lesson14]$ ./test 
这是一个测试代码!!![lyh_sky@localhost lesson14]$ 

_exit

[lyh_sky@localhost lesson14]$ cat test.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("这是一个测试代码!!!");
    _exit(0);
    return 0;
}
[lyh_sky@localhost lesson14]$ ./test 
[lyh_sky@localhost lesson14]$ 

🍰2.3、终止后,内核做了什么?

  • 我们都知道进程由内核数据结构、进程代码和数据组成

  • OS可能不会释放该进程的内核数据结构,比如:task_struct 和 mm_struct

  • OS会将它们放到内核数据结构缓冲池,“slab分配器”

  • slab是内存管理模块的数据结构,变相的可以提高内存的利用率!!!


🍂3、进程等待

🍰3.1、为什么需要进程等待

  • 前面的篇章说过,子进程退出,父进程如果不管它,子进程就会从S/R状态变为Z状态(僵尸),进而造成内存泄漏的问题!

  • 进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程,就算是OS也无能为力

  • 最后就是,父进程交给子进程的任务完成的如何,我们需要知道,如:子进程运行完成,结果如何,是否正常退出

  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息


🍱3.2、进程等待方法

  1. 使用“wait”函数等待子进程退出
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
  • pid_t:成功返回被等待进程pid,等待失败返回-1

  • status:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

  • wait函数可以解决回收子进程的Z(僵尸)状态,让子进程正常进入X(死亡)状态

举个例子:

[lyh_sky@localhost lesson14]$ ls
process2  process2.c
[lyh_sky@localhost lesson14]$ cat process2.c 
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <sys/wait.h>

int count = 5;

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        while (1)
        {
            printf("我是一个子进程, %d秒后退出\n", count);
            sleep(1);

            --count;
            if (count == 0)
            {
                printf("子进程已退出,等待父进程回收!!!\n");
                break;
            }
        }     
    }
    else
    {
        printf("我是一个父进程,等待子进程退出!!!\n");
        pid_t id = wait(NULL);
        printf("子进程已退出: %d\n", id);
    }
    return 0;
}
[lyh_sky@localhost lesson14]$ ./process2

在这里插入图片描述


  1. 使用“waitpid”函数等待子进程退出
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
  1. 返回值(pid_t):
  • 当正常返回的时候,waitpid返回收集到的子进程的进程PID

  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0

  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

  1. pid参数:
  • pid = -1,等待任一个子进程
  • pid > 0,等待与pid值相等的进程返回给父进程
  1. status参数:
  • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  1. options参数:
  • WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID

🍲3.3、获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充

  • 通过调用wait/waitpid函数可以从该函数内拿出特定的数据

  • status可以从子进程task_struct中拿出子进程的退出码和退出信号等等…

  • 如果传递NULL,表示不关心子进程的退出状态信息

  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究低16比特位):

int是四个字节,那么有32个比特位,这里面每八个比特位标识着一个状态

在这里插入图片描述

子进程代码执行完成,以退出码的方式返回给父进程

[lyh_sky@localhost lesson14]$ cat process.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <wait.h>

int cnt = 5;

int main()
{
    pid_t id = fork();

    if (id == 0)
    {
        while (1)
        {
            printf("我是一个子进程: %d\n", getpid());
            sleep(1);

			// 循环执行五次后跳出
            cnt--;
            if (!cnt)
            {
                break;
            }
        }
        printf("子进程已经执行完毕,即将退出!!!");
        exit(12);
    }
    else
    {
        int status;
        printf("我是一个父进程: %d, 等待子进程退出!!!\n", getpid());
        sleep(8);
        pid_t ret = waitpid(id, &status, 0);
        // 这里使用右移和按位与的方式获取退出码和终止信号
        printf ("子进程已经退出,pid: %d, 子进程退出码: %d, 进程退出信号: %d\n", ret, (status >> 8) & 0xFF, (status & 0x7F));
    }
    return 0;
}

在这里插入图片描述

子进程以异常的方式退出,父进程获取异常终止的信号

[lyh_sky@localhost lesson14]$ cat process.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <wait.h>

int cnt = 5;

int main()
{
    pid_t id = fork();

    if (id == 0)
    {
        while (1)
        {
            printf("我是一个子进程: %d\n", getpid());
            sleep(1);

            cnt--;
            if (!cnt)
            {
                break;
            }
        }

		// 空指针访问,段错误
        int* p = NULL;
        *p = 100;

        printf("子进程已经执行完毕,即将退出!!!");
        exit(12);
    }
    else
    {
        int status;
        printf("我是一个父进程: %d, 等待子进程退出!!!\n", getpid());
        sleep(8);
        pid_t ret = waitpid(id, &status, 0);
        printf ("子进程已经退出,pid: %d, 子进程退出码: %d, 进程退出信号: %d\n", ret, (status >> 8) & 0xFF, (status & 0x7F));
    }
    return 0;
}

在这里插入图片描述

退出码和终止信号先看谁呢?

  • 一旦进程在执行时出现异常,进程会直接退出,我们只需要关注终止信号即可

  • 退出码是进程执行完毕后,才会返回给父进程


🍳3.4、options参数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/7875.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【附源码】Python计算机毕业设计数据学院工作量管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

力扣刷题day48|583两个字符串的删除操作、72编辑距离

文章目录583. 两个字符串的删除操作动态规划思路一动态规划思路二动态规划五部曲72. 编辑距离思路动态规划五部曲583. 两个字符串的删除操作 力扣题目链接 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符…

K_A05_001 基于 STM32等单片机驱动8X8点阵模块(MAX7219)显示0-9

目录 一、资源说明 二、基本参数 1、参数 2、引脚说明 三、通信协议说明 工作时序 对应程序: 四、部分代码说明 1、接线说明 1.1、STC89C52RC8X8点阵模块&#xff08;MAX7219&#xff09; 1.2、STM32F103C8T68X8点阵模块&#xff08;MAX7219&#xff09; 2、亮度调节 五、基…

Python中内置数据库!SQLite使用指南!

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; Python3◉技能提升系列&#xff1a;https://www.showmeai.tech/tutorials/56 &#x1f4d8; 本文地址&#xff1a;https://www.showmeai.tech/article-detail/390 &#x1f4e2; 声明&#xff1a;版权所有&#xf…

Docker数据卷

Docker数据卷、Docker安全Docker数据卷Docker数据卷管理bind mountdocker数据卷两种方式不同和相同卷插件简介convoy卷插件实践Docker数据卷 主要解决存储问题、容器数据滞留 Docker数据卷管理 bind mount “-v”宿主机路径容器内nginx发布目录 新建一个首页 现在容器内目录…

猿创征文|工具百宝箱-编辑器-笔记工具-日常小工具-原型设计工具

这篇文主要介绍了开发者工具之外的一些日常小工具&#xff0c;我用这些小工具主要完成什么工作。分享给大家 官方活动入口&#xff1a;「猿创征文 」第四季 | 2022 年我的开发者工具 猿创征文&#xff5c;工具百宝箱-代码编辑器-版本控制工具-终端神器-项目与事务跟踪工具-SFTP…

pdf文件转txt怎么转?这几个方法你值得收藏

平时我们在网络上搜索资料的时候&#xff0c;会发现很多资料都是以PDF格式显示的&#xff0c;虽然这种文件格式很方便我们查看&#xff0c;但是如果将其保存到手机中&#xff0c;又太占用内存了。其实我们可以将其转换成txt格式&#xff0c;因为我发现它不仅不占空间&#xff0…

助力数据中心双碳发展,存储如何变得越来越绿?

2022年11月10日&#xff0c;我看到曙光发了首款液冷存储&#xff0c;目标锁定数据中心PUE1.1以下。在“双碳”发展趋势下&#xff0c;聚焦液冷存储与液冷服务器的创新&#xff0c;曙光对绿色数据中心的可持续发展将带来行业引领效应。 双碳大趋势下&#xff0c;绿色存储呼之欲出…

计算机网络复习

考试重点 要掌握OSI七层模型&#xff0c; 会根据数据画模拟和数字信号的图&#xff0c;尤其是TCP和UDP所提供的服务&#xff0c; 掌握TCP连接建立和释放的完整过程&#xff0c; 掌握滑动窗口的概念&#xff0c; 还要了解端到端的含义&#xff0c; 了解ARP、ICMP、CIDR等协议的…

干了3年软件测试,2022年我离职了...

今天在网上刷到一个帖子&#xff0c;说软件测试岗&#xff0c;在公司呆了三年&#xff0c;由于疫情原因&#xff0c;公司效益不是很好&#xff0c;加上自己的技术一直停留在功能测试&#xff0c;在公司可有可无&#xff0c;被公司裁后找不到工作… 逛百度贴吧、逛技术论坛&…

[附源码]java毕业设计基于servlet技术实现游戏娱乐平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

FFplay文档解读-48-多媒体过滤器二

32.8 ebur128 EBU R128扫描仪滤光片。此过滤器将音频流作为输入并以不变的方式输出。默认情况下&#xff0c;它以10Hz的频率记录消息&#xff0c;具有瞬时响度&#xff08;由M标识&#xff09;&#xff0c;短期响度&#xff08;S&#xff09;&#xff0c;集成响度&#xff08;…

简单工厂,工厂方法,抽象工厂模式

软件设计七大原则 一、简单工厂&#xff08;静态工厂方法&#xff09; 它存在的目的很简单&#xff1a;定义一个创建对象的接口。组成&#xff1a; 工厂类角色&#xff1a;这是本模式的核心&#xff0c;含有一定的商业逻辑和判断逻 辑。在java中它往往由一个具体类实现。 抽…

南非醉茄来源的天然产物之活性大盘点

图 1. 南非醉茄 (Withania Somnifera) 的多种药理活性[2] 化学成分 到目前为止&#xff0c;大约有超过 12 种生物碱和 40 多种甾体内脂类化合物从南非醉茄中被分离出来 。其中&#xff0c;醉茄内脂 (Withanolides) 因其广泛的药理活性 (抗肿瘤、抗菌、抗炎和免疫调节活性等) 受…

RabbitMQ系列【9】过期时间

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录概念队列过期控制台代码消息过期删除策略概念 TTL全称Time To Live&#xff0c;是指存活时间或过期时间。当消息到达存活时间后&#xff0c;还没有被消费&#xff0c;会被自动清除。 RabbitMQ…

二十六、设置时序电路初始状态的方法(初始值设置)

----------------------------------------------------------------------------------------------------- 该专栏主要介绍用场效应管设计基本电路,由浅到深,从用场效应管设计最基本的非门、与非门、或非门、与门、或门的设计,到用场效应管设计触发,再到用场效应管设计具…

【论文阅读】Combinatorial Benders’ Cuts for the Strip Packing Problem

文章目录一、摘要二、求解条形装箱的Benders分解2.1 Notation2.2 SPP的数学逻辑模型2.3 分解方法三、从问题的解决方案3.1 复杂性分析3.2 y-check的算法3.2.1 预处理过程3.2.1.1 Merge Items 合并项目3.2.1.2 Lift Item Widths 增大项目宽度3.2.1.3 Shrink the Strip 缩小长条容…

实战+代码!Selenium + Phantom JS爬取天天基金数据

功能&#xff1a; 通过程序实现从基金列表页&#xff0c;获取指定页数内所有基金的近一周收益率以及每支基金的详情页链接。再进入每支基金的详情页获取其余的基金信息&#xff0c;将所有获取到的基金详细信息按近6月收益率倒序排列写入一个Excel表格。 思路&#xff1a; 通过…

python-pandas用法大全

目录1 修改 DataFrame 某一列的数据类型2 读取和保存3 特定值的替换4 两个 DataFrame 的连接4.1 join4.2 某列作为拼接的依据5 删除某一列5.1 删除第n列5.2 删除特定名称列6 行、列重排6.1列重排6.2 行重排6.3 根据某一列的值排序6.4 随机打乱所有行7 修改某列的名称7.1 全局修…

m基于自适应门限软切换的3G和Wifi垂直切换算法的matlab仿真

目录 1.算法概述 2.仿真效果预览 3.核心MATLAB预览 4.完整MATLAB程序 1.算法概述 这里还是考虑位置信息和强度联合切换判决的方法&#xff0c;如果你的设备没法提供具体的位置信息的话&#xff0c;那么就把位置信息的权值设置为0。强度判决的权值设置为1即可。 需要传输的数…