linux学习进程控制【创建-终止-等待】

news2024/12/23 4:34:39

目录

1.进程创建

1.1fork函数 

1.2写时拷贝

2.进程终止

2.1进程退出场景

2.2进程退出方式 

3.进程等待

3.1进程等待的必要性 

3.2等待方式 

3.2.1wait() 

3.2.2waitpid() 

3.3轮训等待

总结:



1.进程创建

1.1fork函数 

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

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1
进程调用 fork ,当控制转移到内核中的 fork 代码后,内核做:
  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

 

fork 函数返回类型为 pid_t,相当于 typedef int,不过是专门用于进程的,同时它拥有两个返回值:

  • 如果进程创建失败,返回 -1
  • 进程创建成功后
    • 给子进程返回 0
    • 给父进程返回子进程的 PID 值
当一个进程调用 fork 之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以
开始它们自己的旅程,看如下程序

 

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;
}

运行结果为:

运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0
这里看到了三行输出,一行 before ,两行 after 。进程 43676 先打印 before 消息,然后它有打印 after 。另一个 after
消息有 43677 打印的。注意到进程 43677 没有打印 before ,为什么呢?如下图所示

所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器 决定。

1.2写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本,创建进程的时候,系统就会生成页表, 页表有一列特征值就是权限, 当没有发生写时拷贝的时候,权限都是只读属性,一旦父子进程有一方试图写入操作系统就会在内存中重新申请一块内存,并将页表中物理地址和内存重新映射。具体如下图所示:

注意:

  • 写时拷贝不止可以发生在常规栈区、堆区,还能发生在只读的数据段和数据段
  • 写时拷贝后,生成的是副本,不会对原数据造成影响

 

2.进程终止

2.1进程退出场景

进程退出场景包含以下三种情况:

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

2.2进程退出方式 

 对一个正在运行中的进程,存在两种终止方式:外部终止和内部终止,外部终止时,通过 kill -9 PID 指令(发信号),强行终止正在运行中的程序,或者通过 ctrl + c 终止前台运行中的程序(快捷键=kill-9)

 

如果是正常终止的话,可以通过 echo $?查看进程退出码 这个?就是显示最近一次进程运行的退出码,因为echo也是命令行也是进程,所以第二次输出之后就是 0 如下图所示:

 

 内部终止是通过函数 exit() 或 _exit() 实现的,同时,需要注意的是在我们之前写的main函数中,我们总会在程序执行完之后,写一个 return 0,这个0在进程信息中就代表sucess因为信号都是从1号开始的,在main函数中,return 0和exit(0)代表的含义是一样的 .

        我们重点说一下exit()和_exit().

 

zvoid exit(int status);

void _exit(int status);

这两个退出函数,从本质上来说,没有区别,都是退出进程,但在实际使用时,还是存在一些区别,推荐使用 exit()

比如在下面这段程序中,分别使用 exit() 和 _exit() 观察运行结果

int main()
{
  printf("You can see me");
  //exit(-1); //退出程序
  //_exit(-1);  //第二个函数
  return 0;
}

 使用 exit() 时,输出语句

使用 _exit() 时,输出语句

 

原因:

  • exit() 是对 _exit() 做的封装实现
  • _exit() 就只是单纯的退出程序(都要冲刷内核区)
  • 而 exit() 在退出之前还会做一些事,比如冲刷缓冲区,再调用 _exit()
  • 程序中输出语句位于输出缓冲区,不冲刷的话,是不会输出内容的

3.进程等待

3.1进程等待的必要性 

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,杀人不眨眼kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源获取子进程退出信息

3.2等待方式 

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);

pid_t waitpid(pid_t pid, int* status, int options);

3.2.1wait() 

wait()函数,之前有介绍过,没有说过形参status,因为接下来介绍的waitpid()包含退出状态status所以会在下面介绍 

3.2.2waitpid() 

返回值:

等待成功时,返回 >0 的值
等待失败时,返回 -1
等待中,返回 0


