【1++的Linux】之进程(四)

news2024/11/22 14:44:45

👍作者主页:进击的1++
🤩 专栏链接:【1++的Linux】

文章目录

  • 一,进程创建
  • 二,进程等待
  • 三,进程终止

一,进程创建

在Linux中我们通过fork来创建一个新的进程。新创建的进程叫做子进程,原来的进程叫做父进程。
fork()给父进程返回子进程的id,给子进程返回0 ,出错时返回-1 。

为什么会有两个返回值的问题我们前面的文章已经提到过了。

如下:

#include<stdio.h>
#include<unistd.h>
int main()
{
    int ret=fork();
    if(ret<0)
    {
        perror("错误");
    }
    else if(ret>0)
    {
        
        printf("父进程:%d\n",getpid());
        sleep(2);
        
    }
    else if(ret==0)
    {
        printf("父进程:%d,子进程:%d\n",getppid(),getpid());
        
    }
    return 0;
}

为什么在父进程那里要休眠两秒呢?
是因为若不休眠,则我们的父进程就可能先执行完退出了,那么子进程就成了孤儿进程,它的父进程就成了bash。

在这里插入图片描述
有了上述对fork的简单认识,我们接下来回答一个问题:fork创建子进程,操作系统都做了什么?
fork创建子进程后系统中就多了一个进程。进程具体是什么?----->内核结构+进程代码和数据。代码和数据一般是从磁盘中来的,内核结构就是我们一直说的所谓的pcb,为了描述和控制进程模块,系统为每一个进程都定义了一个数据结构—也就是我们的task_struct。它是进程实体的一部分,也是进程存在的唯一标志。
Linux下用于创建进程的API有三个:fork ,vfork,clone。这三个函数分别是通过系统调用sys_fork,sys_vfork以及sys_clone实现的。但是其最终都会且只会调用do_fork。
创建进程,当然要先创建它自己的进程控制模块,因为它是进程存在的唯一标志!!!
所以下面我们讲讲子进程创建进程控制模块的过程。
先是为该进程创建一个内核栈然后通过拷贝父进程的task_struct,为子进程创建一个task_struct。此时的父进程和子进程是没有区别的,接着,会根据自己的情况修改task_struct中的一些参数。这也是进程独立性的一方面体现。
讲完了内核结构,我们再将进程的数据与代码。 进程的数据与代码采用读时共享,写时拷贝的设计方法。
我们在创建内核结构后,进程也就有了自己的地址空间。但此时父子映射的物理内存还都是同一块,为了提高内存的使用效率,当我们只读某些数据时,便不需要进行拷贝,父子共享就可以;只有当遇到要写的数据时,我们再将这个数据进行拷贝,使得在物理内存中,其相互不干扰,这是进程独立性的另一种体现。
如下图:
在这里插入图片描述
再来回答一个问题:fork之后,父子代码共享,是所有代码共享还是fork函数之后的代码分享!!!
那么子进程是怎么知道它的代码起始位置在哪里呢?
我们的计算机中会有记录当前执行代码的下一行代码的地址的寄存器—PC也叫程序计数器。因此在子进程创建之时就把它的首地址给它了。所以它虽然能够看到父进程的代码但从fork之后才开始执行。

fork调用失败的原因:系统进程过多;用户的进程数超过限制

二,进程等待

首先我们来回答问什么要有进程等待???
要回答这个问题我们先来了解僵尸进程。僵尸进程就是当一个子进程在退出时,父进程没有收到其状态信息便形成了僵尸进程。
僵尸进程有什么危害呢?

  1. unix提供了一种机制来使得父进程能够得知子进程结束时的状态信息。这种机制是当每个进程在退出时,内核会释放其所有资源包括,打开的文件,占用的内存等,但是仍会保留一部分信息(退出信息)。知道父进程用wait或waitpid来获取时才会释放。
    因此当一个程序的僵尸进程过多时,便会造成资源的浪费。
  2. 僵尸进程已经相当于一个死进程,我们无法杀死一个已经死掉的进程。
  3. 获取不到子进程退出时的状态信息,我们就无法得知父进程派给其的任务完成的怎么样了。

因此进程等待就很有必要了!!!

那么该如何等待呢?
其实在回答上一个问题时我们就已经给出了答案-----wait或waitpid。

