[Linux]进程概念

news2024/12/26 11:09:57

[Linux]进程概念

文章目录

  • [Linux]进程概念
    • 进程的定义
    • 进程和程序的关系
    • Linux下查看进程
    • Linux下通过系统调用获取进程标示符
    • Linux下通过系统调用创建进程-fork函数使用

进程的定义

进程是程序的一个执行实例,是担当分配系统资源(CPU时间,内存)的实体。

进程和程序的关系

由编程语言编写的代码经过编译后形成的二进制程序会存储在硬盘中,当计算机启动一个程序时,会将程序的相关代码和和数据加载到内存中,供CPU来使用:

image-20230803184610548

程序的代码和数据加载到内存后,操作系统就要对程序进行管理,为了更好的管理这些程序,需要对先创建相应的结构来描述这些程序,在操作系统中,用于描述程序的结构叫做进程控制块(Process Control Block,简称 PCB),Linux系统下的PCB名为task_struct,PCB中也会记录相应的代码和数据的地址,为了更好的访问这些PCB使用链式结构将其组织起来:

image-20230803185254037

task_ struct内容分类如下:

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息。

如果只是将程序的代码和数据加载到内存中,但是操作系统没有为其创建PCB进行管理,操作系统就不会调度它,它就无法完成程序的执行,因此进程的本质是内存中的代码和数据+进程控制块,有了PCB后,操作系统就将对进程的管理转化为了对PCB的管理,比如如果要关闭一个进程就将其PCB删除,然后对应的内存就会清空其在内存中的代码和数据:

image-20230803190524985

Linux下查看进程

为了更好的在Linux操作系统上查看进程,创建源文件myprocess.c和makefile文件来创建二进制程序,

源文件中内容如下:

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

int main()
{
  while(1)
  {
    printf("hello myprocess\n");
    sleep(1);
  }
  return 0;
}

其中makefile的内容如下:

myprocess:myprocess.c
	gcc -o myprocess myprocess.c
.PHONY:clean
clean:
	rm -f myprocess

创建好以上文件并编译得到名为myprocess的二进制程序,然后在Linux下启动两个客户端,其中一个启动程序变成进程:

image-20230803193450976

再另一个客户端输入ps axj | head -1 && ps axj | grep myprocess | grep -v grep查看myprocess进程:

image-20230803193707725

以上为使用指令查看进程,指令如下:

ps axj | head -1 && ps axj | grep 进程名 | grep -v 进程名

另外还可以在/proc目录下看到进程:

image-20230803193931708

/proc目录是一个内存级的目录,不存在于硬盘中,目录中会有命名和pid相同的目录,该目录中会记录对应进程的task_struct,如果进程关闭了对应的目录也就删除了。

Linux下通过系统调用获取进程标示符

Linux操作系统为了唯一标识一个进程,给每个进程设置了一个进程标识符在PCB中,也就是pid。并且也提供了系统接口函数getpid来获取当前进程的pid,其介绍如下:

image-20230803195336806

为了测试getpid函数修改源文件myprocess.c,内容如下:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
  while(1)
  {
    printf("hello myprocess, 我的pid是%d\n", getpid());
    sleep(1);
  }
  return 0;
}

用指令查询进程pid和查看进程执行结果:

image-20230803195628555

另外Linux操作系统中还设置了父进程标识符,用于记录当前进程的父进程pid,也就是ppid,同时也提供了getppid函数来获取当前进程的ppid,ppid的介绍如下:

image-20230803200016344

为了测试getppid函数修改源文件myprocess.c,内容如下:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
  while(1)
  {
    printf("hello myprocess, 我的pid是%d, 我的ppid为%d\n", getpid(), getppid());
    sleep(1);
  }
  return 0;
}

用指令查询进程pid和查看进程执行结果:

image-20230803201344196

多次利用 ctrl+c关闭进程,然后重新启动进程:

image-20230803201448298

可以看出,无论进程的pid如何变化,进程的ppid都不会变化,我们尝试用指令查看这个父进程:

image-20230803201620342

