【深入浅出】之Linux进程(二)

news2025/1/11 21:02:38

在这里插入图片描述

📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞


【深入浅出】之Linux进程(二)

  • 父进程与子进程
    • 使用系统调用函数fork给当前进程创建一个子进程
      • fork函数的使用
      • fork函数的原理
      • demo代码:一次创建多个进程
  • 进程的状态介绍
    • 关于进程排队
    • 模拟上述计算过程
    • 进程的几种状态
    • 僵尸进程
    • 孤儿进程

父进程与子进程

刚刚介绍PCB的常见属性,我们已经介绍了,pid,每一个进程都有唯一的一个pid,普通的进程一般都有自己的父进程。

即使我们不同时间执行相同的可执行程序,它的进程pid都会变化,因为每一次它都是一个新的进程,操作系统会在内存中给它重新创建PCB对象,多说无益,我们可以用下面代码验证一下:

#include<stdio.h>
#include <unistd.h>

int main()
{
   int  i = 0;
   printf("我的进程pid为%d\n",getpid());
   printf("我的父进程pid为%d\n",getppid());
   return 0;
}

运行结果:

20241103_17_33_57_468

使用系统调用函数fork给当前进程创建一个子进程

fork系统调用函数:

image-20241107114835459

返回值:

image-20241107115048691

  • 如果成功子进程的PID将被返回给父进程,0会被返回给子进程。如果失败,-1将会被返回给父进程,没有子进程被创建,errno将被设置。

fork函数的使用

先试着使用fork函数:

#include<stdio.h>
#include <unistd.h>

int main()
{
  printf("before fork,I am a process,my pid is %d,my ppid is %d\n",getpid(),getppid());

  sleep(5);
  printf("开始创建进程了!!!\n");
  sleep(1);

  pid_t id = fork();

  if(id < 0) return 1;

  else if(id == 0)
  {
    //子进程
   while(1)
   {
     printf("after fork:我是子进程:my pid is %d,my ppid is %d,my return id is %d\n",getpid(),getppid(),id);
     sleep(1);
   }
  }
  else
  {
    //父进程
    while(1)
    {   
      printf("after fork:我是父进程:my pid is %d,my ppid is %d,my return id is %d\n",getpid(),getppid(),id);
      sleep(1);
    }
  }
  return 0;
}

运行结果:

20241107_12_30_06_813

  • 返回值符合预期,下面我们来研究以下fork函数的工作原理。

fork函数的原理

关于fork函数还有如下问题,需要我们解决:

  1. 返回值id为什么要给父进程返回子进程的pid,而给子进程返回0呢?

    这样设计可以区分子进程和父进程。给父进程返回子进程的pid,可以便于父进程通过pid管理子进程。父进程对应自己的子进程,是一对多的关系。

  2. fork进程为什么会返回两次?

    fork 函数之所以会返回两次,是因为它创建了一个新的进程,这个新进程是原进程(父进程)的一个完全复制。这个复制过程导致了 fork 函数在两个进程中分别返回,每个进程的返回值不同,从而区分父进程和子进程。

  3. id作为一个变量,为什么会同时既大于0,又等于0。

    子进程的id变量和父进程的id变量的值是不同的,因为fork返回的时候就不同,return的本质是写入,而子进程和父进程是独立的,id变量的实际地址是不同的,即使你可能会发现它们打印出来的虚拟地址是相同的。

    • 子进程和父进程是独立的,有不同的内存空间
    • 子进程是父进程的拷贝,它的很多东西都会进程父进程,比如进程的工作目录cwd,但是它是写时拷贝的,就是同一个变量,当子进程中发生拷贝,系统就会给子进程重新开一块实际的内存空间,以此做到内存之间是独立的。

    image-20241107163907080

  • 写时拷贝:写时拷贝是一种优化技术,旨在减少内存的使用和提高进程创建的效率。当创建一个新的进程,操作系统并不会立马为新进程开辟一块新的内存页面,而是父进程和子进程共享一块内存页面,这个页面被标记为可读。如果一旦有某个进程试图修改共享内存页面的数据,操作系统就会为这个想要修改数据的进程,重新开辟一块新的物理内存页面,并把原先的可读数据复制一份,把新数据修改到新的页面,原页面不变,继续被另一个进程使用。只会拷贝部分内存页面,因为还有一些内存页面是没有被修改的!!!,按需复制

  • 某一个进程崩溃了,不会影响其它进程,因为有写时拷贝技术的存在,而且它们的数据的物理内存不同。父子进程共享代码但是写操作发生后不再共享物理内存,所以代码中的某个变量可能有不同的物理内存,但是它们的虚拟内存可能一样。