在这里插入图片描述

我们来看下面的代码:

#include<stdio.h>
#include<unistd.h>
int main()
{
    int ret=fork();
    if(ret<0)
    {
        perror("错误");

    }
    else if(ret==0)
    {
        sleep(2);
        printf("I am child:%d\n",getpid());
    }
    else{
       int id= wait(NULL);
       if(id>0)
       {
           printf("回收成功\n");
       }
       else if(id==-1)
       {
           printf("失败\n");
       }
       else{

       }
    }

    return 0;
}

在这里插入图片描述

若没有回收操作:

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
    int ret=fork();
    if(ret<0) { perror("错误"); }
    else if(ret==0)
    {
     printf("I am child:%d\n",getpid());
    }
    else{
        sleep(5);
    }
    return 0;
}

在这里插入图片描述
我们发现其子进程处于僵尸状态。
除了上述这种方法,还有一种进程等待的方法:waitpid函数。
这里就不再进行演示。
接下来我们详细说说这两个函数的形参及其返回值!!!

返回值:

  1. wait:成功返回回收进程的pid,失败返回-1.
  2. waitpid:正常返回收集到的进程的pid。失败返回-1 。若是设置了WNOHANG,若发现没有已退出的子进程可回收,返回0 。

参数:

  1. pid :pid=-1,回收任何进程,与wait一样;pid>0回收与pid进程号一样的子进程。
  2. status :status是一个输出型参数,为int指针,在这里我们只研究其低十六位:次低八位表示退出状态;最低七位表示退出信号,中间那一位叫做core-dump我们现在只需知道它是用于调试的。
  3. option:若为0,则表示在等待期间父进程会挂起,阻塞式等待,若为WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID 。

下面我们进行详细的验证:

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
    int ret=fork();
    if(ret>0)
    {
        int status;
        int t;

        do
        {
            t= waitpid(-1,&status,WNOHANG);
             if(t>0)
             {
                 printf("正常退出,退出码:%d,退出信号为:%d\n",status>>8&0xff,status&0x7f);
                
             }
        }
        while(!t);

    }
    else{
        sleep(2);
        printf("我是子进程:%d\n",getpid());
        exit(1);
    }
    return 0;
}

在这里插入图片描述
用kill命令将子进程杀死
在这里插入图片描述
在这里插入图片描述
我们发现退出信号变为了9!!!

因此程序异常退出也不光光是代码的问题,也可能是外部原因

我们再看认识两个宏:WIFEXITED:查看进程是否正常退出,非0表示正常退出;0表示不正常退出。WEXITSTATUS:若进程正常退出,则提取进程的退出码。

既然进程具有独立性,进程退出码,退出信号是子进程的数据,父进程凭什么能拿到呢?
首先我们要知道,拿到子进程退出状态的信息,实际是去读子进程的task_struct结构体。并且,wait/waitpid是系统调用,它们有权利去访问内核空间!!!

接下来我们进行详细的演示进程等待的两种方式!!!

阻塞式等待:

void Hang()
{
    int ret=fork();
    if(ret==0)
    {
        printf("I am child:%d\n",getpid());
        sleep(5);
        exit(1);//退出码为1
    }
    else if(ret>0)
    {
        printf("I am father:%d\n",getpid());
        int status=0;
        waitpid(ret,&status,0);//挂起
        if(WIFEXITED(status))
        {
            printf("退出成功--退出码:%d\n",WEXITSTATUS(status));
        }
        else{
            printf("退出失败---退出信号:%d\n",status&0x7f);
        }
            
    }
    else{
        perror("错误\n");
    }

}
int main()
{
    Hang();
    return 0;
}

正常退出:
在这里插入图片描述

杀掉进程:
在这里插入图片描述

在这里插入图片描述

非阻塞式等待:

void NOHang()
{
    int pid=fork();
    if(pid==0)
    {
        printf("I am child:%d\n",getpid());
        sleep(5);
    }
    else if(pid>0)
    {
        int ret=0;
        int status=0;
        do{
             ret=waitpid(pid,&status,WNOHANG);
             if(ret==0)
             {
                 printf("child is running\n");
             }
             sleep(1);
          }
        while(!ret);
        if(WIFEXITED(status))
        {
            printf("正常退出--退出码:%d\n",WEXITSTATUS(status));
        }
        else{
            printf("不正常退出---退出码:%d,退出信号:%d\n",WEXITSTATUS(status),status&0x7f);
        }
    

    }   
    else{
        perror("错误\n");
    }
}
int main()
{
   // Hang();
    NOHang();
    return 0;
}