实际上这个这个父进程就是bash,通过如上现象我们可以得到如下结论:

  • 命令行解释器(bash)本质也是一个进程。
  • 命令行启动的所有程序最终都会变成进程,而该进程对应的父进程都是bash。

Linux下通过系统调用创建进程-fork函数使用

fork函数是Linux系统提供的创建子进程的系统调用。

  • fork函数运行成功后,执行流会变成两个,一个是调用fork函数的父进程,另一个是fork函数创建的子进程。
  • 创建的子进程会和父进程共享父进程代码和数据,子进程会执行父进程fork函数创建子进程之后的代码。
  • fork函数给父进程返回子进程的pid,给创建的子进程返回0,出错返回-1。

为了测试fork函数修改源文件myprocess.c,内容如下:

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

int main()
{
  pid_t id = fork();
  if (id == 0)
  {
    //子进程
    printf("我是子进程,我的pid是%d, 我的ppid是%d\n", getpid(), getppid());
    sleep(2);
  }
  else if (id > 0)
  {
    //父进程
    printf("我是父进程,我的pid是%d, 我的ppid是%d\n", getpid(), getppid());
    sleep(3);
  }
  else 
  {
    //fork函数出错
    assert(1);
  }
  return 0;
}

说明:

  • fork函数所需要的头文件是unistd.h
  • 使用条件判断来控制父子进程执行不同的代码。

用指令查询进程和查看进程执行结果:

image-20230804105943957

fork函数的原理

进程的本质是PCB+内存中的代码和数据,由于fork函数创建的子进程是和父进程共享代码和数据的,因此fork函数创建子进程的原理是创建一个PCB给子进程,该PCB中大部分数据是和父进程相同的,并且指向同一份代码和数据:

image-20230804113115080

进程独立性在fork中的体现

首先给出如下定理:进程之间是相互独立的,一个进程的任何操作都不会影响其他进程。

在使用fork函数创建子进程进程之间的独立性也能得到保证,为了验证独立性修改源文件myprocess.c,内容如下:

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

int main()
{
  pid_t id = fork();
  if (id == 0)
  {
    //子进程
    printf("我是子进程,我的pid是%d, 我的ppid是%d\n", getpid(), getppid());
    sleep(20);
    printf("我是子进程,我的pid是%d, 我的ppid是%d\n", getpid(), getppid());
    printf("我是子进程,我已经关闭了\n");
  }
  else if (id > 0)
  {
    //父进程
    printf("我是父进程,我的pid是%d, 我的ppid是%d\n", getpid(), getppid());
    sleep(3);
    printf("我是父进程,我已经关闭了\n");
  }
  else 
  {
    //fork函数出错
    assert(1);
  }
  return 0;
}

用指令查询进程和查看进程执行结果:

开始时,父子进程一起执行:

image-20230804114253740

父进程关闭,子进程正常运行:

image-20230804114347222

最后子进程关闭:

image-20230804114411668

由以上测试可以看出,父进程的关闭不影响子进程正常执行,保证了一定的独立性。另外由于代码是只读的,父进程无法通过修改代码来影响子进程,而数据的修改会触发写时拷贝机制,保证了一定的独立性。

为了观察写时拷贝现象,修改源文件myprocess.c,内容如下:

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

int main()
{
  int a = 0;
  pid_t id = fork();
  if (id == 0)
  {
    //子进程
    printf("我是子进程,我的pid是%d, 我的ppid是%d, a:%d, &a:%p\n", getpid(), getppid(), a, &a);
    sleep(5);
    printf("我是子进程,我的pid是%d, 我的ppid是%d, a:%d, &a:%p\n", getpid(), getppid(), a, &a);
  }
  else if (id > 0)
  {
    //父进程
    printf("我是父进程,我的pid是%d, 我的ppid是%d, a:%d, &a:%p\n", getpid(), getppid(), a, &a);
    a = 666;
    printf("我是父进程,我的pid是%d, 我的ppid是%d, a:%d, &a:%p\n", getpid(), getppid(), a, &a);
    sleep(3);
    printf("我是父进程,我已经关闭\n");
  }
  else 
  {
    //fork函数出错
    assert(1);
  }
  return 0;
}

查看进程执行结果:

image-20230804120919703