demo代码:一次创建多个进程

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <stdlib.h>

const int num = 10;

void Worker()
{
  int cnt = 12;
  while(cnt--)
  {
    printf("child process %d is running!!!cnt %d \n",getpid(),cnt);
    sleep(1);
    cnt--;
  }
}
int main()
{
  for(int i = 0;i < num;++i)
  {
     pid_t id = fork();
     if(id < 0) return 1;

     else if(id == 0)
     {
       Worker();
       exit(0);//子进程执行完任务直接退出了
     }
     
     printf("chilid process created sucess!!!,child pid is %d\n",getpid());
     sleep(1);
  }

  sleep(5);//只有父进程可以执行到这
  printf("执行完成!!!\n");
  return 0;
}

运行结果:

20241109_20_29_02_513

可以看到,我们一个创建了5个子进程,它们在完成任务之后,就退出了。但是状态变成了Z,也就是僵尸进程,因为父进程没有读取。

进程的状态介绍

关于进程排队

进程不是一直在运行的,因为有时间片的存在,每个进程都只运行固定的时间,然后保存好上下文数据,切换为其它的进程,这就涉及到调度算法。

另外,即使进程放在了CPU上也不一定一直会运行,因为它可能会在等待某种硬件资源,比如C语言中,我们在使用scanf的时候,如果一直键盘没有输入相应的数据,进程就不会往下执行,停顿在了那个位置,没有继续运行。

Linux中如何进行进程排队呢?

image-20241108094048956

image-20241108094112929

  • 我们上面只是简单的写一下只给结构体某个成员的地址,如何得到结构体对象地址的思考,用C语言来写要考虑到指针加减的不同类型问题。
  • 上面的做法解耦了数据结构和链表管理
    • 数据结构独立task_struct 结构体可以独立于具体的链表管理逻辑。这样,task_struct 的定义可以更加专注于进程本身的属性和状态,而不必关心如何被组织到链表中。
    • 链表节点独立:通过在 task_struct 中嵌入链表节点(例如 struct list_head),内核可以灵活地将 task_struct 插入到不同的链表中,而不需要修改 task_struct 的核心定义。

模拟上述计算过程

只给结构体中某个成员的地址,得到结构体对象地址,并访问它的成员。

#include<stdio.h>

typedef struct Student
{
    char name[20];
    char sex[10];
    int age;
}Stu;

int main()
{
    Stu a = {"xiaoming","男",18};
    int* pf = &a.age;
    Stu* b = (Stu*)((char*)pf - (char*)(&((Stu*)0)->age));
    printf("%s\n", b->name);
    printf("%s\n", b->sex);
    printf("%d\n", b->age);
    return 0;
}

运行结果:

Linux平台(gcc):

image-20241108182304518

Windows平台(vs2019):

image-20241108182441992

image-20241108182503547

  • 只有都换成char*才能满足我们的要求。

进程的几种状态

教材上关于进程状态的表述:运行、就绪、阻塞。

image-20241108182821051

实际上在Linux内核中,状态就是一个整型变量:

image-20241108183926295

Linux中进程的状态绝对了进程的后续动作:Linux中同时存在多个进程都要根据它的状态执行后续动作。

image-20241108185553197

当进程在等待某种资源时,会被操作系统挂为阻塞状态,它的PCB对象会被放到该硬件资源的等待队列中:

image-20241108192245439