参数列表:

pid 表示所等子进程的 PID
status 表示状态,为整型,其中高 16 位不管,低 16 位中,次低 8 位表示退出码,第 7 位表示 core dump,低 7 位表示终止信号
options 为选项,比如可以选择父进程是否需要阻塞等待子进程退出,默认为0就是阻塞等待还有轮训等待。

status

下面通过代码演示:

int main()
{
  //演示 waitpid()
  pid_t id = fork();  //创建子进程
  if(id == 0)
  {
    int time = 5;
    int n = 0;
    while(n < time)
    {
      printf("我是子进程,我已经运行了:%d秒 PID:%d   PPID:%d\n", n + 1, getpid(), getppid());
      sleep(1);
      n++;
    }

    exit(5);  //子进程退出
  }

  int status = 0; //状态
  pid_t ret = waitpid(id, &status, 0); //参数3 为0,为默认选项

  if(ret == -1)
  {
    printf("进程等待失败!进程不存在!\n");
  }
  else if(ret == 0)
  {
    printf("子进程还在运行中!\n");
  }
  else
  {
    printf("进程等待成功,子进程已被回收\n");
  }

  printf("我是父进程, PID:%d   PPID:%d\n", getpid(), getppid());

  //通过 status 判断子进程运行情况
  if((status & 0x7F))
  {
    printf("子进程异常退出,core dump:%d   退出信号:%d\n", (status >> 7) & 1, (status & 0x7F));
  }
  else
  {
    printf("子进程正常退出,退出码:%d\n", (status >> 8) & 0xFF);
  }

  return 0;
}

不发出终止信号,让程序自然跑完

发出终止信号,强行终止进程

 waitpid() 的返回值可以帮助我们判断此时进程属于什么状态(在下一份测试代码中表现更明显),而 status 的不同部分,可以帮助我们判断子进程因何而终止,并获取 退出码(终止信号)

在进程的 PCB 中,包含了 int _exit_code 和 int _exit_signal 这两个信息,可以通过对 status 的位操作间接获取其中的值,同时,在子进程结束,代码和数据会释放,同时会将自己的退出码,退出信息防止在pcb中,等待父进程回收。

注意:

如果觉得 (status >> 8) & 0xFF (status & 0x7F) 这两个位运算难记,系统还提供了两个宏来简化代码

WIFEXITED(status) 判断进程退出情况,当宏为真时,表示进程正常退出
WEXITSTATUS(status) 相当于 (status >> 8) & 0xFF,直接获取退出码

3.3轮训等待

我们介绍一下,waitpid的第三个参数 options  

 

//options 参数
WNOHANG

//比如
waitpid(id, &status, WNOHANG);

 父进程并非需要一直等待子进程运行结束(阻塞等待),可以通过设置 options 参数,进程解除  状态,父进程变成 等待轮询 状态,不断获取子进程状态(是否退出),如果没退出,就可以干点其他事

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件

int main()
{
  //演示 waitpid()
  pid_t id = fork();  //创建子进程
  if(id == 0)
  {
    int time = 9;
    int n = 0;
    while(n < time)
    {
      printf("我是子进程,我已经运行了:%d秒 PID:%d   PPID:%d\n", n + 1, getpid(), getppid());
      sleep(1);
      n++;
    }

    exit(244);  //子进程退出
  }

  int status = 0; //状态
  pid_t ret = 0;
  while(1)
  {

    ret = waitpid(id, &status, WNOHANG); //参数3 设置为非阻塞状态
    
    if(ret == -1)
    {
      printf("进程等待失败!进程不存在!\n");
      break;
    }
    else if(ret == 0)
    { 
      printf("子进程还在运行中!\n");
      printf("我可以干一些其他任务\n");
      sleep(3);
    }
    else
    {
      printf("进程等待成功,子进程已被回收\n");
      //通过 status 判断子进程运行情况
      if(WIFEXITED(status))
      {
        printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));
        break;
      }
      else
      {
        printf("子进程异常退出,code dump:%d   退出信号:%d\n", (status >> 7) & 1, (status & 0x7F));
        break;
      }
    }
  }

  return 0;
}