正常退出:
在这里插入图片描述

杀掉进程:
在这里插入图片描述

三,进程终止

进程终止的常见方式:

  1. 代码运行完毕结果正确
  2. 代码运行完毕结果不正确
  3. 代码异常退出

这里我们再来回答一个问题:我们经常写的main函数的返回值的意义是什么?

我们在上一小节中讲了进程等待。并题到进程退出时会将退出状态的信息给父进程读。那么父进程也是另一个进程的子进程,那么其应该将退出状态的信息,其父进程也因该能读到。所以我们经常在main函数写的return
0就是返回给上一进程的退出码。所以它可以是任何值,只是为了方便定位错误信息,会对其返回码做出规定。

常见的进程退出方法:
正常退出

  1. main返回
  2. 调用exit
  3. 调用_exit
    异常退出
  4. ctrl C
  5. 信号终止

我们接下来看exit与_exit的区别!!!

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    printf("hhhhhh");
    //exit(1);
    _exit(1);
    return 0;
}

在这里插入图片描述
由于我们的printf没有带换行,所以没办法自己冲刷缓冲,通过结果我们看到,当用exit时,其能够输出,用_exit时,没有输出,则证明,exit具有冲刷缓冲的作用而_exit没有。
其时exit最终调用的也是系统函数_exit。只不过在调用之前还做了一些事情:

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

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

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

相关文章

No149.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

No147.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

logback.xml springboot 项目通用logback配置,粘贴即用,按日期生成

<configuration scan"false" scanPeriod"10 seconds"><!-- 定义日志存放的根目录 --><property name"log.dir" value"./logs" /><!-- 彩色日志依赖的渲染类 --><conversionRule conversionWord"clr&q…

求职面试,如何赢得面试官的满意赞许?

在面试的时候&#xff0c;我们会遇到很多突发事件&#xff0c;这些事件让我们不能很好地将面试进行下去。那么&#xff0c;在出现事情的时候&#xff0c;我们应该如何让面试官满意&#xff1f; 1、选择得体的服装 在面试的时候&#xff0c;想要面试官更好地选择自己&#x…

后端基础php

虚拟机安装网络方面名词介绍快速自建web环境&#xff08;phpstudy&#xff09;前端基础mysql语法前端【展示】----后端【功能实现】标准php 【ASP / ASPX / PHP / JSP】0基础 --->php入门编程--->代码 对逻辑要求高变量--->会改变的量 php---->$aHello…

C理解(二):指针,数组,字符串,函数

指针 int *p; 未绑定:*表示p为指针变量,占4字节 int a 1;p &a; 绑定:p与a地址绑定即p中存放a的地址 *p *p 1; 解引用:p间接访问a的存储空间 左值与右值 int a 1; 左值&#xff1a;变量对应的内存空间 右值&#xff1a;内存空间存储的数 野指针 野指针:指针变量定…

2023下半年信息系统集成设计师

选择题 第一章 信息基础知识第二章 信息系统集成第三章 专业技能知识第四章 项目管理一般知识第五章 项目立项管理第六章 项目整体管理第七章 项目范围管理第八章 项目进度管理第九章 项目成本管理第十章 项目质量管理第十一章 项目管理干系人第十三章 合同管理第十五章 配置管…

秋招应届毕业生求职 如何通过在线测评?

对于应届毕业生来说&#xff0c;求职应聘必定会遇到在线测评的环节&#xff0c;很多同学都会疑惑&#xff0c;不知道怎么应对&#xff0c;也有些同学说我已经很认真了&#xff0c;为何我别刷了下来&#xff1f; 这里面到底有何玄机&#xff1f; 1、应聘前要多做性格测试 …

Django模板加载与响应

前言 Django 的模板系统将 Python 代码与 HTML 代码解耦&#xff0c;动态地生成 HTML 页面。Django 项目可以配置一个或多个模板引擎&#xff0c;但是通常使用 Django 的模板系统时&#xff0c;应该首先考虑其内置的后端 DTL&#xff08;Django Template Language&#xff0c;D…