观察现象可以发现,父进程修改a的值后,子进程的a的值并没有改变,但是父进程和子进程的a变量的地址是相同,这就是发生了写时拷贝造成的现象。

fork函数返回两个返回值的原理

由于fork创建的子进程和父进程共享代码和数据,并且fork函数也是父进程的代码的一部分,因此父进程完成子进程的创建后,子进程也会执行fork函数创建子进程后续的剩余代码,其中就包括fork函数中return返回的部分,因此父进程执行了return部分,子进程也执行了return部分,造成fork函数返回两个返回值的现象:

image-20230804121340511

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

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

相关文章

android 重新签名bat

1.新建txt&#xff0c;修改后缀改为bat文件 sign.bat echo off:apk未签名文件名称 set apk_unsign"":apk签名文件名称 set apk_sign"":设置文件 set settingFileE:\apk\bat\sign\setting_sign.txt:读取settingFile第0行的 apk_unsign 值 for /f "to…

DETR-《End-to-End Object Detection with Transformers》论文精读笔记

DETR&#xff08;基于Transformer架构的目标检测方法开山之作&#xff09; End-to-End Object Detection with Transformers 参考&#xff1a;跟着李沐学AI-DETR 论文精读【论文精读】 摘要 在摘要部分作者&#xff0c;主要说明了如下几点&#xff1a; DETR是一个端到端&am…

Python入门之最基础1.0

记录学渣的学习过程 python入门学习1.0 前言一、 python学习之初需要注意的三个问题二、python所有内置函数的查看方式dir(__builtins__) 总结 前言 一、 python学习之初需要注意的三个问题 二、python所有内置函数的查看方式 dir(builtins) 总结 做自己的时候是发光的

【OpenMp】openmp库的基本语法

目录 OpenMP并行运行结构图句式parallel制导命令隐式同步 parallel的for命令parallel的for命令静态调度dynamic参数guided参数 sections制导指令single制导指令解决多线程竞争临界区矩阵所有元素1任务池同步点shared和private单语句原子操作#pragma omp atomic复杂样例程序 Ope…

【day8】驱动

作业&#xff1a;通过GPIO子系统编写LED灯的驱动&#xff0c;应用程序测试 在led驱动中设置一个定时器&#xff0c;实现底板三盏灯一秒亮一秒灭 1.找引脚 led1---->gpioz组5号引脚 led2---->gpioz组6号引脚 led3---->gpioz组7号引脚 2.加设备树节点 //led的设备树节点…

Nginx中location语法有哪些?【杭州多测师_王sir】

1、location 介绍location是Nginx中的块级指令(block directive)&#xff0c;location指令的功能是用来匹配不同的url请求&#xff0c;进而对请求做不同的处理和响应&#xff0c;这其中较难理解的是多个location的匹配顺序&#xff0c;本文会作为重点来解释和说明。开始之前先明…

【Linux】GNOME图形化界面安装

Linux下具有多种图形化界面&#xff0c;每种图形化界面具有不同的功能&#xff0c;在这里我们安装的是GNOME。 1、 挂载yum源 挂载之前首先确保使用ISO映像文件 2.挂载之前先在/mnt下面创建一个cdrom目录用来作为挂载点目录 挂载完成之后那么就要去修改yum源了 Vi /etc/yum.r…

IntelliJ IDEA maven配置,设置pom.xml的配置文件

IntelliJ IDEA项目&#xff0c;选择 文件 设置&#xff0c;弹窗 构建、执行、部署 构建工具 Maven就可以 maven配置好以后&#xff0c;在pom.xml的配置文件中就可以设置对应的jar包了&#xff0c;这样构建的时候自动需要的jar&#xff0c;在项目中导入即 需要的jar包设置在po…

数据驱动工作效率提升的5个层次—以PreMaint设备数字化平台为例

在现代工业领域&#xff0c;数据分析已成为提升工作效率和优化生产的不可或缺的工具。从描述性分析到规范性分析&#xff0c;数据分析逐步揭示了设备运行和维护的深层信息&#xff0c;帮助企业更明智地做出决策。本文将以PreMaint设备数字化平台为例&#xff0c;探讨工业数据驱…