程序正常运行,父进程通过 等待轮询 的方式,在子进程执行的同时,执行其他任务

 

注意: 如果不写进程等待函数,会引发僵尸进程问题

总结:

进程还有进程替换,因为内容比较多,我放在下一章梳理。以上就是关于 Linux进程控制(创建、终止、等待) 的相关知识了,我们学习了 子进程 是如何被创建的,创建后又是如何终止的,以及 子进程 终止 父进程 需要做些什么,有了这些知识后,在对 进程 进行操作时能更加灵活和全面 。

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

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

相关文章

给定n个结点m条边的简单无向图,判断该图是否存在鱼形状的子图:有一个环,其中有一个结点有另外两条边,连向不在环内的两个结点。若有,输出子图的连边

题目 思路: #include <bits/stdc++.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn = 1e6 + 5, inf = 1e18 * 3, maxm = 4e4 + 5,…

对树莓派上配置mdadm的一些补充

1、如果要重新配置该如何回退到初始状态&#xff1f; 答&#xff1a;可参考以下指令&#xff1a; cat /proc/mdstat sudo umount /dev/md0 sudo mdadm --stop /dev/md0 sudo mdadm --zero-superblock /dev/sda sudo mdadm --zero-superblock /dev/sdb sudo nano /etc/fstab&a…

不具备这十个能力,真不能说是专业的B端系统设计师

B端系统的复杂程度要远远的超过C端&#xff0c;作为这类设计师绝对不能满足于&#xff0c;画个界面&#xff0c;拼一下组件能搞定的&#xff0c;真的需要精心研究&#xff0c;本文列举了十项能力&#xff0c;帮助设计师们针对的提升。 一、什么是B端管理系统设计 B端管理系统设…

谷歌新动作:双子模型大放送,开发者福音来了!

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

wayland(xdg_wm_base) + egl + opengles——dma_buf 作为纹理数据源(五)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、EGL dma_buf import 相关的数据结构和函数1. EGLImageKHR2. eglCreateImageKHR()3. glEGLImageTargetTexture2DOES()二、egl 中 import dma_buf 作为纹理的代码实例1. egl_wayland_dmabuf_…

Flink理论—Flink架构设计

Flink架构设计 Flink 是一个分布式系统&#xff0c;需要有效分配和管理计算资源才能执行流应用程序。它集成了所有常见的集群资源管理器&#xff0c;例如Hadoop YARN&#xff0c;但也可以设置作为独立集群甚至库运行,例如Spark 的 Standalone Mode 本节概述了 Flink 架构&…

单片机学习笔记---直流电机驱动(PWM)

直流电机介绍 直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极&#xff0c;当电极正接时&#xff0c;电机正转&#xff0c;当电极反接时&#xff0c;电机反转 直流电机主要由永磁体&#xff08;定子&#xff09;、线圈&#xff08;转子&#xff09;和换向器…

leetcode(双指针)15.三数之和(C++详细解释)DAY10

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的…

shell脚本之高级变量

目录 一、高级变量赋值 1、高级变量赋值总结表 2、相关操作 二、变量间接引用 1、eval命令 一、高级变量赋值 1、高级变量赋值总结表 变量配置方式str 无配置str 为空字符串str 已配置为非空字符串var${str-expr}varexprvarvar$strvar${str:-expr}varexprvarexprvar$str…

RK3399平台开发系列讲解(调试篇)死锁检测工具lockdep

🚀返回专栏总目录 文章目录 一、常见死锁场景二、lockdep使用方法三、lockdep技术原理3.1、锁类状态3.2、检查规则沉淀、分享、成长,让自己和他人都能有所收获!😄 📢介绍死锁检测工具lockdep。 资料 一、常见死锁场景 场景1:进程重复申请同一个锁,称为AA死锁。例如…