挂起状态:当计算机资源很吃紧时,进程会被设置为挂起状态。

image-20241108192650631

Linux中定义的进程状态:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 下面我们在ubuntu24.04下寻找一下上述进程状态的身影:
  1. 正在运行的进程:

    image-20241109191805949

    • R+:R表示正在运行,+表示是前台进程。
  2. S:状态表示进程正在休眠:

    image-20241109192806652

  3. s:小写s表示进程是一个会话leader,因为它管理着多个子进程,每个子进程处理一个单独的客户端连接。image-2024110992932436

  4. t:进程正在被调试:

    image-20241109193725998

  5. 进程被系统管理员使用kill命令中断(T):

    image-20241109194119666

  6. 模拟长时间的 I/O,让我们的程序读取一个很大的文件,磁盘 I/O 操作:创建一个文件并进行大量的读写操作,可以让进程进入D状态。这样可以保证读取的完整性,而且硬件交互很特殊,不仅时间长,而且中间状态难以恢复,所以要将进程设置为不可中断睡眠:

    image-20241109195738227

  7. 僵尸状态(Z):

    image-20241109201952922

  8. X状态(dead):进程已经变成死亡状态,这种状态一般不会显示到进程状态列表中。

僵尸进程

上述什么是僵尸状态我们已经介绍过了,处于僵尸状态的进程就是僵尸进程,这是因为子进程退出了,而父进程没有退出,在父进程没有读取子进程的结果前,子进程会一直处于僵尸进程,我们可以调用系统调用函数来解决这个问题。

  1. wait函数解决僵尸进程的问题。

    今天我们只简单的使用一下,wait函数,更多细节在进程地址空间的时候再谈。

    image-20241109212014476

    • 这个函数的参数是一个指针,这个指针是什么,我们今天也不谈,直接给它传NULL,快速使用一下:
    #include <unistd.h>
    #include<stdio.h>
    #include<sys/wait.h>
    int main()
    {
      printf("开始创建进程了!!!!我的pid is %d,我的ppid is %d\n",getpid(),getppid());
      pid_t id = fork();
      if(id < 0) return 1;
      else if(id == 0)
      {
        printf("hello !!!\n");
        printf("开始创建进程了!!!!我的pid is %d,我的ppid is %d\n",getpid(),getppid());
        exit(0);
      }
    
      else//父进程
      {
        printf("我是父进程\n");
        sleep(10);
        printf("开始回收子进程了!!!\n");
        wait(NULL);
        printf("回收完成!!!\n");
      }
      return 0;
    }
    

    运行结果:

    20241109_21_34_17_987

    • sleep10秒后,主进程调用wait系统调用函数等待子进程,子进程资源被回收,僵尸状态变成死亡状态。

孤儿进程

当父进程比子进程提前结束,子进程就会变成孤儿进程,因为它没有父进程了,父进程已经变成死亡状态,如果子进程不被等待就会变成僵尸进程,导致资源泄漏,所以当这种情况发生时,系统会给子进程分配一个父进程。

看下面代码,子进程会变成孤儿进程:

#include <unistd.h>
#include<stdio.h>
#include<sys/wait.h>
int main()
{
  printf("开始创建进程了!!!!我的pid is %d,我的ppid is %d\n",getpid(),getppid());
  pid_t id = fork();
  if(id < 0) return 1;
  else if(id == 0)
  {
    printf("hello !!!\n");
    printf("开始创建进程了!!!!我的pid is %d,我的ppid is %d\n",getpid(),getppid());
    while(1)
    {
      printf("Running!!!!\n");
    }
  }

  else//父进程
  {
    sleep(10);	  
    printf("我要退出了!!!!\n");
    exit(0);//父进程退出
  }
}

运行结果:

20241109_22_17_34_739

  • 当父进程退出后,子进程被pid为1的init进程接管,它负责清理子进程的资源。

    image-20241109222103604

  • 本人知识、能力有限,若有错漏,烦请指正,非常非常感谢!!!
  • 转发或者引用需标明来源。

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

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

相关文章