【Java】复制数组的四种方式

1. System.arraycopy() 用来将一个数组的&#xff08;一部分&#xff09;内容复制到另一个数组里面去。 定义&#xff1a; void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);例&#xff1a; int[] arr1 { 1, 2, 3, 4, 5 }; int[] arr2 new…

青藏高原1-km分辨率生态环境质量变化数据集(2000-2020)

青藏高原平均海拔4000米以上&#xff0c;人口1300万&#xff0c;是亚洲九大河流的源头&#xff0c;为超过15亿人口提供淡水、食物和其他生态系统服务&#xff0c;被誉为地球第三极和亚洲水塔。然而&#xff0c;在该地区的人与自然的关系的研究是有限的&#xff0c;尤其是在精细…

1500*C. Kefa and Park(dfstree)

Kefa and Park - 洛谷 Problem - 580C - Codeforces Examples input 4 1 1 1 0 0 1 2 1 3 1 4 output 2 input 7 1 1 0 1 1 0 0 0 1 2 1 3 2 4 2 5 3 6 3 7 output 2 解析&#xff1a; dfs遍历&#xff0c;记录前一个结点权值是否为1&#xff0c;并且累计路径1的个数…

MySQL学习笔记23

逻辑备份&#xff1a; 1、回顾什么是逻辑备份&#xff1f; 逻辑备份就是把数据库、数据表或者数据进行导出&#xff0c;导出到一个文本文件中。 2、逻辑备份工具&#xff1a; mysqldump&#xff1a;提供全库级、数据库级别以及表级别的数据备份。 mysqldumpbinlog&#xff…

1200*A. Flipping Game(前缀和)

解析&#xff1a; 100数据量&#xff0c;两层遍历每个区间&#xff0c;然后前缀和计算1的个数&#xff0c;维护最大值即可。 #include<bits/stdc.h> using namespace std; #define int long long const int N110; int n,a[N],res,sum[N]; signed main(){scanf("%ll…

[Linux调查局] 编译过程

编译过程 引子编译阶段预处理汇编编译链接 链接详解release && debug 引子 一个程序的编译分为4个部分: 预处理 头文件的展开条件编译宏的展开去掉注释 编译 生成汇编汇编 生成计算机认识的机器指令, 即二进制文件链接 将程序和 库 链接 &#x1f5e8;️这里有一个疑…

《Reinforcement Learning: An Introduction》第8章笔记

文章目录 Chapter 8 Planning and Learning with Tabular Methods8.1 Models and Planning8.2 Dyna: Integrated Planning, Acting, and Learning8.3 When the Models Is Wrong8.4 Prioritized Sweeping8.5 Expected vs. Sample Updates8.6 Trajectory Sampling8.7 Real-time D…

南京大学【软件分析】13 Static Analysis for Security

文章目录 1. Information Flow Security2. Confidentiality and Integrity3. Explicit Flows and Covert/Hidden Channels4. Taint Analysis污点分析案例 1. Information Flow Security 引起安全问题最主要的两大原因是&#xff1a;injection errors&#xff08;2013-2019排名…

Disucz插件-免费最全Disucz插件大全

在网络世界里&#xff0c;拥有一个活跃并充满吸引力的社区论坛是许多网站管理员和品牌经营者的梦想。然而&#xff0c;要让一个论坛充满活力、吸引大量用户&#xff0c;需要大量的内容。这就是为什么许多人转向Disucz插件的原因。Disucz插件不仅可以帮助您创建一个互动性强大的…

数据响应式原理

面试题&#xff1a;请阐述vue2响应式原理 vue官方阐述&#xff1a;https://cn.vuejs.org/v2/guide/reactivity.html 响应式数据的最终目标&#xff0c;是当对象本身或对象属性发生变化时&#xff0c;将会运行一些函数&#xff0c;最常见的就是render函数。 在具体实现上&#x…

基于SpringBoot的飘香水果购物网站

目录 前言 一、技术栈 二、系统功能介绍 水果信息管理 减积分管理 会员购买订单管理 首页 水果 购买水果订单管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息互联网购物的飞速发展&#xff0c;一般企业都去创建属于自己的电商平台以及购物…