多模态(三)--- BLIP原理与源码解读

1 BLIP简介 BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation 传统的Vision-Language Pre-training &#xff08;VLP&#xff09;任务大多是基于理解的任务或基于生成的任务&#xff0c;同时预训练数据多是从web获…

CMU和ETH联合研发了一个名为 「敏捷但安全」的新框架,为四足机器人在复杂环境中实现高速运动提供了解决方案

在高速机器人运动领域&#xff0c;实现同时兼顾速度和安全一直是一大挑战。但现在&#xff0c;卡内基梅隆大学&#xff08;CMU&#xff09;和苏黎世联邦理工学院&#xff08;ETH&#xff09;的研究团队带来了突破性进展。他们开发的新型四足机器人算法&#xff0c;不仅能在复杂…

程序的控制结构详解

程序的控制结构 结构化程序设计方法的基础 在计算机刚出现的早期&#xff0c;它的价格昂贵、内存很小、速度慢。程序员为了在很小的内存中解决大量的科学计算问题&#xff0c;并为了节省昂贵的CPU机时费&#xff0c;不得不使用巧妙的手段和技术&#xff0c;手工编写各种高效的…

吴恩达机器学习全课程笔记第一篇

目录 前言 P1 - P8 监督学习 ​无监督学习 P9-P14 线性回归模型 成本&#xff08;代价&#xff09;函数 P15-P20 梯度下降 P21-P24 多类特征 向量化 多元线性回归的梯度下降 P25-P30 特征缩放 检查梯度下降是否收敛 学习率的选择 特征工程 多项式回归 前言…

【力扣hot100】刷题笔记Day5

前言 回学校了&#xff0c;荒废了半天之后打算奋发图强猛猛刷题&#xff0c;找实习&#xff01;赚钱&#xff01;&#xff01; 560. 和为 K 的子数组 - 力扣&#xff08;LeetCode&#xff09; 前缀法 哈希表 这个题解解释比官方清晰&#xff0c;截个图方便看&#xff0c;另一…

【Java】文件操作与IO

文件操作与IO Java中操作文件针对文件系统的操作File类概述字段构造方法方法及示例 文件内容的读写 —— 数据流Java提供的 “流” API文件流读写文件内容InputStream 示例读文件示例1&#xff1a;将文件完全读完的两种方式示例二&#xff1a;读取汉字 写文件谈谈 OutputStream…

Practical User Research for Enterprise UX

2.1 Why It’s Hard to Get Support for Research in Enterprises 2.1.1 Time and Budget Instead of answering the question “What dowe gain if we do this research?”, ask instead “What do we stand to lose if we don’t do the research?” 2.1.2 Legacy Thinkin…

HMI界面:感官与体验俱佳的智能家居界面分享

Hello&#xff0c;我是大千UI工场&#xff0c;本期分享HMI人机交互界面在智能家居领域的案例&#xff0c;关注大千&#xff0c;学习N多UI干货&#xff0c;有设计需求&#xff0c;可以联络。 设计感官和体验俱佳智能家居的UI界面时&#xff0c;可以考虑以下几个方面&#xff1a;…

算法中关于数学的题目练习

算法中关于数学的题目练习 1、买不到的数目题目信息思路题解 2、蚂蚁感冒题目信息思路题解 3、饮料换购题目信息思路题解 1、买不到的数目 题目信息 思路 数学结论&#xff08;证明略&#xff09;&#xff1a; p、q为正整数且互质&#xff0c;不能由p、q凑出来的最大的数为(p…

DNS服务正反解析

1.正向解析 1.配置基本 1.1防火墙配置 二者都要关闭 setenforce 0 systemctl stop firewalld #关闭防火墙 yum install bind -y #下载bind软件 客户端可以不用下 1.2服务端配置静态ip&#xff0c; ip a 查看网卡 nmcli c modify ens33 ipv4.method manual ipv4.addresses …