bert-base-chinese模型使用教程

向量编码和向量相似度展示 import torch from transformers import BertTokenizer, BertModel import numpy as npmodel_name "C:/Users/Administrator.DESKTOP-TPJL4TC/.cache/modelscope/hub/tiansz/bert-base-chinese"sentences [春眠不觉晓, 大梦谁先觉, 浓睡…

Qt/C++ 海康SDK开发示例Demo

*** 工业相机在机器视觉中起到关键作用&#xff0c;本文基于海康 SDK 详细解读了设备连接与控制的各个步骤。内容涵盖设备枚举、句柄创建、图像采集回调以及设备异常处理&#xff0c;帮助开发者快速理解如何通过代码控制相机&#xff0c;实时采集并处理图像数据。*** 1. 搜索并…

RabbitMQ的应用

七种工作模式介绍 1.Simple(简单模式) P&#xff1a;生产者&#xff0c;也就是要发送信息的程序 C&#xff1a;消费者&#xff0c;消息的接收者 Queue&#xff1a;消息队列。图中黄色背景部分&#xff0c;类似一个邮箱&#xff0c;可以缓存发送信息&#xff1b;生产者向其中…

K8S网络插件故障处理

1网络插件故障 1此故障问题处理方法 查询ip是否正常是否是主节点IP地址如果不是需要更改 更改方式 1 修改calico.yaml文件的相应参数 # Cluster type to identify the deployment type - name: IP_AUTODETECTION_METHOD #增加内容value: "interfaceens*" 或者 value…

【论文速看】DL最新进展20241109-图像超分、物理信息神经网络、扩散模型

目录 【图像超分】【物理信息神经网络】【扩散模型】 【图像超分】 [2024 红外图像超分] Infrared Image Super-Resolution via Lightweight Information Split Network 论文链接&#xff1a;https://arxiv.org/pdf/2405.10561v2 代码链接&#xff1a;无 单图像超分辨率&…

Python学习从0到1 day26 第三阶段 Spark ① 数据输入

要学会 剥落旧痂 然后 循此新生 —— 24.11.8 一、Spark是什么 定义&#xff1a; Apache Spark 是用于大规模数据处理的统一分析引擎 简单来说&#xff0c;Spark是一款分布式的计算框架&#xff0c;用于调度成百上千的服务器集群&#xff0c;计算TB、PB乃至EB级别的海量数据…

[Python学习日记-63] 继承与派生

[Python学习日记-63] 继承与派生 简介 继承 派生 简介 上一篇文章我们学习了类如何使用&#xff0c;以及相关特性&#xff0c;也做了相关的练习&#xff0c;在练习当中发现类与类之间有时也会存在重复代码&#xff0c;其实在类中我们还有一个继承和派生的概念没有说&#xf…

基于 Encoder-only 架构的大语言模型

基于 Encoder-only 架构的大语言模型 Encoder-only 架构 Encoder-only 架构凭借着其独特的双向编码模型在自然语言处理任务中表现出色&#xff0c;尤其是在各类需要深入理解输入文本的任务中。 核心特点&#xff1a;双向编码模型&#xff0c;能够捕捉全面的上下文信息。 En…

Python学习------第四天

Python的判断语句 一、布尔类型和比较运算符 二、 if语句的基本格式 if语句注意空格缩进&#xff01;&#xff01;&#xff01; if else python判断语句的嵌套用法&#xff1a;

uniapp实现H5和微信小程序获取当前位置(腾讯地图)

之前的一个老项目&#xff0c;使用 uniapp 的 uni.getLocation 发现H5端定位不准确&#xff0c;比如余杭区会定位到临平区&#xff0c;根据官方文档初步判断是项目的uniapp的版本太低。 我选择的方式不是区更新uniapp的版本&#xff0c;是直接使用高德地图的api获取定位。 1.首…

测试网空投进行中 — 全面了解 DePIN 赛道潜力项目 ICN Protocol 及其不可错过的早期红利

随着云计算技术的飞速发展&#xff0c;越来越多的企业和个人对云服务的需求变得多样化且复杂化。然而&#xff0c;传统的中心化云服务平台&#xff08;如AWS、微软Azure等&#xff09;往往存在着高成本、数据隐私保护不足以及灵活性差等问题。 为了解决这些挑战&#xff0c;Imp…

IntelliJ IDEA 使用心得与常用快捷键

刚开始学习写Java的时候&#xff0c;用的eclipse&#xff0c;正式工作后&#xff0c;主要用的myeclipse&#xff0c;去年初在前辈的推荐下&#xff0c;在2折的时候买了正版的 IntelliJ IDEA 和 Pycharm&#xff0c;12.0版终生使用&#xff0c;一年更新。 使用前早就久闻其名&am…

【rust】rust基础代码案例

文章目录 代码篇HelloWorld斐波那契数列计算表达式&#xff08;加减乘除&#xff09;web接口 优化篇target/目录占用一个g&#xff0c;仅仅一个actix的helloWorld demo升级rust版本&#xff0c; 通过rustupcargo换源windows下放弃吧&#xff0c;需要额外安装1g的toolchain并且要…

施工企业为什么要用工程项目管理软件?工程项目管理软件的用处是什么?

施工企业一定会遇到哪些问题&#xff1f;工人怠工、材料浪费、数据造假、工期拖延、质量问题、安全隐患等。这些问题正在悄然侵蚀建施工业的经济效益。每一个环节的失控都可能导致巨大的经济损失&#xff0c;还可能损害企业的声誉。面对日益复杂的工程管理环境&#xff0c;如何…

【C++】详解RAII思想与智能指针

&#x1f308; 个人主页&#xff1a;谁在夜里看海. &#x1f525; 个人专栏&#xff1a;《C系列》《Linux系列》 ⛰️ 丢掉幻想&#xff0c;准备斗争 目录 引言 内存泄漏 内存泄漏的危害 内存泄漏的处理 一、RAII思想 二、智能指针 1.auto_ptr 实现原理 模拟实现 弊端…

所谓的情商高,其实就是会说话!

所谓的情商高&#xff0c;其实就是会说话&#xff01; 1.当遇到不知道的事情时&#xff0c;不要直截了当地说“不知道”。而应委婉地表达为“我想听听你的看法”。 如此既能避免尴尬&#xff0c;又能展现出对对方见解的尊重和期待。 2.不要简单地说“我迟到了”&#xff0c;…

ALB搭建

ALB: 多级分发、消除单点故障提升应用系统的可用性&#xff08;健康检查&#xff09;。 海量微服务间的高效API通信。 自带DDoS防护&#xff0c;集成Web应用防火墙 配置&#xff1a; 1.创建ECS实例 2.搭建应用 此处安装的LNMP 3.创建应用型负载均衡ALB实例 需要创建服务关联角…

【spark面试】spark的shuffle过程

概述 所有的shuffle的过程本质上就是一个task将内存中的数据写入磁盘&#xff0c;然后另一个task将磁盘中的数据读入内存的过程。 对于mapreduce来说&#xff0c;我们将内存中的数据写入磁盘成为maptask&#xff0c;将磁盘中的数据读入内存称为reducetask。 而对于spark来说&…

Android 实现一个系统级的悬浮秒表

前言 由于项目需要将手机录屏和时间日志对应起来&#xff0c;一般的手机录屏只能看到分钟&#xff0c;但是APP的日志输出通常都是秒级别的&#xff0c;于是决定自己手撸一个悬浮秒表&#xff08;有拖拽效果&#xff09;。 效果如下 具体实现 大致的实现思路&#xff1a; 创…

【科普小白】LLM大语言模型的基本原理

一、要了解LLM大模型的基本原理就要先来了解一下自然语言处理&#xff08;NLP&#xff09;。 NLP 是 AI 的一个子领域&#xff0c;专注于使计算机能够处理、解释和生成人类语言&#xff0c;主要任务包括&#xff1a;文本分类、自动翻译、问题回答、生成文本等。到底是NLP促生了…