平面设计除了PS还有哪些工具推荐

平面设计在我们的日常生活中无处不在。无论是传统媒体还是网络媒体&#xff0c;我们每天都会沉浸在大量的平面设计作品中。因此&#xff0c;我们或多或少会对设计有自己的看法。其实&#xff0c;即使是非专业人士&#xff0c;市场上也有很多平面设计软件&#xff0c;本文盘点了…

docker 搭建私有仓库和制作镜像

目录 1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 1.1 启动mysql镜像 1.2 启动owncloud镜像 1.3 浏览器访问 1.4 总结 2、安装搭建私有仓库 Harbor 2.1 下载docker-compose并赋予执行权限 2.2 磁盘挂载&#xff0c;保存harbor 2.3 修改配置文件…

最新的单机Lustre文件系统的安装

1.单机Lustre的搭建&#xff1a; 主机名IP地址内存添加的共享磁盘大小Centos7.9-test机192.168.10.30/241G20G 1.安装E2fsprogs包 下载OSS服务器所需要的包&#xff1a;E2fsprogs包只是在Ext4的原版RPM包基础上增加了对Lustre⽀持 mkdir ~/e2fsprogs && cd ~/e2fspro…

day-29 代码随想录算法训练营 回溯part5

491.递增子序列 分析&#xff1a;存在重复元素&#xff0c;求递增子序列思路&#xff1a;1.树层去重2.当 i>0 时当前位大于上一位 思路&#xff1a; 去重逻辑在每一层都需要重新创建&#xff08;每一层遍历&#xff09;&#xff0c;且不能影响到下一层递归 class Soluti…

认识SpringIOC容器

目录 一、SpringFrameWork 二、SpringIOC容器 三、SpringIoc的核心功能 一、SpringFrameWork 1.SpringFrameWork与Spring之间的关系 SpringFrameWork是SpringCould、SpringMVC等等技术的基础实现的&#xff0c;而所有的SpringCloud、SpringFrameWork、SpringMVC等等技术组…

spring之深入理解Spring框架的核心模块与功能

深入理解Spring框架的核心模块与功能 标题: 深入理解Spring框架的核心模块与功能摘要:引言:词汇解释:详细介绍:详细介绍Spring的核心模块&#xff1a;Spring Core、Beans、ContextSpring Core:Beans:Context: 注意事项:Spring数据访问模块&#xff1a;JDBC、ORM、事务管理JDBC&…

【LVS集群】

目录 一、集群概述 1.负载均衡技术类型 2.负载均衡实现方式 二、LVS结构 1.三层结构 2.架构对象 三、LVS工作模式 四、LVS负载均衡算法 1.静态负载均衡 2.动态负载均衡 五、ipvsadm命令详解 1. -A 2. -D 3. -L 4. -a 5. -d 6. -l 7. -t 8. -s 9. -r 10. -…

【IDEA配置创建类注释模板和方法模板教程】

IDEA配置创建类注释模板和方法模板教程 废话不多说直接上干货 废话不多说直接上干货 先看效果: 类: 方法: IDEA类注释模板 &#xff0c;配置步骤&#xff1b; 直接用模板: /*** description: ${description}* author: Lynn.OuYang* create: ${YEAR}-${MONTH}-${DAY} ${HOU…

实验六 u-boot-2013.01移植

【实验目的】 了解u-boot 的代码结构及移植的基本方法 【实验环境】 ubuntu 14.04发行版FS4412实验平台交叉编译工具arm-none-linux-gnueabi- 【注意事项】 实验步骤中以“$”开头的命令表示在 ubuntu 环境下执行 【实验步骤】 一、建立自己的平台 下载uboot源码 在uboo…

怎么检测UI卡顿?(线上及线下)

什么是UI卡顿&#xff1f; 在Android系统中&#xff0c;我们知道UI线程负责我们所有视图的布局&#xff0c;渲染工作&#xff0c;UI在更新期间&#xff0c;如果UI线程的执行时间超过16ms&#xff0c;则会产生丢帧的现象&#xff0c;而大量的丢帧就会造成卡顿&#xff0c;影响用…